/*hey! thanks for stopping by to read this. i've been working on this for the better part of a month.
it's completely okay if you lift some lines of code for your own projects, go crazy with it, but
please remember to credit me, whether in the description of the message board post, or in the
code comments themselves.

SilverVortex, 03/01/2021 (holy shit it's been a long time)
*/

--11/07/2021: we still be neglecting amie

rawset(_G,"flowabilities",{})

freeslot(
	"S_BOOST1",
	"S_BOOST2",
	"S_BOOST3",
	"S_BOOST4",
	"S_BOOST5",
	"SPR_BSTD",
	"MT_BOOST",
	"S_PLAY_AMYFALL",
	"SPR_AMFL",
	"SPR_UPCK",
	"S_UPCORK",
	"MT_UPCORK"
)

states[S_UPCORK] = {SPR_UPCK, A, -1, nil, 1, 2, S_UPCORK}

mobjinfo[MT_UPCORK] = {
	doomednum = -1,
	spawnstate = S_UPCORK,
	spawnhealth = 1000,
	radius = 64 << FRACBITS,
	height = 8 << FRACBITS,
	flags = MF_MISSILE,	-- This will help them die on walls properly so that we don't have to do it ourselves.
}

states[S_PLAY_AMYFALL] = {SPR_PLAY, SPR2_AMFL, -1, nil, 0, 0, S_PLAY_FALL}

mobjinfo[MT_BOOST] = {
	doomednum = -1,
	spawnstate = S_BOOST1,
	flags = MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_NOCLIPHEIGHT
}

states[S_BOOST1] = {
	sprite = SPR_BSTD,
	frame = TR_TRANS40|FF_PAPERSPRITE|A,
	tics = 10,
	next = S_BOOST2
}

states[S_BOOST2] = {
	sprite = SPR_BSTD,
	frame = TR_TRANS30|FF_PAPERSPRITE|A,
	tics = 10,
	next = S_BOOST3
}

states[S_BOOST3] = {
	sprite = SPR_BSTD,
	frame = TR_TRANS20|FF_PAPERSPRITE|A,
	tics = 10,
	next = S_BOOST4
}

states[S_BOOST4] = {
	sprite = SPR_BSTD,
	frame = TR_TRANS10|FF_PAPERSPRITE|A,
	tics = 10,
	next = S_BOOST5
}

states[S_BOOST5] = {
	sprite = SPR_BSTD,
	frame = TR_TRANS10|FF_PAPERSPRITE|A,
	tics = 10,
	next = S_NULL
}

/////////////
//Sonic's abilities
/////////////
addHook("PlayerThink",function(p)
	if p.homattack == nil
		p.homattack = false
		p.canhome = false
		p.weakthok = false
		p.jetboost = false
	end
	if P_IsObjectOnGround(p.mo)
		p.homattack = false
		p.canhome = false
		p.weakthok = false
		p.jetboost = false
	end
	if p.mo.eflags & MFE_SPRUNG
		p.homattack = false
		p.canhome = false
		p.weakthok = false
		p.jetboost = false
	end
	if p.mo.eflags & MFE_JUSTHITFLOOR
		p.jetboost = false
		p.homattack = false
		p.canhome = false
		p.weakthok = false
	end
	if P_PlayerInPain(p)
		p.homattack = false
	end
	
	if p.mo.skin == "sonic"
		if p.mo.eflags & MFE_JUSTHITFLOOR
		and p.homattack
			p.pflags = $|PF_SPINNING
			p.mo.state = S_PLAY_ROLL
		end
	end
end)
local function homingattack(p) //allow the player to constantly pursue their target after a jump press
	local mo = p.mo
	
	if p.mo.skin == "sonic"
	and mo.hometarget and mo.hometarget.valid and mo.hometarget.health //make sure we have a target
		if p.homattack //need PF_THOKKED to start zero-ing in
			local zdist = (P_MobjFlip(mo) == 1) and (mo.hometarget.z + mo.hometarget.height) - (mo.z + mo.height) or (mo.hometarget.z - mo.z) //variables for distance and stuff!
			local dist = P_AproxDistance(P_AproxDistance(mo.hometarget.x - mo.x, mo.hometarget.y - mo.y), zdist)
			local angle = R_PointToAngle2(mo.x, mo.y, mo.hometarget.x, mo.hometarget.y)
			local zangle = R_PointToAngle2(0, mo.z, P_AproxDistance(mo.x - mo.hometarget.x, mo.y - mo.hometarget.y), mo.hometarget.z)
			local maxspeed = ((p.powers[pw_super] or p.powers[pw_sneakers] or MODID == 14) and 2 or 1)*FixedMul(p.normalspeed, mo.scale)
			local x = FixedMul(maxspeed, FixedMul(cos(angle), cos(zangle)))
			local y = FixedMul(maxspeed, FixedMul(sin(angle), cos(zangle)))
			local z = FixedMul(maxspeed, sin(zangle))
			
			//listen buddy i like special effects in my sonic the hedgehog videogame
			local thokfx = P_SpawnMobj(p.mo.x,p.mo.y,p.mo.z,MT_THOK)
			thokfx.color = p.mo.color
			thokfx.fuse = 8
			thokfx.destscale = 0
			thokfx.blendmode = AST_ADD
			
			//lol i remember i didn't have this one here and i hit a target while in the falling state as super sonic lolllll
			p.mo.state = S_PLAY_JUMP
			
			p.drawangle = R_PointToAngle2(mo.x, mo.y, mo.hometarget.x, mo.hometarget.y)
			p.mo.angle = R_PointToAngle2(mo.x, mo.y, mo.hometarget.x, mo.hometarget.y)
			
			if not p.powers[pw_super]
				mo.momx = FixedMul(FixedDiv(mo.hometarget.x-mo.x, dist), 55*FRACUNIT)
				mo.momy = FixedMul(FixedDiv(mo.hometarget.y-mo.y, dist), 55*FRACUNIT)
				mo.momz = FixedMul(FixedDiv(zdist, dist), 55*FRACUNIT)
			end
			
			//momentum now works the same when you're super sonic lol
			if p.powers[pw_super]
				mo.momx = FixedMul(FixedDiv(mo.hometarget.x-mo.x, dist), 100*FRACUNIT)
				mo.momy = FixedMul(FixedDiv(mo.hometarget.y-mo.y, dist), 100*FRACUNIT)
				mo.momz = FixedMul(FixedDiv(zdist, dist), 100*FRACUNIT)
			end
			
			if p.powers[pw_pushing]
				p.homattack = false
				p.playerline = lines[p.lastlinehit]
				
				if p.playerline
					p.bounceside = P_PointOnLineSide(p.mo.x, p.mo.y, p.playerline)
					P_InstaThrust(p.mo,p.bounceside + ANGLE_180, 5*FRACUNIT)
				end
			end
		end
	end
