// 2.1 Metal Sonic Battle - Ported from 2.1.25
// PLEASE LOAD LUA_CMMN AND LUA_OJTF BEFORE THIS

freeslot(
	// Sprites
	"SPR_OMTS",
	"SPR_OMTC",
	"SPR_OENR",
	// Objects
	"MT_21METALSONIC_BATTLE",
	"MT_21MSSHIELD_FRONT",
	"MT_21MSGATHER",
	"MT_OLDENERGYBALL",
	// States
	"S_21METALSONIC_FLOAT",
	"S_21METALSONIC_VECTOR",
	"S_21METALSONIC_STUN",
	"S_21METALSONIC_BLOCK",
	"S_21METALSONIC_RAISE",
	"S_21METALSONIC_GATHER",
	"S_21METALSONIC_DASH",
	"S_21METALSONIC_BOUNCE",
	"S_21METALSONIC_SHOOT",
	"S_21METALSONIC_PAIN",
	"S_21METALSONIC_DEATH",
	"S_21METALSONIC_FLEE1",
	"S_21METALSONIC_FLEE2",
	"S_21METALSONIC_FLEE3",
	"S_21METALSONIC_FLEE4",
	"S_21MSSHIELD_F1",
	"S_OLDENERGYBALL1",
	"S_OLDENERGYBALL2"
)

// Quick functions meant to imitate right and left shifts, because the ones in 
// Lua do weird things, sometimes
local function RightShift(fixed, shift)
	return (fixed / 2^shift)
end

local function LeftShift(fixed, shift)
	return (fixed * 2^shift)
end

// MT_METALSONIC_BATTLE-specific code of P_SpawnMissile ported from 2.1.25
local function P_SpawnMetalMissile(source, dest, ttype)
	local th
	local an
	local dist
	local z
	local gsf = 6
	local speed
	
	assert(source and source.valid)
	assert(dest and dest.valid)
	/*
	if source.type == MT_JETTGUNNER
		if (source.eflags & MFE_VERTICALFLIP)
			z = source.z + source.height - FixedMul(4*FRACUNIT, source.scale)
		else
			z = source.z + FixedMul(4*FRACUNIT, source.scale)
		end
	else*/
		z = source.z + source.height/2
	//end
	
	if (source.eflags & MFE_VERTICALFLIP) then
		z = $ - FixedMul(mobjinfo[ttype].height, source.scale)
	end
	
	th = P_SpawnMobj(source.x, source.y, z, ttype)
	//th = P_SpawnMobjFromMobj(source, 0, 0, FixedDiv(source.height, source.scale)/2, ttype)
	
	if (source.eflags & MFE_VERTICALFLIP) then
		th.flags2 = $|MF2_OBJECTFLIP
	end
	
	th.destscale = source.scale
	P_SetScale(th, source.scale)
	//th.scale = source.scale
	
	if source.type == MT_21METALSONIC_BATTLE and source.health < 4 then
		speed = FixedMul(FixedMul(th.info.speed, 3*FRACUNIT/2), th.scale)
	else
		speed = FixedMul(th.info.speed, th.scale)
	end
	
	if speed == 0 then
		speed = FixedMul(mobjinfo[MT_TURRETLASER].speed, th.scale)
	end
	
	if th.info.seesound then
		S_StartSound(source, th.info.seesound)
	end
	
	th.target = source // where it came from
	
	if ttype == MT_OLDENERGYBALL then // More accurate!
		an = R_PointToAngle2(source.x, source.y,
				dest.x + (dest.momx*gsf),
				dest.y + (dest.momy*gsf))
	else
		an = R_PointToAngle2(source.x, source.y, dest.x, dest.y)
	end
	
	th.angle = an
	th.momx = FixedMul(speed, cos(an))
	th.momy = FixedMul(speed, sin(an))
	
	if ttype == MT_OLDENERGYBALL then // More accurate!
		dist = FixedHypot(dest.x+(dest.momx*gsf) - source.x, dest.y+(dest.momy*gsf) - source.y)
	else
		dist = FixedHypot(dest.x - source.x, dest.y - source.y)
	end
	
	dist = dist / speed
	
	if dist < 1 then dist = 1 end
	
	if ttype == MT_OLDENERGYBALL then // More accurate!
		th.momz = (dest.z + (dest.momz*gsf) - z) / dist
	else
		th.momz = (dest.z - z) / dist
	end
	
	if (th.flags & MF_MISSILE) then
		dist = P_CheckMissileSpawn(th)
	else
		dist = 1
	end
	return (dist and th or nil)
