// 2.1 Metal Sonic Battle - Ported from 2.1.25

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_21MSSHIELD_F2",
	"S_21MSSHIELD_F3",
	"S_21MSSHIELD_F4",
	"S_21MSSHIELD_F5",
	"S_21MSSHIELD_F6",
	"S_21MSSHIELD_F7",
	"S_21MSSHIELD_F8",
	"S_21MSSHIELD_F9",
	"S_21MSSHIELD_F10",
	"S_21MSSHIELD_F11",
	"S_21MSSHIELD_F12",
	"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 != nil)
	assert(dest != nil)
	/*
	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)
		z = $ - FixedMul(mobjinfo[ttype].height, source.scale)
	end
	
	th = P_SpawnMobj(source.x, source.y, z, ttype)
	
	if (source.eflags & MFE_VERTICALFLIP)
		th.flags2 = $|MF2_OBJECTFLIP
	end
	
	th.destscale = source.scale
	P_SetScale(th, source.scale)
	
	if source.type == MT_21METALSONIC_BATTLE and source.health < 4
		speed = FixedMul(FixedMul(th.info.speed, 3*FRACUNIT/2), th.scale)
	else
		speed = FixedMul(th.info.speed, th.scale)
	end
	
	if speed == 0
		speed = FixedMul(mobjinfo[MT_TURRETLASER].speed, th.scale)
	end
	
	if th.info.seesound
		S_StartSound(source, th.info.seesound)
	end
	
	th.target = source // where it came from
	
	if ttype == MT_OLDENERGYBALL // 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 // 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
		dist = 1
	end
	
	if ttype == MT_OLDENERGYBALL // More accurate!
		th.momz = (dest.z + (dest.momz*gsf) - z) / dist
	else
		th.momz = (dest.z - z) / dist
	end
	
	if (th.flags & MF_MISSILE)
		dist = P_CheckMissileSpawn(th)
	else
		dist = 1
	end
	return (dist and th or nil)
end

// 2.1 Metal Sonic

// P_Boss9Thinker ported from 2.1.25
addHook("BossThinker", function(mobj)
	if mobj.state == mobj.info.spawnstate
		mobj.flags2 = $ & ~MF2_FRET
	end
	
	if not mobj.tracer
		local last = nil
		
		// Initialize the boss, spawn jet fumes, etc.
		mobj.threshold = 0
		mobj.reactiontime = 0
		mobj.watertop = mobj.floorz + 32*FRACUNIT
		A_21BossJetFume(mobj, 2, 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()
			if th.type == MT_BOSS9GATHERPOINT // Replace MT_BOSS9GATHERPOINT if it changes in the future
				if last
					last.hnext = th
				else
					mobj.hnext = th
				end
				th.hprev = last
				last = th
			end
		end
	end
	
	if mobj.health <= 0
		return true
	end
	
	if not mobj.target or not (mobj.target.flags & MF_SHOOTABLE)
		P_BossTargetPlayer(mobj, false)
		if mobj.target and (not P_IsObjectOnGround(mobj.target) or (mobj.target.player.pflags & PF_SPINNING))
			mobj.target = nil	// Wait for them to hit the ground first
		end
		if not mobj.target // 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
			mobj.fuse = 10*TICRATE
		end
	end
	
	// AI goes here.
		local danger = true
		local angle
		if mobj.threshold
			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
			local spawner
			local dist = 0
			angle = 0x06000000*leveltime
			
			// Alter your energy bubble's size/position
			if mobj.health > 3
				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
				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)
					break
				end
				spawner = spawner.hnext
			end
			if spawner
				local missile = P_SpawnMetalMissile(spawner, mobj, MT_21MSGATHER)
				missile.momz = FixedDiv(missile.momz, 7*FRACUNIT/4)
				if dist == 0
					missile.fuse = 0
				else
					missile.fuse = dist/FixedHypot(missile.momx, missile.momy)
				end
				if missile.fuse > mobj.fuse
					P_RemoveMobj(missile)
				end
			end
		end
		
		// Pre-threshold reactiontime stuff for attack phases
		if mobj.reactiontime and mobj.movecount == 3
			if mobj.movedir == 0 or mobj.movedir == 2 // Pausing between bounces in the pinball phase
				if mobj.target.player.powers[pw_tailsfly] // 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)
					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
			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
				if not (mobj.threshold&1)
					local missile
					if mobj.info.seesound
						S_StartSound(mobj, mobj.info.seesound)
					end
					mobj.state = mobj.info.missilestate
					if mobj.extravalue1 == 3
						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
						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
						local i
						local spread
						missile.flags = $|MF_MISSILE
						for i = 0, 4, 1 do
							if i == 2
								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
						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)
				if mobj.state != mobj.info.seestate
					mobj.state = mobj.info.seestate
				end
				if mobj.movedir == 0 // mobj health == 1
					P_InstaThrust(mobj, mobj.angle, 38*FRACUNIT)
				elseif mobj.health == 3
					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) // Hit a wall? Find a direction to bounce
					mobj.threshold = $-1
					//print(mobj.threshold)
					if mobj.threshold
						//print("Does this run?")
						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) // 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
							//print("Aim for the player")
						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
							//print("Bounce")
						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)
				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
			// Go into stun after dodge
				// from 3*TICRATE down to 1.25*TICRATE
				//mobj->reactiontime = 5*TICRATE/4 + (FixedMul((7*TICRATE/4)<<FRACBITS, FixedDiv((mobj->health-1)<<FRACBITS, (mobj->info->spawnhealth-1)<<FRACBITS))>>FRACBITS);
				// from 3*TICRATE down to 2*TICRATE
				// LOOK OVER THIS LINE
				/*
				mobj->reactiontime = 2*TICRATE + 
									(
										FixedMul(
											(1*TICRATE)<<FRACBITS,
											FixedDiv(
												(mobj->health-1)<<FRACBITS,
												(mobj->info->spawnhealth-1)<<FRACBITS
											)
										)>>FRACBITS
									)
				*/
				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_SPECIAL|MF_SHOOTABLE
				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
			mobj.fuse = 0
		end
		
		// reactiontime is used for delays.
		if mobj.reactiontime
			// Stunned after vector form
			if mobj.movedir > ANGLE_180 and mobj.movedir <= ANGLE_MAX
				/*
				FixedAngle(
					FixedMul(
						AngleFixed(
							InvAngle(mobj->movedir)
						),
						FixedDiv(
							mobj->reactiontime<<FRACBITS,
							24<<FRACBITS
						)
					)
				)
				*/
				mobj.angle = $ - 
							FixedAngle(
								FixedMul(
									AngleFixed(
										InvAngle(mobj.movedir)
									),
									FixedDiv(
										LeftShift(mobj.reactiontime, FRACBITS),
										LeftShift(24, FRACBITS)
									)
								)
							)
				//print("InvAngle" + AngleFixed(InvAngle(mobj.movedir)))
			else
				/*
				FixedAngle(
					FixedMul(
						AngleFixed(mobj->movedir),
						FixedDiv(
							mobj->reactiontime<<FRACBITS,
							24<<FRACBITS
						)
					)
				)
				*/
				mobj.angle = $ + 
							FixedAngle(
								FixedMul(
									AngleFixed(mobj.movedir),
									FixedDiv(
										LeftShift(mobj.reactiontime, FRACBITS),
										LeftShift(24, FRACBITS)
									)
								)
							)
				//print("RegularAngle" + AngleFixed(mobj.movedir))
			end
			mobj.reactiontime = $-1
			if not mobj.reactiontime
				// 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_SPECIAL|MF_SHOOTABLE)
		
		if mobj.health <= mobj.info.damage and mobj.fuse and not (mobj.fuse%TICRATE)
			A_BossScream(mobj, 1, MT_BOSSEXPLODE)
		end
		
		// Don't move if we're still in pain!
		if (mobj.flags2 & MF2_FRET)
			return true
		end
		
		if mobj.state == mobj.info.raisestate
		// Charging energy
			if mobj.momx != 0 or mobj.momy != 0 // 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
			// It's time to attack! What are we gonna do?!
			//print(mobj.target)
			if mobj.movecount == 1
				// Okay, we're up? Good, time to gather energy...
				if mobj.health > mobj.info.damage
				// 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
					S_StartSound(mobj, mobj.info.attacksound)
				end
				A_FaceTarget(mobj)
			elseif mobj.movecount == 2
				// We're all charged and ready now! Unleash the fury!!
				if mobj.health > mobj.info.damage
					local removemobj = mobj.tracer
					mobj.tracer = mobj.hnext
					P_RemoveMobj(removemobj)
				end
				if mobj.health <= mobj.info.damage
					// Attack 1: Pinball dash!
					if mobj.health == 1
						mobj.movedir = 0
					else
						mobj.movedir = 2
					end
					if mobj.info.seesound
						S_StartSound(mobj, mobj.info.seesound)
					end
					mobj.state = mobj.info.seestate
					if mobj.movedir == 2
						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
						mobj.extravalue1 = 0
					 elseif mobj.health >= 5
						mobj.extravalue1 = 2
					 elseif mobj.health >= 4
						mobj.extravalue1 = 1
					 else
						mobj.extravalue1 = 3
					 end
					 
					 if mobj.extravalue1 == 1 // shoot 3 times
						mobj.threshold = 3*2
					 elseif mobj.extravalue1 == 3 // 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
				// 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
					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
			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
				mobj.angle = $+(angle/8)
			else
				mobj.angle = $-(InvAngle(angle)/8)
			end
			//A_FaceTarget(mobj)
			
			// Check if we're being attacked
			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]
			)
				danger = false
			end
			if mobj.target.x+mobj.target.radius+abs(mobj.target.momx*2) < mobj.x-mobj.radius
				danger = false
			end
			if mobj.target.x-mobj.target.radius-abs(mobj.target.momx*2) > mobj.x+mobj.radius
				danger = false
			end
			if mobj.target.y+mobj.target.radius+abs(mobj.target.momy*2) < mobj.y-mobj.radius
				danger = false
			end
			if mobj.target.y-mobj.target.radius-abs(mobj.target.momy*2) > mobj.y+mobj.radius
				danger = false
			end
			if mobj.target.z+mobj.target.height+mobj.target.momz*2 < mobj.z
				danger = false
			end
			if mobj.target.z+mobj.target.momz*2 > mobj.z+mobj.height
				danger = false
			end
			if danger
				// An incoming attack is detected! What should we do?!
				// Go into vector form!
				/*
				FixedAngle(
					FixedMul(
						AngleFixed(ANGLE_11hh),
						FixedDiv(
							(mobj->info->spawnhealth - mobj->health)<<FRACBITS,
							(mobj->info->spawnhealth-1)<<FRACBITS
						)
					)
				)
				*/
				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)
					mobj.movedir = InvAngle(mobj.movedir)
				end
				//print(mobj.movedir)
				//print(ANGLE_180)
				/*
				FixedMul(
					24<<FRACBITS, 
					FixedDiv(
						(mobj->info->spawnhealth - mobj->health)<<FRACBITS, 
						(mobj->info->spawnhealth-1)<<FRACBITS
					)
				)>>FRACBITS
				*/
				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
					S_StartSound(mobj, mobj.info.activesound)
				end
				if mobj.info.painchance
					mobj.state = mobj.info.painchance
				end
				return true
			end
			
			// 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
				P_Thrust(mobj, mobj.angle, -4*FRACUNIT)
			elseif dist > 180*FRACUNIT
				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)
	return true