end
addHook("PlayerThink",homingattack) //needs to be active for every frame, but only starts when we hit jump while we have a target in range.

addHook("AbilitySpecial",function(p)
	local mo = p.mo
	local angle = mo.angle

	local momangle = R_PointToAngle2(0,0,p.rmomx,p.rmomy)

	if p.mo.skin == "amy"
		if not p.amyjump
		and not (p.pflags & PF_THOKKED)
		and not p.hammerjumpshitidkfuckoff
			P_SetObjectMomZ(p.mo,7*FRACUNIT,false)
			P_InstaThrust(p.mo,momangle,2*p.speed/3)
			
			p.amyjump = true
			
			p.mo.state = S_PLAY_SPRING
			
			S_StartSound(p.mo,sfx_jump)
			
			for i = 1,6
				local d = P_SpawnMobj(p.mo.x,p.mo.y,p.mo.z+((p.mo.eflags & MFE_VERTICALFLIP)/MFE_VERTICALFLIP * (p.mo.height - mobjinfo[MT_SPINDUST].height)),MT_SPINDUST)
				d.colorized = true
				d.color = p.mo.color
				d.angle = p.mo.angle + (ANG1*60*i)
				P_InstaThrust(d,d.angle,10*FRACUNIT)
			end
		end
		return true
	end

	if mo and mo.skin == "sonic"
		if not p.homattack
			/*if p.pflags & PF_JUMPED 
				if not mo.hometarget
					mo.hometarget = P_LookForEnemies(p,true,true)
				end
			end*/
			if mo.hometarget
				p.homattack = true
				S_StartSound(mo,sfx_thok)
				homingattack(p) //execute your homing attack
			else
				if p.weakthok == false
					local weakthokeffect = P_SpawnMobjFromMobj(p.mo,0,0,0,MT_THOK)
					weakthokeffect.scale = FRACUNIT
					weakthokeffect.destscale = FRACUNIT*6
					weakthokeffect.color = p.mo.color
					S_StartSound(mo,sfx_thok)
					p.weakthok = true
					if not p.powers[pw_super]
						if p.speed < 50*FRACUNIT
							P_InstaThrust(mo,angle,40*FRACUNIT)
						else
							P_InstaThrust(mo,angle,p.speed)
						end
					else
						P_InstaThrust(mo,angle,90*FRACUNIT)
					end
				end
			end
		end
		
		return true
	end
end)

