// Max Control Tails
// By Inferno

freeslot(
"MT_TAILSWIPE",
"S_TAILSWIPE",
"S_TAILSWIPE_END",
"SPR_TLSW"
)

mobjinfo[MT_TAILSWIPE] = {
	doomednum = -1,
	spawnstate = S_TAILSWIPE,
	spawnhealth = 1000,
	radius = 48*FRACUNIT,
	height = 64*FRACUNIT,
	flags = MF_NOGRAVITY
}

states[S_TAILSWIPE] = {
	sprite = SPR_TLSW,
	frame = A|FF_ANIMATE|FF_FULLBRIGHT|TR_TRANS30,
	tics = 19,
	var1 = 3,
	var2 = 2,
	nextstate = S_TAILSWIPE_END
}

states[S_TAILSWIPE_END] = {
	sprite = SPR_TLSW,
	frame = A|FF_ANIMATE|FF_FULLBRIGHT|TR_TRANS30,
	tics = 1,
	var1 = 3,
	var2 = 2,
	nextstate = S_NULL
}

sfxinfo[sfx_s1bb] = {
	singular = false,
	caption = "Rebounding off ground"
}

addHook("MobjThinker", function(mobj)
	if mobj and mobj.valid
		if mobj.state == S_TAILSWIPE_END
			for i = 0, 12
				local strikesparkle = P_SpawnMobj(mobj.x,mobj.y,mobj.z,MT_STRIKESPARKLES)
				strikesparkle.color = mobj.color
				strikesparkle.colorized = true
				strikesparkle.scale = mobj.scale
			
				strikesparkle.momx = P_RandomRange(5,-5)*FRACUNIT
				strikesparkle.momy = P_RandomRange(5,-5)*FRACUNIT
				strikesparkle.momz = P_RandomRange(5,-5)*FRACUNIT
			end
		end
	end
end, MT_TAILSWIPE)

local function TailsTweaks(player)	
	local flighttime = 4*TICRATE
	
	local verticalspeed = 1
	
	if player.whirljump == nil
		player.whirljump = false
	end
	
	if player.whirljump
		if P_IsObjectOnGround(player.mo)
		or P_PlayerInPain(player)
		or player.powers[pw_carry]
		or player.powers[pw_justsprung]
			player.whirljump = false
		end
	end
	
	if player.whirljump
	and not (player.pflags & PF_JUMPED)
	and player.mo.momz * P_MobjFlip(player.mo) < 0
		player.pflags = $1|PF_JUMPED
	end
	
	if player.mo.skin == "mctails"
	and player.charability == CA_FLY
		local flight_decel = player.speed
		local newspeed
		
		if (player.powers[pw_super] or player.powers[pw_sneakers]) then
			player.increasecap = 72*FRACUNIT
		else
			player.increasecap = 42*FRACUNIT
		end
		
		if (player.powers[pw_super] or player.powers[pw_sneakers]) then
			player.decreasecap = 48*FRACUNIT
		else
			player.decreasecap = 36*FRACUNIT
		end
		
		if player.mo.eflags & MFE_UNDERWATER
			player.decreasecap = $1 / 2
			player.increasecap = $1 / 2
		end
		
		if player.cmd.buttons & BT_JUMP
			player.fly1 = 1
		end
		
		if player.powers[pw_tailsfly] > flighttime
		and not player.mchyper
			player.powers[pw_tailsfly] = flighttime
		end
		
		// Had to reference ChrispyChars here! Will have to credit.
		// No clue why Lach didn't just use player.rmom(x/y) here, though.
		// I see why now.
		
		if player.mo.momz  * P_MobjFlip(player.mo) > FixedMul(FRACUNIT*verticalspeed, player.mo.scale) // Are we going upwards fast enough?
		and (player.mo.state == S_PLAY_FLY or player.mo.state == S_PLAY_SWIM) // And are we flying?
			if player.speed > player.decreasecap // And are we above the speed reduce cap?
				newspeed = flight_decel - FRACUNIT/2 // If so, reduce speed.
				player.mo.momx = FixedMul(FixedDiv($ - player.cmomx, flight_decel), newspeed)
				player.mo.momy = FixedMul(FixedDiv($ - player.cmomy, flight_decel), newspeed)
				flight_decel = newspeed
			end
		elseif not player.mo.state == S_PLAY_FLY
			flight_decel = player.speed
		end
		
		if (player.mo.state == S_PLAY_FLY or player.mo.state == S_PLAY_SWIM)
		and player.mo.momz * P_MobjFlip(player.mo) < 0
		and player.speed < player.increasecap
		and player.speed > 5*FRACUNIT
		and player.cmd.buttons & BT_SPIN
			newspeed = flight_decel + FRACUNIT
			player.mo.momx = FixedMul(FixedDiv($ - player.cmomx, flight_decel), newspeed)
			player.mo.momy = FixedMul(FixedDiv($ - player.cmomy, flight_decel), newspeed)
			flight_decel = newspeed
		end
		
		if player.mo.state == S_PLAY_FLY 
		or player.mo.state == S_PLAY_SWIM
			if player.cmd.buttons & BT_JUMP
			and player.cmd.buttons & BT_SPIN
			and not player.spindown
			and player.savedskin != "mctails"
				P_DoJump(player, false)
				player.mo.state = S_PLAY_JUMP
				player.pflags = $|PF_JUMPED
				if player.powers[pw_shield] == SH_WHIRLWIND
				or player.powers[pw_shield] == SH_THUNDERCOIN
					if not player.powers[pw_super]
						player.mo.momz = $ * 3 / 2
					end
				end
				player.pflags = $|PF_THOKKED
				if player.powers[pw_shield] == SH_WHIRLWIND
					S_StartSound(player.mo, sfx_wdjump, player)
				elseif player.powers[pw_shield] == SH_THUNDERCOIN
					S_StartSound(player.mo, sfx_s3k45, player)
				else
					S_StartSound(player.mo, sfx_jump, player)
				end
				for i = 0, 8
					local fa = (i*(ANGLE_180/4))
					local dust
					if player.powers[pw_shield] == SH_THUNDERCOIN
						dust = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_THUNDERCOIN_SPARK)
						dust.fuse = 18
					else
						dust = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_SPINDUST)
					end	
					if player.mo.eflags & MFE_VERTICALFLIP // readjust z position if needed
						dust.z = player.mo.z + player.mo.height - dust.height
						P_SetObjectMomZ(dust, 5*FRACUNIT, false)
					else
						P_SetObjectMomZ(dust, -5*FRACUNIT, false)
					end
					
					dust.momx = FixedMul(sin(fa),10*FRACUNIT)
					dust.momy = FixedMul(cos(fa),10*FRACUNIT)
					dust.scale = player.mo.scale
				end
			end
		end
		
		if player.powers[pw_super]
			player.actionspd = skins[player.mo.skin].actionspd * 3/2
		else
			player.actionspd = skins[player.mo.skin].actionspd
		end
	end