end

// 2.1 Metal Sonic

// Allow projectiles to hit 2.1 Metal Sonic (not accurate to 2.1, 
// but better for gameplay reasons (mostly Fang))
addHook("MobjCollide", function(thing, tmthing)
	if not (thing and thing.valid
	and tmthing and tmthing.valid) then
		return
	end
	
	// Projectiles should only collide with Metal Sonic if they are not his own, 
	// and while he's in his "floating" state
	if not ((tmthing.flags & MF_MISSILE)
	and tmthing.target ~= thing
	and thing.state == thing.info.spawnstate) then
		return
	end
	
	if thing.z <= tmthing.z + tmthing.height
	and tmthing.z <= thing.z + thing.height then
		thing.flags2 = $|MF2_CLASSICPUSH
		return false
	end
end, MT_21METALSONIC_BATTLE)

// Gets around a hack that allows 2.1 Metal Sonic to be hit by projectiles
addHook("TouchSpecial", function(special, toucher)
	if not (special and special.valid
	and toucher and toucher.valid) then
		return
	end
	
	if (special.flags & MF_SHOOTABLE) then return end
	return true
end, MT_21METALSONIC_BATTLE)

// P_Boss9Thinker ported from 2.1.25
addHook("BossThinker", function(mobj)
	if not (mobj and mobj.valid) then return end
	
	if mobj.state == mobj.info.spawnstate then
		mobj.flags2 = $ & ~MF2_FRET
	end
	
	if not (mobj.tracer and mobj.tracer.valid) then
		local last = nil
		
		// Initialize the boss, spawn jet fumes, etc.
		mobj.threshold = 0
		mobj.reactiontime = 0
		mobj.watertop = mobj.floorz + 32*FRACUNIT
		A_OldBossJetFumes(mobj, 1, 0)
		
		// Run through the thinkers ONCE and find all of the MT_BOSS9GATHERPOINT in the map.
		// Build a hoop linked list of 'em!
		for th in mobjs.iterate() do
			if not (th and th.valid) then continue end
			if th.type == MT_BOSS9GATHERPOINT then // Replace MT_BOSS9GATHERPOINT if it changes in the future
				if last then
					last.hnext = th
				else
					mobj.hnext = th
				end
				th.hprev = last
				last = th
			end
		end
	end
	
	if mobj.health <= 0 then
		return true
	end
	
	if not (mobj.target and mobj.target.valid and (mobj.target.flags & MF_SHOOTABLE)) then
		P_BossTargetPlayer(mobj, false)
		if mobj.target and mobj.target.valid and (not P_IsObjectOnGround(mobj.target) or (mobj.target.player.pflags & PF_SPINNING)) then
			mobj.target = nil	// Wait for them to hit the ground first
		end
		if not (mobj.target and mobj.target.valid) then // Still no target, aww.
			// Reset the boss.
			mobj.state = mobj.info.spawnstate
			mobj.fuse = 0
			mobj.momx = FixedDiv(mobj.momx, FRACUNIT + (RightShift(FRACUNIT, 2)))
			mobj.momy = FixedDiv(mobj.momy, FRACUNIT + (RightShift(FRACUNIT, 2)))
			mobj.momz = FixedDiv(mobj.momz, FRACUNIT + (RightShift(FRACUNIT, 2)))
			return true
		elseif not mobj.fuse then
			mobj.fuse = 10*TICRATE
		end
	end
	
	if (mobj.flags & MF_SHOOTABLE) then
		mobj.flags2 = $ & ~MF2_INVERTAIMABLE // Let Fang aim at 2.1 Metal Sonic when he's shootable
	end
	
	// AI goes here.
		local danger = true
		local angle
		if mobj.threshold then
			mobj.momz = (mobj.watertop-mobj.z)/16 // Float to your desired position FASTER
		else
			mobj.momz = (mobj.watertop-mobj.z)/40 // Float to your desired position
		end
		
		if mobj.movecount == 2 then
			local spawner
			local dist = 0
			angle = 0x06000000*leveltime
			
			// Alter your energy bubble's size/position
			if mobj.health > 3 then
				mobj.tracer.destscale = FRACUNIT + (4*TICRATE - mobj.fuse)*(FRACUNIT/2)/TICRATE + FixedMul(cos(angle), FRACUNIT/2)
				P_SetScale(mobj.tracer, mobj.tracer.destscale)
				P_TeleportMove(mobj.tracer, mobj.x, mobj.y, mobj.z + mobj.height/2 - mobj.tracer.height/2)
				mobj.tracer.momx = mobj.momx
				mobj.tracer.momy = mobj.momy
				mobj.tracer.momz = mobj.momz
			end
			
			// Face your target
			P_BossTargetPlayer(mobj, true)
			angle = R_PointToAngle2(mobj.x, mobj.y, mobj.target.x, mobj.target.y) // absolute angle
			angle = (angle-mobj.angle) // relative angle
			if angle < ANGLE_180 then
				mobj.angle = $+(angle/8)
			else
				mobj.angle = $-(InvAngle(angle)/8)
			end
			
			// Spawn energy particles
			spawner = mobj.hnext
			while spawner do
				dist = FixedHypot(spawner.x - mobj.x, spawner.y - mobj.y)
				if (P_RandomRange(1,RightShift(dist, FRACBITS)/16) == 1) then
					break
				end
				spawner = spawner.hnext
			end
			if spawner then
				local missile = P_SpawnMetalMissile(spawner, mobj, MT_21MSGATHER)
				missile.momz = FixedDiv(missile.momz, 7*FRACUNIT/4)
				if dist == 0 then
					missile.fuse = 0
				else
					missile.fuse = dist/FixedHypot(missile.momx, missile.momy)
				end
				if missile.fuse > mobj.fuse then
					P_RemoveMobj(missile)
				end
			end
		end
		
		// Pre-threshold reactiontime stuff for attack phases
		if mobj.reactiontime and mobj.movecount == 3 then
			if mobj.movedir == 0 or mobj.movedir == 2 then // Pausing between bounces in the pinball phase
				if mobj.target.player.powers[pw_tailsfly] then // trying to escape, eh?
					mobj.watertop = mobj.target.z + mobj.target.momz*6 // Readjust your aim. >:3
				else
					mobj.watertop = mobj.target.floorz + 16*FRACUNIT
				end
				if not (mobj.threshold%4) then
					mobj.angle = R_PointToAngle2(mobj.x, mobj.y, mobj.target.x + mobj.target.momx*4, mobj.target.y + mobj.target.momy*4)
				end
			end
			// Pausing between energy ball shots
			mobj.reactiontime = $-1
			return true
		end
		
		// threshold is used for attacks/maneuvers.
		if mobj.threshold then
			local speed = 20*FRACUNIT + FixedMul(40*FRACUNIT, FixedDiv(LeftShift((mobj.info.spawnhealth - mobj.health),FRACBITS), LeftShift(mobj.info.spawnhealth, FRACBITS)))
			local tries = 0
			
			// Firin' mah lazors
			if mobj.movecount == 3 and mobj.movedir == 1 then
				if not (mobj.threshold&1) then
					local missile
					if mobj.info.seesound then
						S_StartSound(mobj, mobj.info.seesound)
					end
					mobj.state = mobj.info.missilestate
					if mobj.extravalue1 == 3 then
						mobj.reactiontime = TICRATE/16
					else
						mobj.reactiontime = TICRATE/8
					end
					
					A_FaceTarget(mobj)
					missile = P_SpawnMetalMissile(mobj, mobj.target, mobj.info.speed) // Replace this with a 2.1 variant, perhaps?
					//P_SpawnMetalMissile(mobj, mobj.target, missile)
					if mobj.extravalue1 == 2 or mobj.extravalue1 == 3 then
						missile.destscale = RightShift(FRACUNIT, 1)
						P_SetScale(missile, missile.destscale)
					end
					missile.fuse = 3*TICRATE
					P_TeleportMove(missile, missile.x, missile.y, missile.z - missile.height/2)
					
					if mobj.extravalue1 == 2 then
						local i
						local spread
						missile.flags = $|MF_MISSILE
						for i = 0, 4, 1 do
							if i == 2 then continue end
							spread = P_SpawnMobj(missile.x, missile.y, missile.z, missile.type)
							//P_SpawnMetalMissile(mobj, mobj.target, spread)
							spread.angle = missile.angle+(ANGLE_11hh/2)*(i-2)
							P_InstaThrust(spread, spread.angle, spread.info.speed)
							spread.momz = missile.momz
							spread.destscale = RightShift(FRACUNIT, 1)
							P_SetScale(spread, spread.destscale)
							spread.fuse = 3*TICRATE
						end
						missile.flags = $ & ~MF_MISSILE
					end
				else
					mobj.state = states[mobj.state].nextstate
					if mobj.extravalue1 == 3 then
						mobj.reactiontime = TICRATE/8
					else
						mobj.reactiontime = TICRATE/4
					end
				end
				mobj.threshold = $-1
				return true
			end
			
			P_SpawnGhostMobj(mobj)
			
			//Pinball attack!
			if mobj.movecount == 3 and (mobj.movedir == 0 or mobj.movedir == 2) then
				if mobj.state ~= mobj.info.seestate then
					mobj.state = mobj.info.seestate
				end
				if mobj.movedir == 0 then // mobj health == 1
					P_InstaThrust(mobj, mobj.angle, 38*FRACUNIT)
				elseif mobj.health == 3 then
					P_InstaThrust(mobj, mobj.angle, 22*FRACUNIT)
				else // mobj health == 2
					P_InstaThrust(mobj, mobj.angle, 30*FRACUNIT)
				end
				if not P_TryMove(mobj, mobj.x+mobj.momx, mobj.y+mobj.momy, true) then // Hit a wall? Find a direction to bounce
					mobj.threshold = $-1
					if mobj.threshold then
						mobj.state = states[mobj.state].nextstate
						//if mobj.info.mass
							S_StartSound(mobj, sfx_mspogo) // Replace with a 2.1 variant, if necessary
						//end
						if not (mobj.threshold%4) then // We've decided to lock onto the player this bounce.
							mobj.angle = R_PointToAngle2(mobj.x, mobj.y, mobj.target.x + mobj.target.momx*4, mobj.target.y + mobj.target.y*4)
							mobj.reactiontime = TICRATE // targetting time
						else // No homing, just use P_BounceMove
							P_BounceMove(mobj)
							mobj.angle = R_PointToAngle2(0,0,mobj.momx,mobj.momy)
							mobj.reactiontime = TICRATE/4 // Just a puase before you bounce away
						end
						mobj.momx = 0
						mobj.momy = 0
					end
				end
				return true
			end
			
			// Vector form dodge!
			mobj.angle = $+mobj.movedir
			P_InstaThrust(mobj, mobj.angle, -speed)
			tries = $+1
			while (not P_TryMove(mobj, mobj.x+mobj.momx, mobj.y+mobj.momy, true) and tries < 16) do
				tries = $+1
				mobj.angle = $+mobj.movedir
				P_InstaThrust(mobj, mobj.angle, -speed)
			end
			mobj.momx = 0
			mobj.momy = 0
			mobj.threshold = $-1
			if not mobj.threshold then
			// Go into stun after dodge
				mobj.reactiontime = 2*TICRATE + 
									RightShift(
										FixedMul(
											LeftShift(1*TICRATE, FRACBITS),
											FixedDiv(
												LeftShift(mobj.health-1, FRACBITS),
												LeftShift(mobj.info.spawnhealth-1, FRACBITS)
											)
										),
										FRACBITS
									)
				mobj.flags = $|MF_SHOOTABLE // Originally included MF_SPECIAL
				mobj.state = states[mobj.state].nextstate
			end
			return true
		end
		
		angle = 0x06000000*leveltime
		mobj.momz = $+FixedMul(cos(angle), 2*FRACUNIT) // Use that "angle" to bob gently in the air
		// This is below threshold because we don't want to bob while zipping around
		
		// Ohh you're in for it now..
		if (mobj.flags2 & MF2_FRET) and mobj.health <= mobj.info.damage then
			mobj.fuse = 0
		end
		
		// reactiontime is used for delays.
		if mobj.reactiontime then
			// Stunned after vector form
			if mobj.movedir > ANGLE_180 and mobj.movedir <= ANGLE_MAX then
				mobj.angle = $ - 
							FixedAngle(
								FixedMul(
									AngleFixed(
										InvAngle(mobj.movedir)
									),
									FixedDiv(
										LeftShift(mobj.reactiontime, FRACBITS),
										LeftShift(24, FRACBITS)
									)
								)
							)
			else
				mobj.angle = $ + 
							FixedAngle(
								FixedMul(
									AngleFixed(mobj.movedir),
									FixedDiv(
										LeftShift(mobj.reactiontime, FRACBITS),
										LeftShift(24, FRACBITS)
									)
								)
							)
			end
			mobj.reactiontime = $-1
			if not mobj.reactiontime then
				// Out of stun.
				mobj.state = states[mobj.state].nextstate
			end
			return true
		end
		
		// Not stunned? Can hit.
		// Here because stun won't always get the chance to complete due to pinch phase activating, being hit, etc.
		mobj.flags = $ & ~(MF_SHOOTABLE) // Originally included MF_SPECIAL
		
		if mobj.health <= mobj.info.damage and mobj.fuse and not (mobj.fuse%TICRATE) then
			A_BossScream(mobj, 1, MT_BOSSEXPLODE)
		end
		
		// Don't move if we're still in pain!
		if (mobj.flags2 & MF2_FRET) then return true end
		
		if mobj.state == mobj.info.raisestate then
		// Charging energy
			if mobj.momx ~= 0 or mobj.momy ~= 0 then // Apply the air breaks
				if abs(mobj.momx)+abs(mobj.momy) < FRACUNIT
					mobj.momx = 0
					mobj.momy = 0
				else
					P_Thrust(mobj, R_PointToAngle2(0, 0, mobj.momx, mobj.momy), -6*FRACUNIT/8)
				end
			end
			return true
		end
		
		if mobj.fuse == 0 then
			mobj.flags2 = $ & ~MF2_INVERTAIMABLE // Don't let Fang aim at 2.1 Metal Sonic
			// It's time to attack! What are we gonna do?!
			if mobj.movecount == 1 then
				// Okay, we're up? Good, time to gather energy...
				if mobj.health > mobj.info.damage then
				// No more bubble if we're broken (pinch phase)
					local shield = P_SpawnMobj(mobj.x, mobj.y, mobj.z, MT_21MSSHIELD_FRONT)
					mobj.tracer = shield
					shield.target = mobj
				else
					P_LinedefExecute(LE_PINCHPHASE, mobj, nil)
				end
				mobj.fuse = 4*TICRATE
				mobj.flags = $|MF_PAIN
				if mobj.info.attacksound then
					S_StartSound(mobj, mobj.info.attacksound)
				end
				A_FaceTarget(mobj)
			elseif mobj.movecount == 2 then
				// We're all charged and ready now! Unleash the fury!!
				if mobj.health > mobj.info.damage then
					local removemobj = mobj.tracer
					mobj.tracer = mobj.hnext
					P_RemoveMobj(removemobj)
				end
				if mobj.health <= mobj.info.damage then
					// Attack 1: Pinball dash!
					if mobj.health == 1 then
						mobj.movedir = 0
					else
						mobj.movedir = 2
					end
					if mobj.info.seesound then
						S_StartSound(mobj, mobj.info.seesound)
					end
					mobj.state = mobj.info.seestate
					if mobj.movedir == 2 then
						mobj.threshold = 16 // bounce 16 times
					else
						mobj.threshold = 32 // bounce 32 times
					end
					mobj.watertop = mobj.target.floorz + 16*FRACUNIT
					P_LinedefExecute(LE_PINCHPHASE, mobj, nil)
				else
					// Attack 2: Energy shot!
					 mobj.movedir = 1
					 
					 if mobj.health >= 8 then
						mobj.extravalue1 = 0
					 elseif mobj.health >= 5 then
						mobj.extravalue1 = 2
					 elseif mobj.health >= 4 then
						mobj.extravalue1 = 1
					 else
						mobj.extravalue1 = 3
					 end
					 
					 if mobj.extravalue1 == 1 then // shoot 3 times
						mobj.threshold = 3*2
					 elseif mobj.extravalue1 == 3 then // shoot like a freakin' machinegun
						mobj.threshold = 8*2
					else // 0 = shoot once; 2 = spread-shot
						mobj.threshold = 2
					end
				end
			
			elseif mobj.movecount == 3 then
				// return true to idle
				mobj.watertop = mobj.target.floorz + 32*FRACUNIT
				mobj.state = mobj.info.spawnstate
				mobj.flags = $ & ~MF_PAIN
				mobj.fuse = 10*TICRATE
			else // case 0/default:
				// Fly up and prepare for an attack!
				// We have to charge up first, so let's go up into the air
				mobj.state = mobj.info.raisestate
				if mobj.floorz >= mobj.target.floorz then
					mobj.watertop = mobj.floorz + 256*FRACUNIT
				else
					mobj.watertop = mobj.target.floorz + 256*FRACUNIT
				end
			end
			mobj.movecount = $+1
			mobj.movecount = $%4
			return true
		end
		
		// Idle AI
		if mobj.state == mobj.info.spawnstate then
			local dist
			
			// Target the closest player
			P_BossTargetPlayer(mobj, true)
			
			// Face your target
			angle = R_PointToAngle2(mobj.x, mobj.y, mobj.target.x, mobj.target.y) // absolute angle
			angle = angle-mobj.angle // relative angle
			if angle < ANGLE_180 then
				mobj.angle = $+(angle/8)
			else
				mobj.angle = $-(InvAngle(angle)/8)
			end
			//A_FaceTarget(mobj)
			
			// Check if we're being attacked
			if (mobj.flags2 & MF2_CLASSICPUSH) then // Missile fix borrowed from 2.2.10's code
				mobj.flags2 = $ & ~MF2_CLASSICPUSH
			else
				if not ((mobj.target.player.pflags & (PF_JUMPED|PF_SPINNING))
				or mobj.target.player.powers[pw_tailsfly]
				or mobj.target.player.powers[pw_invulnerability]
				or mobj.target.player.powers[pw_super]) then
					danger = false
				end
				if mobj.target.x+mobj.target.radius+abs(mobj.target.momx*2) < mobj.x-mobj.radius then
					danger = false
				end
				if mobj.target.x-mobj.target.radius-abs(mobj.target.momx*2) > mobj.x+mobj.radius then
					danger = false
				end
				if mobj.target.y+mobj.target.radius+abs(mobj.target.momy*2) < mobj.y-mobj.radius then
					danger = false
				end
				if mobj.target.y-mobj.target.radius-abs(mobj.target.momy*2) > mobj.y+mobj.radius then
					danger = false
				end
				if mobj.target.z+mobj.target.height+mobj.target.momz*2 < mobj.z then
					danger = false
				end
				if mobj.target.z+mobj.target.momz*2 > mobj.z+mobj.height then
					danger = false
				end
			end
			if danger then
				// An incoming attack is detected! What should we do?!
				// Go into vector form!
				mobj.movedir = ANGLE_11hh - 
								FixedAngle(
									FixedMul(
										AngleFixed(ANGLE_11hh),
										FixedDiv(
											LeftShift(mobj.info.spawnhealth - mobj.health, FRACBITS),
											LeftShift(mobj.info.spawnhealth - 1, FRACBITS)
										)
									)
								)
				if P_RandomChance(FRACUNIT/2) then
					mobj.movedir = InvAngle(mobj.movedir)
				end
				mobj.threshold = 6 + 
								RightShift(
									FixedMul(
										LeftShift(24, FRACBITS),
										FixedDiv(
											LeftShift(mobj.info.spawnhealth - mobj.health, FRACBITS),
											LeftShift(mobj.info.spawnhealth - 1, FRACBITS)
										)
									),
									FRACBITS
								)
				if mobj.info.activesound then
					S_StartSound(mobj, mobj.info.activesound)
				end
				if mobj.info.painchance then
					mobj.state = mobj.info.painchance
				end
				return true
			end
			
			mobj.flags2 = $|MF2_INVERTAIMABLE // Let Fang aim at 2.1 Metal Sonic
			
			// Move normally: Approach the player using normal thrust and simulated friction.
			dist = FixedHypot(mobj.x-mobj.target.x, mobj.y-mobj.target.y)
			P_Thrust(mobj, R_PointToAngle2(0, 0, mobj.momx, mobj.momy), -3*FRACUNIT/8)
			if dist < 64*FRACUNIT then
				P_Thrust(mobj, mobj.angle, -4*FRACUNIT)
			elseif dist > 180*FRACUNIT then
				P_Thrust(mobj, mobj.angle, FRACUNIT)
			end
			mobj.momz = $+FixedHypot(mobj.momx, mobj.momy)/12 // Move up higher the faster you're going.
		end
	
	return true
end, MT_21METALSONIC_BATTLE)