addHook("PlayerThink",function(p)
	if p.amyjump == nil
		p.amyjump = false
	end
	
	if P_IsObjectOnGround(p.mo)
	or p.mo.eflags & MFE_JUSTHITFLOOR
	or p.mo.eflags & MFE_SPRUNG
	or P_PlayerInPain(p)
	or p.playerstate != PST_LIVE
		p.amyjump = false
	end
	
	if p.amyjump
		local ghost = P_SpawnGhostMobj(p.mo)
		ghost.fuse = 4
	end

	if skins[p.mo.skin].flags & SF_NOJUMPSPIN
		if p.mo.eflags & MFE_SPRUNG
		and not P_IsObjectOnGround(p.mo)
			p.pflags = $|PF_JUMPED|PF_NOJUMPDAMAGE
		end
		
		if not P_IsObjectOnGround(p.mo)
		and not P_PlayerInPain(p)
		and not p.exiting
		and p.playerstate == PST_LIVE
		and not (p.pflags & PF_JUMPED)
		and not (p.pflags & PF_THOKKED)
		and not (p.powers[pw_carry])
		and not p.hammerjumpshitidkfuckoff
			p.pflags = $|PF_JUMPED|PF_NOJUMPDAMAGE
		end
	end
	
	if p.mo.skin == "sonic"
		if not p.mo.hometarget
		and p.pflags & PF_JUMPED
			p.mo.hometarget = P_LookForEnemies(p,true,true)
		end
	end

	local mom = FixedDiv(FixedHypot(p.rmomx,p.rmomy),p.mo.scale)
	local friction = FixedDiv(p.mo.friction,p.mo.movefactor)
	local momangle = R_PointToAngle2(0,0,p.rmomx,p.rmomy)
	
	if p.amyinstashield == nil
		p.amyinstashield = false
	end
	
	
	--as you can see i gave up creating new and unique variables so that i don't clash with other mods
	if p.hammerjumpshitidkfuckoff == nil
		p.hammerjumpshitidkfuckoff = false
	end
	
	if p.hammerjumpshitidkfuckoff == true
		p.pflags = $ & ~PF_NOJUMPDAMAGE|PF_JUMPED
	end
	
	if p.mo.eflags & MFE_JUSTHITFLOOR
	or P_IsObjectOnGround(p.mo)
	or p.mo.eflags & MFE_SPRUNG
	or P_PlayerInPain(p)
	or p.playerstate != PST_LIVE
	or P_IsObjectInGoop(p.mo)
		p.hammerjumpshitidkfuckoff = false
		p.pflags = $ & ~PF_JUMPED
	end
	
	if p.mo.skin == "amy"
		if p.mo.state == S_PLAY_MELEE_LANDING
			if p.cmd.buttons & BT_JUMP
				P_InstaThrust(p.mo,momangle,p.speed + 20*FRACUNIT)
				
				if p.speed > skins[p.mo.skin].normalspeed
					P_SetObjectMomZ(p.mo,(4*p.speed/14),false)
				else
					P_SetObjectMomZ(p.mo,12*FRACUNIT,false)
				end
				
				p.mo.state = S_PLAY_ROLL
				p.pflags = $ & ~PF_THOKKED
				
				p.hammerjumpshitidkfuckoff = true
			end
		end
		
		if p.mo.state == S_PLAY_TWINSPIN
		and not P_IsObjectOnGround(p.mo)
		and not P_PlayerInPain(p)
		and not (p.powers[pw_carry])
		and p.playerstate == PST_LIVE
			p.mo.colorized = true
			p.amyinstashield = true
			p.mo.frame = ($1 & FF_FRAMEMASK)|FF_FULLBRIGHT
			if p.spinbuf == 1
				for i = 0, 3
					local amysparkle = P_SpawnMobj(p.mo.x+(P_RandomRange(4,-4)*FRACUNIT),p.mo.y+(P_RandomRange(4,-4)*FRACUNIT),p.mo.z+(p.mo.height/2)+(P_RandomRange(2,-2)*FRACUNIT),MT_IVSP)
					amysparkle.color = p.mo.color
					amysparkle.colorized = true
					amysparkle.scale = p.mo.scale * 3 / 2
					
					amysparkle.momx = P_RandomRange(3,-3)*FRACUNIT
					amysparkle.momy = P_RandomRange(3,-3)*FRACUNIT
					amysparkle.momz = P_RandomRange(3,-3)*FRACUNIT
				end
			end
		else
			p.mo.colorized = false
		end
	end
	
	if p.bouncecons == nil
		p.bouncecons = false
	end
	
	if P_IsObjectOnGround(p.mo)
	or p.mo.eflags & MFE_JUSTHITFLOOR
	and not (p.pflags & PF_BOUNCING)
		p.bouncecons = false
	end
	
	if p.mo.skin == "fang"
		if p.pflags & PF_BOUNCING
		and p.cmd.buttons & BT_JUMP
		and not p.bouncecons
			if not P_IsObjectOnGround(p.mo)
			and not (p.mo.eflags & MFE_JUSTHITFLOOR)
				p.mo.momx = FixedMul(P_ReturnThrustX(nil,momangle,mom),p.mo.scale)
				p.mo.momy = FixedMul(P_ReturnThrustY(nil,momangle,mom),p.mo.scale)
				
				p.bouncecons = true
			end
		end
	end
	
	/*if p.snapcons == nil
		p.snapcons = false
	end
	
	if p.mo.skin == "shadow"
		if p.mo.state == S_CHAOSSNAP
		and not P_IsObjectOnGround(p.mo)
			p.snapcons = true
		end
		
		if p.snapcons == true
		and p.mo.state ~= S_CHAOSSNAP
			p.mo.momx = FixedMul(P_ReturnThrustX(nil,momangle,mom),p.mo.scale)
			p.mo.momy = FixedMul(P_ReturnThrustY(nil,momangle,mom),p.mo.scale)
		end
	end*/

	
	p.lastz = p.mo.z

	p.lastangleturn = p.cmd.angleturn

	if P_IsObjectOnGround(p.mo)
		if p.speed > skins[p.mo.skin].normalspeed/2
			p.normalspeed = max(p.normalspeed,FixedDiv(mom,friction))
		else
			p.normalspeed = skins[p.mo.skin].normalspeed
		end
		
		if p.lastz > p.mo.z
			P_Thrust(p.mo,momangle,p.speed/80)
		end
		
		if p.speed > skins[p.mo.skin].normalspeed
			local ghost = P_SpawnGhostMobj(p.mo)
			ghost.fuse = 3
			ghost.blendmode = AST_ADD
			ghost.colorized = true
			ghost.color = p.mo.color
			
			if ghost.tracer and ghost.tracer.valid
				ghost.tracer.fuse = 3
				ghost.tracer.blendmode = AST_ADD
				ghost.tracer.colorized = true
				ghost.tracer.color = p.mo.color
			end
			
			if p.speed > 3*skins[p.mo.skin].normalspeed/2
				local dust = P_SpawnMobj(
				p.mo.x + P_RandomRange(-7,7)*FRACUNIT,
				p.mo.y + P_RandomRange(-7,7)*FRACUNIT,
				p.mo.z + P_RandomRange(0,4)*FRACUNIT + ((p.mo.eflags & MFE_VERTICALFLIP)/MFE_VERTICALFLIP * (p.mo.height - mobjinfo[MT_SPINDUST].height)),
				MT_SPINDUST)
				
				dust.momx = P_RandomRange(-7,7)*FRACUNIT
				dust.momy = P_RandomRange(-7,7)*FRACUNIT
				dust.momz = P_RandomRange(0,4)*FRACUNIT
			end
			
			if p.speed > skins[p.mo.skin].normalspeed*2
			and p.mo.skin ~= "modernsonic"
				p.acceleration = skins[p.mo.skin].acceleration/6
			else
				p.acceleration = skins[p.mo.skin].acceleration
			end
		end
		
		if p.speed > 3*skins[p.mo.skin].normalspeed/2
			/*if ((FixedAngle(p.cmd.angleturn-p.lastangleturn)/FRACUNIT) > 2)
			and ((FixedAngle(p.cmd.angleturn-p.lastangleturn)/FRACUNIT) < 90)
			or ((FixedAngle(p.cmd.angleturn-p.lastangleturn)/FRACUNIT) < -2)
			and ((FixedAngle(p.cmd.angleturn-p.lastangleturn)/FRACUNIT) > -90)
				p.mo.state = S_PLAY_SPINDASH
				p.drifting = true
				
				local fx = P_SpawnMobj(p.mo.x,p.mo.y,p.mo.z,MT_SPINDUST)
			else			
				if p.drifting
					if p.mo.state == S_PLAY_SPINDASH
						p.mo.state = S_PLAY_RUN
						p.drifting = false
					end
				end
			end
		else
			if p.drifting
				if p.mo.state == S_PLAY_SPINDASH
					p.mo.state = S_PLAY_RUN
					p.drifting = false
				end
			end*/
		end
	else
		p.normalspeed = skins[p.mo.skin].normalspeed
		p.acceleration = skins[p.mo.skin].acceleration
	end
	
	if p.powers[pw_justlaunched]
		P_SetObjectMomZ(p.mo,p.mo.momz/5,true)
	end

	local mo = p.mo
	mo.hometarget = P_LookForEnemies(p,true,true)
	
	//target availability check for the reticle arrow
	if mo.hometarget and mo.hometarget.valid and mo.hometarget.health
	and mo.skin == "sonic"
	and p.pflags & PF_JUMPED
		if not p.powers[pw_shield] == SH_ATTRACT
			P_SpawnLockOn(p,mo.hometarget,S_LOCKON2) //the fact that this isn't highlighted makes me nervous that i'm getting one of the arguments wrong lol
		else
			P_SpawnLockOn(p,mo.hometarget,S_LOCKON1)
		end
	end
	
	if (p.charflags & SF_DASHMODE)
		if p.powers[pw_carry]
		or p.mo.state == S_PLAY_PAIN
		or p.playerstate == PST_DEAD
		or p.exiting
			p.wasmetdashing = false
		end
		if not P_IsObjectOnGround(p.mo)
			if p.wasmetdashing == true
				p.dashmode = max(p.dashmode, 3*36)
			end
		end
	end
	
//anti-airwalk code
	if mo.skin ~= "metalsonic"
		if mo.state == S_PLAY_STND or mo.state == S_PLAY_WAIT or mo.state == S_PLAY_WALK or mo.state == S_PLAY_RUN
			if mo.momz > 0
				mo.state = S_PLAY_SPRING //use the spring animation if we're going up
			elseif mo.momz < 0
				mo.state = S_PLAY_FALL //use the fall animation if we're going down
			end
		else return end //otherwise just do normal shit lol
		
		if p.jetboost
		and p.powers[pw_carry] > 0
			p.jetboost = false
			p.boosttimer = 0
		end
	end
	
	//cool jump visual effect
	if (p.pflags & PF_JUMPED) and not p.lastticjumped
		P_SpawnGhostMobj(p.mo)
	end
	p.lastticjumped = p.pflags & PF_JUMPED
end)