end

addHook("PlayerThink", TailsTweaks)

addHook("AbilitySpecial", function(player)
	if player.mo.skin == "mctails"
	and player.mo
	and player.mo.valid
		if player.mo.state == S_DROPDASH
			return true
		else
			if player.mchyper
			and player.powers[pw_tailsfly]
				player.powers[pw_tailsfly] = 999*TICRATE
			end
		end
	end
end)

local function DropDashT(player)
	if not player.mo and player.mo.valid return end
	
	if player.droptimer == nil
		player.droptimer = 0
	end
	
	if player.walldash == nil
		player.walldash = false
	end
	
	if player.dropdashed == nil
		player.dropdashed = false
	end
	
	if player.dropspeed == nil
		player.dropspeed = 36*FRACUNIT
	end
	
	if player.mo.skin != "mctails" return end
	
	if player.powers[pw_super] == 1
		return
	end
	
	if player.downtwirl 
		return
	end
	
	if P_PlayerInPain(player) or not(player.playerstate == PST_LIVE) or player.exiting or player.powers[pw_nocontrol]
		player.triedtodash = false
		player.droptimer = 0
//		player.mo.state = S_PLAY_ROLL
	end
		
	if (player.pflags & PF_JUMPED)
		if (player.triedtodash) then
			player.droptimer = 0
			player.mo.state = S_PLAY_JUMP
		else
			if (player.pflags & PF_USEDOWN) then
				player.droptimer = $1 + 1
				if player.savedskin == "mcsonic"
					if player.dropspeed < 60*FRACUNIT and not player.mchyper
					or player.mchyper and player.dropspeed < 120*FRACUNIT
						if player.powers[pw_super]
							player.dropspeed = $ + FRACUNIT * 6 / 4
						else
							player.dropspeed = $ + FRACUNIT * 3 / 4
						end
					end
				else
					if player.dropspeed < 48*FRACUNIT and not player.mchyper
					or player.mchyper and player.dropspeed < 96*FRACUNIT
						if player.powers[pw_super]
							player.dropspeed = $ + FRACUNIT / 2
						else
							player.dropspeed = $ + FRACUNIT / 4
						end
					end
				end
//				print(player.dropspeed/FRACUNIT)
			elseif player.droptimer > 0 then
				player.droptimer = 0
				player.triedtodrop = true
				player.dropspeed = 36*FRACUNIT
			end
		end
	end
	
	if (player.droptimer == 15) then
