// Here we go.
// Preface: This includes absolutely no code from the original Advance Sonic.
freeslot(
	"sfx_asjdsh",
	"sfx_asdash",
	"sfx_asyeah",
	"sfx_asbnd3",
	"SPR2_SKFW",
	"S_PLAY_SKILLFRONT",
	"SPR2_SKUP",
	"S_PLAY_SKILLUP",
	"SPR2_SKDW",
	"S_PLAY_BOUND",
	"SPR_JDSH",
	"MT_ASJDSH",
	"S_ASJDSH",
	"SPR_ASST",
	"S_ASSTMP1",
	"S_ASSTMP2",
	"S_ASSTMP3",
	"S_ASSTMP4",
	"SPR_TSPK",
	"MT_ASTSPK",
	"S_ASTSPK",
	"SPR_ASSP",
	"SPR_ASSW",
	"MT_ASSPK",
	"S_ASSP",
	"S_ASSW1",
	"S_ASSW2",
	"S_ASSW3"
)

//Captioning sounds
sfxinfo[sfx_asjdsh].caption = "Jump dash"
sfxinfo[sfx_asdash].caption = "Breaking sound barrier"
sfxinfo[sfx_asyeah].caption = "'Yeah!'"
sfxinfo[sfx_asbnd3].caption = "Bound impact"

//Defining functions
local function WaterSpeed(mo, var1) //Single water momentum cut instead of calling the check internally for *every* move.
	if (mo.eflags & (MFE_UNDERWATER|MFE_GOOWATER))
		var1 = FixedDiv(var1,2*FRACUNIT)
	end
	return var1
end

local function Qualifications(player) //Blanket list of general requirements to use an ability.
	if (player.playerstate == PST_LIVE)
	and not (P_PlayerInPain(player))
	and not (player.gotflag)
	and not (player.exiting)
	and not (player.pflags & (PF_STASIS|PF_JUMPSTASIS))
	and not (player.powers[pw_carry])
		return true
	end
end

local function JumpDashSpeed(player) //Jump thok speed calculations
	local amount = 40*FRACUNIT
	if player.speed > 36*FRACUNIT //So it doesn't murder your speed anymore
		amount = player.speed + (4*FRACUNIT)
	end
	amount = WaterSpeed(player.mo,amount)
	return amount
end

local function GasValveCheck(mo,object) //Funny, there was a closed merge request to make these things an actual spring type.
	if (mo.eflags & MFE_VERTICALFLIP)
		if ((mo.z + mo.height) >= (object.z - mo.height))
		and (mo.z < object.z)
			return true
		end
	else
		if (mo.z <= (object.z + object.height + mo.height))
		and ((mo.z + mo.height) > (object.z + object.height))
			return true
		end
	end
end

local function BoostSpawnRoutine(mo)
	for i = 1,16 do
		local xdist = P_RandomRange(1,3)*FRACUNIT*6
		local ydist = P_RandomRange(1,3)*FRACUNIT*6 //Probably don't need two of these but...
		local zdist = P_RandomRange(-3,3)*FRACUNIT
		local boostparticle = P_SpawnMobj(mo.x+(i<9 and xdist or -xdist),mo.y+(i<9 and ydist or -ydist),mo.z+zdist+(i<9 and mo.height or 0),MT_THOK)
		boostparticle.state = S_ASSP
		boostparticle.angle = R_PointToAngle2(0,0,mo.momx,mo.momy)
		boostparticle.scale = mo.scale*2
		boostparticle.momx = mo.momx
		boostparticle.momy = mo.momy
		boostparticle.momz = FixedDiv(mo.momz,FRACUNIT*2)
		if (mo.eflags & MFE_VERTICALFLIP)
			boostparticle.eflags = $1 & MFE_VERTICALFLIP
		end
		P_Thrust(boostparticle,boostparticle.angle,P_RandomRange(3,6)*FRACUNIT)
		P_Thrust(boostparticle,boostparticle.angle+ANGLE_90,P_RandomRange(3,6)*FRACUNIT*(i<9 and -1 or 1))
		P_SetObjectMomZ(boostparticle,P_RandomRange(0,2)*FRACUNIT,true)
	end
end