//for some reason knuckles' code won't work without me putting it in a separate PlayerThink hook so...here it is...
addHook("PlayerThink",function(p)
	
	if p.gunlimit == nil
		p.gunlimit = 5
	end
	
	if p.curltimer == nil
		p.curltimer = 0
	end
	
	if p.curltimer > 0
		p.curltimer = $-1
	end
	
	if P_IsObjectOnGround(p.mo)
	or p.mo.eflags & MFE_JUSTHITFLOOR
	or p.mo.eflags & MFE_SPRUNG
		p.gunlimit = 5
	end
	
	if p.mo.skin == "fang"
		if p.gunlimit < 5
		and p.curltimer == 0
		and not P_PlayerInPain(p)
		and p.playerstate == PST_LIVE
		and not (p.pflags & PF_THOKKED)
		and not (p.powers[pw_carry])
			p.mo.state = S_PLAY_FALL
			p.pflags = $|PF_NOJUMPDAMAGE
		end
	
		if p.spinbuf == 1
		and not P_IsObjectOnGround(p.mo)
		and not (p.mo.eflags & MFE_JUSTHITFLOOR)
		and not P_PlayerInPain(p)
		and p.playerstate == PST_LIVE
		and not (p.pflags & PF_SLIDING)
		and not (p.pflags & PF_BOUNCING)
		and not (p.powers[pw_carry] > 0)
		and not (p.powers[pw_shield] == 2)
		and not (p.powers[pw_shield] == 3)
		and not (p.powers[pw_shield] > 4)
		and p.gunlimit > 0
		and p.curltimer == 0
			p.gunlimit = $-1
			
			p.curltimer = 10
			
			P_SetObjectMomZ(p.mo,5*FRACUNIT,false)
			local bullet = P_SpawnMobj(p.mo.x,p.mo.y,p.mo.z+((p.mo.eflags & MFE_VERTICALFLIP)/MFE_VERTICALFLIP * (p.mo.height - mobjinfo[MT_UPCORK].height)),MT_UPCORK)
			
			if p.mo.eflags & MFE_VERTICALFLIP
				P_SetObjectMomZ(bullet,10*FRACUNIT,false)
			else
				P_SetObjectMomZ(bullet,-10*FRACUNIT,false)
			end
			bullet.target = p.mo
			
			if p.mo.eflags & MFE_VERTICALFLIP
				bullet.eflags = $|MFE_VERTICALFLIP
				bullet.flags2 = $|MF2_OBJECTFLIP
			end
			
			p.mo.state = S_PLAY_ROLL
			p.pflags = $ & ~PF_NOJUMPDAMAGE
			
			if bullet.eflags & MFE_JUSTHITFLOOR
			or P_IsObjectOnGround(bullet)
				P_RemoveMobj(bullet)
				S_StartSound(p.mo,sfx_corkh)
			end
			S_StartSound(p.mo, sfx_corkp)
		end
	end
	
	//=================================================
	//knuckles' various code things (some things lifted from CrossMomentum, like the removal of the momentum cut)
	//=================================================
	
	local mo = p.mo
	local orientation = 1
	local scale = p.mo.scale //Normalspeed calculations are affected by scale
	local friction = FixedDiv(p.mo.friction,p.mo.movefactor) //Reversing friction is required for sustaining ground momentum
	local pmom = FixedDiv(FixedHypot(p.rmomx,p.rmomy),scale) //Current speed, scaled for normalspeed calculations
	local pwup = (p.powers[pw_sneakers]) or (p.powers[pw_super]) //Speed-ups multiply max allowed speed by 5/3
	local momangle = R_PointToAngle2(0,0,p.rmomx,p.rmomy) //Used for angling new momentum in ability cases
	
	//variable multiplier to autoreverse momentum when flipped
	if (p.mo.eflags & MFE_VERTICALFLIP)
		orientation = -1
	else
		orientation = 1
	end
	
	
	if p.mo.skin == "knuckles"
		if p.pflags & PF_GLIDING
			P_Thrust(p.mo,p.mo.angle,FRACUNIT/4) //gradually gain speed while gliding
			if p.powers[pw_super] and p.cmd.buttons & ~BT_SPIN
				P_SetObjectMomZ(p.mo,0,false)
			end
		end
		if p.powers[pw_super]
			p.charflags = $|SF_MULTIABILITY
		else
			p.charflags = $ & ~SF_MULTIABILITY
		end
		
		//ladies and gents, the dive boost
		
		if p.cmd.buttons & BT_SPIN //spspspin to win
			if p.pflags & PF_GLIDING // listen bud ya gotta be gliding
			and not P_IsObjectOnGround(p.mo) //for some reason you can also glide on the ground so this is here too
				p.mo.state = S_PLAY_ROLL //start rolling
//				p.mo.state = S_PLAY_NIGHTS_DRILL //<----the source of many laughs right here
				local drilleffect = P_SpawnMobjFromMobj(p.mo,0,0,0,MT_THOK)
				drilleffect.fuse = 6
				drilleffect.color = p.mo.color
				drilleffect.blendmode = AST_ADD
				drilleffect.destscale = 0
				
				if not p.powers[pw_super]
					p.mo.momz = $-(FRACUNIT*2)*orientation //start descending
				end
				if p.powers[pw_super]
					p.mo.momz = $-40*FRACUNIT*orientation //start descending (but super!)
				end
				P_Thrust(p.mo,p.mo.angle,2*FRACUNIT/3)
			end
		elseif p.cmd.buttons & ~BT_SPIN and p.pflags & PF_GLIDING and not P_IsObjectOnGround(p.mo)
			p.mo.state = S_PLAY_GLIDE
		end
	end
	
	//behaviour for when you hit the floor while boosting
	
	if p.mo.skin == "knuckles"
		if p.pflags & PF_GLIDING
		and p.mo.eflags & MFE_JUSTHITFLOOR
		and p.cmd.buttons & BT_SPIN
			if not p.powers[pw_super]
				p.mo.momz = (0)*FRACUNIT*orientation
			else
				p.mo.momz = 0*FRACUNIT*orientation
				P_Earthquake(mo,mo,500*FRACUNIT)
			end
			p.pflags = $ & ~PF_JUMPED & ~PF_GLIDING & ~PF_THOKKED|PF_SPINNING
			p.mo.state = S_PLAY_ROLL
	//		p.mo.state = S_PLAY_NIGHTS_DRILL
			//S_StartSound(p.mo,sfx_pstop)
			P_SpawnThokMobj(p)
			S_StartSound(p.mo,sfx_spin)
			P_Thrust(p.mo,R_PointToAngle2(0,0,p.rmomx,p.rmomy),2*FRACUNIT)
		elseif p.mo.eflags & MFE_JUSTHITFLOOR
		and p.cmd.buttons & BT_SPIN
		and p.pflags & ~PF_GLIDING
		and p.mo.state == S_PLAY_ROLL
			S_StartSound(p.mo,sfx_spin)
		end
	end
	
	if P_IsObjectOnGround(p.mo)
		p.exhausted = false
	end
	
	if p.flighttimer == nil
		p.flighttimer = 14
	end
	
	//HOLY SHIT I DID PROPER FLIGHT-CANCEL
	if mo.skin == "tails"
		if p.pflags & PF_THOKKED
			p.powers[pw_tailsfly] = p.flighttimer
			if not p.powers[pw_super] and p.cmd.buttons & BT_JUMP and p.jumpbuf == 1
			or p.cmd.buttons & BT_SPIN and p.spinbuf == 1
				p.flighttimer = $-1
			end
			if p.flighttimer < 0
				p.powers[pw_tailsfly] = 0
			end
		end
	end
	
	if P_IsObjectOnGround(mo)
		p.flighttimer = 14
	end
	
	if p.spinbuf == nil
		p.spinbuf = 0
	end
	
	if p.cmd.buttons & BT_SPIN
		p.spinbuf = $+1
	else
		p.spinbuf = 0
	end
	
//	print (p.spinbuf)

	if p.shotcooldown == nil
		p.shotcooldown = 0
	end
	if p.shotcooldown > 0
		p.shotcooldown = $-1
	end
	
//	print (p.shotcooldown)
	
	if p.mo.skin == "fang"
		
		if p.mo.state != S_PLAY_FIRE or p.mo.state != S_PLAY_FIRE_FINISH
			p.guntarget = P_LookForEnemies(p,false,true)
		end
		
		if p.guntarget
		and p.guntarget.valid
		and p.guntarget.health
			
			p.gunningangle = R_PointToAngle2(p.mo.x,p.mo.y,p.guntarget.x,p.guntarget.y)
			if p.mo.state == S_PLAY_WALK
			or p.mo.state == S_PLAY_RUN
			or p.mo.state == S_PLAY_STND
			or p.mo.state == S_PLAY_WAIT
			and P_IsObjectOnGround(p.mo)
				P_SpawnLockOn(p,p.guntarget,S_LOCKON1)
			end
		end
		if p.mo.state == S_PLAY_WALK
		or p.mo.state == S_PLAY_RUN
		or p.mo.state == S_PLAY_STND
		or p.mo.state == S_PLAY_WAIT
			if p.cmd.buttons & BT_SPIN
			and p.spinbuf == 1
			and p.shotcooldown == 0
			and p.spindown == true
				if p.guntarget and p.guntarget.valid
					P_SpawnMissile(p.mo,p.guntarget,MT_CORK)
					if not p.powers[pw_super]
						p.shotcooldown = 35
					else
						p.shotcooldown = 15
					end
				else
					P_SPMAngle(p.mo,MT_CORK,p.mo.angle)
					if not p.powers[pw_super]
						p.shotcooldown = 35
					else
						p.shotcooldown = 15
					end
				end
				if p.mo.state != S_PLAY_FIRE or p.mo.state != S_PLAY_FIRE_FINISH
					p.mo.state = S_PLAY_FIRE
					S_StartSound(p.mo, sfx_corkp)
					if not p.powers[pw_super]
						p.mo.momx = $*1/2
						p.mo.momy = $*1/2
					end
//					p.mo.momz = 3*FRACUNIT
					if p.guntarget
						p.mo.angle = p.gunningangle
						p.drawangle = p.gunningangle
					end
				end
			end
		end
		if p.spindown == nil
			p.spindown = false
		end
		if p.cmd.buttons & BT_SPIN
			p.spindown = false
		else
			p.spindown = true
		end
	end
	
	if p.bombdelay == nil
		p.bombdelay = 0
	end
	if p.bombdelay > 0
		p.bombdelay = $-1
	end
	
	if p.flightcancelled == nil
		p.flightcancelled = false
	end
	if P_IsObjectOnGround(p.mo)
		p.flightcancelled = false
	end
	
	if p.candoublejump == nil
		p.candoublejump = true
		p.canceltimer = 0
	end
	
	if P_IsObjectOnGround(p.mo)
		p.candoublejump = true
	end
	
	if p.canceltimer > 0
		p.canceltimer = $ - 1
	end
	
	local mo = p.mo

	
	if p.actiontime == nil
		p.actiontime = 0
	end
	if P_IsObjectOnGround(p.mo)
		p.actiontime = 0
	end
	
	if p.jumpbuf == nil
		p.jumpbuf = 0
	end
	if p.cmd.buttons & BT_JUMP
		p.jumpbuf = $+1
	else
		p.jumpbuf = 0
	end
	
	if p.cancelactivationtimer == nil
		p.cancelactivationtimer = 0
	end
	if p.cancelactivationtimer > 0
		p.cancelactivationtimer = $-1
	end
	
	if p.cmd.buttons & BT_JUMP and p.pflags & PF_THOKKED and p.mo.skin == "tails"
		p.cancelactivationtimer = 5
	end
	
	if p.dropdashenabled == nil
		p.dropdashenabled = "off"
	end
	if p.dropdashready == nil
		p.dropdashready = false
	end
	
//	print (p.cancelactivationtimer)
	
//	print (p.candoublejump)

	
	//====================
	//Scrapped Metal Sonic Drop Dash: Activated with the command "metdrop"
	//====================
	
	if p.dropdashenabled == "on"
		if (mo and mo.valid and mo.skin == "metalsonic")
		and (p.pflags & PF_JUMPED)
		and p.cmd.buttons & BT_SPIN
//			print ("we're ready to drop dash!")
			P_SpawnThokMobj(p)
			mo.state = S_PLAY_ROLL
			p.pflags = $ & ~PF_THOKKED | PF_SPINNING
			p.dropdashready = true
		elseif (mo and mo.valid and mo.skin == "metalsonic")
		and P_IsObjectOnGround(p.mo)
		and p.cmd.buttons & BT_SPIN
		and p.dropdashready == true
			P_Thrust(mo,mo.angle,40*FRACUNIT)
			S_StartSound(mo, sfx_zoom)
			p.pflags = $ | PF_SPINNING
			mo.state = S_PLAY_ROLL
//			print ("we're drop dashing")
			p.dropdashready = false
		end
	end
end)
addHook("PlayerThink", function(p)
//flight cancel + letting go of other players when flight is cancelled

//cancel flight into a falling state
	
	local mo = p.mo
	local orientation = 1

	if mo.eflags & MFE_VERTICALFLIP
		orientation = -1
	else
		orientation = 1
	end
	if mo.skin == "tails" //if you can't fly you better fuck off
		if p.pflags & PF_THOKKED & ~PF_SHIELDABILITY and p.mo.state == S_PLAY_FLY or p.mo.state == S_PLAY_SWIM or p.mo.state == S_PLAY_FLY_TIRED//can be either swimming or flying
			if p.cmd.buttons & BT_SPIN and p.cmd.buttons & BT_JUMP and p.jumpbuf == 1 and p.candoublejump == true// and p.cancelactivationtimer == 0
				p.pflags = $ & ~PF_THOKKED & ~PF_CANCARRY | PF_NOJUMPDAMAGE | PF_JUMPED
				p.mo.state = S_PLAY_EDGE
				S_StartSound(p.mo,sfx_cdfm02)
				S_StartSound(p.mo,sfx_cdfm01)
				if not p.powers[pw_super]
					p.candoublejump = false
					p.mo.momz = (25/2)*FRACUNIT*orientation
				else
					if mo.eflags & MFE_UNDERWATER
						p.mo.momz = 6*FRACUNIT*orientation
					else
						p.mo.momz = 20*FRACUNIT*orientation
					end
				end
			elseif p.candoublejump == false and p.cmd.buttons & BT_SPIN and p.cmd.buttons & BT_JUMP and p.jumpbuf == 1// and p.cancelactivationtimer == 0
				p.pflags = $ & ~PF_THOKKED & ~PF_CANCARRY | PF_NOJUMPDAMAGE | PF_JUMPED
				p.mo.state = S_PLAY_EDGE
			end
		end
	end
end)