//	and not player.mo.state == S_DROPDASH
		S_StartSound(player.mo, sfx_drpdsh)
		player.mo.state = S_DROPDASH
	end
	
	if player.mo.state == S_DROPDASH
	and not (player.pflags & PF_USEDOWN)
		player.mo.state = S_PLAY_ROLL
	end
	
	if player.powers[pw_justsprung]
	or P_PlayerInPain(player)
		player.triedtodash = false
		player.droptimer = 0
		player.dropspeed = 36*FRACUNIT
		player.airdashed = false
	end
		
	
	if player.droptimer >= 15
	and player.powers[pw_super] 
	and P_IsObjectOnGround(player.mo)
		P_StartQuake(15*FRACUNIT,6)
	end
			
	if (player.mo.eflags & MFE_JUSTHITFLOOR or P_IsObjectOnGround(player.mo)) then
		if (player.droptimer >= 15) then
			player.mo.state = S_PLAY_ROLL
			player.pflags = $1|PF_SPINNING
			local radius = player.mo.radius >> FRACBITS
			P_SpawnMobjFromMobj(player.mo, P_RandomRange(-radius, radius) << FRACBITS, P_RandomRange(-radius, radius) << FRACBITS, 0, MT_SPINDUST)
			P_SpawnMobjFromMobj(player.mo, P_RandomRange(-radius, radius) << FRACBITS, P_RandomRange(-radius, radius) << FRACBITS, 0, MT_SPINDUST)
			P_SpawnMobjFromMobj(player.mo, P_RandomRange(-radius, radius) << FRACBITS, P_RandomRange(-radius, radius) << FRACBITS, 0, MT_SPINDUST)
			P_SpawnMobjFromMobj(player.mo, P_RandomRange(-radius, radius) << FRACBITS, P_RandomRange(-radius, radius) << FRACBITS, 0, MT_SPINDUST)
			if player.savedskin == "mcsonic"
				P_InstaThrust(player.mo, player.mo.angle, FixedMul(player.dropspeed+(player.speed/8), player.mo.scale))
			else
				P_InstaThrust(player.mo, player.mo.angle, FixedMul(player.dropspeed+(player.speed/4), player.mo.scale))
			end
			S_StartSound(player.mo, sfx_zoom, player)
			if player.mchyper
				P_StartQuake(25*FRACUNIT, 16)
				P_NukeEnemies(player.mo, player.mo, 1500*FRACUNIT)
				if player.mcflashes
					P_FlashPal(player, PAL_WHITE, 8)
				end
				S_StartSound(player.mo, sfx_s3k4e)
			end
			player.dropdashed = true
//			DoSpindust(player, player.dropspeed)
		end
		player.triedtodash = false
		player.droptimer = 0
		player.dropspeed = 36*FRACUNIT
	end
	
	if player.dropdashed
	and player.mo.state != S_PLAY_ROLL
		player.dropdashed = false
	end		
end
				
addHook("PlayerThink", DropDashT)

addHook("FollowMobj", function(player, tails)
	if player.mo.skin != "mctails" return end
	
	if player.mo.state == S_DROPDASH
		tails.angle = player.mo.angle
		tails.scale = player.mo.scale
		local rollangle = R_PointToAngle2(0, 0, FRACUNIT, -P_MobjFlip(player.mo)*player.mo.momz)
		local add2 = 0
		local zoffs = FixedMul(3*FRACUNIT + 12*sin(rollangle), tails.scale)
		local backwards = -12*cos(rollangle)
		zoffs = $ + (4*FRACUNIT)
		backwards = $ / 2
		if rollangle > ANGLE_180
			add2 = 2
		end
		if add2
			rollangle = InvAngle(rollangle)
		end
		rollangle = $ + ANG15
		if (rollangle > ANG60)
			tails.state = S_TAILSOVERLAY_PLUS60DEGREES + add2
		elseif (rollangle > ANG30)
			tails.state = S_TAILSOVERLAY_PLUS30DEGREES + add2
		else
			tails.state = S_TAILSOVERLAY_0DEGREES
		end
		if (player.mo.eflags & MFE_VERTICALFLIP)
			P_TeleportMove(tails, 
			player.mo.x + P_ReturnThrustX(tails, tails.angle, FixedMul(backwards, tails.scale)),
			player.mo.y + P_ReturnThrustY(tails, tails.angle, FixedMul(backwards, tails.scale)),
			player.mo.z - zoffs + (player.mo.height/3))
		else
			P_TeleportMove(tails, 
			player.mo.x + P_ReturnThrustX(tails, tails.angle, FixedMul(backwards, tails.scale)),
			player.mo.y + P_ReturnThrustY(tails, tails.angle, FixedMul(backwards, tails.scale)),
			player.mo.z + zoffs)
		end
		return true
	end
end, MT_TAILSOVERLAY)

addHook("ThinkFrame", function(player)
	for player in players.iterate
		if not player.mo and player.mo.valid return end
	
		if player.mo.skin != "mctails" return end
	
		if player.onrope == nil
			player.onrope = 0
		end
	
		if (player.powers[pw_carry] == CR_ROPEHANG) 
			player.onrope = 15
		else
			if player.onrope > 0
				player.onrope = $ - 1
			end
		end
	end
end)