//Defining objects
//Jump Dash Particle
mobjinfo[MT_ASJDSH] = {
	doomednum = -1,
	spawnstate =  S_ASJDSH,
	flags = MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY
}

//Trick sparkles
mobjinfo[MT_ASTSPK] = {
	doomednum = -1,
	spawnstate = S_ASTSPK,
	flags = MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY
}

//Boost mode particles
mobjinfo[MT_ASSPK] = {
	doomednum = -1,
	spawnstate = S_ASSP,
	flags = MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY
}

//ehehehe
spr2defaults[SPR2_SKFW] = SPR2_ROLL
spr2defaults[SPR2_SKUP] = SPR2_SPNG
spr2defaults[SPR2_SKDW] = SPR2_ROLL

//Defining states
states[S_PLAY_SKILLFRONT] = {
	sprite = SPR_PLAY,
	frame = SPR2_SKFW|FF_ANIMATE,
	tics = -1,
	var1 = 7,
	var2 = 1,
	nextstate = S_PLAY_FALL
}

states[S_PLAY_SKILLUP] = {
	sprite = SPR_PLAY,
	frame = SPR2_SKUP,
	tics = -1,
	nextstate = S_PLAY_FALL
}

states[S_PLAY_BOUND] = {
	sprite = SPR_PLAY,
	frame = SPR2_SKDW|FF_ANIMATE,
	tics = -1,
	var1 = 3,
	var2 = 1,
	nextstate = S_PLAY_ROLL
}

--Particle effect states
states[S_ASJDSH] = {
	sprite = SPR_JDSH,
	frame = A|FF_ANIMATE|FF_FULLBRIGHT,
	tics = 10,
	var1 = 9,
	var2 = 1,
	nextstate = S_DEATHSTATE
}

//Stomp overlay
states[S_ASSTMP1] = {
	sprite = SPR_ASST,
	frame = A|FF_FULLBRIGHT,
	tics = 1,
	var1 = 0,
	var2 = 0,
	nextstate = S_ASSTMP2
}

states[S_ASSTMP2] = {
	sprite = SPR_ASST,
	frame = B|FF_FULLBRIGHT,
	tics = 1,
	var1 = 0,
	var2 = 0,
	nextstate = S_ASSTMP3
}

states[S_ASSTMP3] = {
	sprite = SPR_ASST,
	frame = C|FF_FULLBRIGHT,
	tics = 1,
	var1 = 0,
	var2 = 0,
	nextstate = S_ASSTMP4
}

states[S_ASSTMP4] = {
	sprite = SPR_ASST,
	frame = D|FF_FULLBRIGHT,
	tics = 1,
	var1 = 0,
	var2 = 0,
	nextstate = S_ASSTMP1
}

//Trick sparkles
states[S_ASTSPK] = {
	sprite = SPR_TSPK,
	frame = A|FF_ANIMATE|FF_RANDOMANIM|FF_FULLBRIGHT,
	tics = 7,
	var1 = 6,
	var2 = 1,
	nextstate = S_DEATHSTATE
}

//Boost mode particle states
states[S_ASSP] = {
	sprite = SPR_ASSP,
	frame = A|FF_ANIMATE|FF_RANDOMANIM|FF_FULLBRIGHT,
	tics = 6,
	var1 = 1,
	var2 = 3,
	nextstate = S_ASSW1
}

states[S_ASSW1] = {
	sprite = SPR_ASSW,
	frame = A|FF_PAPERSPRITE|FF_FULLBRIGHT,
	tics = 5,
	action = A_MoveRelative,
	var1 = 0,
	var2 = -6,
	nextstate = S_ASSW2
}

states[S_ASSW2] = {
	sprite = SPR_ASSW,
	frame = B|FF_PAPERSPRITE|FF_FULLBRIGHT,
	tics = 1,
	action = A_ZThrust,
	var1 = 0,
	var2 = 65535,
	nextstate = S_ASSW3
}

states[S_ASSW3] = {
	sprite = SPR_ASSW,
	frame = B|FF_PAPERSPRITE|FF_FULLBRIGHT,
	tics = 5,
	action = A_MoveAbsolute,
	var1 = 0,
	var2 = -15,
	nextstate = S_DEATHSTATE
}