//has to be patched out for now bc of how fucking broken it is

//addHook("JumpSpinSpecial",function(p)
//	local mo = p.mo
	
	//fang throws bombs now
	//they're not great but they do the job
//	if p.pflags & PF_JUMPED | PF_THOKKED
//		if p.mo and p.mo.valid and p.mo.skin == "fang"
//			if p.spinbuf == 1
//				if p.bombdelay == 0
//					p.bomb = P_SpawnMobjFromMobj(p.mo,5*FRACUNIT,5*FRACUNIT,p.mo.height+10*FRACUNIT,MT_FBOMB)
//					p.bomb.scale = FRACUNIT*2
//					P_SetObjectMomZ(p.bomb,5*FRACUNIT,false)
//					P_InstaThrust(p.bomb,p.mo.angle,20*FRACUNIT)
//					if not p.powers[pw_super]
//						p.bombdelay = 15
//					else
//						p.bombdelay = 5
//					end
//					S_StartSound(p.mo,sfx_s3k51)
//				end
//			end
//		end
//	end
//end)

//handle the boost
local function HoverBoost(p)
	local mo = p.mo
	//look callmore i did it i did the thing you wanted
	local angle = p.cmd.angleturn<<16 + R_PointToAngle2(0, 0, p.cmd.forwardmove*FRACUNIT, -p.cmd.sidemove*FRACUNIT)
	local orientation = 1
	
	//obligatory orientation variable
	if (p.mo.eflags & MFE_VERTICALFLIP)
		orientation = -1
	else
		orientation = 1
	end
	
	if p.boosttimer == nil
		p.boosttimer = 17
	end
	
	if p.mo.skin == "metalsonic"
		if p.jetboost and p.boosttimer > 0
			if not p.powers[pw_super]
				p.boosttimer = $1-1
				P_SetObjectMomZ(mo,0,false) //float!!!!! wow!!!!!!!!!!!
			end
			P_Thrust(mo,angle,(5/2)*FRACUNIT)
			if (leveltime % 10)
				local effect = P_SpawnGhostMobj(p.mo) //silver: has no ideas. P_SpawnGhostMobj: i gotchu
				effect.fuse = 5
			end
			
			if p.powers[pw_super]
				if p.cmd.buttons & BT_JUMP
					p.mo.momz = $+FRACUNIT * orientation
				elseif p.cmd.buttons & BT_SPIN
					p.mo.momz = $-FRACUNIT * orientation
				else return
				end
			end
		elseif p.boosttimer < 0
			p.boosttimer = 17
			p.jetboost = false
			return
		end
	end