addHook("MobjFuse", function(mo)
	if not (mo and mo.valid) then return end
	return true
end, MT_21METALSONIC_BATTLE)

addHook("BossDeath", function(mo)
	if not (mo and mo.valid) then return end
	A_21BossDeath(mo)
	return true
end, MT_21METALSONIC_BATTLE)

mobjinfo[MT_21METALSONIC_BATTLE] = {
	//$Name 2.1 Metal Sonic
	//$Sprite OMTSA1
	//$Category Bosses
	//$Flags4Text End level on death
	doomednum = 4048,
	spawnstate = S_21METALSONIC_FLOAT,
	spawnhealth = 8,
	seestate = S_21METALSONIC_DASH,
	seesound = sfx_s3k54,
	attacksound = sfx_trpowr,
	painstate = S_21METALSONIC_PAIN,
	painchance = S_21METALSONIC_VECTOR,
	painsound = sfx_dmpain,
	meleestate = S_21METALSONIC_BLOCK,
	missilestate = S_21METALSONIC_SHOOT,
	deathstate = S_21METALSONIC_DEATH,
	xdeathstate = S_21METALSONIC_FLEE1,
	deathsound = sfx_s3k6e,
	speed = MT_OLDENERGYBALL,
	radius = 16*FRACUNIT,
	height = 48*FRACUNIT,
	mass = 0, // sfx_mspogo doesn't work here, because MF_PAIN results in it instantly killing the player
	damage = 3,
	activesound = sfx_mswarp, // Replace with a 2.1 variant, if necessary
	flags = MF_NOGRAVITY|MF_BOSS|MF_SLIDEME|MF_SPECIAL,
	raisestate = S_21METALSONIC_RAISE
}