addHook("JumpSpinSpecial", function(player)
	if not player.mo and player.mo.valid return end
	
	if player.mo.skin != "mctails" return end
	
	if (player.powers[pw_super] > 1)
	and player.pflags & PF_JUMPED
	and not player.whirljump
	and player.cmd.buttons & BT_JUMP
	and not player.spindown
	and not player.onrope
		S_StartSound(player.mo, sfx_wdjump, player)
		if player.mchyper
			P_SetObjectMomZ(player.mo, 30*FRACUNIT, false)
		else
			P_SetObjectMomZ(player.mo, 20*FRACUNIT, false)
		end
		player.mo.state = S_PLAY_FALL
		player.pflags = $ & ~PF_JUMPED
		player.whirljump = true
		for i = 0, 8
			local fa = (i*(ANGLE_180/4))
			local strikesparkle = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z-player.mo.height, MT_STRIKESPARKLES)
			strikesparkle.momx = FixedMul(sin(fa),5*FRACUNIT)
			strikesparkle.momy = FixedMul(cos(fa),5*FRACUNIT)
			strikesparkle.color = player.mo.color
			strikesparkle.scale = player.mo.scale * 3 / 2
			strikesparkle.colorized = true
			P_SetObjectMomZ(strikesparkle, -5*FRACUNIT, false)
		end
		if player.mchyper
			P_StartQuake(25*FRACUNIT, 16)
			P_NukeEnemies(player.mo, player.mo, 1500*FRACUNIT)
			if player.mcflashes
				P_FlashPal(player, PAL_WHITE, 8)
			end
			S_StartSound(player.mo, sfx_s3k4e)
		end
	else
		if not player.tailswiped
		and not player.spindown
		and not player.tailswipe
		and not player.airrolled
		and not player.downtwirl
		and not (player.powers[pw_carry] == CR_ROPEHANG)
		and player.savedskin != "mctails"
			player.newdraw = 0
			P_SetObjectMomZ(player.mo, FRACUNIT*775/100, false)
			if player.powers[pw_shield] == SH_FLAMEAURA
				P_InstaThrust(player.mo,
				player.cmd.angleturn << 16 + R_PointToAngle2(0, 0, player.cmd.forwardmove*FRACUNIT, -player.cmd.sidemove*FRACUNIT),
				player.speed * 13 / 10)
				player.pflags = $|PF_SHIELDABILITY
			else
				P_InstaThrust(player.mo,
				player.cmd.angleturn << 16 + R_PointToAngle2(0, 0, player.cmd.forwardmove*FRACUNIT, -player.cmd.sidemove*FRACUNIT),
				player.speed * 15 / 14)
			end
			player.tailswipe = 14
			player.tailswiped = true
			player.spawntails = true
			player.kill = P_LookForEnemies(player, false, false)
			for i = 0, 8
				local fa = (i*(ANGLE_180/4))
				local dust = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_DUST)
				if player.mo.eflags & MFE_VERTICALFLIP // readjust z position if needed
					dust.z = player.mo.z + player.mo.height - dust.height
					P_SetObjectMomZ(dust, 5*FRACUNIT, false)
				else
					P_SetObjectMomZ(dust, -5*FRACUNIT, false)
				end
					
				dust.momx = FixedMul(sin(fa),10*FRACUNIT)
				dust.momy = FixedMul(cos(fa),10*FRACUNIT)
				dust.scale = player.mo.scale
			end
		end
	end
end)

addHook("PlayerThink", function(player)
	if not player.mo and player.mo.valid return end
	
	if player.mo.skin != "mctails" return end
	
	if player.spawntails == true
		player.anglelean2 = -1
		for i = 0, 2
			local tailswipe = P_SpawnMobjFromMobj(player.mo, 0, 0, player.mo.height/2, MT_TAILSWIPE)
			P_InstaThrust(tailswipe, player.mo.angle+(player.anglelean2*ANG10), 15*FRACUNIT+player.speed)
			tailswipe.color = SKINCOLOR_SKY
			tailswipe.scale = player.mo.scale * 5 / 4
			tailswipe.parent = player.mo
			player.anglelean2 = $ + 1
			if player.anglelean2 == 1
				player.spawntails = false
			end
		end
	end
end)

local function EnemyKillDrillTailsTime(enemy, mo, source, damage)
	if not mo or not mo.valid return end
	if mo.skin != "mctails" return end
	local player = mo.player
	if (player == nil) or (player.playerstate != PST_LIVE) return end	
	if not enemy and enemy.valid return end
	
	if enemy.flags & (MF_ENEMY|MF_MONITOR)
		if player.tailswipe
		and player.powers[pw_shield] == SH_ATTRACT
			player.mo.state = S_PLAY_JUMP
			player.tailswiped = false
		end
	end
end

addHook("MobjDeath", EnemyKillDrillTailsTime)