//Jump thok but not the hardcoded one
addHook("AbilitySpecial",function(player)
	if (player.mo.skin == "advancesonic")
	and not (player.pflags & PF_THOKKED)
	and Qualifications(player)
		P_InstaThrust(player.mo,player.mo.angle,JumpDashSpeed(player))
		P_SetObjectMomZ(player.mo,WaterSpeed(player.mo,6*FRACUNIT)+player.mo.pmomz)
		S_StartSound(player.mo,sfx_asjdsh)
		local jdsh = P_SpawnMobj(player.mo.x,player.mo.y,player.mo.z+((player.mo.height/2)*P_MobjFlip(player.mo)),MT_THOK)
		jdsh.color = player.mo.color
		jdsh.state = S_ASJDSH
		player.pflags = $1 & ~PF_NOJUMPDAMAGE
		player.mo.state = S_PLAY_JUMP
		player.pflags = $1|PF_THOKKED
		return true
	end
end)

//Jump button abilities
addHook("JumpSpecial",function(player)
	if (player.mo.skin == "advancesonic")
	and Qualifications(player)
	and not (player.pflags & PF_JUMPDOWN)
		if (player.springstuff == 1) //Hop Jump
			P_SetObjectMomZ(player.mo,WaterSpeed(player.mo,14*FRACUNIT))
			S_StartSound(player.mo,sfx_asyeah)
			local jdsh = P_SpawnMobj(player.mo.x,player.mo.y,player.mo.z+((player.mo.height/2)*P_MobjFlip(player.mo)),MT_THOK)
			jdsh.color = player.mo.color
			jdsh.state = S_ASJDSH
			player.mo.state = S_PLAY_SKILLUP
			player.pflags = $1 & ~PF_JUMPED
			player.pflags = $1|PF_THOKKED
			player.springstuff = 2
			return true
		elseif not (player.pflags & PF_JUMPED)
		and not (P_IsObjectOnGround(player.mo))
			return true
		end
	end
end)

//Spin button abilities
addHook("SpinSpecial",function(player)
	if (player.mo.skin == "advancesonic")
	and Qualifications(player)
	and not (player.pflags & PF_USEDOWN)
		if (player.bound == 0) //Bound Attack
		and (player.pflags & PF_JUMPED)
		and not (player.pflags & PF_THOKKED)
		and not (player.mo.eflags & MFE_GOOWATER)
		and not (P_IsObjectOnGround(player.mo))
			player.bound = 1
			
			if (player.dashmode < 3*TICRATE)
				player.mo.momx = FixedDiv($1,4*FRACUNIT)
				player.mo.momy = FixedDiv($1,4*FRACUNIT)
				player.dashmode = 0
			end
			
			P_SetObjectMomZ(player.mo, WaterSpeed(player.mo,-40*FRACUNIT))
			S_StartSound(player.mo, sfx_thok)
			player.mo.state = S_PLAY_BOUND
			player.pflags = $1|PF_THOKKED
			player.springstuff = 0
		elseif (player.springstuff == 1) //Humming Top
			local speedamount = 52*FRACUNIT
			if player.speed > 52*FRACUNIT
				speedamount = player.speed + 16*FRACUNIT
			end
			speedamount = WaterSpeed(player.mo,speedamount)

			player.mo.momz = 0
			P_InstaThrust(player.mo,player.mo.angle,speedamount)
			S_StartSound(player.mo,sfx_asyeah)
			local jdsh = P_SpawnMobj(player.mo.x,player.mo.y,player.mo.z+((player.mo.height/2)*P_MobjFlip(player.mo)),MT_THOK)
			jdsh.color = player.mo.color
			jdsh.state = S_ASJDSH
			player.mo.state = S_PLAY_SKILLFRONT
			player.pflags = $1 & ~PF_JUMPED
			player.pflags = $1|PF_THOKKED|PF_SPINNING
			player.springstuff = 2
			return true
		end
	end
end)
		
//No shield abilities
addHook("ShieldSpecial",function(player)
	if (player.mo.skin == "advancesonic")
		return true
	end
end, MT_PLAYER)

