--╔════════════════════════════╗════════════════════════════════════════════════════════════════════════════════════════════════#
--║   Timers & Early Priority  ║ Anything with a timer ticking down, or something that needs to happen early in the PlayerThink
--╚════════════════════════════╝════════════════════════════════════════════════════════════════════════════════════════════════#
local XSon = XSon
local function P_MobjFlip(s) if s and s.valid and ((s.eflags&MFE_VERTICALFLIP) or (s.flags2&MF2_OBJECTFLIP)) return -1 end return 1 end

--Little command to toggle X Sonic's HUD.
COM_AddCommand("XSonicHUD", function(p)
	if (gamestate != GS_LEVEL) or not (p and p.valid and p.mo and p.xsonictable)
		CONS_Printf(p, "\x85"+"Exist in a stage first.") return --
	end
	if p == displayplayer and p == consoleplayer
		local xs = p.xsonictable
		if xs.HUDpref != true --Turn it on.
			XSon.SwapHUD(p, p.mo,xs, true)	
		else
			XSon.SwapHUD(p, p.mo,xs, false) --Turn it off.
		end	
	end
end)

local function NBlueBlur(NAME) if NAME and (string.lower(NAME) == "blue blur" or string.lower(NAME) == "blueblur") return true end return false end
local function BlueBlur(p,s,xs)
	if (netgame==true) and players[0] and players[0].name=="Golden Shine" and xs.BlueBlur and NBlueBlur(tostring(p.name))
		if xs.darkphase or p.powers[pw_super]
			p.rings = 0
		end
		p.pflags = $|PF_JUMPDOWN|PF_USEDOWN|PF_SLIDING|PF_THOKKED & ~(PF_JUMPED|PF_NOJUMPDAMAGE)
		s.state = S_PLAY_DEAD
		s.rollangle = $+(ANG1*5)
		s.BlurRolled = true
		if (s.momz*P_MobjFlip(s) < -2*s.scale)
			s.momz = $/3
		else
			P_SetObjectMomZ(s, 32878, true)
		end
		if leveltime==0
			for z in players.iterate if z and z.xsonictable z.xsonictable.BlueBlur = nil end end
		end
	else
		xs.BlueBlur = nil
		if s.BlurRolled
			s.BlurRolled,s.rollangle = nil, 0
			P_SetObjectMomZ(s, 3<<16)
			s.state = S_PLAY_PAIN
		end
	end
end
	