addHook("PlayerThink", function(player)
	if not player.mo and player.mo.valid return end
	
	if player.mo.skin != "mctails" return end
	
	if lines[player.lastlinehit] and lines[player.lastlinehit].valid
		player.playerline = lines[player.lastlinehit]
		player.bounceside = P_PointOnLineSide(player.mo.x, player.mo.y, player.playerline)
	end
	
	if player.mo.momz * P_MobjFlip(player.mo) < 0
		player.walllock = false
	end
	
	if player.tailswipe == nil
		player.tailswipe = 0
	end

	if player.tailswipe > 0
		player.tailswipe = $ - 1
		if player.tailswipe == 0
			player.mo.state = S_PLAY_JUMP
			player.newdraw = 0
		else
			if player.powers[pw_pushing]
				if player.objectcollide
					P_InstaThrust(player.mo, player.mo.angle + ANGLE_180, player.normalspeed*2/3)
				else
					if player.bounceside == 1
						P_InstaThrust(player.mo,R_PointToAngle2(player.playerline.v1.x, player.playerline.v1.y, player.playerline.v2.x, player.playerline.v2.y)+ANGLE_90, player.normalspeed*2/3)
						player.drawangle = R_PointToAngle2(player.playerline.v1.x, player.playerline.v1.y, player.playerline.v2.x, player.playerline.v2.y)+ANGLE_90
					else
						P_InstaThrust(player.mo,R_PointToAngle2(player.playerline.v1.x, player.playerline.v1.y, player.playerline.v2.x, player.playerline.v2.y)+ANGLE_270, player.normalspeed*2/3)
						player.drawangle = R_PointToAngle2(player.playerline.v1.x, player.playerline.v1.y, player.playerline.v2.x, player.playerline.v2.y)+ANGLE_270
					end
				end
				if player.powers[pw_super]
					P_SetObjectMomZ(player.mo, 20*FRACUNIT, false)
				else
					P_SetObjectMomZ(player.mo, 15*FRACUNIT, false)
				end
				if player.mo.eflags & MFE_UNDERWATER
					player.mo.momz = $ * 3 / 4
				end
				player.mo.state = S_PLAY_JUMP
				player.tailswiped = true
				player.tailswipe = 0
				player.pflags = $ |PF_JUMPED
				player.pflags = $ & ~PF_THOKKED
				S_StartSound(player.mo, sfx_s3k8b, player)
			else
				player.mo.state = S_PLAY_EDGE
				player.drawangle = player.newdraw
				player.newdraw = $ + ANGLE_45
				if leveltime % 7 == 0
					S_StartSound(player.mo, sfx_s3k42, player)
				end
				if player.dashmode2 > TICRATE
					local ghost = P_SpawnGhostMobj(player.mo)
					ghost.fuse = 4
					ghost.colorized = true
					if ghost.tracer
						ghost.tracer.fuse = 4
						ghost.tracer.colorized = true
					end		
				end
				if player.powers[pw_shield] == SH_ATTRACT
				and not player.powers[pw_super]
					if player.kill and player.kill.valid
						P_HomingAttack(player.mo, player.kill)
					end
				end
			end
		end
	end
		
	if player.tailswiped
		if (P_PlayerInPain(player))
		or player.powers[pw_carry]
		or player.mo.eflags & MFE_SPRUNG
		or P_IsObjectOnGround(player.mo)
			player.tailswiped = false
			player.tailswipe = 0
		end
	end
	
	if not player.powers[pw_pushing]
	and player.objectcollide
		player.objectcollide = false
	end
end)

local function WeRamIntoObject(plmo, mo)
	if not (plmo and plmo.valid) return end
	if plmo.skin != ("mctails") return end
    if plmo.type ~= MT_PLAYER return end
    if plmo.z + plmo.height < mo.z
	or mo.z + mo.height < plmo.z return end
    local player = plmo.player --plmo is the player mobj, this gets the player
	player.objectcollide = true
end

addHook("MobjMoveCollide", WeRamIntoObject, MT_PLAYER)

local function AirRollT(player)
	if not (player.mo and player.mo.valid) then return end 
	if player.mo.skin != "mctails" return end
	
	if not P_IsObjectOnGround(player.mo)
	and not (player.mo.state == S_PLAY_JUMP)
	and not (player.mo.state == S_PLAY_ROLL)
	and not (player.mo.state == S_DROPDASH)
	and not (player.mo.state == S_PLAY_FLY)
	and not (player.mo.state == S_PLAY_SWIM)
	and not (player.mo.state == S_PLAY_FLY_TIRED)
	and not (P_PlayerInPain(player))
	and not (player.powers[pw_shield] == SH_WHIRLWIND and player.pflags & PF_SHIELDABILITY)
	and not player.tailswipe
	and not player.whirljump
	and not player.downtwirl
		player.mo.state = S_PLAY_ROLL
		player.pflags = $1 & ~PF_SPINNING
		player.pflags = $1 | PF_JUMPED
		player.pflags = $1 & ~PF_THOKKED
		player.airrolled = true
		S_StartSound(player.mo, sfx_airrol)
	end
end
				
addHook("SpinSpecial",AirRollT)

local function FloorBreak2(mo, sec)
	if not (mo and mo.valid) then return end
	
	for x = mo.x, mo.x + mo.radius, mo.radius
		for y = mo.y, mo.y + mo.radius, mo.radius
			for fof in sec.ffloors()
				if (fof.flags & FF_EXISTS) and (fof.flags & FF_BUSTUP)
				and (mo.z + mo.height == fof.bottomheight or mo.z == fof.topheight
				or (mo.eflags & MFE_VERTICALFLIP and (mo.z - mo.height == fof.topheight or mo.z == fof.bottomheight)))
					EV_CrumbleChain(fof)
				end
			end
		end
	end
end