// Some of these states differ from 2.1's code to prevent 2.1 Metal Sonic from potentially 
// disappearing when fighting him in a multiplayer game.
states[S_21METALSONIC_FLOAT] =	{SPR_OMTS,	E,	-1,	nil,	0,	0,	S_21METALSONIC_FLOAT}
states[S_21METALSONIC_VECTOR] =	{SPR_OMTS,	M,	-1,	nil,	0,	0,	S_21METALSONIC_STUN}
states[S_21METALSONIC_STUN] =	{SPR_OMTS,	A,	-1,	nil,	0,	0,	S_21METALSONIC_FLOAT}
states[S_21METALSONIC_BLOCK] =	{SPR_OMTS,	N,	-1,	nil,	0,	0,	S_21METALSONIC_FLOAT}
states[S_21METALSONIC_RAISE] =	{SPR_OMTS,	N,	40,	nil,	0,	0,	S_21METALSONIC_GATHER}
states[S_21METALSONIC_GATHER] =	{SPR_OMTS,	O,	-1,	nil,	0,	0,	S_21METALSONIC_FLOAT}
states[S_21METALSONIC_DASH] =	{SPR_OMTS,	J,	-1,	nil,	0,	0,	S_21METALSONIC_BOUNCE}
states[S_21METALSONIC_BOUNCE] =	{SPR_OMTS,	O,	-1,	nil,	0,	0,	S_21METALSONIC_FLOAT}
states[S_21METALSONIC_SHOOT] =	{SPR_OMTS,	N,	-1,	nil,	0,	0,	S_21METALSONIC_GATHER}