end, MT_21METALSONIC_BATTLE)

addHook("BossDeath", function(mo)
	A_21BossDeath(mo)
	return true
end, MT_21METALSONIC_BATTLE)

mobjinfo[MT_21METALSONIC_BATTLE] = {
	//$Name 2.1 Metal Sonic
	//$Sprite 21EMA1
	//$Category Bosses
	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_odmpai,
	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,
	raisestate = S_21METALSONIC_RAISE
}

states[S_21METALSONIC_FLOAT] = {
	sprite = SPR_OMTS,
	frame = E,
	tics = -1
}

states[S_21METALSONIC_VECTOR] = {
	sprite = SPR_OMTS,
	frame = M,
	tics = -1,
	nextstate = S_21METALSONIC_STUN
}

states[S_21METALSONIC_STUN] = {
	sprite = SPR_OMTS,
	frame = A,
	tics = -1,
	nextstate = S_21METALSONIC_FLOAT
}

states[S_21METALSONIC_BLOCK] = {
	sprite = SPR_OMTS,
	frame = N,
	tics = -1
}

states[S_21METALSONIC_RAISE] = {
	sprite = SPR_OMTS,
	frame = N,
	tics = 40,
	nextstate = S_21METALSONIC_GATHER
}

states[S_21METALSONIC_GATHER] = {
	sprite = SPR_OMTS,
	frame = O,
	tics = -1
}