addHook("PlayerThink", function(player)
	if not player.mo and player.mo.valid return end
	
	if player.mo.skin != "mctails" return end
	
	if player.downtwirl == nil
		player.downtwirl = false
	end
	
	if player.downtwirllag == nil
		player.downtwirllag = false
	end
	
	if player.downtwirlrebound == nil
		player.downtwirlrebound = false
	end
	
	if not P_IsObjectOnGround(player.mo)
		player.savedmomz = player.mo.momz
	end
	
	if P_PlayerInPain(player)
	or player.powers[pw_carry]
	or player.mo.eflags & MFE_SPRUNG
		player.downtwirl = false
		player.downtwirllag = false
	end
	
	if player.cmd.buttons & BT_CUSTOM1
	and player.pflags & PF_JUMPED
	and not player.custom1down
	and not player.downtwirl
	and not (player.exiting)
	and not (player.mo.state == S_PLAY_FLY)
	and not (player.mo.state == S_PLAY_SWIM)
	and not (player.mo.state == S_PLAY_FLY_TIRED)
	and not player.downtwirllag
	and not player.tailswipe
		player.downtwirl = true
		P_SetObjectMomZ(player.mo, -28*FRACUNIT, false)
		S_StartSound(player.mo, sfx_s3kces, player)
		if (player.powers[pw_shield] & SH_FORCE)
		and not player.powers[pw_super]
			player.mo.momx = 0
			player.mo.momy = 0
			player.pflags = $|PF_SHIELDABILITY
		else
			P_InstaThrust(player.mo, R_PointToAngle2(0, 0, player.mo.momx, player.mo.momy), player.speed/4*3)
		end
		player.newdraw = 0
	end
	
	if player.downtwirl
		if P_IsObjectInGoop(player.mo)
			player.downtwirl = false
			player.downtwirllag = true
			player.mo.state = S_PLAY_JUMP
			player.pflags = $1 | PF_JUMPED
		end
		
		if P_IsObjectOnGround(player.mo)
			if player.powers[pw_shield] == SH_BUBBLEWRAP
			and not player.powers[pw_super]
				player.mo.momz = FixedMul(-player.savedmomz, 4*FRACUNIT/8) // 2/4, 3/4.
				player.downtwirl = false
				player.downtwirllag = false
				player.mo.state = S_PLAY_JUMP
				player.pflags = $1 & ~PF_SPINNING
				player.pflags = $1 | PF_JUMPED
				player.pflags = $1 & ~PF_THOKKED
			else
				P_SetObjectMomZ(player.mo, 5*FRACUNIT, false)
				player.downtwirl = false
				player.downtwirllag = true
				player.downtwirlrebound = true
				player.mo.state = S_PLAY_SPRING
				if (player.powers[pw_shield] == SH_ARMAGEDDON)
				and not player.powers[pw_super]
					player.mo.momz = $ * 3
					P_BlackOw(player)
					local extraorb = P_SpawnMobj(player.mo.x,player.mo.y,player.mo.z,MT_ASH_ORB)
					extraorb.target = player.mo
					P_SwitchShield(player, SH_ASH)
				end
			end
			if player.powers[pw_shield] == SH_ELEMENTAL
			and not player.powers[pw_super]
				P_ElementalFire(player, true)
			else
				for i = 0, 8
					local fa = (i*(ANGLE_180/4))
					local dust = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_SPINDUST)
					dust.momx = FixedMul(sin(fa),20*FRACUNIT)
					dust.momy = FixedMul(cos(fa),20*FRACUNIT)
					dust.scale = player.mo.scale
				end
			end
			S_StartSound(player.mo, sfx_s1bb)
			if player.mchyper
				P_StartQuake(25*FRACUNIT, 16)
				P_NukeEnemies(player.mo, player.mo, 1500*FRACUNIT)
				if player.mcflashes
					P_FlashPal(player, PAL_WHITE, 8)
				end
				S_StartSound(player.mo, sfx_s3k4e)
			end
			FloorBreak2(player.mo, player.mo.subsector.sector)
		else
			if player.mo.eflags & MFE_SPRUNG
				player.downtwirl = false
				player.downtwirllag = false
				player.mo.momz = $*6/5
				player.mo.momx = $*4/3
				player.mo.momy = $*4/3
				player.mo.state = S_PLAY_SPRING
				if player.powers[pw_shield] == SH_ELEMENTAL
				and not player.powers[pw_super]
					P_ElementalFire(player, true)
				else
					for i = 0, 8
						local fa = (i*(ANGLE_180/4))
						local dust = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_SPINDUST)
						dust.momx = FixedMul(sin(fa),20*FRACUNIT)
						dust.momy = FixedMul(cos(fa),20*FRACUNIT)
						dust.scale = player.mo.scale
					end
				end
				S_StartSound(player.mo, sfx_sprong, player)
			else
				if not P_IsObjectInGoop(player.mo)
					player.mo.state = S_PLAY_EDGE
					player.drawangle = player.newdraw
					local dust = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_SPINDUST)
					dust.momx = FixedMul(sin(player.drawangle),10*FRACUNIT)
					dust.momy = FixedMul(cos(player.drawangle),10*FRACUNIT)
					P_SetObjectMomZ(dust, -5*FRACUNIT, false)
					dust.scale = player.mo.scale
					player.newdraw = $ + ANG60
					if (player.powers[pw_shield] & SH_FORCE)
					and not player.powers[pw_super]
						player.mo.momx = 0
						player.mo.momy = 0
					end
				end
			end
		end
	end
	
	if P_IsObjectOnGround(player.mo)
		if not player.downtwirlrebound
			player.downtwirllag = false
		else
			player.downtwirlrebound = false
		end
	end
	
	if player.cmd.buttons & BT_CUSTOM1
		player.custom1down = true
	else
		player.custom1down = false
	end
	
	if player.cmd.buttons & BT_SPIN
		player.spindown = true
	else
		player.spindown = false
	end
end)