//	print(p.boosttimer)
	//reset the timer when you hit the ground
	if P_IsObjectOnGround(mo) or p.mo.eflags & MFE_JUSTHITFLOOR
		if not p.powers[pw_super]
			p.boosttimer = 17
		else
			p.boosttimer = TICRATE
		end
	end
end
addHook("PlayerThink",HoverBoost)

//handles starting the boost jet + the shockwave effect; probably should put the effect in a separate function
//to refer back to here.
addHook("AbilitySpecial",function(p)
	local mo = p.mo
	local angle = p.mo.angle
	
	if p.exhausted == nil
		p.exhausted = false
	end
	
	if p.mo.skin == "metalsonic"
		if not p.exhausted //can't already have used the ability!
			//shockwave effect that i'm actually kinda proud of! wow! i freeslotted something!
			local boostshockwave = P_SpawnMobjFromMobj(p.mo,0,0,10*FRACUNIT,MT_BOOST)
			boostshockwave.scale = FRACUNIT
			boostshockwave.destscale = FRACUNIT*7
			boostshockwave.scalespeed = FRACUNIT/10
			boostshockwave.angle = p.mo.angle + ANGLE_90
			if boostshockwave.scale == boostshockwave.destscale and boostshockwave.state == S_BOOST5
				P_KillMobj(boostshockwave,p.mo,p.mo) //works* (citation needed)
			end
			S_StartSound(mo,sfx_bkpoof)
			p.mo.state = S_PLAY_RUN //i fucking hate having to use this so much i despise it with every fibre of my being
			p.jetboost = true
			HoverBoost(p) //handle the boost
			p.exhausted = true //use up the charge until you hit the ground again
			
			return true
		else return true end
	end
end)