//Meat of it all
addHook("ThinkFrame", do
	for player in players.iterate
		if player.mo and (player.mo.skin == "advancesonic")
			//Variables
			if player.advsonicvar == nil
				player.dashpreserve = 0 // Boost
				player.groundspringcheck = 0
				player.springstuff = 0 // Tricks
				player.devilvariable = 0
				player.bound = 0 // Bound
				player.boundoverlay = 0
				player.advsonicvar = true // Finish
			end
			//End Variables
				
			//(Re)setting variables
			if not Qualifications(player)
				player.dashmode = 0
				player.dashpreserve = 0
				player.springstuff = 0
				player.bound = 0
			end
			
			if (player.mo.eflags & MFE_GOOWATER) //Nah man.
			or (player.pflags & PF_STARTDASH)
			or (((player.speed < player.runspeed) or (player.dashmode < 3*TICRATE)) and (player.mo.eflags & MFE_UNDERWATER))
				player.dashmode = 0
				player.dashpreserve = 0
			end
			
			if P_IsObjectOnGround(player.mo)
				if (player.speed < player.runspeed) //Kill dashmode if you're too slow
				and not (player.bound)
					player.dashmode = 0
					player.dashpreserve = 0
				end
				player.springstuff = 0 //Reset tricks
				if (player.bound == 2) //Regain stomp
					player.bound = 0
				end
			end
			//End resets
			
			//Bound attack effects
			if (player.boundoverlay) and (player.boundoverlay.valid)
			and not (player.bound == 1)
				P_RemoveMobj(player.boundoverlay)
				player.boundoverlay = 0
			end
			
			if (player.bound == 1)
				if not (player.boundoverlay)
					player.boundoverlay = P_SpawnMobj(player.mo.x,player.mo.y,player.mo.z,MT_OVERLAY)
					player.boundoverlay.target = player.mo
					player.boundoverlay.state = S_ASSTMP1
					player.boundoverlay.color = player.mo.color
				end
			end
			
			//Dashmode from speed boosters or horizontal springs
			if (player.dashmode < 3*TICRATE)
			and (player.groundspringcheck)
			and (player.speed > player.normalspeed)
				player.dashmode = (35/10)*TICRATE
				S_StartSound(mo,sfx_asdash)
			end
			
			//Ground spring checks
			if (player.groundspringcheck)
			and not (player.mo.eflags & MFE_SPRUNG)
				player.groundspringcheck = 0
			end
			
			if (player.mo.eflags & MFE_SPRUNG)
			and (P_IsObjectOnGround(player.mo))
				player.groundspringcheck = 1
			end
			
			//Allowing tricks after hitting a spring
			if (player.mo.eflags & MFE_SPRUNG)
			and not (player.pflags & PF_JUMPED)
			and not (P_IsObjectOnGround(player.mo))
			and Qualifications(player)
				player.bound = 0
				player.springstuff = 1
				player.pflags = $|PF_NOJUMPDAMAGE //Just in case
			end
			if (player.dashmode < 3*TICRATE)
				player.runspeed = skins[player.mo.skin].runspeed
				player.normalspeed = skins[player.mo.skin].normalspeed
				player.actionspd = skins[player.mo.skin].actionspd
			end
			//Devil tricks
			if not (P_PlayerInPain(player))
			and (player.powers[pw_nocontrol])
			and (player.mo.state == S_PLAY_PAIN)
				player.devilvariable = 1
			else
				if (player.devilvariable)
					player.springstuff = 1
					player.devilvariable = 0
				end
			end
			
			//Edge case
			if (player.mo.state == S_PLAY_SKILLFRONT)
			and (P_IsObjectOnGround(player.mo))
			and not (player.springstuff)
				player.pflags = $1 & ~PF_SPINNING
				if (player.dashmode >= 3*TICRATE)
					player.mo.state = S_PLAY_DASH
				elseif (player.speed > player.runspeed)
					player.mo.state = S_PLAY_RUN
				else
					player.mo.state = S_PLAY_WALK
				end
			end
			
			//Particle effects
			if ((player.dashmode >= 3*TICRATE) and not (player.mo.eflags & MFE_UNDERWATER)) //Dashmode Afterimages
			and (leveltime%2 == 0)
				local aitrail = P_SpawnGhostMobj(player.mo)
				aitrail.color = player.mo.color
				aitrail.fuse = 4
				aitrail.colorized = true
				aitrail.frame = ($1 & ~FF_TRANSMASK)|FF_FULLBRIGHT
			end
			
			if (player.springstuff == 2) //Trick sparkles
			and (leveltime%12 == 0)
				local tricksparks = P_SpawnMobj(player.mo.x,player.mo.y,player.mo.z+(player.mo.height/2),MT_ASTSPK)
				tricksparks.scale = player.mo.scale*2
				if (player.mo.eflags & MFE_VERTICALFLIP)
					tricksparks.eflags = $1|MFE_VERTICALFLIP
				end
			end
			
		end
	end
end)