states[S_21METALSONIC_DASH] = {
	sprite = SPR_OMTS,
	frame = J,
	tics = -1,
	nextstate = S_21METALSONIC_BOUNCE
}

states[S_21METALSONIC_BOUNCE] = {
	sprite = SPR_OMTS,
	frame = O,
	tics = -1
}

states[S_21METALSONIC_SHOOT] = {
	sprite = SPR_OMTS,
	frame = N,
	tics = -1,
	nextstate = S_21METALSONIC_GATHER
}

states[S_21METALSONIC_PAIN] = {
	sprite = SPR_OMTS,
	frame = L,
	tics = 40,
	action = A_Pain,
	nextstate = S_21METALSONIC_FLOAT
}

states[S_21METALSONIC_DEATH] = {
	sprite = SPR_OMTS,
	frame = A,
	tics = -1,
	action = A_BossDeath
}

states[S_21METALSONIC_FLEE1] = {
	sprite = SPR_OMTS,
	frame = D,
	tics = 4,
	nextstate = S_21METALSONIC_FLEE2
}

states[S_21METALSONIC_FLEE2] = {
	sprite = SPR_OMTS,
	frame = E,
	tics = 4,
	action = A_BossScream,
	var1 = 0,
	var2 = MT_BOSSEXPLODE,
	nextstate = S_21METALSONIC_FLEE3
}