//allow metal to damage enemies and monitors with his boost jet
addHook("PlayerCanDamage",function(p)
	if p.mo.skin == "metalsonic" 
	and p.jetboost
	and p.mo.state == S_PLAY_RUN 
	and not P_IsObjectOnGround(p.mo) then 
		return true 
	end
	
	if p.mo.skin == "hibiki"
	and p.thrust
		return true
	end
end)

//rebound after a homing attack

local function hatkrebound(mo,mobj)
	if mobj.flags & (MF_MONITOR|MF_ENEMY|MF_BOSS)
	and mo.skin == "sonic"
	and mo.player.homattack
	and not P_PlayerInPain(mo.player)
		if (mo.z + mo.height < mobj.z) 
		or (mo.z > mobj.z + mobj.height)
			return false
		else
			if mo.player.cmd.buttons & BT_JUMP
				P_SetObjectMomZ(mo,12*FRACUNIT,false)
				mo.momx = $/2
				mo.momy = $/2
				mo.player.homattack = false
			else
				mo.player.homattack = false
				P_SetObjectMomZ(mo,5*FRACUNIT,false)
			end
		end
	end
end
addHook("MobjMoveCollide",hatkrebound)

//remake of super sonic's float because for some reason it just...doesn't activate if you have a custom ability?
addHook("JumpSpinSpecial", function(p)
	local floating = false
	local mo = p.mo
	local angle = p.mo.angle
	local runspeed = p.runspeed
	
	if p.mo.skin == "sonic"
		if p.powers[pw_super]
			P_SetObjectMomZ(p.mo, 0, false)
			p.pflags = $ & ~PF_SPINNING
			if p.speed > runspeed
				p.mo.state = S_PLAY_RUN
				floating = true
			elseif p.speed < runspeed
				p.mo.state = S_PLAY_FLOAT
				floating = true
			else 
				floating = false
			end
		end
	end
end)