states[S_21METALSONIC_PAIN] =	{SPR_OMTS,	L,	40,	A_Pain,	0,	0,	S_21METALSONIC_FLOAT}

states[S_21METALSONIC_DEATH] =	{SPR_OMTS,	A,	-1,	A_BossDeath,	0,	0,	S_NULL}

states[S_21METALSONIC_FLEE1] =	{SPR_OMTS,	D,	4,	nil,			0,	0,				S_21METALSONIC_FLEE2}
states[S_21METALSONIC_FLEE2] =	{SPR_OMTS,	E,	4,	A_BossScream,	0,	MT_BOSSEXPLODE,	S_21METALSONIC_FLEE3}
states[S_21METALSONIC_FLEE3] =	{SPR_OMTS,	F,	4,	nil,			0,	0,				S_21METALSONIC_FLEE4}
states[S_21METALSONIC_FLEE4] =	{SPR_OMTS,	E,	4,	nil,			0,	0,				S_21METALSONIC_FLEE1}

// 2.1 Metal Sonic Front Shield

mobjinfo[MT_21MSSHIELD_FRONT] = {
	doomednum = -1,
	spawnstate = S_21MSSHIELD_F1,
	spawnhealth = 1,
	radius = 32*FRACUNIT,
	height = 48*FRACUNIT,
	flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY
}