local function EnemyKillDrillT(enemy, mo, source, damage)
	if not mo or not mo.valid return end
	if mo.skin != "mctails" return end
	local player = mo.player
	if (player == nil) or (player.playerstate != PST_LIVE) return end	
	if not enemy and enemy.valid return end
	
	if enemy.flags & (MF_ENEMY|MF_MONITOR)
		player.pflags = $ & ~PF_THOKKED
		if player.downtwirl
			player.mo.momz = $ * 3 / 4
			player.downtwirl = false
			player.mo.state = S_PLAY_JUMP
			player.pflags = $ & ~PF_THOKKED
			player.pflags = $|PF_JUMPED
		end
	end
end

addHook("MobjDeath", EnemyKillDrillT)

local function WeRamIntoObject2(plmo, mo)
	if not (plmo and plmo.valid) return end
    if mo.type == MT_PLAYER return end
    if plmo.z + plmo.height < mo.z
	or mo.z + mo.height < plmo.z return end
	if mo.flags & (MF_ENEMY|MF_MONITOR|MF_BOSS)
		P_DamageMobj(mo, plmo, plmo)
	end
end

addHook("MobjMoveCollide", WeRamIntoObject2, MT_TAILSWIPE)

addHook("MobjMoveBlocked", function(tailswipe)
	if tailswipe and tailswipe.valid
		for i = 0, 12
			local strikesparkle = P_SpawnMobj(tailswipe.x,tailswipe.y,tailswipe.z,MT_STRIKESPARKLES)
			strikesparkle.color = tailswipe.color
			strikesparkle.colorized = true
			strikesparkle.scale = tailswipe.scale
			
			strikesparkle.momx = P_RandomRange(5,-5)*FRACUNIT
			strikesparkle.momy = P_RandomRange(5,-5)*FRACUNIT
			strikesparkle.momz = P_RandomRange(5,-5)*FRACUNIT
		end		
		P_RemoveMobj(tailswipe)
	end
end, MT_TAILSWIPE)
		
local function DoSpindust(player, speed)
	local mo = player.mo
	local prandom = {}
	for i = 0, (leveltime%7) >> 1 // 1, 2, 3 or 4 particles
		local particle = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_SPINDUST)
		
		if mo.eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)
			particle.state = S_SPINDUST_BUBBLE1
		end

		particle.target = mo
		particle.destscale = (mo.destscale << 1)/3
		
		P_SetScale(particle, particle.destscale)
		if mo.eflags & MFE_VERTICALFLIP // readjust z position if needed
			particle.z = mo.z + mo.height - particle.height
		end
		prandom[0] = P_RandomFixed()<<2 // P_RandomByte()<<10
		prandom[1] = P_RandomRange(-30, 30) // P_RandomRange(-ANG30/FRACUNIT, ANG30/FRACUNIT)*FRACUNIT
		prandom[2] = P_RandomFixed()<<3 // P_RandomByte()<<11
		P_SetObjectMomZ(particle, speed/50 + prandom[0], false)
		P_InstaThrust(particle,
				player.drawangle + prandom[1]*ANG1,
				-FixedMul(speed/12 + FRACUNIT + prandom[2], mo.scale))
		P_TryMove(particle, particle.x + particle.momx, particle.y + particle.momy, true)
	end
end

addHook("ThinkFrame", function()
	for player in players.iterate
		if player.pflags & PF_SLIDING
			player.mcsliding = true
		else
			player.mcsliding = false
		end
	end
end)

addHook("PlayerThink", function(player)
	if not player.mo and player.mo.valid return end
	
	if player.mo.skin != "mctails" return end
	
	if player.myheadisspinning == nil
		player.myheadisspinning = false
	end	
	
	if player.mcsliding == nil
		player.mcsliding = false
	end
	
	if player.myheadisspinning
		if (P_PlayerInPain(player))
		or player.powers[pw_carry]
		or player.mo.eflags & MFE_SPRUNG
		or (player.playerstate != PST_LIVE)
			player.myheadisspinning = false
		end
		if player.cmd.buttons & BT_JUMP
			player.myheadisspinning = false
			P_DoJump(player, true)
			player.mo.state = S_PLAY_JUMP
		end
	end
	
	if player.cmd.buttons & BT_CUSTOM1
	and P_IsObjectOnGround(player.mo)
	and not player.downtwirl
	and not (player.exiting)
	and player.speed > 5*FRACUNIT
	and not player.myheadisspinning
	and not player.mcsliding
		player.newdraw2 = 0
		player.myheadisspinning = true	
		player.pflags = $ & ~PF_SPINNING
	end
	
	if player.myheadisspinning
		if player.cmd.buttons & BT_CUSTOM1
			player.mo.state = S_PLAY_EDGE
			player.drawangle = player.newdraw2
			player.newdraw2 = $ + ANGLE_45
			if leveltime % 7 == 0
				S_StartSound(player.mo, sfx_s3k42, player)
			end
			if player.dashmode2 < TICRATE
				local ghost = P_SpawnGhostMobj(player.mo)
				ghost.fuse = 4
				ghost.colorized = true
				if ghost.tracer
					ghost.tracer.fuse = 4
					ghost.tracer.colorized = true
				end	
				if P_IsObjectOnGround(player.mo)
					DoSpindust(player, player.speed*2)
				end
			end
			if player.mcsliding
				player.myheadisspinning = false
			end
			player.pflags = $ & ~PF_SPINNING
		else
			player.myheadisspinning = false
		end
	end
end)