addHook("MobjThinker", function(mo)
	if mo and (mo.skin == "advancesonic")
		//Skipping dashmode threshold for auditory reasons
		if (mo.player.dashmode >= (297/100)*TICRATE)
		and (mo.player.dashmode < 3*TICRATE)
		and Qualifications(mo.player)
			mo.player.dashmode = (35/10)*TICRATE
			S_StartSound(mo,sfx_asdash)
			BoostSpawnRoutine(mo)
		end
		
		//Dashmode preservation
		if mo.player.dashmode >= 3*TICRATE
			mo.player.dashpreserve = 4*TICRATE
		end
		
		if ((not P_IsObjectOnGround(mo) and not (mo.eflags & MFE_UNDERWATER)) or mo.player.springstuff or mo.player.bound)
		and (mo.player.dashpreserve)
		and Qualifications(mo.player)
			mo.player.dashmode = mo.player.dashpreserve
		end
		
		
		//Bound Attack
		if (mo.eflags & MFE_GOOWATER) //Goop moment
		and (mo.player.bound == 1)
			mo.player.bound = 0
			mo.player.state = S_PLAY_JUMP
		end
		
		if (mo.eflags & MFE_JUSTHITFLOOR) //Bouncing
		and (mo.player.bound == 1)
			local jdsh = P_SpawnMobj(mo.x,mo.y,mo.z+((mo.height/2)*P_MobjFlip(mo)),MT_THOK)
			jdsh.color = mo.color
			jdsh.state = S_ASJDSH
			P_SetObjectMomZ(mo,WaterSpeed(mo,8*FRACUNIT))
			S_StartSound(mo, sfx_asbnd3)
			mo.state = S_PLAY_JUMP
			mo.player.pflags = $1|PF_JUMPED
			mo.player.pflags = $1 & ~PF_NOJUMPDAMAGE
			mo.player.pflags = $1 & ~PF_THOKKED
			mo.player.bound = 2
		end
		
		if mo.player.bound == 1 //Falling
		and not (P_IsObjectOnGround(mo))
			P_SetObjectMomZ(mo, WaterSpeed(mo,-40*FRACUNIT))
			if (leveltime%2 == 0)
				local boundtrail = P_SpawnGhostMobj(mo)
				boundtrail.color = mo.color
				boundtrail.colorized = true
				boundtrail.fuse = 4
				boundtrail.frame = ($1 & ~FF_TRANSMASK)|FF_FULLBRIGHT
			end
			
			if (mo.player.dashmode < 3*TICRATE)
				mo.player.dashmode = 0
				if (leveltime%6 == 0)
					mo.momx = FixedDiv($1,4*FRACUNIT)
					mo.momy = FixedDiv($1,4*FRACUNIT)
				end
			end
		end
		
		if (mo.state ~=  S_PLAY_BOUND) //Teleporters are a trip
		and (mo.player.bound == 1)
			mo.player.bound = 0
		end	
	end
end, MT_PLAYER)

addHook("MobjMoveCollide",function(mo,object) //Valve check
	if (mo and mo.skin == "advancesonic")
	and (object.valid and object.type == MT_STEAM)
	and GasValveCheck(mo,object)
		mo.player.bound = 0
		mo.player.springstuff = 1
		mo.player.pflags = $|PF_NOJUMPDAMAGE
	end
end, MT_PLAYER)

addHook("MobjThinker",function(mo) //Hide target since you can't use the attraction attack anyway
	if mo.valid
		if displayplayer and displayplayer.mo.skin == "advancesonic"
		or secondarydisplayplayer and secondarydisplayplayer.mo.skin == "advancesonic"
			mo.flags2 = $|MF2_DONTDRAW
			return true
		end
	end
end,MT_LOCKON)