states[S_21MSSHIELD_F1] =	{SPR_OMTC,	FF_FULLBRIGHT|FF_TRANS30|A|FF_ANIMATE,	-1,	nil,	11,	1,	S_NULL}

// 2.1 Metal Sonic Gathering Orb

mobjinfo[MT_21MSGATHER] = {
	doomednum = -1,
	spawnstate = S_OLDJETFUME1,
	spawnhealth = 1,
	speed = 24*FRACUNIT,
	radius = 6*FRACUNIT,
	height = 12*FRACUNIT,
	flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY
}

// 2.1 Metal Sonic Energy Ball

mobjinfo[MT_OLDENERGYBALL] = {
	doomednum = -1,
	spawnstate = S_OLDENERGYBALL1,
	spawnhealth = 1000,
	seesound = sfx_s3k54,
	reactiontime = 8,
	speed = 40*FRACUNIT,
	radius = 60*FRACUNIT,
	height = 120*FRACUNIT,
	mass = 100,
	damage = 20,
	flags = MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT
}

states[S_OLDENERGYBALL1] =	{SPR_OENR,	A|FF_FULLBRIGHT|FF_TRANS30,	1,	nil,	0,	0,	S_OLDENERGYBALL2}
states[S_OLDENERGYBALL2] =	{SPR_NULL,	0,							1,	nil,	0,	0,	S_OLDENERGYBALL1}