addHook("SpinSpecial",function(p)
	if P_IsObjectOnGround(p.mo)
	and p.pflags & PF_SPINNING 
	and p.mo.state == S_PLAY_ROLL 
	and not (p.pflags & (PF_SPINDOWN|PF_STARTDASH)) then
		p.mo.state = S_PLAY_RUN
		p.pflags = ($ & ~PF_SPINNING) | PF_SPINDOWN
	end
end)

addHook ("MobjMoveCollide", function (p, spike)
	if p.player ~= nil and p.player.powers[pw_super]
		if (spike.type == MT_SPIKE
		or spike.type == MT_WALLSPIKE)
			if (p.z + p.height < spike.z) 
			or (p.z > spike.z + spike.height)
				return false
			else
				P_KillMobj(spike,p,p)
			end
		end
	end
end)

local function InstaCheck(amy, inflictor, source, damage, element)
	if (amy.player) and amy.valid and amy.state == S_PLAY_TWINSPIN and amy.skin == "amy"
		if not (element == DMG_INSTAKILL) and not (element == DMG_DROWNED) and not (element == DMG_SPACEDROWN) and not (element == DMG_DEATHPIT) and not (element == DMG_CRUSHED) and not (element == DMG_SPECTATOR)
			return false  //do not damage player while instashield is active
		end
	end
end

addHook("ShouldDamage", InstaCheck)

local function EnableDropDash(p,arg)
	if p.mo and p.mo.valid
		if arg
			arg = arg:lower()
			if arg == "off"
			or arg == "no"
			or arg == "false"
				p.dropdashenabled = "off"
				CONS_Printf(p,"Metal Sonic's Drop Dash has been disabled.")
			elseif arg == "on"
			or arg == "yes"
			or arg == "true"
				p.dropdashenabled = "on"
				CONS_Printf(p,"Metal Sonic's Drop Dash has been enabled.")
			end
		else
			CONS_Printf(p, "Toggles Metal Sonic's unused Drop Dash ability. Currently " + p.dropdashenabled + ".")
		end
	end
end

COM_AddCommand("metdrop",function(p,arg)
	EnableDropDash(p,arg)
end)