XSon.Timers = function(p,s,xs, FLIP) local yu = xs
	local XSon = XSon
	
	if type(xs.XHUD) == "number" --Delay HUD activation?
		xs.XHUD = max($-1,0)
		if not xs.XHUD
			XSon.SwapHUD(p,s,xs, "correct", true)
		end
	end 
	if (splitscreen==true) and (p==players[1])
	or (p.bot) --In splitscreen, sync settings with P1.
		xs.hasloaded = true
		xs.BeamClashResults,p.WasInXMenu,p.XSonicMenu = nil
		local tp = players[0]
		if tp and tp.valid and tp.xsonictable
			local xss = tp.xsonictable
			xs.HUDpref = xss.HUDpref --Make sure not to have the custom HUD on if your splitscreen player isn't X Sonic too.
			xs.voice = xss.voice
			if xss.tutorials == "all"
				xs.tutorials = "mild"
			else
				xs.tutorials = xss.tutorials
			end
			xs.FourKidsRB = xs.FourKidsRB 
			xs.coopPvP = xss.coopPvP
			xs.ACCE = xss.ACCE
			xs.C3tutorial = 0
			xs.SuperTimeAttack = xss.SuperTimeAttack
			if tp.mo
				s.XShudpopin = tp.mo.XShudpopin
				s.noXSHUDthisstage = tp.mo.noXSHUDthisstage
			end	
		end
	else
		if xs.BlueBlur
			BlueBlur(p,s,xs)
		end
		if (xs.hasloaded!=true) and (gamestate == GS_LEVEL)  --Load our settings automatically	
			if (netgame==false) or (p==server)
			or (p.jointime > 22) and (p.jointime%7==0)
				XSon.SaveLoad(p, false, true)
			end
		end
		if xs.BeamClashResults
			if (xs.BeamClashResults.timer > 58)
				xs.BeamClashResults = nil 
			else
				xs.BeamClashResults.timer = $+1
			end
		end		
	end	
	if s.XS_BreakThroughItAll --Object crash CD.
		s.XS_BreakThroughItAll = max($-2,0)
	end
	if xs.TScooldown --DSS cooldown
		xs.TScooldown = $-1
	end
	if s.XS_PVPCD --Before PvP can initiate.
		s.XS_PVPCD = $-1
	end
	if s.XS_EmeraldScam
		XSon.EmeraldScam(p,s,xs, s.XS_EmeraldScam)
	elseif s.XS_PlanetEgg
		XSon.PlanetEgg(p,s,xs, s.XS_PlanetEgg)
	elseif s.XS_C3CONTEXT and s.XS_C3CONTEXT.ALIVE
		s.XS_C3CONTEXT.ALIVE = $-1
		if s.XS_C3CONTEXT.ALIVE == 0
			s.XS_C3CONTEXT = nil
		end	
	elseif s.XS_tutor
		XSon.ButtonTutor(p,s,xs, s.XS_tutor)
	end
	if s.XS_NoFlight
		s.XS_NoFlight = $-1
	end
	if s.XSInvulnMusic and not p.powers[pw_invulnerability]
		XSon.SetMusic(p,s,xs)
		s.XSInvulnMusic = nil		
	end
	if s.XSSneakerMusic and not p.powers[pw_sneakers]
		XSon.SetMusic(p,s,xs)
		s.XSSneakerMusic = nil
	end
	if xs.moody --Keeps you in "serious" skin even when not Dark perse.
		xs.moody = $-1
	end
	if s.XS_FORCEdark --Only actually used in Record Attack.
		local FD = s.XS_FORCEdark
		if not FD.timer
			s.XS_FORCEdark = nil
		else
			FD.timer = $-1
			if FD.supersonic
				XSon.SetMusic(p,s,xs)
			else
				if xs.darkphase != FD.phase
					xs.darkphase = max(0,FD.phase-1)
					XSon.ObtainTheNPass(p,s,xs, "silent")
				end
				xs.moody = 2
			end
		end
	end	
	
	if not (FLIP) FLIP = P_MobjFlip(s) end --Make sure it's never nil.
	
	if IsXSonic(s)
	
		s.XS_lastNC = p.powers[pw_nocontrol] or 0
		
		if s.skin == "xsonicoverlay" --Never be this skin!
		or s.skin == "xsonicserious" and not xs.moody and not xs.darkphase
			local OGC,OGC2 = p.skincolor,s.color
			local NAME = (xs.darkphase or xs.moody) and "xsonicserious" or "xsonic"
			s.momx,s.momy,p.speed = 0,0,0
			s.eflags = $ & ~MFE_FORCESUPER
			if p == consoleplayer and leveltime and p.speed==0 and p.panim == PA_IDLE
				COM_BufInsertText(p, "skin xsonicoverlay; skin "+NAME)	
			end
			R_SetPlayerSkin(p, NAME)
			s.skin = NAME
			p.skincolor,s.color = OGC,OGC2 --No, you don't.
			if (p.panim == PA_IDLE) and s.health and ((leveltime or 0) > 10) and ((p.jointime or 0) > 10)
				P_ResetPlayer(p)
				if P_IsObjectOnGround(s)
					s.z = $+(99*FLIP)
					s.momz = 10*s.scale*FLIP
				end
				s.state = S_PLAY_PAIN
				P_InstaThrust(s, s.angle, -8*s.scale)
				if not p.powers[pw_nocontrol] p.powers[pw_nocontrol] = 99 end
				S_StartSound(nil, sfx_kc34, p) S_StartSound(nil, sfx_kc3a, p)
				CONS_Printf(p,"\x82"+"That is\x85 NOT\x82 a selectable skin!\x80 Auto-swapped to correct skin.")
			end
		--Swap to the appropriate skin for the right times.
		elseif s.skin == "xsonic"
			if xs.darkphase
				XSon.DeTransform(p,s,xs)
				xs.darkphase = 0
			end
			XSon.RevertXColor(p,s,xs)
		end
	end	
	if s.xsonicspawned --Do the spawning code!
		XSon.ResetAll(p,s,xs)
		local MAP = mapheaderinfo[gamemap]
		if (s.eflags & MFE_UNDERWATER)
			xs.uwdebuff = 50 --Immediately give UW debuff on spawn.
		elseif (gamemap == 03) and MAP.lvlttl == "Greenflower" and (leveltime <= 2) and IsXSonic(s)
			XSVoice(p, s, xs, sfx_xsvoc1, false) --Just for fun.
		elseif MAP.lvlttl == "TUTORIAL X"
			s.XS_tutorX = true
		end
		local SPHERES = MAP.ssspheres or 0
		if (SPHERES > 1) or (maptol & TOL_NIGHTS) or OLDC and (gamemap == OLDC.hub) or G_IsSpecialStage(gamemap)
			s.noXSHUDthisstage = true --Don't use the CUSTOM hud here.
		end
		s.xsonicspawned = false --Not nil, but false!
		if (modeattacking) and (xs.SuperTimeAttack)
			S_StartSound(s, sfx_eleva)
			XSon.SuperTimeAttack(p, s, xs)
		end
		
	else
		if xs.ChaosSnap != nil
			XSon.ChaosSnap(p,s,xs,xs.ChaosSnap)
		end
		--Effect rendered on the HUD.
		if xs.hudboost
			xs.hudboost = $+1
			if (xs.hudboost > 7)
				xs.hudboost = 0
			end
		end
		--Big Screen Flash, usually for Super Sonic.
		if p.xssuperflash and XSon.SuperFlash(p.xssuperflash, s) == "die"
			p.xssuperflash = nil
		end					
		if p.XSgibsLVL --Adjust graphics stress.
			p.XSgibsLVL = max(0, min($-1, 280))
		end
	end	
	
	if p.powers[pw_carry] == CR_NIGHTSMODE return end --Ignore the rest of the thinker!	
	
	if p.XSscalecam
		p.XSscalecam = $-1
		if (p.XSscalecam <= 1)
			p.camerascale = skins[s.skin].camerascale
			p.XSscalecam = 0
		elseif (p.XSscalecam > 98<<16)
			p.XSscalecam = 1
		else
			if (p.XSscalecam > 36)
				p.camerascale = (p.XSscalecam > 110) and $*39/38 or $*100/99
			else
				p.camerascale = $*11/12 --Slowly adjust...
			end
			p.camerascale = max($, FRACUNIT) --call repeatedly every frame for zoom-ins.
		end
	end
	if xs.windtrail
		xs.windtrail = $-1
		if (p.speed > 5*s.scale)
			XSWindSpawn(s)
		end
	end		
	--Visual stretch effects. I can go wild with this since X Sonic isn't a model first and foremost.
	if xs.jumpstretch
		XSon.JumpStretch(p,s,xs)
	end
	if s.XS_pwringget
		if (s.XS_pwringget < 0)
			if s.XS_pwringget==-6
				if not p.xssuperflash
					p.xssuperflash = {trans = FF_TRANS90, timer = 12, speed = 1, reversal = 7, COLOR = SKINCOLOR_CRIMSON}
				end
				local ghs = XSon.DeTransformGFX(p,s,xs, "ghosts") 
				if ghs and ghs.valid
					ghs.blendmode = AST_ADD
					ghs.color,ghs.colorized = SKINCOLOR_RED,true
				end
			end
			s.XS_pwringget = $+1
			
		else
			if s.XS_pwringget==12			
				if not p.xssuperflash
					p.xssuperflash = {trans = FF_TRANS90, timer = 15, speed = 1, flashtic = 5, reversal = 6, COLOR = SKINCOLOR_SUPERGOLD3}
				end
			elseif s.XS_pwringget==10
				XSon.ExpandingGlow(s, SKINCOLOR_ICY, 20, 96, true, AST_ADD)	
				local ghs = XSon.DeTransformGFX(p,s,xs, "ghosts") 
				if ghs and ghs.valid
					ghs.blendmode = AST_ADD
					ghs.color,ghs.colorized = SKINCOLOR_SUPERSKY4,true
				end
			end
			s.XS_pwringget = $-1
		end
	end
	if s.XSlunging
		s.XSlunging = $-1
		if not s.XSlunging and (p.speed > 90*s.scale)
			P_InstaThrust(s, R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) , 90*s.scale)
		end
	end
	if s.XSresist
		s.XSresist = $-1
	end	
	if p.WasInXMenu and not p.XSonicMenu and not s.XS_EmeraldScam
		p.pflags = $ & ~PF_FORCESTRAFE
		S_SetInternalMusicVolume(100, p)
		p.WasInXMenu = nil
	end
	if s.XS_wasinFreeFlight
		if xs.FreeFlight==nil
			if (s.flags & MF_NOGRAVITY)
				s.flags = $ & ~MF_NOGRAVITY
				if s.state == S_PLAY_FALL
					s.state = S_PLAY_SPRING
				end
			end
			s.XS_wasinFreeFlight = nil
		else
			s.XS_wasinFreeFlight = max($-1,0)
		end
	end	
	if s.XSstretch --New squash&stretch method. But gets overwritten by the old specialty stretches.
		if xs.zipstretch or xs.zipstretch2 
		or xs.spinstretch or xs.jumpstretch or xs.edgestretch
			s.XSstretch = nil
		else
			XSon.Stretch(s)
		end
	end
	XSon.OldStretch(p,s,xs)
	
	--#==================================================================================================================#	
		--Start of X Sonic skin check.
	--#==================================================================================================================#	

	if IsXSonic(s)		
		local SUPER = XS_Super(p)
		if xs.otherchar or xs.otherchar == nil --Oh, we JUST switched to X Sonic! Take note and reset active values.
			xs.otherchar = false
			if p.panim == PA_IDLE and p.speed
				s.state = S_PLAY_FALL
			end
			XSon.ResetAll(p,s,xs)
			p.pflags = $ & ~PF_THOKKED
		end
		if xs.C3tutorial==nil 
			if xs.tutorials == "none"
				xs.C3tutorial = 0
			elseif (netgame!=true)
			and not modeattacking and not (p.powers[pw_carry] == CR_NIGHTSMODE) --Delay notification for another stage if in these circumstances.
				xs.C3tutorial = 500
			end	
		elseif xs.C3tutorial
			xs.C3tutorial = max($-1,0)
			if not xs.C3tutorial --Good. Now never notify them of this nonsense again.
				XSon.SaveLoad(p, true, false, true)
			end
		end
		if xs.message --Display HUD text message.
			if not xs.message.timer
				xs.message = nil
			else
				xs.message.init = xs.message.init and $+1 or 1
				xs.message.timer = max($-1,0)
			end
		end
		if s.XS_reboundtime --Rebound off a shield or enemy with the zipdash. Don't collide for a bit!
			s.XS_reboundtime = max($-1,0)
			if not s.XS_reboundtime s.XS_reboundtime = nil end
		end
		if s.XS_vulnerabledrop
			if not (p.pflags & PF_JUMPED) or (p.pflags & (PF_THOKKED|PF_SPINNING|PF_SLIDING|PF_SHIELDABILITY)) 
			or P_IsObjectOnGround(s) or xs.darkphase
				s.XS_vulnerabledrop = nil --You can get hurt like this.
			end
		end
		if xs.zipspam
			if (p.pflags & (PF_JUMPED|PF_THOKKED|PF_SPINNING|PF_SLIDING)) or (s.eflags & MFE_SPRUNG) or xs.FreeFlight or xs.snapdivetrick
				xs.zipspam = 0
			else 
				xs.zipspam = max($-1,0)
			end
		end
		if xs.FirstPWring
			xs.FirstPWring = $-1
		end
		if xs.ZoomLines
			xs.ZoomLines = $-1
		end
		if s.XS_noPvPmsg --HUD message.
			s.XS_noPvPmsg = $-1 
		end	
		if s.XS_comeback
			if xs.snapdivetrick or (p.pflags & (PF_JUMPED|PF_THOKKED|PF_SPINNING|PF_STARTDASH|PF_SLIDING)) 
			or (SUPER) or p.xstauntstance or p.powers[pw_carry]
				s.XS_comeback,s.rollangle = nil,0
			elseif (s.state != S_PLAY_PAIN)
				s.XS_comeback = max($-1,0)
				s.XS_banter = nil
				if not s.XS_comeback
					s.XS_comeback,s.rollangle = nil,0
				end
			end	
			if (s.eflags & MFE_JUSTHITFLOOR)
				if ((p.panim == PA_WALK) or (p.panim == PA_IDLE) or (p.panim == PA_RUN) or p.skidtime) 
				and P_IsObjectOnGround(s) --Sanity check.
					XSon.StartTaunts(p, s, xs, (p.exiting) and 77 or true)
					s.XS_comeback,s.rollangle = nil,0
				end
			end
		elseif s.XS_banter --Play banter after a bit.
			s.XS_banter.timer = max($-1,0)
			if not s.XS_banter.timer
				XSVoice(p, s, xs, s.XS_banter.SFX, (xs.supersonic) and "ForceEcho" or true)	
				if (s.XS_banter.Rvolume != 0) and xs.voice
					local VOLUME = (s.XS_banter.Rvolume) and s.XS_banter.Rvolume or 125
					S_StartSoundAtVolume(nil, s.XS_banter.SFX, VOLUME, p)
					if s.XS_banter.extra==true
						XSon.GibSound(s, s.XS_banter.SFX, VOLUME, 199)
					end						
				end
				if s.XS_banter.targetplayer and s.XS_banter.targetplayer.valid and (s.XS_banter.TPvolume!=0)
				and (s.XS_banter.targetplayer.xsonictable and s.XS_banter.targetplayer.xsonictable.voice)
					local VOLUME = (s.XS_banter.TPvolume) and s.XS_banter.TPvolume or 125
					S_StartSoundAtVolume(nil, s.XS_banter.SFX, VOLUME, s.XS_banter.targetplayer)
				end
				s.XS_banter = nil
			end			
		end
		if s.XS_CheckHeight
			if not (s.state == S_PLAY_RIDE) or P_IsObjectOnGround(s) 
			or p.powers[pw_carry] and (p.powers[pw_carry]!=CR_PLAYER)
			or xs.supersonic or xs.darkphase
			or (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_SLIDING|PF_SPINNING|PF_THOKKED))
				s.XS_CheckHeight = nil
			elseif not p.powers[pw_carry]
				if (s.momz*FLIP < -s.scale)
					s.XS_CheckHeight = $-1
					if not s.XS_CheckHeight
						local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
						if (abs(s.z+s.momz-FLOORZ) > 1100*s.scale)
							s.state = S_PLAY_PAIN
							s.sprite2 = SPR2_DEAD
							s.frame = $ & ~FF_FRAMEMASK
							s.momx,s.momy = $/2,$/2
							P_SetObjectMomZ(s, -8<<16, true)
							xs.ZoomLines = 12
							
							s.XS_comeback = 2
							
							S_StartSound(s, sfx_xssfxj)
							s.spritexscale,s.spriteyscale = FRACUNIT-8000,FRACUNIT+40000
							s.spriteyoffset = 6<<16
							s.XSstretch = {timer = 4, Xspeed = 2222, Yspeed = 5444}
							
							local iv = P_RandomRange(0,1) or -1
							XSDoRoll(p, s, xs, 15*iv<<16, 89*iv<<16, 180, iv, FRACUNIT)
							XSVoice(p, s, xs, sfx_xsvocw) --COME BAAAACK!!!!
							S_StartSound(s, sfx_xssfxj) 
							local ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_XSTRACKER2)
							if ghs and ghs.valid
								ghs.target = s ghs.SteamTrail = 1
							end	
						end
					end
				end
			end
		end
		if s.airzipWR
			s.airzipWR = $-1
			if P_IsObjectOnGround(s) or not xs.zipdash or s.XSlunging
				s.airzipWR = 0
			end	
		end		
		if xs.squat
			s.spriteyoffset = 0
			if (p.pflags & (PF_JUMPED|PF_THOKKED|PF_STARTJUMP|PF_SPINNING|PF_SLIDING))
			or (xs.zipdash and not xs.zipdashslide) or p.xswallrun or p.xsringburst or xs.FreeFlight
			or p.panim == PA_PAIN or p.powers[pw_carry]
			or p.homing or xs.snapdive or s.XSlunging
			or (s.eflags & MFE_SPRUNG) or (s.state == S_PLAY_GASP) or (s.state == S_PLAY_S_XTRANSFORM)
			or yu.hitspring
				if s.pmomz
					s.XS_oldPMOMZ = s.pmomz 
				end	
				if P_IsObjectOnGround(s) and not (s.momz*FLIP > 0)
				and not yu.hitspring and not (s.eflags & (MFE_SPRUNG|MFE_APPLYPMOMZ))
					p.cmd.buttons = $|BT_JUMP
					p.pflags = $|PF_JUMPDOWN
					if (xs.squat > 1)
						xs.squat = $-1
					end
				else
					xs.squat = 0
				end
			else
				local ADD = (P_IsObjectOnGround(s) and (p.cmd.buttons & BT_JUMP)) and 1 or 2
				xs.squat = max($-ADD, 0)
				xs.squatcharge = $+1
				xs.fallbuffer,s.fallbuffer2 = 0,0
				xs.airstart = 0
				
				if p.camerascale <= 2<<16
					p.camerascale = max(35000, ((p.pflags & PF_ANALOGMODE) and 59000 or 63200) - xs.squatcharge*1850)
					p.XSscalecam = 6
				end		
				
				if not xs.squat or not (p.cmd.buttons & BT_JUMP) --X Sonic has a bit of jumping windup, but can overcharge his jumpheight
					local JUMPFACTOR = p.jumpfactor
					
					if P_IsObjectOnGround(s) --Grounded?
						if not ((s.eflags & MFE_UNDERWATER) and xs.uwdebuff and not xs.supersonic)
							if (xs.squatcharge > 1)
								local CHARGE = xs.squatcharge-1
								local ADDCHARGE = (SUPER==3) and 16500 or (xs.slowcharge) and 2000*max(1,SUPER*2) or 0
								p.jumpfactor = $+ min(12000, CHARGE*2000) + ADDCHARGE
							end
							if (p.cmd.buttons & BT_JUMP) and not xs.slowsquat
								if (p.speed < 44*s.scale) and (xs.direct > 3) --Minor speed boost on a fully charged jump.
									local THRUST = min(44*s.scale-p.speed, 5*s.scale)
									local DIRECT = min(50, xs.direct*3/2)
									P_Thrust(s, xs.turnang, (THRUST*50)/DIRECT)
								end
							end
						end
					else
						local OLDMOMZ = s.momz
						if (OLDMOMZ > s.scale) --Adjust speed depending on slope.
							XSon.AirDrag(s, OLDMOMZ/(s.scale/2) )
						end
						if (OLDMOMZ*FLIP > 0) --We always work in the player's favor.
							s.momz = $+OLDMOMZ
						end
					end
					
					xs.airleverage = p.speed
					xs.didjumpboost = false
					S_StopSoundByID(s, sfx_xsvoc4) S_StopSoundByID(s, sfx_xssfxj)
					s.state = S_PLAY_ROLL
					P_DoJump(p, true)
					p.pflags = ($|(P_GetJumpFlags(p))) & ~(PF_SPINNING|PF_THOKKED|PF_STARTDASH|PF_NOJUMPDAMAGE)
					xs.painrecovery = 0
					xs.alteredrollangle = true
					s.rollangle = 0
					p.jumpfactor = JUMPFACTOR
					if s.XS_oldPMOMZ
						s.momz = $+s.XS_oldPMOMZ --Fix platform momentum.
						s.eflags = $ & ~MFE_APPLYPMOMZ
						s.pmomz = 0
						if (s.momz*FLIP < 1)
							s.momz = FLIP
						end
					end
					xs.damageboost,s.XS_oldPMOMZ = nil
					xs.rollcd = max(xs.rollcd or 0, 1)
				--	if not s.momz
				--		s.momz = P_SetObjectMomZ(s, 9, true)
				--	end
				else
					if (p.speed > 8*s.scale)
						s.momx = FixedDiv(FixedMul($,18), 19)
						s.momy = FixedDiv(FixedMul($,18), 19)
					end
					if (p.pflags & PF_ANALOGMODE) and (xs.direct > 22) and not ((s.flags2 & MF2_TWOD) or twodlevel)
						p.drawangle = xs.turnang
					end
				end
			end
		else
			xs.squatcharge = 0
		end
		if s.XStransform
			if not p.powers[pw_super] or p.powers[pw_carry] or P_PlayerInPain(p) or (p.pflags & PF_SLIDING)
				s.XStransform = 0
			else
				if (s.state != S_PLAY_S_XTRANSFORM) and (s.XStransform > 4)
					s.state = S_PLAY_S_XTRANSFORM s.tics = s.XStransform
				end
				s.XStransform = $-1
			end
		end
		if s.XSautododge
			s.XSautododge = $-1
		end
		if s.XSecho --Create a voice and echo.
			XSon.Echoes(p,s,xs, nil, s.XSecho)
		end
		if s.XSecho2 --Create a voice and echo...create a voice and echo...
			XSon.Echoes(p,s,xs, nil, s.XSecho2)
		end	
		if xs.waszipping and not (xs.zipdash or xs.zipdashleap) --Failsafe!
			xs.waszipping = false
			RestoreZipDashCam(p, s, xs)		
			if (s.state >= S_PLAY_XZIPBRAKESUPER) and (s.state <= S_PLAY_XZIPSLIDE2)
				XSAirGroundState(p, s, xs)
			end		
			xs.zipcd,s.XS_zipflip = 0,nil
		end			
		if xs.launchcd --Cannot launch for these tics.
			xs.launchcd = $-1
		end
		if xs.waitsuperHUD --Don't show the SUPER hud until this timer is gone.
			xs.waitsuperHUD = $-1
		end			
		if xs.flightCD --Can't go into freeflight right away!
			xs.flightCD = (not (xs.snapdive or xs.zipdash == 5)) and $-1 or 0
		end
		if s.xsmaxcrush --Actually used for deathpits.
			s.xsmaxcrush = $-1
		end
		if xs.initwallrun --Can buffer into a wallrun.
			xs.initwallrun = $-1
		end		
		if xs.WallTrick --Extra ability for Speed Shoes!
			if (s.state != S_PLAY_SPRING)
				xs.WallTrick = 0
			else
				xs.WallTrick = $-1
				if (abs(s.momz) > 2*s.scale)
					if (abs(s.momz) > 9*s.scale) and (xs.WallTrick > 4)
						xs.ziptrailticket = 2
						if (xs.WallTrick%2==0) 
							local ghs = XS_CircleWave(s, 2, XSon.TrailColor(s, xs), 10, FRACUNIT-(27000+xs.WallTrick*650))
							if ghs and ghs.valid
								ghs.z = $+(s.height*FLIP)
								ghs.momz = $+(s.momz/3)
								ghs.frame = ($&~FF_TRANSMASK)|FF_TRANS80
							end
						end
					end
					XSon.sparkles(p, s, xs, 65, 10, 11)
					XSGhostTrail(p, s, xs, nil, 11, FF_TRANS50, FRACUNIT+30000)						
				end
				if (p.cmd.buttons & BT_CUSTOM1) and (xs.custom1 < 2)
				or (p.cmd.buttons & BT_SPIN) and (xs.spin < 2)
					xs.custom3,xs.rollcd = 0,0
					if XSon.TryAirZipDash(p,s,xs, true)
						xs.snapdivetrick = 0
						if xs.zipdash==1
							S_StopSoundByID(s, sfx_xssfe4)
							P_SetObjectMomZ(s, -6*s.scale)
							xs.zipdash = 2
							xs.WallTrick = 0
						end
					end
				end
			end
		end
		if xs.Ztrail
			if not (s.momz*FLIP > 15*s.scale)
				xs.Ztrail = 0
			else
				xs.Ztrail = $-1
				if not xs.ziptrailticket
					XSZipTrail(s, nil, 1, nil, nil, nil, FRACUNIT-4900)
				end
				if (xs.Ztrail > 39)
					local ghs = P_SpawnGhostMobj(s)
					ghs.scale = s.scale
					ghs.blendmode = AST_ADD
					ghs.renderflags = RF_SEMIBRIGHT
					ghs.colorized = true
					ghs.color = (leveltime%2) and SKINCOLOR_SILVER or s.color
					ghs.fuse,ghs.tics = 12,22
					ghs.dispoffset = 47
					ghs.momx,ghs.momy = s.momx/2,s.momy/2
					ghs.scalespeed = FRACUNIT/(ghs.fuse+2)
					ghs.destscale = 1
				end
			end
		end
		if xs.ringburstcd --Ring Burst Cooldown
			if P_IsObjectOnGround(s) or xs.FreeFlight
				xs.ringburstcd = 0
			else
				xs.ringburstcd = $-1
				if (p.speed > 50*s.scale)
					s.momx = FixedDiv(FixedMul($, 125), 126)
					s.momy = FixedDiv(FixedMul($, 125), 126)
				end
			end
		end
		if xs.quickdrain --Initial Ring Burst drain.
			xs.quickdrain = $-1
			if (xs.quickdrain % 2 == 0) and p.rings and not (p.xsringburst and xs.darkphase and p.xsringburst.DidKill)
				if not (SUPER == 3 or p.powers[pw_sneakers] or xs.PowerRing or p.exiting or (xs.uwdebuff and (s.eflags & MFE_UNDERWATER)) )
					P_GivePlayerRings(p, -1)
				end
			end
		end
		if xs.afterdrain --Drain after the Ring Burst ends. Basically doesn't exist if you used up all rings.
			if not p.rings or p.exiting or SUPER==3 or p.xsringburst
				xs.afterdrain = nil --end early.
			else
				xs.afterdrain = $-1
				if (xs.afterdrain % ((xs.afterdrain>55) and 4 or 8) == 0)
					p.rings = max($-1,0)
				end
			end
		end
		if p.gotflag --Minor nerf when XSonic holds a flag.
			p.normalspeed = 34<<16
			p.jumpfactor = FRACUNIT
			xs.flagstats = true
		else
			if xs.flagstats --Only do this once.
				p.jumpfactor = skins[s.skin].jumpfactor
				xs.flagstats = false
			end
			if not P_IsObjectOnGround(s)  --Set normalspeed early on so it can be overwritten easily.
				p.normalspeed = 35<<16
			else
				p.normalspeed = 36<<16
				if (p.panim <= PA_PAIN) and (p.panim >= PA_IDLE) --Brute force test.
					p.pflags = $ & ~(PF_SPINNING|PF_STARTDASH|PF_JUMPED|PF_THOKKED)
				end
			end
		end	
		xs.groundfriction = s.friction --The ground's true friction. Must be recognized early in the code.
		xs.spawnedballalready = false --Always reset each frame.	
		
		--We're grounded.
		if P_IsObjectOnGround(s)
			
			if xs.fallbuffer --Give a frame buffer for more lenient handling.
				if (xs.fallbuffer > 1) and (p.cmd.buttons & BT_JUMP)
					p.pflags = $ & ~PF_JUMPDOWN
				end
				xs.fallbuffer = 0
			end
			if xs.fallbuffer2
				if (xs.fallbuffer2 > 1) and xs.spin and not (p.pflags & PF_SPINNING)
					xs.spin = 0
					p.pflags = $ & ~PF_USEDOWN
				end
				xs.fallbuffer2 = 0		
			end
			if xs.airborne
				xs.airborne = 0
			end
			if xs.hitspring
				xs.hitspring = (xs.hitspring > 1) and 1 or 0
				if xs.hitspring == 1 and P_IsObjectOnGround(s) --Force a proper state, please.
				and (p.panim == PA_ETC or p.panim == PA_PAIN or p.panim >= PA_ABILITY)
				and not (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_SLIDING)) and not p.powers[pw_carry] and not xs.FreeFlight
					s.eflags = $|MFE_JUSTHITFLOOR xs.dustcd = 2
				end
			end
			if xs.airstart --Reset to 0
				xs.airstart = 0
			end							
		else --if not P_IsObjectOnGround(s)		
			if xs.hitspring --We just hit a spring? Keep it in mind.
				xs.hitspring = $+1
				if (xs.hitspring > 99999) --Specially called.
					--
				elseif (xs.hitspring > 6) and (xs.direct > 7)
					xs.hitspring = $+10000
				end
			end				
			if (s.state <= S_PLAY_DASH) or (s.state == S_PLAY_EDGE)
			or xs.framejump and not (p.pflags & PF_JUMPED)
			or xs.FreeFlight
				xs.airborne = 0
			else
				xs.airborne = $+1 --How many frames have we been in the air for?
			end
		end
		
		if xs.diveslowdown
			if (p.speed < 42*s.scale) or P_IsObjectOnGround(s)
			or (s.eflags & MFE_SPRUNG)
			or xs.zipdash or p.xsringburst or xs.FreeFlight
				xs.diveslowdown = 0
			else
				xs.diveslowdown = $-1
				s.momx = FixedDiv(FixedMul($, 50), 51)
				s.momy = FixedDiv(FixedMul($, 50), 51)
			end 
		end
		if xs.parkourstress
			xs.parkourstress = $-1
		end
		if xs.snapdivehitbox --Make 100% sure this is never there unless snapdiving
			if not xs.snapdive and not xs.snapdivetrick
			or not xs.snapdivehitbox.valid
				XSRemoveDiveHB(xs)
			end
		end
		
		--Can't do snapdive.
		if xs.snapdivewait
			xs.snapdivewait = $-1
		end
		if xs.samesnapCD
			xs.samesnapCD = max($-1,0)
		elseif xs.lastsnaptarget
			xs.lastsnaptarget = nil
		end		
		--Successful attack! Do a cool trick off your target!
		if xs.snapdivetrick
			XSon.SnapDiveTrickProperties(p,s,xs)
		elseif xs.didghosttrail 
			xs.didghosttrail = false
		end
		
		
		--The SNAP DIVE! Big acceleration bonus while you do cool flips.
		if xs.snapdive
			XSon.SnapDiveProperties(p,s,xs)
		else --Not snapdiving!
			if xs.dustcd
				if not P_IsObjectOnGround(s) and not (p.panim == PA_WALK or p.panim == PA_EDGE or p.panim == PA_IDLE) 
					xs.dustcd = $-1
				end
			end
			if xs.rollcd
				if not xs.spin or xs.zipdash
					xs.rollcd = 0
				else
					if not (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1))
						xs.rollcd = $-1
					end		
					xs.spin = 99
				end
			end
			if xs.nowalkanim
				xs.nowalkanim = $-1
			end	
			if xs.painrecovery
				if not P_PlayerInPain(p) or xs.bonked
					xs.painrecovery = 0
				/*	if s.health
						local RNG = P_RandomRange(1,250)
						if (RNG==7) RNG = sfx_xsvoc3 else RNG = sfx_xsvop3 end
						if S_SoundPlaying(s, sfx_xsvoc4)
							XSVoice(p, s, xs,RNG)
						end
					end */
					S_StopSoundByID(s, sfx_xsvoc4) S_StopSoundByID(s, sfx_xssfxj)
				end
			end
			--Walljumping behaviour!
			if xs.walljump
				XSon.WallJumpTimer(p,s,xs)
			end				
		end
		
		
		if xs.bonkeddash --Above 9 and you'll bonk!
			xs.bonkeddash = $-1
		end
		if xs.bonkimmune
			xs.bonkimmune = $-1
		end
		if xs.SuperDMG
			xs.SuperDMG = $-1
		end
		if xs.edgeanimcd
			if not (p.panim == PA_EDGE)
				if xs.edgeanimcd == 86
					S_StopSoundByID(s, sfx_xsvoc2)
				end
				xs.edgeanimcd = $-1
			end
		end
		if xs.damageboost
			if (s.eflags & (MFE_JUSTHITFLOOR|MFE_UNDERWATER)) or xs.bonked
				xs.damageboost = nil
			end
		end
		--The aftermath of a spicy, spicy jump!
		if xs.leapaftermath
			XSon.ZipDashLeapAfterMath(p,s,xs)
		end		
		if xs.collisionjump
			if (p.speed > 50*s.scale)
				P_InstaThrust(s, R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy), 50*s.scale) 
			end 
			xs.collisionjump = nil
		end
		if xs.rollprep
			xs.rollprep = $-1
			if (s.eflags & MFE_JUSTHITFLOOR) or not P_IsObjectOnGround(s) or (xs.zipdash > 5)
				xs.rollprep = 0
			end
		end
		if s.XS_JL
			s.XS_JL = $-1
		end
		
		if xs.zipdashadd
			xs.zipdashadd = max($-FRACUNIT, 0)
			if not xs.zipdash
				xs.zipdashadd = 0
			elseif xs.zipdashslide or not P_IsObjectOnGround(s)
				xs.zipdashadd = max($-2<<16, 0)
			end
		end
		if xs.zipdashminus
			xs.zipdashminus = $-1
			if not xs.zipdash
			or SUPER == 3 and not (xs.FreeFlight and xs.FreeFlight.LockOn and xs.FreeFlight.LockOn.valid)
				xs.zipdashminus = 0
			end
		end
		if xs.zipdashpause
			xs.zipdashpause = $-1
			if not xs.zipdashpause or (xs.zipdashx == nil or xs.zipdashy == nil)
				xs.zipdashx,xs.zipdashy = nil,nil
				if P_IsObjectOnGround(s)
					XSon.GetOutOfTheFloor(p, s)
					if s.state == S_PLAY_ROLL and not (p.pflags & PF_SPINNING)
						s.state = S_PLAY_RUN
					end
				end
			end
		end
		if xs.zipclimbedwall
			xs.zipclimbedwall = $-1
		end
		if xs.zipdashfavor
			xs.zipdashfavor = $-1
		end
		if xs.zipleapbuffer
			xs.zipleapbuffer = $-1
		end
		
		--We're in the zipdash state! Alter controls and a LOT of other things!
		if xs.zipdash and s.health		
			local ZIPSPEED = (((s.eflags & MFE_UNDERWATER) and (SUPER != 3)) and 36) or 112	
			
			XSon.ZipDashProperties(p,s,xs, ZIPSPEED)
		
			--Cancel conditions
			if (p.pflags & PF_JUMPED) and not xs.zipdashleap and (SUPER != 3)
			or (p.pflags & (PF_SLIDING|PF_STARTDASH))
			or (s.eflags & MFE_SPRUNG) and (p.panim == PA_SPRING)
			or (s.eflags & MFE_UNDERWATER) and xs.uwdebuff > 19 and (xs.zipdash > 5) and not xs.zipdashleap and not xs.darkphase
			or P_PlayerInPain(p) 
			or p.powers[pw_carry]

				if xs.zipdashleap and not (s.eflags & MFE_SPRUNG)
					s.momz = $/12
				end
				XSZipDashCancel(p, s, xs, true)
		
			elseif xs.zipdashleap --The Zip Leap behaviour. Sometimes called the Super Leap.

				XSon.ZipDashLeapBehaviour(p,s,xs, ZIPSPEED)			
		
			elseif (xs.zipcd > 8)
			or (xs.zipdash < 6) and not (xs.spin or ((xs.direct > 2) and not (p.pflags & PF_ANALOGMODE)) )	

				XSZipDashCancel(p, s, xs, true) --Cancel!
		
			elseif (xs.jump <= 1) and (p.cmd.buttons & BT_JUMP) or xs.zipleapbuffer --Perform a ZIP DASH LEAP!	

				XSon.ZipDashLeapOrNot(p,s,xs, ZIPSPEED)

			elseif (xs.zipdash < 6) --The Start Up! This is first initiated by the XSPrepZipDash function in XS-ZIPDASH	

				XSon.ZipDashShiftBack(p,s,xs, ZIPSPEED)
				
			elseif xs.zipdashslide
			or xs.FreeFlight and not xs.custom1
			or (not xs.FreeFlight) and not xs.spin	--Sliding Conditions	

				XSon.ZipDashSlideTimer(p,s,xs, ZIPSPEED)
				
			elseif xs.zipdash --The MAIN event!

				XSon.ZipDashTimer(p,s,xs, ZIPSPEED)	
				if xs.zipleapbuffer and xs.zipdash and (p.cmd.buttons & BT_JUMP)
					XSon.ZipDashLeapOrNot(p,s,xs, ZIPSPEED, true)
				end
			end
		elseif p.runspeed == 24<<16+222
			p.runspeed = skins[s.skin].runspeed
		end

		--Used for the snap dive now rather than the pouty animation.
		if xs.poutydrop
			if P_IsObjectOnGround(s)
				s.spriteyoffset = 0
				xs.poutydrop = 0
			else
				xs.poutydrop = $-1
				s.spriteyoffset = max($-FRACUNIT, 0)
			end
		end
		
		--Change everything back when we're done!
		XSon.AlterScaleorRoll(p,s,xs)
		
		--Undo bustwall charflags?
		if xs.changedcharflags
			if (SUPER != 3) and not xs.zipdash
				xs.changedcharflags = $-1
				if not xs.changedcharflags
					p.charflags = $ & ~(SF_CANBUSTWALLS|SF_NOSKID)
					xs.changedcharflags = nil
				end
			end
		end
		if s.XS_watergiftcd
			s.XS_watergiftcd = $-1
		end
		if xs.ExitState
			xs.ExitState = max($-1,0)
			if not xs.ExitState and (gametyperules & GTR_RACE)
				xs.spin,xs.custom1,xs.jump = 0,0,0
			end
		end
		
		if s.health		
			--A mildly stronger spring bounce when snapdiving
			if xs.strongspring and not (xs.snapdivetrick and xs.snapdivetrick < 9)
				if (xs.strongspring == 2)
				or not P_IsObjectOnGround(s) and (s.momz*FLIP > 0)
					p.cmd.buttons = $|BT_JUMP
					p.pflags = $|PF_JUMPDOWN
					s.momx,s.momy,s.momz = $/3,$/3, $*40/33
					if not (xs.sprung)
						S_StopSoundByID(s, sfx_sprong)
						if (xs.strongspring != 2) --If it's 2, don't play a sound!
							XSon.Sound(s, sfx_sprong)	
							xs.sprung = 1
						elseif xs.direct and (xs.direct>8) and (p.speed < 24*s.scale)
						and not (s.flags2 & MF2_TWOD) and not (twodlevel) and xs.turnang!=nil
							P_Thrust(s, xs.turnang, xs.direct*(s.scale/8)) --A mild initial start!
						end
					end
				end
				xs.strongspring = nil
			end
			if xs.sprung --trixx r 4 kidz
				if not xs.snapdivetrick
					xs.sprung = 0
				elseif (s.momz*FLIP > 0)
					if (xs.snapdivetrick < 13)
						local ghs = P_SpawnGhostMobj(s)
						ghs.scale = s.scale
						ghs.blendmode = AST_ADD
						ghs.colorized = true
						ghs.fuse,ghs.tics = 8,99
						ghs.destscale = 1
					end
					if xs.sprung == 1 
						local ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_IVSP)
						ghs.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
						ghs.blendmode = AST_ADD
						ghs.momz = s.momz/3
						ghs.color,ghs.colorized = SKINCOLOR_YELLOW,true
					end
				end
			end
			if s.XS_crushimmune --Few frames of immunity to crushing right after parkour.
				s.XS_crushimmune = max($-1,0)
			end
			if (s.eflags & MFE_GOOWATER)
				s.XS_gootime = s.XS_gootime and min(64, $+1) or 1
				if not xs.wasingoo and (s.momz*FLIP < 0) and not xs.zipdash and not p.xsringburst
					if (p.pflags & PF_JUMPED)
						p.pflags = $ & ~PF_THOKKED --Regain snap dive on entry
					end
					if ((s.XS_gootime > 18) or xs.snapdive and (s.XS_gootime > 2)) and (abs(s.momz) > 7*s.scale)
						s.momz = $*2/3
					elseif (s.momz*FLIP < -4*s.scale) and not (xs.snapdive and (abs(s.momz) > 6*s.scale) and (s.XS_gootime > 3))
						s.momz = $*12/7 --Altered to undo the effects of X Sonic's different jump physics.
					end
				end
				if (p.pflags & PF_JUMPED) and (s.XS_gootime>=59) and not xs.snapdive
					p.pflags = $ & ~PF_THOKKED --Repeatedly regain snapdive.
					s.XS_gootime = 31
				end
				xs.wasingoo = 3
				if xs.zipdash --Prevents an oddity.
					xs.snapdivewait = 2
					p.pflags = $|PF_JUMPDOWN
				end
			else
				if xs.wasingoo
					xs.wasingoo = $-1
					if not xs.wasingoo and (p.pflags & PF_JUMPED) and not xs.snapdive
						p.pflags = $ & ~PF_THOKKED --Regain snap dive on exit.
					end
				elseif s.XS_gootime
					s.XS_gootime = max($-2,0)
				end
			end	
			
			if xs.uwdebuff --Underwater stat debuff
				if not (s.eflags & MFE_UNDERWATER)
				or SUPER == 3
				or (p.powers[pw_shield] & SH_PROTECTWATER)
					xs.uwdebuff = 0			
				elseif (p.speed < 20*s.scale)
					xs.uwdebuff = min($+25, FRACUNIT)
				elseif not P_IsObjectOnGround(s)
					xs.uwdebuff = min($+2, FRACUNIT)
				else
					xs.uwdebuff = min($+1, FRACUNIT)
				end
			end	
			
			--How fast we need to go for friction alterations
			if (p.speed < 50*s.scale) and not xs.zipdashslide
				xs.frictionspeedtreshold = $+1
			else
				xs.frictionspeedtreshold = 0
			end
			
			if xs.bonked
				XSon.Bonking(p,s,xs)  --Bonk animation!
			else
				if p.xswallrun
					XSon.WallRunning(p,s,xs, p.xswallrun)
				end
				--Cooldown Timer for Sonic's pain grunt 
				if xs.paingrunt and not P_PlayerInPain(p)
					xs.paingrunt = $-1
				end
			
				 --Don't stop on the edge at all again UNTIL...!
				if xs.ledgereactivate
					if (p.speed > 8*s.scale) or (p.pflags & PF_SPINNING)
					or xs.hitspring or xs.zipdash or not P_IsObjectOnGround(s)
						xs.ledgereactivate = nil
					end
				end	
				if s.wallspintrans 
					XSon.HalfPipeWallRun(p,s,xs)
				end		
				XSon.SFSUPER(p,s,xs, "ON") --SF_SUPER flags kicks in mainly for mod/sector compatibility.			
			end
			--Ugh, 2D mode SUCKS. I can't just lock momy either because mappers use the Y-axis for visual spectacle.
			if xs.yteleport != nil 
				if ((s.flags2 & MF2_TWOD) or twodlevel) and not xs.hitspring and not (s.eflags & MFE_SPRUNG)
					s.momy = 0 P_TryMove(s, s.x, xs.yteleport, true) s.momy = 0
				end xs.yteleport = nil
			end	

			--Don't stop on ledges for a while.
			if xs.ledgeprotectcd
				xs.ledgeprotectcd = max($-1, 0)	
				if not xs.ledgeprotectcd
					xs.ledgeprotectangle = nil
					if ((s.sprite2 & ~FF_SPR2SUPER) == SPR2_EDGE) and (p.panim == PA_IDLE)
						s.state = S_PLAY_EDGE
						p.panim = PA_EDGE
					end
				end
			end			
		end
		if xs.HUDpref==true
			if p.exiting
				s.XShudpopin = s.XShudpopin and min($+1,60) or 1
			elseif s.XShudpopin
				s.XShudpopin = min(28, max($-1,0))
			elseif s.XShudpopin == nil 
				s.XShudpopin = 28
			end
			if xs.RingHUD
				if (xs.RingHUD >= 9)
					xs.RingHUD = 0
				else
					xs.RingHUD = $+1
				end
			end
			if xs.LastRings != p.rings
				if not xs.RingHUD and xs.LastRings != nil 
				and ((p.rings or 1) > xs.LastRings)
					xs.RingHUD = 1
				end			
				xs.LastRings = p.rings
			end
		end
		--Friction modifiers to keep Sonic's speed in some situations!	
		xs.runfriction = min($, FRACUNIT)		
		if xs.groundfriction != xs.runfriction and (xs.runfriction != 0)  
			XSon.FrictionModifiers(p,s,xs)	
		end		
	end
end