local function StatesWithDamage2(player, badnik)
	if player.mo and player.mo.valid
		if player.myheadisspinning
			return true
		end
	end
end

addHook("PlayerCanDamage", StatesWithDamage2)

addHook("SpinSpecial", function(player)
	if not player.mo and player.mo.valid return end
	
	if player.mo.skin != "mctails" return end
	
	if P_IsObjectOnGround(player.mo)
	and player.myheadisspinning
		return true
	end
end)

// Heavy referenced fromt he the tag-team for Crystal Sonic and Rose Gold Amy
// (Even if this wasn't reusable, Bloops literally directed me to using it as reference, LOL)

local function The_Person_This_Function_Reffered_To_Is_Not_Such(p,b)
	if p.skin == 'mcsonic'
		R_SetPlayerSkin(b.player, 'mctails')
	else
		R_SetPlayerSkin(b.player, 'mcsonic')
	end
	P_ResetPlayer(p.player)
end

local function Stolen_Bounding_Trigger(p, b)
	if (p.skin == 'mcsonic' and b.skin == 'mctails')
	or (b.skin == 'mcsonic' and p.skin == 'mctails')
		b.waspreviouslyworking = true
		return true
	end
	if (p.skin == 'mcsonic' or p.skin == 'mctails')
	and b.waspreviouslyworking 
		b.waspreviouslyworking = false --IT BROKE!!!!!!!
		The_Person_This_Function_Reffered_To_Is_Not_Such(p,b)
	end
end

addHook("ThinkFrame", function()
	local p = players[0]
	local b = players[1]
	if netgame
	or multiplayer
		return
	end
	
	if p and p.mo
	and b and b.mo
	and Stolen_Bounding_Trigger(p.mo,b.mo)
		if p.cmd.buttons & BT_TOSSFLAG and not b.mo.tfdown // Changed to tossflag due to both characters using Custom 1 as main moveset.
		and P_IsObjectOnGround(p.mo)			
			local oldstate = p.mo.state // State of the player, preserved
			// The skins, so that it can swap them.
			local skin1 = p.mo.skin
			local skin2 = b.mo.skin
		
			local nsp = p.normalspeed // Player 1 normalspeed
			local jfp = p.jumpfactor // Player 1 jump factor
			local nsb = b.normalspeed // Player 2 normalspeed
			local jfb = b.jumpfactor // Player 2 jump factor
		
			local love = p.dashmode2
			// The actual swapping.
			S_StartSound(p.mo,sfx_s1ba)
			P_ResetPlayer(p)
			R_SetPlayerSkin(p, skin2)
			R_SetPlayerSkin(b, skin1)
			
			// Done to fix a bug where MCTails would inherit MCSonic's victory pose.
			
			if (p.mo.state >= S_POSE1 and p.mo.state <= S_POSE3)
			and p.mo.skin == "mctails"
				p.mo.state = S_PLAY_STND
			end
			
			if p.mo.state == S_PLAY_SPINDASH
				p.mo.state = S_PLAY_STND
			end
			if b.mo.state == S_PLAY_SPINDASH
				b.mo.state = S_PLAY_STND
			end
		
			p.normalspeed = nsb
			p.jumpfactor = jfb		
			b.normalspeed = nsp
			b.jumpfactor = jfp
		
			if p.powers[pw_super]
				p.mo.color = skins[p.mo.skin].supercolor + abs( ( (leveltime >> 1) % 9) - 4)
				b.mo.color = skins[b.mo.skin].supercolor + abs( ( (leveltime >> 1) % 9) - 4)
			end
		
			-- shield storage!
			// This is indeed shield storage, you comment.
			
			local storedshield = p.powers[pw_shield]
			local storedshield2 = b.powers[pw_shield]
			
			p.powers[pw_shield] = 0
			b.powers[pw_shield] = 0
			P_SwitchShield(b, storedshield)
			P_SwitchShield(p, storedshield2)
			for i = 0, 12
				local strikesparkle = P_SpawnMobj(p.mo.x,p.mo.y,p.mo.z+(p.mo.height/2),MT_STRIKESPARKLES)
				strikesparkle.color = p.mo.color
				strikesparkle.colorized = true
				strikesparkle.scale = p.mo.scale
			
				strikesparkle.momx = P_RandomRange(5,-5)*FRACUNIT
				strikesparkle.momy = P_RandomRange(5,-5)*FRACUNIT
				strikesparkle.momz = P_RandomRange(5,-5)*FRACUNIT
				
				local strikesparkle2 = P_SpawnMobj(b.mo.x,b.mo.y,b.mo.z+(b.mo.height/2),MT_STRIKESPARKLES)
				strikesparkle2.color = b.mo.color
				strikesparkle2.colorized = true
				strikesparkle2.scale = b.mo.scale
			
				strikesparkle2.momx = P_RandomRange(5,-5)*FRACUNIT
				strikesparkle2.momy = P_RandomRange(5,-5)*FRACUNIT
				strikesparkle2.momz = P_RandomRange(5,-5)*FRACUNIT
			end
		end
		p.savedskin = b.mo.skin
		p.savedplayer = b
		b.savedskin = b.mo.skin
		b.savedplayer = p
		b.mo.tfdown = (p.cmd.buttons & BT_TOSSFLAG) // This works? TIL
	end
end)