states[S_21METALSONIC_FLEE3] = {
	sprite = SPR_OMTS,
	frame = F,
	tics = 4,
	nextstate = S_21METALSONIC_FLEE4
}

states[S_21METALSONIC_FLEE4] = {
	sprite = SPR_OMTS,
	frame = E,
	tics = 4,
	nextstate = 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] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|A,
	tics = 1,
	nextstate = S_21MSSHIELD_F2
}

states[S_21MSSHIELD_F2] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|B,
	tics = 1,
	nextstate = S_21MSSHIELD_F3
}

states[S_21MSSHIELD_F3] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|C,
	tics = 1,
	nextstate = S_21MSSHIELD_F4
}

states[S_21MSSHIELD_F4] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|D,
	tics = 1,
	nextstate = S_21MSSHIELD_F5
}

states[S_21MSSHIELD_F5] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|E,
	tics = 1,
	nextstate = S_21MSSHIELD_F6
}

states[S_21MSSHIELD_F6] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|F,
	tics = 1,
	nextstate = S_21MSSHIELD_F7
}

states[S_21MSSHIELD_F7] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|G,
	tics = 1,
	nextstate = S_21MSSHIELD_F8
}

states[S_21MSSHIELD_F8] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|H,
	tics = 1,
	nextstate = S_21MSSHIELD_F9
}

states[S_21MSSHIELD_F9] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|I,
	tics = 1,
	nextstate = S_21MSSHIELD_F2
}

states[S_21MSSHIELD_F10] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|J,
	tics = 1,
	nextstate = S_21MSSHIELD_F11
}

states[S_21MSSHIELD_F11] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|K,
	tics = 1,
	nextstate = S_21MSSHIELD_F12
}

states[S_21MSSHIELD_F12] = {
	sprite = SPR_OMTC,
	frame = FF_FULLBRIGHT|FF_TRANS30|L,
	tics = 1,
	nextstate = S_21MSSHIELD_F1
}

// 2.1 Metal Sonic Gathering Orb

mobjinfo[MT_21MSGATHER] = {
	doomednum = -1,
	spawnstate = S_21JETFUME1,
	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] = {
	sprite = SPR_OENR,
	frame = A|FF_FULLBRIGHT|FF_TRANS30,
	tics = 1,
	nextstate = S_OLDENERGYBALL2
}

states[S_OLDENERGYBALL2] = {
	sprite = SPR_NULL,
	tics = 1,
	nextstate = S_OLDENERGYBALL1
}
