--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════#
--║   GRINDING MOBJ THINKERS   ║ PlayerThinkers & MobjThinkers hooks. MobjThinker are called from a tracker if not a player.
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════#
local RAIL = GS_RAILS if RAIL.blockload return end
local RAILTYPE = GS_RAILS_TYPES
local DF = RAILTYPE[0] --The default table for railtyppes.
local DFSTATS = GS_RAILS_SKINS["Default"] --The default character S_SKIN.
--local LOOPTYPE = GS_LOOP_TYPES

--Replace this locally with one that accounts for upsidedown rails/hangrails and vertical rails better.
local P_SpawnSkidDust = function(p, DIST)
	local s = p.mo if not s return end
	local ANG = p.drawangle+ANGLE_90
	local Z = 1
	local rail = s.GSgrind and s.GSgrind.myrail or nil
	local UPSIDEDOWN = 1
	
	DIST = (DIST or 32<<16)/FRACUNIT
	DIST = P_RandomRange(-DIST, DIST)*s.scale
	if rail and type(rail)=="userdata" and rail.valid
		if rail.GSVertRail
			Z = DIST --Randomized Z dist instead.
			DIST = max(-11*s.scale, min($, 11*s.scale)) 
			UPSIDEDOWN = 2
		elseif rail.GSupsidedown or rail.GSupsidedown
			Z = $+abs(s.height) --Higher!
			UPSIDEDOWN = -1
		end
	end
	local ghs = P_SpawnMobjFromMobj(s, FixedMul(DIST, cos(ANG)), FixedMul(DIST, sin(ANG)), Z, MT_GSRAILGFX)
	if ghs and ghs.valid
		if (leveltime%2)
			ghs.scale = $/2
		end
		ghs.renderflags = $|RF_SEMIBRIGHT
		ghs.state = S_SPINDUST1
		ghs.fuse = 25
		if UPSIDEDOWN==2
			P_Thrust(ghs, p.drawangle, (DIST/3))
		else
			ghs.momz = abs(DIST/7)*P_MobjFlip(s)*UPSIDEDOWN
		end
		ghs.sttfade = 17
		ghs.scalespeed = s.scale/25
		ghs.destscale = s.scale*3
		return ghs --
	end
end

--Returns how far "s" is on the rail.
RAIL.RAILPROGRESS = function(s, GS, rail, VERTRAIL)
	local SPEED = (GS.railspeed or 0)+((rail.GSconveyor) and rail.GSconveyor<<16 or 0)
	local m = rail.GSnextrail
	local LENGTH = rail.GSrailXYlength or 0
	if (SPEED >= 0)
		if rail.GSnextrail and rail.GSnextrail.GSrailXYlength
			LENGTH = $+(rail.GSnextrail.GSrailXYlength/2)
		else
			LENGTH  = $+(1<<16)
		end
		return min(LENGTH, FixedHypot(s.x-rail.x, s.y-rail.y)), LENGTH  --
	else
		if rail.GSprevrail and rail.GSprevrail.GSrailXYlength
			LENGTH = $+(rail.GSprevrail.GSrailXYlength/2)
		else
			LENGTH  = $+(1<<16)	
		end
		return min(LENGTH, FixedHypot(s.x-m.x, s.y-m.y)), LENGTH --
	end
end

--Checks if we should even be on the rail or not. GS and CONVEYOR are optional, but recommended.
--Returns XYlength if valid, otherwise 0.
RAIL.ValidGrind = function(GS, rail, CONVEYOR)
	if rail and rail.valid and rail.GSrailXYlength	
		if CONVEYOR==nil 
			CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
		end
		local NEXT = rail.GSnextrail 
		if (((GS.railspeed or 0)+CONVEYOR) <0) 
			NEXT = rail.GSprevrail
		end
		if NEXT and NEXT.valid
			return rail.GSrailXYlength or 1 --
		end
	end return 0 --
end

--Check if a rail is a vertical one. GS can simply just be for checking, ALTNEXT forces the next rail to use for reference.
RAIL.VertRail = function(rail, GS, ALTNEXT)
	if rail and rail.valid
		local RAILLIMIT = rail.GSrailXYlength or 0
		if (RAILLIMIT <= 34<<16)
			local NEXT = rail.GSnextrail
			if (ALTNEXT) NEXT = ALTNEXT end
			if NEXT and NEXT.valid
				local STARTZ, DESTZ = rail.z, NEXT.z
				local ZLIMIT = max(STARTZ, DESTZ)-min(STARTZ, DESTZ)
				local XYLIMIT = max(3, RAILLIMIT*3/2)
				if (ZLIMIT > RAILLIMIT)
					if GS=="check" or (ALTNEXT)
						return STARTZ,DESTZ,ZLIMIT,NEXT
					elseif GS
						NEXT = rail.GSprevrail 
						if (GS.railspeed+((rail.GSconveyor and not (GS and (GS.grinding or 0)<=2)) and rail.GSconveyor<<16 or 0) >= 0) 
							NEXT = rail.GSnextrail
						end
						if NEXT and NEXT.valid
							return STARTZ or 1,DESTZ,ZLIMIT,NEXT
						end
					end
				end
			end	
		end
	end
end

--Temporary debug
local print = function(text)
	if (consoleplayer) and consoleplayer==players[0] and consoleplayer.name=="Golden Shine" and not (players[1] and netgame==true)
		CONS_Printf(consoleplayer, text)
	end
end

--Check if we're not BARELY missing a spring/booster on the rail's edge while grinding.
RAIL.ScanCollide = function(s, GS, COLLIDESKIP, SPEED, XYDIST)
	if GS.justtouchedrailthing return end --Just touched one.
	if (s.GScollide) and not (COLLIDESKIP) return end --Already colliding.
	
	local rail = GS.myrail if not (rail and rail.valid and rail.GSnextrail)  print("\x81 Lolnah") return end --
	if SPEED==nil
		SPEED = (GS.railspeed or 0) + ((rail.GSconveyor) and rail.GSconveyor<<16 or 0)
	end
	if XYDIST==nil
		XYDIST = (48*max(FRACUNIT, s.scale)) + abs(SPEED) + abs(s.radius) --Distance to check for.
	end
	local NEXT = (SPEED >= 0) and rail.GSnextrail or rail.GSprevrail
	local RETURNVALUE, TYPE = nil
--	print("Start time. XYDIST:\x83+"+XYDIST/FRACUNIT)
	
	searchBlockmap("objects", function(ref, t)
		if not (t and t.valid and t.GSrailobject and not t.player) return nil end --
		
		TYPE = RAIL.AttachList[t.type]
		if not ((t.flags & MF_SPRING) or TYPE=="booster" or TYPE=="damage") return nil end --
		local TODIST = FixedHypot(t.x-s.x, t.y-s.y)
		
		if (TODIST > ((XYDIST)+4<<16)) return nil end --  print("\x85"+"Too-far:\x82"+TODIST/FRACUNIT+"\x80 XYDIST:\x82"+XYDIST/FRACUNIT)
		
		if (abs(s.z-t.z) > ((XYDIST/3)+99*s.scale) ) return nil end -- print("\x85"+"Too high/low")
		
		if not (t.GSrailobject==rail or t.GSrailobject==NEXT) return nil end -- print("\x85"+"Not connected to our rail.")
		
		if TYPE=="booster" or (t.flags & MF_SPRING)
			if (P_TryMove(s, t.x, t.y, true)) s.z = t.z end
		--	 print("\x82"+"Move to Spring") 
		end
		
		s.GScollide,GS.justtouchedrailthing = t,6
		RETURNVALUE = true
	--	print("\x81 SWAP RAIL COLLIDE! ["+RETURNVALUE+"]")
		return true --
		
	end, s, s.x-XYDIST, s.x+XYDIST, s.y-XYDIST, s.y+XYDIST)
	if GS.justtouchedrailthing and s.GScollide
	--	print("Do collide!\x82"+"["+RETURNVALUE+"]\x80 TYPE:\x8a"+TYPE)
		RAIL.AttachedCollission(s, rail, GS)
		s.GScollide = nil
		if GS --In some extremely rare cases, GS might be nil now.
			GS.exittic = nil 
		end
		return RETURNVALUE --See where the next tic takes us!
	end
end

--Return vertical Z position depending on railspeed+conveyor.
RAIL.SetVertZ = function(rail, SPEED, s)
	local VALUE = rail.z or 0
	if not (rail.GSnextrail and rail.GSnextrail.valid)
		if (s) VALUE = s.z end
	else
		local RESULT = (rail.GSnextrail.z < rail.z) and -1 or 1 
		if ((SPEED or 0) < 0) RESULT = $*-1 end
		if RESULT==1
			VALUE = max(rail.z, rail.GSnextrail.z)
		else
			VALUE = min(rail.z, rail.GSnextrail.z)
		end
	end
	if rail
		if rail.GSVertRailBotZ!=nil
			VALUE  = max(rail.GSVertRailBotZ, $)
		end
		if rail.GSVertRailTopZ!=nil
			VALUE  = min(rail.GSVertRailTopZ, $)
		end	
	end
	return VALUE --
end

--Didn't intend to support this vertical nonsense at first, but here we are.
RAIL.VerticalGrind = function(p,s,GS, CONVEYOR, REVERSE, STARTZ, DESTZ, ZDIST)
	if not (s.health) or not (GS.grinding) or GS.leftrail return end 
	
	ZDIST = ZDIST or 0
	
	local rail = GS.myrail
	local SPEED = (GS.railspeed or 0)+(CONVEYOR or 0)
	local DISTMOVE = SPEED
	local NEXT = rail.GSnextrail if (SPEED < 0) NEXT = rail.GSprevrail end
	
	local RSTATS = GS.MyRailStats	
	local LOOP = RAIL.LearnLoopType(rail or GS.LastRail)
	local RAILANG = RAIL.ANG(rail)
	local NANG = RAIL.ANG(rail)+ANGLE_180 // if NANG==nil NANG = R_PointToAngle2(rail.x, rail.y, rail.GSnextrail.x, rail.GSnextrail.y) end
	local DANG = (rail.GSnextrail.z < rail.z) and ANGLE_180 or 0
	local OGZ, RZ = s.z, s.z
	local ZSPEED = 0
	local LOOPFLAGS = rail.GSloopflags or 0
	
	if (RSTATS.INIT < 2) --JUST got on the rail!
		RSTATS.INIT = 2
		RSTATS.falling = 0
		if rail.GSVertRailBotZ!=nil
			OGZ = max(rail.GSVertRailBotZ, $)
		end
		if rail.GSVertRailTopZ!=nil
			OGZ = min(rail.GSVertRailTopZ, $)
		end	
		RSTATS.LASTZ, RZ = OGZ, OGZ
		local X,Y = rail.x, rail.y
		if rail.GSVertRail==1 --If next rail is above, this is 1.
			if (SPEED >= 0)
				X,Y = rail.GSnextrail.x, rail.GSnextrail.y
			end
		elseif rail.GSVertRail==-1 --If next rail is above, this is 1.
			if (SPEED < 0)
				X,Y = rail.GSnextrail.x, rail.GSnextrail.y
			end		
		end
		P_MoveOrigin(s, X, Y, OGZ) 
		GS.railprogress = min(GS.railprogress or 0, 1)
		DISTMOVE,SPEED = $/2,$/2 --Ease into it.
	elseif RSTATS.LASTZ!=nil
		RZ = RSTATS.LASTZ
		OGZ = RSTATS.LASTZ
	end
	local RX,RY = rail.x, rail.y
	if (DANG) and (DISTMOVE) --The NEXT rail is BELOW the CURRENT one!
		if (SPEED < 0) --You're going to "prevrail", which is ABOVE you. AKA, you should be going UP unless reversing.
			DISTMOVE = abs($)*P_MobjFlip(s)
		else --if (SPEED < 0) --You're going to "NEXT" rail, which is BELOW you. AKA, you should be going DOWN unless reversing.
			DISTMOVE = -abs($)*P_MobjFlip(s)
		end
	else --The NEXT rail is ABOVE the CURRENT one.
		if (SPEED < 0) --You're going to "prevrail", which is BELOW you. AKA, you should be going DOWN unless reversing.
			DISTMOVE = -abs($)*P_MobjFlip(s)
		else --if (SPEED < 0) --You're going to "NEXT" rail, which is ABOVE you. AKA, you should be going UP unless reversing.
			DISTMOVE = abs($)*P_MobjFlip(s)
		end		
	end
	if LOOP and (LOOP.walljump)
		if RSTATS.INIT==2
			RSTATS.WJTIMER = 0
			if (abs(GS.railspeed) > 70*s.scale)
				GS.railspeed = ($<0) and -70*s.scale or 70*s.scale
			end
			GS.railspeed = $/2
			SPEED = $/2
			DISTMOVE = $/2
		else
			RSTATS.falling = 0
			RSTATS.WJTIMER = RSTATS.WJTIMER and $+1 or 1
			if (RSTATS.WJTIMER > 55)
				RSTATS.falling = 99<<16 --Time to fall.
			end
		end
	elseif rail.GSrailspeed and DISTMOVE and SPEED
		DISTMOVE = FixedDiv(FixedMul($, rail.GSrailspeed), 100)
		SPEED = FixedDiv(FixedMul($, rail.GSrailspeed), 100)
	end
	
	if (DESTZ < STARTZ)
		RZ = $+DISTMOVE
		ZSPEED = RZ-OGZ
		
		if (RZ < DESTZ) and (ZSPEED < -s.scale)
			GS.railprogress = abs(ZDIST)+(6999<<16)
		elseif (RZ > STARTZ) and (ZSPEED > s.scale)
			GS.railprogress = abs(ZDIST)+(6999<<16)
		else
			GS.railprogress = abs( min(DESTZ, RZ)-max(DESTZ, RZ) )
		end
		
	else
	
		RZ = $+DISTMOVE
		ZSPEED = RZ-OGZ
		
		if (RZ < min(DESTZ, STARTZ)) and (ZSPEED < -s.scale)
			GS.railprogress = abs(ZDIST)+(6999<<16)
		elseif (RZ > max(DESTZ, STARTZ)) and (ZSPEED > s.scale)
			GS.railprogress = abs(ZDIST)+(6999<<16)
		else
			GS.railprogress = max(STARTZ, RZ)-min(STARTZ, RZ)
		end
	end		
	if p
		if not ((rail.GShangrail) and not (s.eflags & MFE_VERTICALFLIP)) --Makes no sense to set balancing mechanics here.
		and not ((s.eflags & MFE_VERTICALFLIP) and (rail.GShangrail))
			GS.badbalance = false
			GS.railtilt,GS.turntoia = 0,0
			GS.perfectcorner = max(GS.perfectcorner or 0, 5)
			GS.ANGswap = nil
			if GS.flipanim or GS.flip --Can't fliptrick on completely vertical rails, duh.
				if not (LOOP and (LOOP.walljump or LOOP.anim and LOOP.anim!=0))
					GS.flipanim = min($, 2)	
				else
					GS.flipanim,GS.flip = nil
					if GS.animcall=="flipanim" GS.animcall = nil end
					s.mirrored = false
					s.spritexoffset,s.spriteyoffset = 0,0
				end
			end
		end
		if ((GS.grinding or 0) < 3) and not (LOOP)
		or LOOP and LOOP.walljump --Face the highest side of the rail.
			local P = (rail.GSnextrail and (rail.GSnextrail.z > rail.z)) and ANGLE_180 or 0
			p.drawangle = RAILANG+P
			GS.willfacerail,GS.turntoia,GS.camwait = 0,0,0
			local ANG,PLUS = RAIL.AngleDifference(RAILANG+P, s.angle, 2, ANG20*4, 2)
			if (ANG > ANG60)
				s.angle = RAILANG+P
			end
		end
		if not (LOOP) or LOOP.anim==0 
		or ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
			GS.nocam = max(GS.nocam or 0, 3)
		end
	elseif not (LOOP) and not (s.target and s.target.player) --For non-players.
		local P = (rail.GSnextrail and (rail.GSnextrail.z > rail.z)) and ANGLE_180 or 0
		s.angle = RAILANG+P	
		GS.willfacerail = 0
	end
	if rail.GSVertRail==1 --If next rail is above, this is 1.
		if (SPEED >= 0)
			RX,RY = rail.GSnextrail.x, rail.GSnextrail.y
		end
	elseif rail.GSVertRail==-1 --If next rail is above, this is 1.
		if (SPEED < 0)
			RX,RY = rail.GSnextrail.x, rail.GSnextrail.y
		end		
	end
	
	P_MoveOrigin(s, RX, RY, RZ)--Teleport, Mehrio.
	local USEANG = NANG+DANG
	if rail.GSforceUDMFang!=nil
		USEANG = rail.GSforceUDMFang+ANGLE_180
	end
	if not (rail.GSinvisiblerail) and not (rail.flags2 & MF2_DONTDRAW) and (rail.sprite)
		s.spritexoffset,s.spriteyoffset = 0,0
		local DIST = ((LOOP and LOOP.walljump) and 23 or 38)*s.scale
		for i = 1,3
			if (P_TryMove(s, s.x-FixedMul(DIST, cos(USEANG)), s.y-FixedMul(DIST, sin(USEANG)), true)) break end
			DIST = $/2
		end
	elseif LOOP and GS.grinding and not (LOOP.anim==0) //and (LOOP.walljump)
		local DIST = 5*s.scale
		for i = 1,6 --Shoot forwards a bit on invisible walljump rails to make life easier for mappers.
			if not (P_TryMove(s, s.x+FixedMul(DIST, cos(USEANG)), s.y+FixedMul(DIST, sin(USEANG)), true))
				DIST = $/2
			end
		end
	end
	RSTATS.LASTZ = RZ --Remember where we're meant to be so SRB2 clipping doesn't interfere.
	RSTATS.VERTRAIL = 2

	if not (rail) or not GS.grinding
		RAIL.ExitRail(s, true)
	elseif not (GS.railprogress > ZDIST) --Swap to the next rail if there is one!
	and not (GS.FORCEKICK)
		P_InstaThrust(s, p and p.drawangle or RAILANG, 99)
		RSTATS.ZSPEED = ZSPEED
		if ((RSTATS.falling or 0) > 15)
			RAIL.ExitRail(s, true) --No rail. Convert momentum and go!	
			if GS
				GS.cannotgrind = max((GS.cannotgrind or 0)+8, 24)
			end
		elseif RSTATS.ZSPEED!=nil
			local ADD = 0
			if LOOP and LOOP.walljump
				ADD = s.scale/2 if (RSTATS.ZSPEED < 0) ADD = -s.scale/4 end
			else
				if (RSTATS.ZSPEED < 0) 
					ADD = (abs(GS.railspeed or 0) < 35*s.scale) and -(s.scale*5/3) or -(s.scale*4/3)
				else
					ADD = (abs(GS.railspeed or 0) < 37*s.scale) and s.scale/2 or s.scale*2/3 
				end
			end
			if ADD
				GS.railspeed = $-(ADD*RAIL.GrindFlip(s, rail)) --Replace conventional slope physics like so.
			end
		end
		return true --
	else
		local XNEXT = rail if (SPEED < 0) XNEXT = rail.GSnextrail end	
		
		if not rail
		or not (NEXT and NEXT.valid) or (NEXT.flags2 & MF2_DONTDRAW) or NEXT.GSrailXYlength==nil
		or ((SPEED < 0) or (ZSPEED < 0)) and (not rail.GSprevrail or not rail.GSprevrail.valid or rail.GSprevrail.GSrailXYlength==nil)
		or (GS.FORCEKICK)
			RAIL.ExitRail(s, true) --No rail. Convert momentum and go!	
		else
			if (SPEED < 0)
				if not rail.GSprevrail or not rail.GSprevrail.valid or rail.GSprevrail.GSrailXYlength==nil
				or not NEXT.GSprevrail or not NEXT.GSprevrail.valid or NEXT.GSprevrail.GSrailXYlength==nil
					RAIL.ExitRail(s, true) return true --No rail. Convert momentum and go!
				end
			end
			if (RSTATS.INIT > 2)
				local NEWRAILANG = RAIL.ANG(NEXT)
				if NEXT and NEXT.GSforceUDMFang!=nil
					
				end
				GS.prevrailang = GS.grinddirection
				if p 
					p.drawangle = NEWRAILANG+GS.raildirection
				else
					s.angle = NEWRAILANG+GS.raildirection
				end
				P_MoveOrigin(s, XNEXT.x,  XNEXT.y, RAIL.SetVertZ(GS.myrail, SPEED, s))
				
				GS.myrail, GS.railprogress = NEXT,0 --Attach to the new rail.
				RSTATS.INIT = 0 if RSTATS.ZSPEED RSTATS.ZSPEED = $/99 end
			end
		end
	end return true --
end

--Temporary debug
local print = function(text)
	if (consoleplayer) and consoleplayer==players[0] and consoleplayer.name=="Golden Shine"
		CONS_Printf(consoleplayer, text)
	end
end

--This is the main grinding code, meant for all mobjs, not just players!
RAIL.GrindRail = function(s, rail)
	local GS = s.GSgrind --Setup missing vars so I don't have to do a billion nil checks.
	if GS==nil or GS.cannotgrind==nil or GS.grinding==1 RAIL.SetupMobjRailVars(s) GS=s.GSgrind end
	if not (s.health)
		if not (GS.leftrail) RAIL.ExitRail(s, true, true) end return "exit" -- Dead men grind no rails.	
	end
	if not (rail and rail.valid)
		if not (GS.myrail and GS.myrail.valid)
			GS.myrail = nil 
			if not GS.leftrail
				RAIL.ExitRail(s, true, true) return "exit" --
			end
		end
		rail = GS.myrail --The rail we're on. Vital info we really can't miss.
	end
	local p = s.player --This is NOT a player-specific thinker, only a shortcut! It may be invalid.

	if GS.leftrail --Just left a rail one to two tics ago. Abort thinker!
		if not p GS.leftrail = max($-1,0) end --PlayerThinker does this too!
		return "justleft" --
	end
	
	local TYPE = RAILTYPE[RAIL.LearnRailType(rail)]
	local HANGRAIL = RAIL.HangRail(s, rail) --Determine if we're on a hangrail!
	local RAILANG = RAIL.ANG(rail)
	local CURVEANG = RAILANG
	local SKIN = (p) and GS_RAILS_SKINS[s.skin] or nil
	local STATS = (SKIN) and SKIN["S_SKIN"] or nil

	local GRINDING = GS.grinding or 0 --Uses LAST tic's GS.grinding value!
	local LOOP = RAIL.LearnLoopType(rail)
	local JUMPFLAGS,LOOPFLAGS,GRINDFLAGS,CONVEYOR = 0,0,0

	GS.grinding = $+1 --Count upwards to keep track of how long we're grinding.
	GS.cannotgrind = max($, 2) --Don't bother with rail collision after we're attached.
	if rail and rail.valid
		CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
		JUMPFLAGS,LOOPFLAGS,GRINDFLAGS = rail.GSjumpflags or 0, rail.GSloopflags or 0, rail.GSgrindflags or 0
		if P_IsObjectOnGround(s)
			s.z = $+(s.scale*P_MobjFlip(s))		
		end
		if not (p and (p.pflags & PF_STARTJUMP) and (p.cmd.buttons & BT_JUMP))
			s.momz = -((P_GetMobjGravity(s) or 0)*9/8)
		end
		s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_APPLYPMOMZ|MFE_JUSTSTEPPEDDOWN)		
	end

	--Calculate rail grinding speed!
	if (GRINDFLAGS & 524288)
		if CONVEYOR
			GS.railspeed = (CONVEYOR<0) and -1 or 1
		else
			GS.railspeed = ((GS.railspeed or 0)<0) and -1 or 1
		end
	elseif GS.railminspeed or not p --Non-players usually get a minimum value of 12, but you can force any number on anything.
		if GS.railminspeed == nil
			GS.railminspeed = 12
		end
		if GS.railminspeed --If it's not 0, apply!
			local MIN = GS.railminspeed*s.scale
			if (MIN > abs(GS.railspeed)+abs(GS.railboost or 0)+abs(CONVEYOR or 0))
				GS.railspeed = (GS.railspeed < 0) and -MIN or MIN
			end
		end
	end

	--Receive a speed penalty for landing on the rail with a weird momentum angle. Allign yourself properly!
	if GS.landangle != nil --Usually given on initial rail collission.
		RAIL.MobjToRailAngle(s, rail, GS.landangle, true)
		if (s.state == S_PLAY_PAIN) and p --Face the direction the player will be expecting.
			p.powers[pw_flashing] = min($,102)
			RAIL.MobjToRailAngle(s, rail, s.angle, false)
		end
		GS.landangle = nil
	end

	--Lazy hack to invert railboost, the equivalant of P_Thrust for railgrinding.
	if GS.railboost and (GS.railspeed < 0)
		GS.railboost = -$ --GS.raildirectioncheck = true --Check next frame for better accuracy.
	end

	--This is our non-sloped rail speed! We don't lose speed in SRB2 on flat rail surfaces.
	GS.railspeed = $+GS.railboost 
	GS.railboost = 0 --Use this value instead of P_Thrust. Mess with GS.railspeed directly to mimmick P_InstaThrust
	
	local REVERSE = 1 --Are we reversing on the rail? Only possible with slopes, normally.
	local GAINING = false
	local RSTATS = GS.MyRailStats
	
	--Now apply slope physics if the rail's sloped!
	if rail.GSrailZDelta and not (RSTATS and RSTATS.VERTRAIL) and not (STATS and STATS.SLOPEPHYSICS==false)
		local ZDELTA = rail.GSrailZDelta
		if (abs(ZDELTA) >= ANG1*9) --Diminishing returns for sanity's sake.
			local ZDELT = abs(ZDELTA)
			local ANG = ZDELT/ANG1
			if (ANG >= 39)
				ZDELT = min(ANG1*39, 	FixedDiv(FixedMul($, 4), 7) )
			elseif (ANG >= 30) 
				ZDELT = min(ANG30, 		FixedDiv(FixedMul($, 12), 16) )
			elseif (ANG >= 22)
				ZDELT = min(ANG1*22, 	FixedDiv(FixedMul($, 25), 30) )
			elseif (ANG >= 16)
				ZDELT = min(ANG1*16, 	FixedDiv(FixedMul($, 50), 57) )
			elseif (ANG >= 12)
				ZDELT = min(ANG1*12, 	FixedDiv(FixedMul($, 50), 55) )
			else
				ZDELT = min(ANG1*9, 	FixedDiv(FixedMul($, 25), 26) )
			end
			ZDELTA = ZDELT*((rail.GSrailZDelta < 0) and -1 or 1)
		end
		local EXTRASPEED = (ZDELTA/1000)*P_MobjFlip(s)
		if EXTRASPEED and not (GRINDFLAGS & 524288)
			REVERSE = (RAIL.AngleDifference(RAIL.ANG(rail),p and p.drawangle or s.angle, 2, ANGLE_90,2) > ANGLE_45) and 1 or -1
			REVERSE = (GS.railspeed<0) and -$ or $	
			if (GS.railspeed < 0) and (GS.railspeed+EXTRASPEED < GS.railspeed)
			or (GS.railspeed >= 0) and (GS.railspeed+EXTRASPEED >= GS.railspeed)
				GAINING = true
			end
			
			local MAX = DFSTATS.MAXSPEED*s.scale if STATS and STATS.MAXSPEED!=nil MAX = STATS.MAXSPEED*s.scale end
			local CSPEED = abs(GS.railspeed)			
			if GS.zboost --Initial boost/slowdown strength depends on your landing momentum. Lasts 2 to 3 tics.
				local BOOST = GS.zboost/FRACUNIT
				local NEXT = GS.railspeed+FixedDiv(FixedMul(EXTRASPEED, 50), max(4, min(53, 53-BOOST)) )
				if not (abs(NEXT) > MAX)
					GS.railspeed = NEXT CSPEED = abs(GS.railspeed)
				end
			end
			if (CSPEED > 20*s.scale) --The boost from slopespeed needs to be stronger the faster you're going to feel impactful
				if (CSPEED > 40*s.scale)
					if (CSPEED > max(200*s.scale, MAX)) and (GAINING) and (s.skin!="sms")
						EXTRASPEED = $/9 --Enough of you and shenanigans!
					else
						if (CSPEED > 99*s.scale) and GS.crouchgrind and GAINING==true
							EXTRASPEED = $/4
						else
							EXTRASPEED = (CSPEED > 70*s.scale) and $/5 or $/6
						end
					end
				else
					EXTRASPEED = $/5
				end
			else
				EXTRASPEED = (CSPEED > 10*s.scale) and $*2/9 or $/4
			end	
			
			if GAINING==false
				if (CSPEED > max(180*s.scale, MAX))
					EXTRASPEED = $*3/2
				end
			else
				if MAX and (CSPEED > MAX)
					if REVERSE==1 or (STATS and STATS.MINSPEED)
						EXTRASPEED = 0 --No more to be gained!
					end
				end
			end
			if EXTRASPEED --Now adjust for crouch slope bonuses.
				if GS.flipanim and not (GS.zboost)
					if (GS.flipanim!=1) --You'll lose less speed but gain less too mid-jump. 
						EXTRASPEED = (GAINING==false) and $ or $/2
					elseif GAINING==false
						EXTRASPEED = (GAINING==false) and $*2 or $*6/5 --Lose & gain more speed upon landing on a hill.
					end
				else
					if GS.crouchgrind --Crouching gives more favorable slope momentum so long as you're well balanced!
					and (GS.perfectcorner or not GS.badbalance)
						if GAINING==false
							EXTRASPEED = $*5/6 --Lose 17% less speed to slopes.
						else
							EXTRASPEED = $*7/5 --Gain 40% more slopespeed
						end
					end
					if (CSPEED < 37*s.scale) and GAINING==false --Some mercy for slow characters.
						EXTRASPEED = (CSPEED > 24*s.scale) and $*3/4 or $*2/3
					end
				end
				if GAINING==true and REVERSE==1 --Initial stat-based multiplier.
					local RMAX = min(240*s.scale, MAX) if RMAX==0 RMAX = 240*s.scale end 
					if (CSPEED < RMAX/2)
						EXTRASPEED = (CSPEED < max(50*s.scale, RMAX/3)) and $*2 or $*3/2
					end
				end
				if STATS and STATS.SLOPEMULT and EXTRASPEED --Multiplier/Reducer of slope physics from S_SKIN?
					local MULT = STATS.SLOPEMULT or 0
					EXTRASPEED = FixedDiv(FixedMul($, 100+MULT), 100)
				end			
				if not (GAINING==true and MAX and (CSPEED > MAX))
				or REVERSE==-1 and (CSPEED < 150*s.scale)
					GS.railspeed = $+EXTRASPEED --FINALLY apply that shit.
				end
			end
		end
	end
	GS.reversing = REVERSE or 1 --Convenience feature.

	--What's the direction we're grinding towards?
	if (GS.railspeed+CONVEYOR < 0)
		GS.grinddirection = RAILANG
	else
		GS.grinddirection = RAILANG+ANGLE_180
	end
	
	local Rspeed = abs(GS.railspeed or 1) --Convenience version Railspeed that's always positive and never 0. Ignores CONVEYOR

	--Rail Grinding start, sounds and visual effects! Sound gets louder depending on speed. GRINDING==2
	local rvolume = min( 255, max(160, (max(Rspeed, s.scale)/s.scale)*4 ) )	
	if GS.grinding == 2 --Our first tic of grinding! Let's make it special!
		s.momx,s.momy,s.momz,s.pmomz = 0,0,0,0
		GS.grindsfx,GS.grindsfx2 = 0,0
		--We just landed on it, so snap to the appropriate point on the rail
		if not rail.GSVertRail --Regular rail snap.
			local snapX,snapY
			if (GRINDFLAGS & 524288)
				GS.railboost,Rspeed,GS.preswitchspeed,GS.startboost = 0,0,nil,nil
				if (JUMPFLAGS & (1048576|2097152)) --Trying to make an 06 jumprope? I'mma help. Snap the player to the center!
				and rail.GSrailXYlength and (rail.GSrailXYlength < 2800<<16) and not (CONVEYOR) 
					snapX = rail.x-FixedMul(rail.GSrailXYlength/2, cos(RAILANG))
					snapY = rail.y-FixedMul(rail.GSrailXYlength/2, sin(RAILANG))
				end
				GS.ropesnap = rail
			end
			if snapX==nil
				snapX, snapY = RAIL.GetRailXYDistFromOrigin(s, rail) --Get our distance from the rail's starting position
			end
			local snapZ = RAIL.GetRailZ(rail, s, 0, nil, {X=snapX, Y=snapY})
			if p s.z = snapZ P_TryMove(s, snapX, snapY, true) end
			P_MoveOrigin(s, snapX, snapY, snapZ) --Now SNAP to the rail, where we're meant to be!
		end
		if rail.GSfaceforward --Forcibly face a certain way?
			CONVEYOR,GS.railspeed = abs($),abs($)	
			GS.reversing = 1
			if rail.GSfaceforward!=-1
				RAIL.MobjToRailAngle(s, rail, RAILANG+ANGLE_180, false)
			else
				RAIL.MobjToRailAngle(s, rail, RAILANG, false)			
			end
		end
		if GS.startboost --If your mobj spawns with this value, start grinding with this speed everytime!
			GS.railboost = $+GS.startboost
		end
		--Add rail switching speed when applicable! Speedrunners will LOVE the small boost.
		if GS.preswitchspeed
			if (Rspeed+abs(CONVEYOR) > 30*s.scale)
				GS.EaseInSpeed = 175 --Next tic, slow down a bit.
			end
			if (CONVEYOR)
				GS.zboost = nil
			elseif not (Rspeed > 75*s.scale)
				GS.railboost = $+min(3*s.scale, abs(GS.preswitchspeed/9))
			end
			GS.railswitching,GS.preswitchspeed = 0,nil --Just incase.
			if GS.zboost and (Rspeed > 24*s.scale) --Don't repeatedly get Z boosts to infinity.
				GS.zboost = $/((Rspeed<38*s.scale) and 3 or (Rspeed<50*s.scale) and 6 or (Rspeed<60*s.scale) and 12 or 48)
			end
		end
		if GS.railspeed
			local SPEEDMOD, STARTMOD = (abs(GS.railspeed)<22*s.scale) and 1 or (abs(GS.railspeed)>61*s.scale) and 3 or 2, 30
			if STATS 
				if STATS.STARTMODIFIER!=nil --Modify initial speed!	
					SPEEDMOD = max(-95, $-STATS.STARTMODIFIER)
				end
				if STATS.STARTSPEEDCONDITION!=nil --When speed modifier takes place. Default is 30.
					STARTMOD = abs(STATS.STARTSPEEDCONDITION)
				end
			end
			if (SPEEDMOD) and (abs(GS.railspeed) > (STARTMOD*s.scale))
				GS.railspeed = FixedDiv(FixedMul($, max(1, 100)), 100+SPEEDMOD)
				if GS.railboost
					GS.railboost = FixedDiv(FixedMul($, max(1, 100)), 100+SPEEDMOD) 
				end
			end
			if rail.GSrailstartcap and (abs(GS.railspeed) > rail.GSrailstartcap*s.scale)
				GS.railspeed = ($<0) and -abs(rail.GSrailstartcap)*s.scale or abs(rail.GSrailstartcap)*s.scale
			end
		end
		if not GS.silentgrind or (GS.silentgrind==2)
			GS.startSFX = true
		end
		if SKIN
			if SKIN["StartGrind"] and SKIN["StartGrind"](p, s, GS, rail) end
		end
		if (GRINDFLAGS & 131072) and (abs(GS.railspeed) < 12*s.scale)
			GS.railspeed = ($<0) and -12*s.scale or 12*s.scale --Don't trap our poor player with nocontrol, pls.
		end
	end
	if GS.startSFX!=nil
		if not (LOOPFLAGS & 1048576)
			local DIDFAST = false
			if (GS.silentgrind!=2) and (Rspeed > 15*s.scale) --Fast start sfx. Creates sparks.
				if GS.startSFX!="noland" and (TYPE.nolandingspark!=true) --Initial landing sparks.
					local COLOR,COLORIZED = (TYPE.sparkscolor) or 0, (TYPE.sparkscolorized) and true or false
					RAIL.LandingSpark(s, COLOR, COLORIZED, HANGRAIL, (TYPE.sparkscale) or FRACUNIT/2)
					RAIL.RailSparks(s, 8, P_RandomRange(8,20), P_RandomRange(-30,30), HANGRAIL)
				end
				if (Rspeed > 40*s.scale) and not GS.ropesnap --Play the speeding sound?
					if (HANGRAIL!=true) or (TYPE.nohangrailSFX==true)
						if not (TYPE.nofastSFX==true or TYPE.faststartSFX==sfx_none)
							DIDFAST = true
							S_StartSoundAtVolume(s, TYPE.faststartSFX or DF.faststartSFX, rvolume)
							GS.grindsfx2 = TYPE.fastSFXinit or DF.fastSFXinit //19
						end
					end
				end
			end
			if (HANGRAIL!=true) or (TYPE.nohangrailSFX==true)
				if (TYPE.grindstartSFX!=sfx_none) and not (DIDFAST==true and TYPE.nostackSFX==true) and not GS.ropesnap
					local STARTSFX = TYPE.grindstartSFX or DF.grindstartSFX
					S_StopSoundByID(s, STARTSFX)
					S_StartSoundAtVolume(s, STARTSFX, rvolume) --Slow start grinding SFX. Stacks with the optional fast SFX.
				--	GS.grindsfx2 = TYPE.SFXinit or DF.SFXinit //31
				end
			end
			if not (TYPE.landSFX==sfx_none) and GS.startSFX!="noland" --Clank! You hit a rail!
				local LANDSFX = DF.landSFX if (TYPE.landSFX!=nil) LANDSFX = TYPE.landSFX end
				S_StopSoundByID(s, LANDSFX) S_StartSound(s, LANDSFX)
			end
		end
		GS.startSFX = nil
	end
	if (GS.grinding!=2)
		if CONVEYOR
			if (GS.railspeed >= 0) and (CONVEYOR < 0)
			or (GS.railspeed < 0) and (CONVEYOR > 0)
				Rspeed = max(1, $-CONVEYOR)
			end	
		elseif (GRINDFLAGS & 131072) and (Rspeed < 12*s.scale) --We're on a cinematic rail and moving WAY too slowly!!
			GS.railspeed = ($<0) and -12*s.scale or 12*s.scale 
		end
		
		if not (GS.silentgrind or GS.silentgrind == 2) and not (LOOPFLAGS & 1048576)
			local RSPEED2 = abs(GS.railspeed or 0) --Without conveyor.
			GS.grindsfx,GS.grindsfx2 = max($-1,0), max($-1,0)
			
			if (abs(GS.railspeed+CONVEYOR) > 47*s.scale) --Speed lines!
			and not (STATS and STATS.NOWINDLINES==true)
			and not (RSTATS and RSTATS.VERTRAIL)
				RAIL.SpeedLines(s, GS, RAIL.ANG(rail)+GS.raildirection)
			end
			if (HANGRAIL==true) and not (TYPE.nohangrailSFX==true) 	--Play Hangrail SFX!
				if (GS.grindsfx < 2) --This one has set-in-stone sounds effects.
					S_StopSoundByID(s, sfx_gsral8)
					S_StartSoundAtVolume(s, sfx_gsral8, rvolume)
					GS.grindsfx = 31 //TYPE.SFXinit or DF.SFXinit //31				
				elseif not S_SoundPlaying(s, sfx_gsral8)
					S_StartSoundAtVolume(s, sfx_gsral8, rvolume)
				end
			elseif not ((GS.flipanim or 0) > 3) --Play Grinding SFX!
				if (TYPE.grindSFX!=sfx_none) and not GS.ropesnap
					local SFX = TYPE.grindSFX or DF.grindSFX
					local FAST = TYPE.fastgrindSFX or DF.fastgrindSFX
					if (GS.grindsfx < 2) --Grinding sounds play constantly.
						local PLAYSLOW = true
						if (RSPEED2 > 40*s.scale) 
							if (TYPE.fastgrindSFX!=sfx_none) --Fast grind sfx. Overlaps with slow.
								S_StartSoundAtVolume(s, FAST, rvolume/2)
								PLAYSLOW = (TYPE.nostackSFX==true) and true or false
								if FAST==sfx_gsralb and (RSPEED2 > 64*s.scale) and TYPE.grindstartSFX==sfx_gsrald
									S_StopSoundByID(s, sfx_gsrald)
									S_StartSoundAtVolume(s, sfx_gsrald, min(255, rvolume*3/2)) --Special case for Modern rails.
								end
							end
							GS.grindsfx2 = TYPE.fastSFXtimer or DF.fastSFXtimer  //26
							if (HANGRAIL!=true)
								local COLOR,COLORIZED = (TYPE.sparkscolor) or 0, (TYPE.sparkscolorized) and true or false
								RAIL.NewSparks(s, rail, COLOR, COLORIZED, (TYPE.sparkscale) or FRACUNIT/2, STATS)
							end
						end	
						if (PLAYSLOW!=true)
							S_StopSoundByID(s, SFX)
							S_StartSoundAtVolume(s, SFX, rvolume)
						end
						GS.grindsfx = TYPE.SFXtimer or DF.SFXtimer //71
					elseif not S_SoundPlaying(s, SFX)
						S_StartSoundAtVolume(s, SFX, rvolume)
					end
				end	
				if (RSPEED2 > 34*s.scale)
					local SCALE = (TYPE.sparkscale) or 34000
					if (RSPEED2 > 40*s.scale)	
						if (TYPE.fastgrindSFX!=sfx_none)
							local FAST = TYPE.fastgrindSFX or DF.fastgrindSFX
							if (GS.grindsfx2 < 2) --Fast grind sfx. Overlaps with slow.
								S_StopSoundByID(s, FAST)
								S_StartSoundAtVolume(s, FAST, rvolume/2)
								GS.grindsfx2 = TYPE.fastSFXtimer or DF.fastSFXtimer //26			
							elseif not S_SoundPlaying(s, FAST)
								S_StartSoundAtVolume(s, FAST, rvolume/2)
							end
						end
						if (RSPEED2 > 51*s.scale)
							SCALE = FixedDiv(FixedMul($, RSPEED2/s.scale), 51)
						end							
					end							
					if (HANGRAIL!=true) --Grindrail effects!
						local SCALE = (TYPE.sparkscale) or 34000
						if (RSPEED2 > 51*s.scale)
							SCALE = FixedDiv(FixedMul($, RSPEED2/s.scale), 51)
						end
						local COLORIZED = (TYPE.sparkscolorized) and true or false
						if (TYPE.nosparks!=true) --Some awesome sparks from MetalWario64!
							local COLOR = (TYPE.sparkscolor) or 0
							RAIL.NewSparks(s, rail, COLOR, COLORIZED, SCALE, STATS)	
						end
					end
				end
			end
			if not ((GS.flipanim or 0) > 4) and not (LOOPFLAGS & 1048576)
				if (RSPEED2 > 31*s.scale) or (GS.grinding < 9) --Always play these.
					if TYPE.spawndust
						RAIL.SpawnDust(s,TYPE.spawndust, SCALE, P_RandomRange(-24,24) or 1, HANGRAIL)
					end
					if TYPE.spawnbubbles
						local COLOR = (TYPE.spawnbubbles!=SKINCOLOR_BLUE) and TYPE.spawnfire or nil
						RAIL.SpawnBubbleDust(s, COLOR, SCALE, HANGRAIL)
					end
					if TYPE.spawnfire
						local COLOR = (TYPE.spawnfire!=SKINCOLOR_RED) and TYPE.spawnfire or nil
						RAIL.SpawnFlameDust(s, COLOR, SCALE, HANGRAIL)
					end
					if TYPE.spawnleaves and not (TYPE.windrail)
						RAIL.SpawnLeaves(s, TYPE.spawnleaves, SCALE, nil, P_RandomRange(40,40)*s.scale, HANGRAIL)
					end				
				end
				if (TYPE.speedtrail) and (RSPEED2 > TYPE.speedtrail*s.scale)
					GS.speedtrail = GS.speedtrail or true
				end
				if (GS.silentgrind != 2) and ((TYPE.nosparks!=true) or (HANGRAIL==true)) --Continuous railgrinding sparks.
					local SPARKSPEED = min( max(4,(RSPEED2/(26*s.scale)) ), 1)
					RAIL.RailSparks(s, SPARKSPEED, //Gain 1 spark per 26 speed. Max 4, minimum 1.
					P_RandomRange(12,24), --RNG value
					P_RandomRange(-20,20), HANGRAIL) --RNG2 value
				end
			end
		end
		if GS.zboost 
			local MAX = DFSTATS.MAXSPEED*s.scale if STATS and STATS.MAXSPEED!=nil MAX = STATS.MAXSPEED*s.scale end
			if (GS.grinding > 4) or (abs(GS.railspeed) > MAX*3/4) --Initial landing slopeboost is only active for a short while.
				GS.zboost = nil
			end
		end
		if GS.IgnoreConveyor
			GS.IgnoreConveyor = $-1
		end
		if s.GScollide --We collided with a special object? Aight, then get this out of the way ASAP!
			RAIL.AttachedCollission(s, rail, GS)
			s.GScollide = nil
			if not GS or not GS.grinding or not (rail and rail.valid)
				return --Whatever we just touched took us away from the rail.
			end
		end		
	end

	--Always face rail direction?
	if GS.willfacerail or p	
		if p and ( ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304)) or LOOP and (LOOP.walljump==true or LOOP.anim and (LOOP.anim!=0)) )
			GS.camwait,GS.willfacerail,GS.turntoia = 0,0,0
			p.drawangle = RAILANG+GS.raildirection
		elseif GS.willfacerail and p --Players usually face their initial rail momentum angle. Other mobjs are different.
			GS.willfacerail = $-1 --Don't adjust angle for players until this is 0
		else
			if p --Do some basic camera nonsense incase the player has the custom camera off.
				if GS.turntoia  --Gradually autoturn camera if the player isn't currently holding directions.
					GS.turntoia = $-1	
				end
				if GS.ropesnap and not ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304)) and (GS.TWOD!=true) and not (CONVEYOR)
					if GS.forceangle==nil
						p.drawangle = s.angle --Face camera angle instead.
						GS.forceangle = s.angle
					end
				else			
					if not LOOP and not rail.GSVertRail
						if (p.cmd.buttons & BT_SPIN) --Face the camera?
							s.angle = RAIL.ANG(rail)+GS.raildirection+(RAIL.GrindState(s, "hang") and (ANG1*3)+75000 or 0)
						else
							local SIDE,FORWARD = GS.side or 0,GS.forward or 0
							if not (abs(SIDE) > 5) and (abs(FORWARD) > 45) --Center camera if holding forwards/backwards.
							or ((GS.railfalltimer or 0) > 15)
								if GS.camwait
									GS.camwait = $-1
								else
									local DESTANG = RAIL.ANG(rail)+GS.raildirection
									s.angle = DESTANG
								end
							else
								GS.camwait = 8
							end
						end
					end
					p.drawangle = RAILANG+GS.raildirection --Always instant adjust player drawangle.
				end
			else
				s.angle = GS.grinddirection
			end
		end
	end
	--Boost to minimum speed if we're below it!f
	if GS.railminspeed and (Rspeed < GS.railminspeed*s.scale) and not CONVEYOR
		GS.railspeed = (($<0) and -1 or 1)*GS.railminspeed*s.scale
	end

--	rail.GSraillength	
--	rail.GSrailXYlength
--	rail.GSrailZdist
--	rail.GSnextrail
--	rail.GSprevrail
--	rail.GSraillength = (FixedHypot(FixedHypot(rail.x-m.x, rail.y-m.y), rail.z-m.z ))

	--Now for the big deal. Our actual rail positioning! GS.railspeed will be negative if we're going backwards!
	if rail and rail.valid and not rail.GSVertRail 
		if not (p)
		
		or (p.powers[pw_justlaunched]!=2)
		and not (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_THOKKED))
		and not (p.powers[pw_carry] and p.powers[pw_carry]!=3888)
			if ((GS.grinding or 0) > 2) and not (s.eflags & MFE_SPRUNG) and not GS.leftrail
				s.momz = -((P_GetMobjGravity(s) or 0)*9/8) --Override regular game behaviour. We'll convert our rail momentum back later!
				if (GS.grinding%4==0) --Every so often, reallign incase of external momentum.
					local snapX, snapY = RAIL.GetRailXYDistFromOrigin(s, rail)
					if (JUMPFLAGS & (1048576|2097152)) and ((rail.GSrailXYlength or 0) < 2800<<16) and (GRINDFLAGS & 524288) 
					and not (CONVEYOR)
						snapX = rail.x-FixedMul(rail.GSrailXYlength/2, cos(RAILANG))
						snapY = rail.y-FixedMul(rail.GSrailXYlength/2, sin(RAILANG))
					end
					local snapZ = RAIL.GetRailZ(rail, s, 0, nil, {X=snapX, Y=snapY})
					P_MoveOrigin(s, snapX, snapY, snapZ)
				end
				P_InstaThrust(s, p and p.drawangle or rail and RAIL.ANG(rail), 180)
			end
		end
	end
	
	if GS.leftrail return end --

	local m = rail.GSnextrail --"Next" is a lua function, so uh...m it is.
	if m == nil RAIL.print("\x85"+"Fatal error, no next rail!", 25) RAIL.ExitRail(s) return end
	local prev = rail.GSprevrail --"Prev" can be optional, but nextrail is mandatory!	
	
	if GS.LastGrindZ==nil
	or GS.grinding==2 and (GS.LastGrindZ!=s.z) and not GS.exittic --Sometimes we gotta setup!
		if RAIL.ValidGrind(GS, rail, CONVEYOR) and not (rail.GSVertRail)
			s.z = RAIL.GetRailZ(rail, s, 0)
		end
		GS.LastGrindZ = s.z
	end
	if not GS.exittic
		if GS.LastGrindZ!=nil GS.LastGrindZ2 = GS.LastGrindZ end --A secondary one to mitigate jank.
		GS.LastGrindZ = s.z --Remember where we were the tic BEFORE we moved!	
	end
	
	if RSTATS==nil or RSTATS.RAIL!=rail		
		local OLDFALLING = 0
		if RSTATS and RSTATS.RAIL and RSTATS.RAIL.valid 
			OLDFALLING = RSTATS.falling or 0 --Put the sparks and sounds away?	
			if ((rail.GShangrail!=RSTATS.GShangrail) or (rail.GSVertRail!=RSTATS.GSVertRail)) and p
				if GS.railtilt GS.railtilt = $/4 end
				GS.perfectcorner = max(GS.perfectcorner or 0, 5)
			end
			if ((rail.GStype!=RSTATS.RAIL.GStype) or (LOOPFLAGS & 1048576)) and (RSTATS.RAIL.GStype!=nil) 
				local OLDTYPE = RAILTYPE[RSTATS.RAIL.GStype]
				if (OLDTYPE)
					if OLDTYPE.nosparks==true or (LOOPFLAGS & 1048576)
						GS.railsparks = nil 
					end
					if (OLDTYPE.grindSFX!=TYPE.grindSFX) or (OLDTYPE.fastgrindSFX!=TYPE.fastgrindSFX) or (LOOPFLAGS & 1048576)
						GS.startSFX,GS.grindsfx,GS.grindsfx2 = "noland",0,0
						local SOUNDSTOP = {OLDTYPE.grindstartSFX or sfx_gsral1, OLDTYPE.grindSFX or sfx_gsral3, 
						OLDTYPE.faststartSFX or sfx_gsral2, OLDTYPE.fastgrindSFX  or sfx_gsral4, sfx_gsral8}
						for _,i in pairs(SOUNDSTOP) if i S_StopSoundByID(s, i) end end --Stop some sounds.					
					end
				end
			end
		end
		GS.MyRailStats = {INIT = 0, RAIL = rail, VERTRAIL = 0, LASTZ = GS.LastGrindZ, INITZ = GS.LastGrindZ, ZSPEED = 0, falling = OLDFALLING}
	else
		RSTATS.INIT = $+1 --How long we've been on this rail.
		if RSTATS.VERTRAIL
			RSTATS.VERTRAIL = max($-1,0)
			if not RSTATS.VERTRAIL
				RSTATS.ZSPEED, GS.MyRailStats.VERTRAIL = 0,0
			end
		end
		local ASPEED = abs((GS.railspeed or 0)+CONVEYOR)
		local SPEEDMET = ((LOOP) and LOOP.speedneeded or 0)*s.scale
		if SPEEDMET and (ASPEED < SPEEDMET) --Too slow to stay on!
		and (RSTATS.VERTRAIL or (LOOPFLAGS & 131072))
			if (GRINDFLAGS & 524288)
				RSTATS.falling = max($-2,0)
			else
				local ADD = (ASPEED < (SPEEDMET/3)) and 4 or (ASPEED < (SPEEDMET/2)) and 3 or (ASPEED < (SPEEDMET*2/3)) and 2 or 1
				RSTATS.falling = RSTATS.falling and $+ADD or ADD
				if (RSTATS.falling > 15)
					RAIL.ExitRail(s, true)
					GS.cannotgrind = max($+7, 24) return --REVERSE, CONVEYOR, GAINING --Early quit!
				end
			end
		elseif RSTATS.falling 
			RSTATS.falling = min(9, $-1)
		end
	end
	if (GRINDFLAGS & 524288) --Zero momentum flag.
		if CONVEYOR
			GS.railspeed = (CONVEYOR<0) and -1 or 1
		else
			GS.railspeed = ($<0) and -1 or 1
			if GS.homingANG!=nil
				if (GS.grinding < 4) and not (LOOPFLAGS & (524288|262144|4194304))
					RAIL.MobjToRailAngle(s, rail, GS.homingANG, true)
					p.cmd.angleturn = GS.homingANG/FRACUNIT
					s.angle = GS.homingANG
					p.drawangle = GS.homingANG
					if GS.railcam and GS.railcam.valid
						GS.railcam.angle = GS.homingANG
					end				
				else
					GS.homingANG = nil
				end
			end			
		end
	end
	local RAILLIMIT = rail.GSrailXYlength or 0
	
	if (rail.GStouchflags & 65536) and p --Player not allowed to interact!
	or (rail.GSdisabled) --The rail is disabled?!
		GS.FORCEKICK = true
	elseif (GRINDFLAGS & 524288) and not (CONVEYOR) --A true Zero Speed rail?!
		RAILLIMIT = max($, 64<<16)
		if ((GS.grinding or 0)<3)
			GS.railprogress = (rail.GSrailXYlength or 2)/2
		end
	elseif rail.GSrailmaxspeed and (abs(GS.railspeed) > rail.GSrailmaxspeed*s.scale) and (s.skin!="sms")
		GS.railspeed = (($<0) and -rail.GSrailmaxspeed or rail.GSrailmaxspeed)*s.scale
	end
	
	--Do Vertical Grinding?
	local STARTZ,DESTZ,ZLIMIT,NEWRAIL = RAIL.VertRail(rail, "check")
	if (ZLIMIT!=nil or NEWRAIL!=nil) and not ((GRINDFLAGS & 524288) and not (CONVEYOR))
		local VERTICALRAIL = RAIL.VerticalGrind(p, s, GS, CONVEYOR, REVERSE, STARTZ, DESTZ, ZLIMIT)
		if (VERTICALRAIL!=nil)
			return REVERSE, CONVEYOR, GAINING, "VERTICALRAIL" --
		end
	end
	local PROGRESS, LIMITTWO = RAIL.RAILPROGRESS(s, GS, rail)
	GS.railprogress = PROGRESS		
	--DOGRIND --DORAIL
	local ADDSPEED = min(max(2, LIMITTWO-1), GS.railspeed)
	LIMITTWO = max((30<<16)+(1<<15), LIMITTWO or 0)	
	if (GRINDFLAGS & 524288) 
		ADDSPEED = 0
		if ((GS.grinding or 0)<3) and not (CONVEYOR)
			GS.railprogress = (rail.GSrailXYlength or 2)/2
			PROGRESS = GS.railprogress
		end	
	elseif rail.GSrailspeed and ADDSPEED
		ADDSPEED = FixedDiv(FixedMul($, rail.GSrailspeed), 100)
	end
	if GS.EaseInSpeed
		ADDSPEED = FixedDiv(FixedMul($, 100), GS.EaseInSpeed)
		GS.EaseInSpeed = (GS.grinding==2) and max(101, $*3/4) or nil
	end
	local MAXNUM = (GS.exittic) and 16 or 1
	
	for i = 1,MAXNUM
		if GS.leftrail return end --ABORT! You JUST left the rail already!
		
		if (i!=1) --Take two!
			m = rail.GSnextrail --"Next" is a lua function, so uh...m it is.
			prev = rail.GSprevrail --This one can be optional, but nextrail is mandatory.
			if m == nil RAIL.print("\x85"+"Fatal error, no next rail!", 25) RAIL.ExitRail(s) return end
		end
		local RAILANG = RAIL.ANG(rail)
		local newrail = prev	
		local NONEXT = (not (newrail and newrail.valid) or (newrail.flags2 & MF2_DONTDRAW) or newrail.GSdisabled) and true or false
		if (GS.railspeed+CONVEYOR >= 0) newrail = rail.GSnextrail end

		if (i==1) or not (GS.railprogress > RAILLIMIT) --Grind towards our DESTINY!	
			local DISTMOVE = ADDSPEED
			if CONVEYOR and not (GS.IgnoreConveyor)
				local CONV = CONVEYOR
				if ( abs(CONVEYOR) > (RAILLIMIT/2) )
					CONV = ($<0) and -RAILLIMIT/2 or RAILLIMIT/2 --Never shoot players past a whole rail in 1 tic!
				end
				if ((GS.flipanim or 0) > 1) and ((GS.railspeed >= 0) and (CONVEYOR < 0) or (GS.railspeed < 0) and (CONVEYOR > 0))
				and not (STATS and STATS.HEROESTWIST==1) --Don't fight conveyor if your twist is cosmetic only.
					CONV = ((GS.flipanim > 4) and (GS.flipanim < 8)) and $/9 
					or ((GS.flipanim > 9) or (GS.flipanim < 2)) and $/2 or $/5
				elseif (GS.grinding < 4) --Start up a little slower!
					CONV = (GS.grinding < 3) and $/3 or $/2
				end
				DISTMOVE = $+CONV	
			elseif (LOOPFLAGS & 131072) and (abs(DISTMOVE) > 60*s.scale)
				DISTMOVE = (abs(DISTMOVE) > 84*s.scale) and FixedDiv(FixedMul($, 7), 8) or FixedDiv(FixedMul($, 8), 9)
			end
			local RX,RY,RZ = s.x-FixedMul(DISTMOVE, cos(RAILANG)), s.y-FixedMul(DISTMOVE, sin(RAILANG)), s.z
			RZ = RAIL.GetRailZ(rail, s, -ADDSPEED-CONVEYOR, nil, {X=RX, Y=RY})
			if RSTATS and RSTATS.INIT
				RZ = min( P_CeilingzAtPos(RX, RY, RZ, 0), max(P_FloorzAtPos(RX, RY, RZ, 0), RZ) )
			end
		--	if not (s.eflags & MFE_VERTICALFLIP) and (s.z < RZ) or (s.eflags & MFE_VERTICALFLIP) and (s.z > RZ) 
				s.z = RZ	
		--	end
			if p P_TryMove(s, RX, RY, true) end --To better collide with items on rails.
			P_MoveOrigin(s, RX, RY, RZ) --print("\x85"+"!!!!!!!!!!!!!!!!!!!"+leveltime)
			GS.railprogress = RAIL.RAILPROGRESS(s, GS, rail)
		end
		
		--Swap to the next rail if there is one!
		if (GS.railprogress+((NONEXT==true) and abs(GS.railspeed or 0) or 0) > RAILLIMIT)
		or GS.FORCEKICK
			if (newrail and newrail.valid and newrail.GSnextrail)
			and not (newrail.flags2 & MF2_DONTDRAW) --Invisible rails set their sprites to SPR_NULL! They DON'T use MF2_DONTDRAW!
			and not ((not newrail.GSrailXYlength) and newrail.GSforceUDMFang!=nil)
			and not (GS.FORCEKICK) --Not allowed to interact!
				local NEWRAILANG = RAIL.ANG(newrail)
				local OLDSLOPE, OLDANG = rail.GSrailZDelta or 0, s.angle
				local OLDNUM = GS.myrail.GSrailnum --For DEBUG
				
				GS.prevrailang = GS.grinddirection
				GS.turntoia = 10 --We just turned. Turn camera/object to angle?
				if p
					GS.ropesnap = nil
					OLDANG = p.drawangle
					p.drawangle = NEWRAILANG+GS.raildirection
					if i==1 and not (RSTATS and RSTATS.VERTRAIL) and (GS.TWOD!=true) and not (GS.myrail.GSVertRail)
						local ANG,PLUS = RAIL.AngleDifference(OLDANG, p.drawangle, 2, ANG1*89, 2)
						if (ANG > ANG1*7) --Players can lose balance overtime.
							GS.railsparks = nil
							local SPEED,MINTIME,CONSEC = abs(ADDSPEED or 0),18,0
							if GS.ANGswap
								MINTIME = max($, GS.ANGswap.timer or 0)
								if (GS.ANGswap.PLUS != PLUS)
									MINTIME = $+12
									ANG = min(ANG1*89, $+ANGLE_22h) --Become unstable easier from crazy swerves.
								end
								if (GS.ANGswap.consec!=nil)
									CONSEC = GS.ANGswap.consec+1
								end
							end
							if (SPEED < 32*s.scale) --Instability becomes somewhat insignicant when moving slowly.
								ANG = ($/7)*max(1, min(6, SPEED/(s.scale*3)) )
							end
							GS.ANGswap = {timer = min(64, max(MINTIME, SPEED/(s.scale*2)) ),
							ANG=ANG, PLUS=PLUS, init = 0, consec = CONSEC}
						else
							if GS.railcam and GS.railcam.valid
								local CAM = GS.railcam
								CAM.dimROD = {timer = 2, div = 2, init = 0}
						--		GS.railcam.easecam = max(GS.railcam.easecam or 0, 4)
							end 
							if (ANG > ANG1/20)
								local CAMADD = GS.camadd or 0
							--	if (abs(CAMADD) < ANG20)
							--		GS.camadd = CAMADD-((ANG/12)*PLUS)
							--		GS.camadd = max(-ANG20, min(ANG20, $)) 
							--	end
								GS.FreeAdd = 11
								if GS.crouchgrind and GS.crouchbutton
								or GS.badbalance==true and (abs(GS.railtilt) > 320)
								or (STATS and STATS.MUSTBALANCE==true)
								or (abs(GS.railtilt) < 182)
									if (GS.grinding > 6) and not (GS.perfectcorner)
									and not (GS.cantcrouch) and not (LOOP and (LOOP.anim!=0))
									and not (RSTATS and RSTATS.VERTRAIL) and not (p.powers[pw_nocontrol])
										local ADDTILT = -min(99, ANG/(ANG1/33))
										if ( (abs(GS.railtilt) < 182) 
										and not (GS.crouchgrind and GS.crouchbutton) and not (STATS and STATS.MUSTBALANCE==true)
										) or GS.flipanim
											ADDTILT = $*4/3 --More balance loss!
										elseif (abs(GS.railtilt) > 400)
											ADDTILT = $*2/3 --They're nearly falling already, take it easy!
										end
										if STATS and STATS.AUTOBALANCE==true
											ADDTILT = $/3
										end
									/*	local SIDE = (p.cmd.sidemove or 0)*GS.invertinput --Holding same direction as turn?
										if (ADDTILT > 0) and (SIDE > 7) or (ADDTILT < 0) and (SIDE < -7)
											GS.railtilt = $*8/9  --Stabilize instead.
											if not (GS.perfectcorner) GS.perfectcorner = 1 end  print("\x83"+"Stabilize:"+GS.railtilt)
										end */
										GS.railtilt = GS.railtilt+(ADDTILT*PLUS)
									end
								end
							end
						end
					end
				end
				
				--NOW SWAP TO THE NEW RAIL!
				if newrail.GSVertRail --Vertical rail swapping nonsense! --RAIL.VertRail(newrail, "check") or 
					local OGZ = s.z
					local SETZ = s.z
					local NEXT,OLD = newrail or GS.myrail, GS.myrail
					if (GS.railspeed+CONVEYOR >= 0)
						SETZ = RAIL.SetVertZ(OLD, SPEED)
						if OLD.GSnextrail NEXT = OLD.GSnextrail end
					elseif OLD.GSnextrail
						SETZ = RAIL.SetVertZ(OLD.GSnextrail, SPEED)
						NEXT = OLD
					end	
					if RSTATS
						RSTATS.ZSPEED = newrail.GSVertRail
						RSTATS.VERTRAIL = 2
						RSTATS.INIT = 0
						RSTATS.LASTZ = OGZ
					end	
					P_TryMove(s, NEXT.x, NEXT.y, true) 
					if newrail.GSVertRailBotZ!=nil
						SETZ = max(newrail.GSVertRailBotZ, $)
					end
					if newrail.GSVertRailTopZ!=nil
						SETZ = min(newrail.GSVertRailTopZ, $)
					end					
					s.z = SETZ
					GS.railprogress,GS.railsparks = 0, nil
					if (abs(GS.railspeed or 0) > 45*s.scale)
						GS.railspeed = FixedDiv(FixedMul($, 20), 21)
					end
					P_TryMove(s, NEXT.x, NEXT.y, true) 
					ADDSPEED = $/3
					GS.myrail = newrail break --
					
				elseif (GS.railspeed+CONVEYOR >= 0) --Move onto the next rail!
				
					local diff = min(LIMITTWO-1, FixedHypot(s.x-newrail.x, s.y-newrail.y))
					local NX,NY = newrail.x-FixedMul(diff, cos(NEWRAILANG)), newrail.y-FixedMul(diff, sin(NEWRAILANG))
					local NZ = RAIL.GetRailZ(newrail, s, 0, nil, {X=NX, Y=NY})
					P_MoveOrigin(s, newrail.x-FixedMul(diff, cos(NEWRAILANG)), newrail.y-FixedMul(diff, sin(NEWRAILANG)), NZ)
					ADDSPEED = min($/2, diff)	
					
				else --Formula is different for previous rail.
					local diff = min(LIMITTWO-1, FixedHypot(s.x-rail.x, s.y-rail.y))	--Use rail instead of newrail dist.
					local NX,NY = rail.x+FixedMul(diff, cos(NEWRAILANG)), rail.y+FixedMul(diff, sin(NEWRAILANG))
					local NZ = RAIL.GetRailZ(newrail, s, 0, nil, {X=NX, Y=NY})
					P_MoveOrigin(s, rail.x+FixedMul(diff, cos(NEWRAILANG)), rail.y+FixedMul(diff, sin(NEWRAILANG)), NZ)		
					ADDSPEED = min($/2, diff)
				end	
				GS.myrail = newrail --Attach to the new rail.
				if RSTATS and RSTATS.INIT
					RSTATS.INIT = 0
				end
				
				--Check if we're not BARELY missing a spring/booster on the rail's edge...
				if (RAIL.ScanCollide(s, GS, false, (GS.railspeed or 0)+CONVEYOR)==true)
					return REVERSE, CONVEYOR, GAINING --
				end
				
				local NEWLIMIT = newrail.GSrailXYlength or 1
				--Either we're EXTREMELY fast or this rail is tiny. 
				if (GS.railprogress > NEWLIMIT)				
					
			--	or ((m.GSconveyor or 0) > 0) and not (m.GSnextrail and m.GSnextrail.valid)
			--	or ((m.GSconveyor or 0) < 0) and not (m.GSprevrail and m.GSprevrail.valid)
				
				--	print("\x85"+"[SWITCH] s.z:\x80"+s.z/FRACUNIT+"\x85 Progress:\x80"+GS.railprogress/FRACUNIT)
					if (GS.railspeed+CONVEYOR >= 0) and newrail.GSnextrail
					and newrail.GSnextrail.valid and not (newrail.GSnextrail.flags2 & MF2_DONTDRAW)
					
					or (GS.railspeed+CONVEYOR < 0) and newrail.GSprevrail
					and newrail.GSprevrail.valid and not (newrail.GSprevrail.flags2 & MF2_DONTDRAW)
						break --This skip business is only for the last one!
					end
				--	print("\x83"+"[SWITCHOVERFLOW] s.z:\x80"+s.z/FRACUNIT+"\x83 Progress:\x80"+GS.railprogress/FRACUNIT)
					
					rail = GS.myrail
					GS.railprogress = max(0, min((rail.GSrailXYlength or 0)-1, $-NEWLIMIT))
				--	print("\x85"+"["+OLDNUM+" > HO-SWAP "+GS.myrail.GSrailnum+"]")	
				--	print("Nope, do it. progress:\x82 "+GS.railprogress>>16+"\x80 newrail.length:\x82"+newrail.GSrailXYlength>>16)
					continue --
			--	else
				--	print("\x83"+"[SWITCH] s.z:\x80"+s.z/FRACUNIT+"\x83 Progress:\x80"+GS.railprogress/FRACUNIT)
				--	print("\x83"+"["+OLDNUM+" > H-SWAP "+GS.myrail.GSrailnum+"]")
				end			
			else	
				if not (GS.FORCEKICK)
					if (GRINDFLAGS & 524288) and not (CONVEYOR)
						GS.railprogress = 1 break --No falling off in this case!
					elseif (RAIL.ScanCollide(s, GS, true, (GS.railspeed or 0)+CONVEYOR)==true)
						break -- --Check if we're not BARELY missing a spring/booster on the rail's edge...
					end
				
					if not (GS.exittic) and ( (GS.railprogress > RAILLIMIT) or ((GS.grinding or 0) < 3) ) and not (CONVEYOR)
						local diff = abs(GS.railspeed+CONVEYOR)
						local NX,NY = s.x-FixedMul(diff, cos(RAILANG)), s.y-FixedMul(diff, sin(RAILANG))
						local NZ = RAIL.GetRailZ(rail, s, 0, nil, {X=NX, Y=NY})		
						P_MoveOrigin(s, NX,NY, NZ)
						GS.exittic = true continue --
					end
					
					if (rail.GSVertRail) or (RSTATS and RSTATS.VERTRAIL) break end --VerticalGrind takes over!
					if (GS.railspeed+CONVEYOR >= 0) --First. Move to the exact endpoint of the rail.
						if not (CONVEYOR) or (FixedHypot(FixedHypot(m.x-s.x, m.y-s.y), m.z-s.z) < 160*s.scale)
							P_MoveOrigin(s, m.x, m.y, m.z)
						end
					elseif not (CONVEYOR) or (FixedHypot(FixedHypot(rail.x-s.x, rail.y-s.y), rail.z-s.z) < 160*s.scale)				
						P_MoveOrigin(s, rail.x, rail.y, rail.z)
					end
				end
				RAIL.ExitRail(s, true) -- --No rail. Convert momentum and go!
			end	
		end	
		break --
	end 
	return REVERSE, CONVEYOR, GAINING --We've made it, bring the good news back to the PlayerThink!
end

--Record a player's button inputs. Usually for getting off the rail in some way,
--but also for custom abilities, crouching, taunting, and heroes-style flipturns.
--STATS are optional.
RAIL.buttons = function(p, GS, STATS)	
	local BUTTONS = p.cmd.buttons or 0
	local rail = GS.myrail 
	if rail and ((rail.GSgrindflags & 131072) or p.powers[pw_nocontrol]) and GS.grinding and p.powers[pw_carry]==3888
		BUTTONS = 0
	end
	GS.railjump = (BUTTONS & BT_JUMP) and $+1 or 0
	GS.backupjump = (BUTTONS & BT_JUMP) and $+1 or 0
	GS.railspin = (BUTTONS & BT_USE) and $+1 or 0
	GS.backupspin = (BUTTONS & BT_USE) and $+1 or 0
	GS.railtossflag = (BUTTONS & BT_TOSSFLAG) and $+1 or 0
	GS.railc1 = (BUTTONS & BT_CUSTOM1) and $+1 or 0
	GS.railc2 = (BUTTONS & BT_CUSTOM2) and $+1 or 0
	GS.railc3 = (BUTTONS & BT_CUSTOM3) and $+1 or 0
	GS.railfire = (BUTTONS & BT_ATTACK) and $+1 or 0 --Some of these existing soley for custom coders/future support.
	GS.railnormalfire = (BUTTONS & BT_FIRENORMAL) and $+1 or 0
	GS.railWEAPPREV = (BUTTONS & BT_WEAPONPREV) and $+1 or 0
	GS.railWEAPNEXT = (BUTTONS & BT_WEAPONNEXT) and $+1 or 0	

	local TWISTBUTTON,CROUCHBUTTON = BT_SPIN, BT_SPIN	
	if STATS
		if (STATS.TWISTBUTTON)
			TWISTBUTTON = STATS.TWISTBUTTON
		end
		if (STATS.CROUCHBUTTON)
			CROUCHBUTTON = STATS.CROUCHBUTTON
			if STATS.TWISTBUTTON==0
				TWISTBUTTON = STATS.CROUCHBUTTON --Mimmick crouch button.
			end	
		end
	end 
	GS.twistbutton = (BUTTONS & TWISTBUTTON) and $+1 or 0
	GS.crouchbutton = (BUTTONS & CROUCHBUTTON) and $+1 or 0 --These two buttons can be different per character.	
	GS.invertinput = 1
	if (GS.grinding > 2) 
		if (GS.TWOD!=true) --Skip starting tics.
			local LOOPFLAGS = GS.LastRail and GS.LastRail.GSloopflags or 0
			if (LOOPFLAGS & 4194304)
				GS.invertinput = -1
				if not p.cmd.forwardmove and not p.cmd.sidemove
					p.cmd.forwardmove = -1
				end
			elseif not (LOOPFLAGS & (262144|524288))
				local CAMANG = p.cmd.angleturn<<16
				if (GS.railcam and GS.railcam.valid) and p.awayviewtics and p.awayviewmobj==GS.railcam
					CAMANG = GS.railcam.angle
				elseif GS.reversing==1 and GS.MyRailStats and ((GS.MyRailStats.ZSPEED or 0) < 0) and rail and rail.GSVertRail
					GS.invertinput = $*-1	
				end	
				local ANGDIFF = RAIL.AngleDifference(CAMANG, p.drawangle, 2, ANG1*89, 2)
				if (ANGDIFF > ANG1*69) --If the camera is backwards (69*2 = 138), invert the player's sideways inputs on some actions.
					GS.invertinput = $*-1
				end
				if not p.cmd.forwardmove and not p.cmd.sidemove
					p.cmd.forwardmove = 1
				end	
			end
		elseif not (p.cmd.sidemove)
			p.cmd.sidemove = (p.drawangle > ANGLE_90) and -1 or 1
		end
	end
end

--Returns true if you're in a state that can be used for grinding. 
--"Hang" specifies if we're checking for hangrails instead of regular rails.
RAIL.GrindState = function(s, HANG)
	if HANG=="any"
		if (s.state==S_PLAY_RIDE) or (s.state==S_PLAY_GSGRINDING)
			return true --	
		end
	elseif (HANG)
		if s.state==S_PLAY_RIDE
			return true --
		end
	elseif (s.state==S_PLAY_GSGRINDING)
		return true --
	end
end

--Adjusted expectations with Dash Mode?
RAIL.DashModeOnRails = function(p, s, GS, rail)		
	local SPEED,ghs = ((max(1, skins[s.skin].normalspeed or 0))*2/3) if not (SPEED) return end

	local Rspeed = abs(GS.railspeed) 
	SPEED = ((Rspeed >= $) and (GS.normalspeed >= $)) and "fast" or "slow"

	for i = -1,3,2
		if i==-1 --The Dashmode Overlay
			if (p.charflags & SF_MACHINE)
				ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_OVERLAY)
				ghs.tracer,ghs.target = s,s
				ghs.color = SKINCOLOR_KETCHUP	
				if s.skin ghs.skin=s.skin end ghs.sprite=s.sprite if s.sprite2 ghs.sprite2=s.sprite2 end
				ghs.frame = (s.frame & ~FF_TRANSMASK)|FF_FULLBRIGHT
				ghs.angle = p.drawangle		
				ghs.tics,ghs.fuse = 2,2
				A_CapeChase(ghs,1,0) 	
			end
		elseif i==1 --The Dashmode Afterimages.
			ghs = P_SpawnGhostMobj(s)
			P_Thrust(ghs, p.drawangle, -Rspeed)
			P_SetOrigin(ghs, ghs.x-FixedMul(Rspeed, cos(p.drawangle)), ghs.y-FixedMul(Rspeed, sin(p.drawangle)), ghs.z)
		else--if i==3 --The Dashmode FollowMobj Afterimages.	
			local FOLLOW = p.followmobj
			if FOLLOW and FOLLOW.valid and not (FOLLOW.skin) and (FOLLOW.sprite)
			and (FOLLOW.sprite!=SPR_PLAY) and not (FOLLOW.flags2 & MF2_DONTDRAW)
				local ghs = P_SpawnGhostMobj(FOLLOW)
				if s.skin=="metalsonic"
					FOLLOW.scale = max($, s.scale*2)
				end
				s = FOLLOW
				ghs.frame = (s.frame&~FF_TRANSMASK)|FF_TRANS40
				ghs.scalespeed = FRACUNIT/33
				ghs.destscale = 1
				if (leveltime%2==0)
					P_Thrust(ghs, p.drawangle, -Rspeed/2)
				end
			else
				break --
			end
		end
		if ghs and ghs.valid
			ghs.tics = 2+i
			ghs.scale = s.scale
			ghs.rollangle = s.rollangle
			ghs.spritexscale,ghs.spriteyscale = s.spritexscale, s.spriteyscale 
			ghs.spritexoffset,ghs.spriteyoffset = s.spritexoffset,s.spriteyoffset
			ghs.mirrored = s.mirrored
			if not ghs.fuse	
				ghs.color,ghs.colorized = s.color,s.colorized
				ghs.momx,ghs.momy,ghs.momz = $+s.momx, $+s.momy, $+s.momz
				ghs.fuse = min(16, ghs.tics or 1)
			end
		end
		if (SPEED=="slow") break end --
	end
end		

--Animation Speed List. Anything not listed defaults to "1".
if RAIL.ANS==nil RAIL.ANS = {} end local ANS = RAIL.ANS
ANS[SPR2_WAIT] = 9
ANS[SPR2_JUMP] = 2
ANS[SPR2_GLID] = 2
ANS[SPR2_LAND] = 3
ANS[SPR2_RIDE] = 2
ANS[SPR2_CLNG] = 5
ANS[SPR2_CLMB] = 6
ANS[SPR2_BNCE] = 4
ANS[SPR2_FIRE] = 3
ANS[SPR2_TWIN] = 2
ANS[SPR2_MLEE] = 2
ANS[SPR2_WALK] = 2
ANS[SPR2_FALL] = 2
ANS[SPR2_SPNG] = 2
ANS[SPR2_FLY_] = 2
ANS[SPR2_SWIM] = 3
ANS[SPR2_TIRE] = 4
for i = SPR2_CNT1,SPR2_TAL4 --idk, this stuff varies heavily. Use 3, I guess.
	if i and type(i)=="number" ANS[i] = 3 end
end

RAIL.DOSKIN = function(SKIN,p,s,GS, animcall)
	if SKIN and SKIN["CustomAnim"] --Anything modders want to add on top of things.
		return SKIN["CustomAnim"](p, s, GS, GS and GS.myrail, true, animcall) 
	end	
end

--Plays a special animation for players, from the Post Think Frame.
RAIL.CustomAnim = function(p, s, GS, FORCESPRITE)
	local OGCALL = GS.animcall
	local SKIN = GS_RAILS_SKINS[s.skin]
	local STATS = SKIN and SKIN["S_SKIN"]
	local SUPER = (p.powers[pw_super] or (s.eflags & MFE_FORCESUPER)) and FF_SPR2SUPER or 0
--	print("Forcesprite:\x84"+GS.forcesprite+"\x80 GS.animcall:\x82"+GS.animcall+"\x80 GS.grinding:\x82"+GS.grinding+"\x80 GS.railtossflag:\x82"+GS.railtossflag)
	
	if GS.forcesprite --Force a certain sprite no matter what situation? GS.animcall must've been SOMETHING beforehand, tho.
		if FORCESPRITE==nil FORCESPRITE = GS.forcesprite end
		GS.animcall,GS.forcesprite = nil
	end	
	if type(GS.animcall)=="string"
		if GS.animcall == "sideflip" --For sidehopping.
			local FLIP = GS.railswitching or 0
			local SIDEFLIP = STATS and STATS.SIDEFLIP
			local FRAME = A
			if SIDEFLIP == 7777
				if s.skin=="adventuresonic" --Prevent issues for most characters!
					FRAME = (leveltime%3==0) and C or (leveltime%2==0) and B or A
					if p.yujumpauratable
						for d = 0,20,10
							if p.yujumpauratable[d] and p.yujumpauratable[d].valid
								P_RemoveMobj(p.yujumpauratable[d])
							end
						end
					end		
				end
				SIDEFLIP = (FLIP > 3) and SPR2_SPNG or SPR2_FALL
			elseif SIDEFLIP==SPR2_ROLL or SIDEFLIP==SPR2_SPIN or SIDEFLIP==SPR2_GLID --Some special sprites in this case!
				s.rollangle = 0
				GS.animcall,GS.curanim = 1,nil
				RAIL.CustomAnim (p, s, GS, SIDEFLIP) return --
			end
			if type(SIDEFLIP)=="number" and P_IsValidSprite2(s, SIDEFLIP)
				if s.sprite==SPR_PLAY
					s.sprite2 = SIDEFLIP|SUPER --Use this animation!
					s.frame = ($ & ~FF_FRAMEMASK)|FRAME
				end
				if (s.tics > 0) s.tics = max($, 5) end
				if (s.anim_duration > 0) s.anim_duration = max($, 5) end	
				
				if not (FLIP) or GS.railswitchANG==nil or SIDEFLIP==SPR2_MLEL
					s.rollangle = 0
					if SIDEFLIP==SPR2_MLEL
						local ANG = p.drawangle if GS.railswitchANG!=nil ANG = GS.railswitchANG end
						local _,P = RAIL.AngleDifference(ANG, R_PointToAngle2(s.x,s.y, s.x+s.momx,s.y+s.momy), 2, ANG60,2)
						p.drawangle = ANG+(FLIP*P*ANGLE_45) --+(ANGLE_135*P)
					end
				else
					local _,P = RAIL.AngleDifference(GS.railswitchANG, R_PointToAngle2(s.x,s.y, s.x+s.momx,s.y+s.momy), 2, ANG60,2)
					local ROT = max(0, min(355, min(11, FLIP)*35) )
					if ROT==0 or SIDEFLIP==SPR2_BOUNCE
						s.rollangle = 0
					else
						s.rollangle = (355-ROT)*ANG1*-P
					end
					p.drawangle = $+( max(0, min(ANG20*4, FLIP*ANG1*8)) *P) --Face somewhat sideways like in Unleashed.
				end
				if SIDEFLIP==SPR2_TWIN or SIDEFLIP==SPR2_BNCE or SIDEFLIP==SPR2_SWIM --More special sprites.
					GS.animcall,GS.curanim = 1,nil
					RAIL.CustomAnim (p, s, GS, SIDEFLIP) return --	
				end
			end	
			
			GS.animcall = 0
			return RAIL.DOSKIN(SKIN,p,s,GS, OGCALL) 
			
		elseif GS.animcall == "flipanim" --For the Sonic Heroes-style turn animation.
			local FLIP = GS.flipanim or 0
			if (FLIP >= 5)
				s.spriteyoffset = (FLIP>10) and $+11<<16 or (FLIP>9) and $+7<<16 or (FLIP>6) and $+4<<16 or (FLIP>5) and $+3<<15 or $+36500
			else --Falling down.
				s.spriteyoffset = max(0, (FLIP>=3) and $-3<<16 or (FLIP>=2) and $-5<<16 or $-8<<15)
			end
			if GS.myrail and GS.myrail.valid --Spin physically.
				local LOOPFLAGS = GS.myrail.GSloopflags or 0
				if (FLIP > 1)
					local STANCE = (FLIP > 5) and SPR2_RIDE and (FLIP > 3) and SPR2_SPNG or SPR2_FALL
					for i = 1,3
						if P_IsValidSprite2(s, STANCE) 
							if (s.sprite==SPR_PLAY)
								s.sprite2 = STANCE|SUPER 
								s.frame = $ & ~FF_FRAMEMASK
								s.tics,s.anim_duration = -1,-1
							end
							GS.animcall = 0 break --
						end STANCE = (i==1) and SPR2_SPNG or SPR2_FALL 
					end
					local ANG = RAIL.ANG(GS.myrail)+GS.raildirection
					if GS.flip
						p.drawangle = ANG+ min(10, FLIP)*(ANG2*16) - (ANG2*27)
					else
						p.drawangle = ANG+ min(11, FLIP)*(ANG1*39) - (ANG2*24)
					end	
					if FLIP==2 and not (LOOPFLAGS & 1048576)
						for i = 1,3 P_SpawnSkidDust(p, 32<<16) end
					end
				elseif not (LOOPFLAGS & 1048576)
					local TYPE = RAILTYPE[RAIL.LearnRailType(GS.myrail)]
					if TYPE
						if not (TYPE.landSFX==sfx_none) --Clank! You hit a rail!
							local LANDSFX = DF.landSFX if (TYPE.landSFX!=nil) LANDSFX = TYPE.landSFX end
							if not (TYPE.spawnleaves) and not (TYPE.nosparks) and not (TYPE.electricrail)
								S_StopSoundByID(s, sfx_gsralg)
								S_StartSoundAtVolume(s, sfx_gsralg, 130) --The heroes experience.
							else
								S_StopSoundByID(s, LANDSFX)
								S_StartSoundAtVolume(s, LANDSFX, 104)
							end
						end
						for i = 1,5 P_SpawnSkidDust(p, 62<<16) end
					end
				end			
			end
		end	
		
		local RESULT = RAIL.DOSKIN(SKIN, p, s, GS, OGCALL) --What does the modder wanna do?
		if (RESULT!=nil)
			return RESULT
		elseif GS.animcall=="force" or GS.animcall=="flipanim" or GS.animcall=="stop"
			GS.animcall = 0 return  --
		else
			if P_IsValidSprite2(s, SPR2_GRND)
				if not (GS.railtossflag and ((p.cmd.buttons & BT_TOSSFLAG) or (GS.railtossflag < 99<<16)))
				or STATS and STATS.NOFUNALLOWED==true
					return -- This IS our animation!
				end
			end
		end	
	end
	
	--This is the default behaviour, whenever GS.animcall is a number, usually 1.
	local SPRITE2 = skins[s.skin].sprites
	if SPRITE2!=nil and (GS.grinding and not GS.flipanim or (FORCESPRITE))
		if GS.curanim --Stay in current animation first.
			if (GS.curanim.SKIN!=s.skin) or ((GS.curanim.MAX or 0)<2)
			or (GS.curanim.TIMER >= GS.curanim.MAX) or not (P_IsValidSprite2(s, GS.curanim.SPRITE2))
				GS.curanim = nil --NO WAY!
			else
				s.sprite = GS.curanim.SPRITE
				s.sprite2 = GS.curanim.SPRITE2
				s.frame = GS.curanim.FRAME
				s.anim_duration = -1
				GS.curanim.TIMER = $+1 return --
			end
		end
		if (s.sprite!=SPR_PLAY) return end --Must be in SPR_PLAY!
		
		local STANCE = SPR2_STND
		local RO = p.GSRO
		if FORCESPRITE!=nil
			STANCE = FORCESPRITE
		elseif RO
			if not GS.railtossflag --What stance to use?
				STANCE = RO.backupgrindstate or SPR2_ROLL
		--		print("BACKUPSTATE:"+spr2names[STANCE]) //+"\x86 SPRITE2.numframes:"+SPRITE2.numframes)
			else
				STANCE = RO.duncegrindstate or SPR2_EDGE
		--		print("DUNCESTATE:"+spr2names[STANCE]) //+"\x86 SPRITE2.numframes:"+SPRITE2.numframes)
				if GS.railtossflag==1
					local ghs = P_SpawnMobjFromMobj(s,s.momx,s.momy,s.momz+s.height/9,MT_GSRAILGFX)
					ghs.color,ghs.colorized = s.color,true
					ghs.sttfade = true
					ghs.state = S_JETFUME1
					ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS50
					ghs.fuse,ghs.tics = 6,6
					ghs.scale = $*4/3
					ghs.scalespeed = $*5
					ghs.destscale = 99<<16
					P_Thrust(ghs, p.drawangle, abs(GS.railspeed or 1))
					S_StartSoundAtVolume(s, (leveltime%2) and sfx_s1b6 or sfx_s1b8, 166)
				end			
			end	
		end
		local LOOP = RAIL.LearnLoopType(GS.myrail)
		if LOOP and LOOP.anim=="context" and (LOOP.walljump)
			if STATS and STATS.WALLCLINGSPRITE and P_IsValidSprite2(s, STATS.WALLCLINGSPRITE)
				STANCE = STATS.WALLCLINGSPRITE
			else
				for i = 0,5
					if (i < 3)
						if (p.charability==CA_GLIDEANDCLIMB) or (string.gmatch(s.skin or "", "knu"))
							STANCE = i==0 and SPR2_CLNG or i==1 and SPR2_CLMB or SPR2_SKID
						else
							STANCE = i==0 and SPR2_SKID or i==1 and SPR2_CLNG or i==2 and SPR2_CLMB
						end
					else
						STANCE = i==3 and SPR2_PAIN or i==4 and SPR2_ROLL or SPR2_STND
					end
					if P_IsValidSprite2(s, STANCE) break end --
				end
			end
			if GS.myrail.GSVertRailDOWNANG!=nil
				p.drawangle = GS.myrail.GSVertRailDOWNANG+ANGLE_180
			end	
		elseif not (P_IsValidSprite2(s, STANCE))
			STANCE = SPR2_STND --Backup!
		end
		s.sprite2 = STANCE|SUPER
		if STANCE == SPR2_STND
			s.frame = $ & ~FF_FRAMEMASK
			GS.curanim = nil
		else
		--	print("STANCE:\x82"+spr2names[STANCE]+"\x80 s.sprite2:\x88"+s.sprite2+"\x80 MAXFRAME:\x8d"+MAXFRAME+"\x80 NAME:\x86"+NAME+"\x80 CURFRAME:\x8e"+(s.frame & FF_FRAMEMASK))
			local NAME,MAXFRAME = nil
			if SPRITE2[s.sprite2] 
				NAME = SPRITE2[s.sprite2]
			--	print("STANCE:\x82"+spr2names[STANCE]+"\x80 s.sprite2:\x88"+s.sprite2+"\x80 MAXFRAME:\x8d"+MAXFRAME+"\x80 NAME:\x86"+NAME+"\x80 CURFRAME:\x8e"+(s.frame & FF_FRAMEMASK))
				MAXFRAME = tonumber(NAME.numframes)
			end
			if STANCE==SPR2_EDGE and ((MAXFRAME or 0)<5)
				s.frame = $ & ~FF_FRAMEMASK
				GS.curanim = nil
			else
				if NAME==nil
				or not MAXFRAME 
				or type(MAXFRAME)!="number" 
				or ((s.frame & FF_FRAMEMASK) >= MAXFRAME-1)
					s.frame = $ & ~FF_FRAMEMASK
				else
					s.frame = $+1
				end
				GS.curanim = {SKIN = s.skin, SPRITE = s.sprite, SPRITE2 = s.sprite2, FRAME = s.frame,
				TIMER = 0, MAX = (ANS[STANCE]) and ANS[STANCE] or 1}
			end
		end
		s.anim_duration = -1
	end return RAIL.DOSKIN(SKIN, p, s,GS, OGCALL)
end

--After JUST exiting a rail.
RAIL.LEFTRAIL = function(p, s, GS)
	GS.leftrail = min(3, max($-1,0)) if not (s.health) return end --
	if GS.LTAB
		if GS.LTAB.doflip and not GS.leftrail
			P_SpawnGhostMobj(s) --Flip jump!
			if (s.eflags & MFE_VERTICALFLIP)
				s.momz = $-(10*s.scale)
				s.flags2 = $ & ~MF2_OBJECTFLIP
				s.eflags = $ & ~MFE_VERTICALFLIP
				P_PlayerZMovement(s)
			else
				s.momz = $+(10*s.scale)
				P_PlayerZMovement(s)
				s.flags2 = $|MF2_OBJECTFLIP
				s.eflags = $|MFE_VERTICALFLIP
			end
			S_StartSoundAtVolume(s, sfx_gravch, 160)
			GS.LTAB.timer = max(GS.LTAB.timer or 0, 11)
			GS.LTAB.doflip = false
		end	
		if (GS.LTAB.init <= 1)
			if GS.LTAB.dojumpboost
				if GS.LTAB.dojumpboost=="2D"
					if (p.cmd.sidemove)
						local ANG = (p.cmd.sidemove < 0) and ANGLE_180 or 0 --0 is right. 180 is left.
						P_Thrust(s, ANG, abs(p.cmd.sidemove)*(s.scale/2))
					end
					GS.LTAB.dojumpboost = nil
				elseif GS.LTAB.dojumpboost=="3D"
					if (p.cmd.forwardmove or p.cmd.sidemove)
						local ANG = p.cmd.angleturn<<16
						if p.awayviewcam and (p.awayviewcam==GS.railcam) and not (GS.nocam)
						and p.awayviewtics and GS.railcam.valid
							ANG = GS.railcam.angle
						end
						ANG = $+(R_PointToAngle2(0, 0, p.cmd.forwardmove<<16, -(p.cmd.sidemove<<16)))
						P_Thrust(s, ANG, min(50, abs(p.cmd.forwardmove)+abs(p.cmd.sidemove))*(s.scale/2))
					end
				end
				GS.LTAB.dojumpboost = nil	
			elseif GS.LTAB.snappedtofloor and P_IsObjectOnGround(s) --and (p.speed < 5*s.scale)
			and not (p.pflags & (PF_SPINNING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_STARTDASH|PF_SLIDING))
			and not (P_PlayerInPain(p)) and not p.powers[pw_carry] and not (GS.LTAB.GetUpAnimANG!=nil)
			--	print("END SPEED:\x82 "+GS.LTAB.thrust or 0+" p.speed:"+p.speed/FRACUNIT+" abs(s.momx+y):\x82"+((abs(s.momx)+abs(s.momy))/FRACUNIT))
				if GS.LTAB.thrust
					if GS.LTAB.MOMANG!=nil
						P_InstaThrust(s, GS.LTAB.MOMANG, GS.LTAB.thrust)
					end
				end
				if GS.LTAB.laststate and s.state!=GS.LTAB.laststate
					s.state = GS.LTAB.laststate
				end
				if s.momz==0
					s.momz = -P_MobjFlip(s)
				end
				s.eflags = $|MFE_JUSTHITFLOOR
			end
		end
	end
	
	local TRIEDJUMP = false							
	if (GS.leftrail < 4)
		s.friction = max($, FRACUNIT)
		if not (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_THOKKED|PF_STARTDASH|PF_FULLSTASIS|PF_JUMPSTASIS)) 
		and (p.panim==PA_FALL or p.panim==PA_ROLL or ((p.panim>=PA_IDLE) and (p.panim<=PA_DASH)) )
		and not (s.eflags & MFE_SPRUNG) and not p.exiting
		and not (p.powers[pw_carry] and (p.powers[pw_carry]!=3888))
			if (p.cmd.buttons & BT_JUMP) and (GS.backupjump < 33) --Buffering stuff!
			and not GS.railswitching and not GS.JumpRamp
				p.pflags = $|PF_JUMPDOWN --Buffer a jump?
				P_DoJump(p, true)
				TRIEDJUMP = true
			elseif not (s.momz*P_MobjFlip(s) > 10*s.scale)
				if not p.cmd.forwardmove and not p.cmd.sidemove
					p.cmd.forwardmove = 1
				end
				local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
				if (abs(s.z-FLOORZ) < 60*s.scale) and GS.LTAB and GS.LTAB.snappedtofloor
					s.z = FLOORZ
					P_SetObjectMomZ(s, -8<<16, true)
					if (GS.backupspin < 28) and (p.cmd.buttons & BT_SPIN) and not (p.pflags & PF_SPINNING) 
						p.pflags = $ & ~PF_USEDOWN --Buffer a spin input?
						if p.yusonictable p.yusonictable.spin = 0 end
					end					
				end
			end
		end	
	end	
	if TRIEDJUMP==false
		local LOOP = RAIL.LearnLoopType(GS.LastRail or GS.myrail)	
		if LOOP and (LOOP.endwithspin!=false) and not (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_JUMPED)) 
			if (p.cmd.buttons & BT_SPIN) or GS.spinstate==true
				local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
				if (abs(s.z-FLOORZ) < 82*s.scale) or LOOP.endwithspin=="always"
					if p.charability2==CA2_SPINDASH or LOOP.endwithspin=="force" or LOOP.endwithspin=="always"
						p.cmd.buttons = $|BT_SPIN
						p.pflags = $|PF_SPINNING
						s.state = S_PLAY_ROLL
					end
				end
			end
		end
	end	
end

--After losing balance on a rail.
RAIL.FAILROLL = function(p,s,GS, START)
	if (START)
		local tilt = GS.railtilt if not (tilt) tilt = (leveltime%2) and -499 or 499 end GS.railtilt = tilt
		local ang = p.drawangle
		local sideang = ang + ( ((tilt or 0)>0) and -ANGLE_90 or ANGLE_90)
		local flingspeed = max(abs(GS.railspeed or 1)/4, 8<<16)
		local TWOD = GS.TWOD
		RAIL.ExitRail(s, true, true) if not (s.health) return end --
		
		s.z = $+(16*s.scale*P_MobjFlip(s))
		if (TWOD!=true)
			P_TryMove(s, s.x+FixedMul(64*s.scale, cos(sideang )), s.y+FixedMul(64*s.scale, sin(sideang )), true)
		end
		
		s.momx,s.momy = $/3,$/3
		p.powers[pw_nocontrol] = max($+8, 8)
		GS.cannotgrind = max($, 24)
		if p==displayplayer
			S_StartSoundAtVolume(nil, sfx_kc55, 50, p) S_StartSoundAtVolume(nil, sfx_cdfm70, 50, p)
		end
		S_StartSound(s, sfx_cdfm70) S_StartSound(s, sfx_kc55)
		P_SetObjectMomZ(s, -3<<16, true)
		for d = 1,5
			P_SpawnSkidDust(p, 50<<16)
		end
		if tilt
			local MULT = (tilt>0) and 1 or -1
			if TWOD!=true
				P_Thrust(s, ang+ANGLE_90, -flingspeed*MULT)
			end
			GS.failroll = 19*MULT
			GS.failrollang = ang+(ANGLE_90*MULT)
		else
			GS.failroll = 17
		end
		GS.railjump = 999<<16
		p.pflags = $|PF_JUMPDOWN|PF_USEDOWN
		s.spritexscale,s.spriteyscale = FRACUNIT+7000,FRACUNIT-7000
		GS.stretch = {timer = 3, Xspeed = 2300, Yspeed = 2300, air = true}			
	end
	
	if not P_IsObjectOnGround(s)
	and ((START) or p.powers[pw_carry]==CR_GRINDRAIL or p.powers[pw_carry]==0)
	and not (p.pflags & (PF_STARTJUMP|PF_STARTDASH))
	and not ((p.pflags & (PF_NOJUMPDAMAGE|PF_STARTJUMP|PF_JUMPED)) and (p.pflags & PF_THOKKED))
	and not (P_PlayerInPain(p) and (p.powers[pw_flashing] > 99))
	and not (p.powers[pw_super]==1)
	and (s.health) 
	and (p.playerstate!=PST_DEAD)
	and (s.state!=S_PLAY_RIDE)
		if (GS.failroll < 0)
			GS.failroll = $+1
		elseif GS.failroll
			GS.failroll = $-1
		end
		GS.rollangle = GS.failroll*(ANG20)
		s.rollangle = GS.rollangle
		s.state = S_PLAY_PAIN
		if (abs(GS.failroll) > 8)
			if GS.failrollang != nil
				p.drawangle = GS.failrollang
			end
			GS.cannotgrind = max($, 3)
		end
	else
		GS.failroll,GS.failrollang = 0,nil
	end
	if not (GS.failroll)
		if p.panim == PA_PAIN and (s.health)
			s.state = S_PLAY_FALL
		end
		s.spritexoffset,s.spriteyoffset,s.rollangle = 0,0,0
		GS.failroll,GS.rollangle = 0,0
		GS.failrollang,GS.ANGswap = nil
	end
end

--Player thinker for rails! Requires "p.mo" to be valid.(s)
--Players have inputs, special animations, balancing, etc, so they need extra code compared to other mobjs.
RAIL.PlayerThink = function(p) 
	if (gamestate!=GS_LEVEL) or not p.valid return end local s = p.mo
	if not (s and s.valid and s.GSgrind) return end --mo.GSgrind is normally not made until rails actually become relevant.
	local GS = s.GSgrind if GS.cannotgrind==nil RAIL.SetupMobjRailVars(s) GS = s.GSgrind end --Re-setup failsafe!
	
	local SKIN = GS_RAILS_SKINS[s.skin]
	local STATS
	if SKIN 
		if SKIN["PlayerThink"] and (SKIN["PlayerThink"](p, s, GS, GS.myrail))==true return end --End early?
		STATS = SKIN["S_SKIN"]
		if STATS and STATS.AUTOVERTICALAIM==false
			GS.forceVaim = max(GS.forceVaim or 0, 2) --Always apply this!
		end
	end

	if GS.movedfactor and not ((GS.railswitching or 0) > 2)
		GS.movedfactor = false
		if s.movefactor==0
			s.movefactor = FRACUNIT --Setting movefactor to nil can apparently only be done in native playerthinkers?
		end
	end
	if GS.forceVaim --Forces free vertical aim if active.
		GS.forceVaim = max($-1,0)
		GS.OGAIM = p.aiming
	elseif GS.OGAIM!=nil
		p.aiming = GS.OGAIM	
		GS.OGAIM = nil
	end
	if (s.flags2&MF2_TWOD) or (twodlevel) or s.currentaxis
		GS.TWOD = true --2D status includes Axis2D.
	else
		GS.TWOD = false		
		if p.homing
			if GS.homingANG==nil
				GS.homingANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
			end
		elseif GS.homingANG!=nil and not GS.grinding
			GS.homingANG = nil
		end
	end
	if p.solcam and p.solcam.GSdisabled
		p.solcam.emergencydisable,p.solcam.GSdisabled = false, false
	end
	if GS.walljump --Table indicator you've walljumped. GS.lastwall prevents you from attaching to the same rail for a bit!
		GS.walljump.timer = max($-1,0)
		GS.walljump.init = $+1
		GS.cannotgrind = min($, 2)
		if (GS.walljump.init > 76) and not GS.walljump.timer or not s.health
		or P_IsObjectOnGround(s) or p.powers[pw_carry] or P_PlayerInPain(p) or (s.eflags & MFE_SPRUNG)
			if GS.walljump.loopcam
				GS.docam = 0
			end	
			GS.walljump = nil
		elseif (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE)) 
			if (GS.walljump.init < 9)
				if GS.walljump.init==1 and (s.momz*P_MobjFlip(s) < 15*s.scale)
					s.momz = $+(((s.momz*P_MobjFlip(s) <= 7*s.scale) and 7 or 5)*s.scale*P_MobjFlip(s)) --Minor boost.
				end
				if GS.walljump.jumpang!=nil 
				and not (p.pflags & (PF_THOKKED|PF_SPINNING)) and (p.panim==PA_SPRING or p.panim==PA_JUMP or p.panim==PA_ROLL)
					if (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1)) and not (GS.walljump.loopcam)
						p.drawangle = s.angle
						GS.walljump.jumpang = nil --Face same direction.
					else
						p.drawangle = GS.walljump.jumpang
						if GS.walljump.loopcam and (GS.TWOD!=true)
							p.cmd.forwardmove = 0	
							P_InstaThrust(s, GS.walljump.jumpang, p.speed)
						end
					end
				end
			end
		end
	elseif p.GSANflags!=nil and not (GS.grinding and (GS.myrail and GS.myrail.GSVertRail)) 
		p.pflags = ($ & ~(PF_ANALOGMODE|PF_DIRECTIONCHAR))|p.GSANflags
		p.GSANflags = nil
	end
/*	if GS.nonANALOGcorrect!=nil and not (GS.walljump and GS.walljump.timer)
		if not (p.pflags & PF_ANALOGMODE) and not p.powers[pw_carry]
		and not P_IsObjectOnGround(s) and not P_PlayerInPain(p)
			p.cmd.angleturn = GS.nonANALOGcorrect/FRACUNIT
			s.angle = GS.nonANALOGcorrect
			if s.momx or s.momy
				p.drawangle = R_PointToAngle2(s.x, s.y, s.x+s.momy, s.y+s.momy)
			else
				p.drawangle = s.angle
			end
		end		
		GS.nonANALOGcorrect = nil
	end */
	if p.GSRO and p.GSRO.NewbieGrinder
		p.GSRO.NewbieGrinder = max($-1,0)
	end
	if GS.stretch --Stretch effects.
		RAIL.Stretch(s, GS, GS.stretch)
	end
	if GS.LTAB --Last rail Table. Keeps track of various things we've done.
		GS.LTAB.init = $+1
		if GS.LTAB.timer
			GS.LTAB.timer = max($-1,0)
			if GS.LTAB.timer
				if (GS.LTAB.GetUpAnimANG!=nil) and (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) and (GS.LTAB.init < 3)
				and not (p.pflags & (PF_THOKKED|PF_SLIDING|PF_SPINNING)) and not p.powers[pw_carry] 
				and not (p.speed > 34*s.scale) and not P_PlayerInPain(p)
					P_InstaThrust(s, GS.LTAB.GetUpAnimANG, 5*s.scale)
					p.drawangle = GS.LTAB.GetUpAnimANG
				end
				if P_PlayerInPain(p) or p.powers[pw_carry] or (s.eflags & MFE_SPRUNG) or GS.walljump
				or p.homing or not s.health
					GS.LTAB.timer = 0
				elseif not GS.grinding and not GS.railswitching
					RAIL.buttons(p, GS, STATS)
					if (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_THOKKED|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE)) 
					or p.panim==PA_ABILITY or p.panim==PA_ABILITY2
						if GS.jump==1 or GS.spin==1 or GS.custom1==1 or GS.custom2==1 or GS.custom3==1
							GS.LTAB.timer = 0
						end
					end
				end
			end
		elseif (GS.LTAB.init > 350) or not s.health --Can't imagine needing this for over 10 seconds.
			GS.LTAB = nil
		end
	end
	GS.alreadytrailed = nil
	
	if GS.leftrail --Literally just left the rail.
		RAIL.LEFTRAIL(p,s,GS)
	elseif (p.powers[pw_flashing]>102 and p.powers[pw_flashing]<106) and ((GS.grinding or 0) > 2) --Don't stay on rail if in pain!
		local ANG,HANG = p.drawangle, (RAIL.GrindState(s, "hang")) and true or false
		RAIL.ExitRail(s, true, true)
		P_DoPlayerPain(p)
		p.drawangle = ANG
		GS.cannotgrind = max($, 15)
		if HANG P_SetObjectMomZ(s, 85356) end --Come crashing down from hangrails rather than bouncing up.
		return --
	end

--	RAIL.RailDebug(p,s) --DEBUG function
	if GS.justtouchedrailthing --We touched an attached rail thing recently.
		GS.justtouchedrailthing = $-1
	end
	if GS.perfectcorner --Doing a perfect corner! Can't lose balance for a bit!
		GS.perfectcorner = $-1
	end
	if GS.cantcrouch --You can't crouch rn!
		GS.cantcrouch = (type(GS.cantcrouch)=="number") and $-1 or nil
	end
	if GS.hanghop --Special sidehop from a hangrail?
		GS.hanghop = $-1
	end
	if GS.animcall --Currently calling an animation for the PostThinkFrame. Can be a string or number.
		GS.animcall = (type(GS.animcall)=="number") and max($-1,0) or 0
	end
	if GS.failroll --When you lost your balance and fall off like an idiot.	
		RAIL.FAILROLL(p,s,GS)
	end
	
	local JUMPFLAGS,LOOPFLAGS,GRINDFLAGS,LOOP = 0,0,0, nil
	local BALANCING = false
	if GS.grinding  --Ahh, railgrinding, are we?
		p.powers[pw_noautobrake] = max($, 3) --Turn that crap off.

		if GS.leftrail return end --Lies.

		local GRINDING = GS.grinding --Remember this!
		local SIDE,FORWARD = GS.side or 0,GS.forward or 0
		local RO = p.GSRO
		LOOP = RAIL.LearnLoopType(GS.myrail)

		if not GS.JumpRamp s.rollangle = 0 end
		s.friction = FRACUNIT
		GS.cannotgrind = max($,2)
		RAIL.buttons(p, GS, STATS) --Record our button presses!
		GS.railminspeed = 0 --Set to 0 for players all the time.	
		
		--Okay! Rail is real, let's do this!
		if GS.myrail and GS.myrail.valid
			if STATS and STATS.MINSPEED
			and not (LOOP and LOOP.walljump) and not ((GS.myrail.GSgrindflags or 0) & 524288)
				GS.railminspeed = abs(STATS.MINSPEED)
			end
			if SKIN and SKIN["PreGrindThinker"] and (SKIN["PreGrindThinker"](p, s, GS, GS.myrail))==true return end --

			local rail,CAM = GS and GS.myrail
			local REVERSE,CONVEYOR,GAINING = RAIL.GrindRail(s, rail, GS) --First, the Mobj Thinker. If it fails, cancel here too.
			if not (REVERSE) or not (GS.myrail and GS.myrail.valid) return end

			rail = GS.myrail  --Can sometimes change last second.
			GS.LastRail = GS.myrail --Record this only for players.
			CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
			LOOP = RAIL.LearnLoopType(rail) --Update! The rail could've changed.
			JUMPFLAGS,LOOPFLAGS,GRINDFLAGS = rail.GSjumpflags or 0,rail.GSloopflags or 0,rail.GSgrindflags or 0
			if GS.crouchbutton and not (STATS and (STATS.CROUCH==false))
			or (abs(GS.railtilt or 0)>320) 
				if not (STATS and (STATS.CROUCH==false and not STATS.MUSTBALANCE==true))
					if not (GS.cantcrouch) 
					and not (GRINDFLAGS & (65536|131072)) and not (rail.GSVertRail)
					and not (LOOP and (LOOP.anim!=0)) and not (LOOPFLAGS & (131072|1048576))
						BALANCING = true --Full-range balancing is active?
					end
				end
			end
			
			s.movefactor = 0
			GS.movedfactor = true
			
			--Gradually set the camera straight if it's rotated.
			if GS.viewrollangle
				if not (GRINDFLAGS & 524288) and not (LOOPFLAGS & (524288|262144)) or not (abs(GS.viewrollangle) > ANG1/128)
					local PLUS = (LOOPFLAGS & 4194304) and -1 or 1
					if GS.badbalance==true or (abs(GS.railtilt or 0) >= 340)
						GS.viewrollangle = FixedDiv(FixedMul(($/180),45),46)*PLUS*180
					else
						GS.viewrollangle = FixedDiv(FixedMul(($/180),14),15)*PLUS*180
					end
					p.viewrollangle = GS.viewrollangle
				else
					p.viewrollangle,GS.viewrollangle = 0,0
				end
			else
				p.viewrollangle = 0
			end
			local Rspeed = abs(GS.railspeed) or 1
			if STATS
				if STATS.MUSTBALANCE==true and not (GS.cantcrouch) and not (GRINDFLAGS & (65536|131072))
				and not (GRINDFLAGS & (65536|131072)) and not (LOOP and (LOOP.anim!=0)) 
				and not (LOOPFLAGS & (131072|1048576)) and not (rail.GSVertRail)
					BALANCING = true
				end
				if (STATS.ACCELSPEED)
					if not GS.flipanim and (p.cmd.forwardmove > 32) and (GS.grinding > 3)
					and not GS.badbalance and not GS.justtouchedrailthing
					and not (rail.GSVertRail)
					and not (LOOP and LOOP.walljump)
					and not (GRINDFLAGS & 65536) --No TwistDrive/FocusGrind also turns off AccelSpeed
						local CAP = (STATS.ACCELCAP or DFSTATS.ACCELCAP)*s.scale
						local ACCEL = max(1, STATS.ACCELSPEED*max(1, s.scale/80)) if s.skin=="sms" and not p.hypermysticsonic CAP=-1 end
		
						if (Rspeed < CAP) and GS.railspeed!=nil
							if (Rspeed > CAP/2) --Diminish slightly as you near the cap.
								ACCEL = (Rspeed > CAP*2/3) and $*2/3 or $*3/4	
							end
							GS.railspeed = (GS.railspeed<0) and $-ACCEL or $+ACCEL
							Rspeed = abs(GS.railspeed) or 1
						end
					end
				end
			end
			local CAM = RAIL.RailCam(p,s, GS, STATS) --Activate rail camera.		
			if LOOP
				if LOOP.spawndust
					P_SpawnSkidDust(p, 48<<16)
				end
				if (LOOP.speedtrail) and (Rspeed > LOOP.speedtrail*s.scale)
					GS.speedtrail = GS.speedtrail or true
				end
				if LOOP.walljump
					GS.cantcrouch,GS.crouchgrind,GS.maxroll = 3,false,69
					BALANCING = false
				else
					GS.WJtag = nil
				end
				if (LOOP.anim!=0)
					if LOOP.anim=="context" --Context sensitive?
						if LOOP.walljump
							GS.forcesprite = SPR2_SKID
						elseif GS.spinstate or (p.cmd.buttons & BT_SPIN)
							GS.forcesprite = SPR2_ROLL
							if not (GS.spinstate)
								GS.spinstate = true
								if s.skin!="adventuresonic"
									local SFX = skins[s.skin].soundsid[SKSSPIN]
									if not (SFX) SFX = sfx_spin end
									if p==displayplayer and not S_SoundPlaying(s, SFX)
										S_StartSoundAtVolume(nil, SFX, 77, p)
									else
										S_StopSoundByID(s, SFX)
									end
									S_StartSound(s, SFX)
								end
							end
						else
							local SPRITE2 = SPR2_WALK
							if ((Rspeed+abs(CONVEYOR)) > 29*s.scale) or (GRINDFLAGS & 524288)
								SPRITE2 = SPR2_RUN
							end
							GS.forcesprite = SPRITE2
							if (abs(p.cmd.forwardmove) < 9) and (abs(p.cmd.sidemove) < 9)
								GS.railspeed = FixedDiv(FixedMul($, 30), 31)
							end
						end
					else
						GS.forcesprite = type(LOOP.anim)=="number" and LOOP.anim or SPR2_ROLL
					end
					GS.animcall = 1
				end
			else
				GS.WJtag = nil
			end
			
			--What state do we go into?
			if not (s.eflags & MFE_VERTICALFLIP) and rail.GShangrail
			or (s.eflags & MFE_VERTICALFLIP) and not rail.GShangrail
				if s.state != S_PLAY_RIDE and s.health
					s.state = S_PLAY_RIDE
				end
				GS.viewrollangle = ((GS.rollangle or 0)/-3)
				if (GS.grinding < 7) --Can't jump off for a few frames.
					GS.railspin = 999
				end	
				GS.cantcrouch,GS.crouchgrind,GS.maxroll = 7,false,290 --Incase of sudden transitions.
				BALANCING = false
				GS.flipanim,GS.flip,GS.flipanimCD = nil
				if SKIN and SKIN["CustomHangAnim"] and SKIN["CustomHangAnim"](p, s, GS, GS and GS.myrail) end
			else
				if (s.state!=S_PLAY_GSGRINDING) and s.health
					s.state = S_PLAY_GSGRINDING
				end
				GS.maxroll = (STATS and (STATS.AUTOBALANCE!=true)) and 300 or 200
				if GS.rollangle!=nil and not (CAM and CAM.valid) and (GS.TWOD!=true)
				and not ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
				and not ((GRINDFLAGS & 524288) and not (CONVEYOR))
					local PLUS = 1
					if s.mirrored
						PLUS = -1
					--	if p==displayplayer and CV_FindVar("renderer").value==2 and CV_FindVar("gr_models").string=="On"
					--		PLUS = 1 --Nope.
					--	end
					end					
					s.rollangle = GS.rollangle*PLUS --fallback.
				end	
				
				if GS.flipanim --Doing a Sonic Heroes style twist! I'll dub it the Twist Drive for the tutorial!
					GS.flipanim,GS.flipanimCD = max($-1,0),2
					if not GS.flipanim
						s.spritexoffset,s.spriteyoffset,GS.railWEAPPREV,GS.railWEAPNEXT = 0,0,0,0
						if GS.railjump and (GS.railjump < 11) and (p.cmd.buttons & BT_JUMP)
							GS.railjump = 0 --Buffer a jump after a flip?
						end
					else
						GS.animcall = "flipanim"
					end
				elseif GS.flipanimCD --Wait a tic or 2, bud.
					GS.flipanimCD = max($-1,0)
				else
					if GS.twistbutton==1 and (GS.railjump!=1) and (GRINDING > 6)
					and not (GS.badbalance==true) 
					and not (STATS and STATS.HEROESTWIST==0)
					and not (rail.GSVertRail)
					and not (LOOP and (LOOP.anim and (LOOP.anim!=0) or LOOP.walljump==true))
					and not (LOOPFLAGS & (131072|1048576)) --Not upsidedown or got rail effects disabled?
					and not (GRINDFLAGS & (65536|131072|524288)) --Controls not disabled, and momentum allowed?
					and not (SKIN and SKIN["TwistDrive"] and SKIN["TwistDrive"](p, s, GS, rail)==true)
						GS.flipanim = 11 --Speen!
						GS.flip = not GS.flip
						GS.animcall = "flipanim"
						s.mirrored = (GS.flip) and true or false		
						s.spritexscale,s.spriteyscale,s.spriteyoffset = 56000,78500, 0
						GS.stretch = {timer = 12, Xspeed = 2200, Yspeed = 1300}
						local GSPEED = abs(GS.railspeed or 0)
						if (GSPEED < 75*s.scale) or (REVERSE==-1) --Tiny boost if you're moving slowly.
						or CONVEYOR and (abs(GS.railspeed+CONVEYOR) < 24*s.scale)
							local BOOST = (STATS and (STATS.HEROESTWIST)) and STATS.HEROESTWIST or 14 --Default!
							if (BOOST!=1) --1 means a visual-only Heroes Twist, added specifically for Modern Sonic.
								local ADD = BOOST*s.scale
								if REVERSE==-1 and (BOOST > 2)
									ADD = ($*5/4)+(5*s.scale) --Extra power to undo reversing!
								else
									if (GSPEED > 21*s.scale)
										ADD = $-min(ADD-(4*s.scale), GSPEED/15)
									end
									if GAINING==true
										ADD = max(1, ($*4)/9)
									end
								end
								ADD = $*(RAIL.GrindFlip(s, rail, true))
								GS.railspeed = $+ ADD //*(RAIL.GrindFlip(s, rail, true))
							end
						elseif (GSPEED > 88*s.scale) --On the other hand, best NOT spam this if you're moving fast already!
							GS.railspeed = FixedDiv(FixedMul($, 76), 77)
						end
					end
				end					
				if rail.GSforcerailanim
					if not (GS.flipanim) --Force a per-rail animation?
						for i = 1,2
							if type(rail.GSforcerailanim)=="number"
								if P_IsValidSprite2(s, rail.GSforcerailanim)
									GS.forcesprite,GS.animcall = rail.GSforcerailanim, "force"
								end break --
							elseif (spr2names[rail.GSforcerailanim]) and type(spr2names[rail.GSforcerailanim])=="number"	
								rail.GSforcerailanim = spr2names[rail.GSforcerailanim]
							else
								break --
							end
						end
					end
				elseif STATS and (STATS.FORCESPRITE) and not (LOOP and (LOOP.anim!=0)) and not (GS.flipanim)
					if not (GS.railtossflag and ((p.cmd.buttons & BT_TOSSFLAG) or (GS.railtossflag < 99<<16)))	
					or STATS.NOFUNALLOWED==true
						if P_IsValidSprite2(s, STATS.FORCESPRITE)
							GS.forcesprite,GS.animcall = STATS.FORCESPRITE, "force"
						end
					end
				end
				
				if SKIN and SKIN["CustomAnim"]
					local RESULT = SKIN["CustomAnim"](p, s, GS, GS and GS.myrail, false)
					if (GS.animcall!="flipanim")
						if RESULT==true
							GS.animcall = "force"
						elseif RESULT==false
							GS.animcall = "forcebutnotskip"
						end
					end
				end

				if (GS.animcall!="force")
					if P_IsValidSprite2(s, SPR2_GRND) --do a basic grinding pose!
					and not (LOOP and (LOOP.anim!=0))
					and not (GS.railtossflag and ((p.cmd.buttons & BT_TOSSFLAG) or (GS.railtossflag < 99<<16)))
						if (Rspeed > 22*s.scale)
							s.tics = 1	--fast anim
						elseif (s.tics < 0) or (s.tics > 2)
							s.tics = 2 --slow anim
						end
						if GS.animcall and type(GS.animcall)=="number"
							GS.animcall = 0
						end
					else --Do a Backup or Dunce pose!
						if not (GS.animcall)
							GS.animcall = 1
						end
						s.tics,s.anim_duration = -1,-1
					end
				end
			end
			p.powers[pw_justlaunched],p.powers[pw_justsprung],p.powers[pw_noautobrake] = 0,0,2
			p.speed = max(1, Rspeed)
			p.pflags = $|PF_JUMPDOWN & ~(PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_STARTJUMP|PF_STARTDASH|PF_NOJUMPDAMAGE|PF_SLIDING|PF_SHIELDABILITY)
			
			local DOVIEWROLL = false
			--Crouching! Greater potential for speed retention and slopespeed, but balancing becomes important! 
			if (s.state==S_PLAY_GSGRINDING) --	if P_IsValidSprite2(s, SPR2_GRND)
			--	print("1! BALANCING:\x83"+BALANCING+"\x80 GS.crouchgrind:\x82"+GS.crouchgrind)
				if (BALANCING==true)  --Harder balancing but more speed payoff!
				and not GS.flipanim and not (STATS and STATS.CROUCH==false)
				and not GS.cantcrouch and not (GRINDFLAGS & (65536|131072))  --Crouch is temporarily disabled!
					if not GS.crouchgrind and not (STATS and STATS.CROUCH==false)
						S_StartSound(s, sfx_s3k52)
						GS.crouchgrind = true
						for d = 1,4
							P_SpawnSkidDust(p, 30*FRACUNIT)
						end
					end
					if ((GS.railfalltimer or 0) > 3) and GS.badbalance==true //and (abs(GS.railtilt or 0) >= 300))
						DOVIEWROLL = true
					--	p.viewrollangle = GS.rollangle //(abs(GS.rollangle) > ANG1/64) and (GS.rollangle) or 0
					end 
					GS.maxroll = 800 --Balancing really comes into play now, so pay attention!
					s.spritexscale = min($+2500, 67001) s.spriteyscale = max($-2400, 63001)
					if (LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304) and not LOOP
						BALANCING = false
					end
				else
					if GS.crouchgrind and not GS.flipanim
						S_StartSound(s, sfx_s3k52)
						GS.stretch = {timer = 4, Xspeed = 2500, Yspeed = 2500, air = true}
					end
					GS.crouchgrind = false
				end
			end
			--Replicate vanilla's basic ugly spintrail, I guess?
			if GS.spinstate and LOOP or GS.forcesprite==SPR2_ROLL
				if not (LOOP and LOOP.endwithspin==false)
					p.pflags = $|PF_SPINNING
					if ((p.skin or 0) < 6) and (Rspeed > 16*s.scale) and ((p.spinitem or 0) <= MT_THOK)
						local ghs = P_SpawnMobjFromMobj(s, 0, 0, 1, MT_THOK)
						if ghs and ghs.valid
							ghs.color = s.color
						end
					end
				end
			end		
			--Sidehop section?
			if (JUMPFLAGS & 65536) and not (LOOP and LOOP.sidehop==false) and not (STATS and STATS.SIDEHOP==0)
			and not (p.powers[pw_nocontrol]) and (GS.TWOD!=true) and not (JUMPFLAGS & 131072) 
			and not ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
				RAIL.SpawnArrow(s, p.drawangle-(ANG20*4), SKINCOLOR_ORANGE, 6, -ANG20*4, true) --Sidehop tutorial arrow
			end
			if RO and RO.devmode and (s.eflags & MFE_SPRUNG) --Debugging for mappers.
			and not s.GScollide and not (GS.debugE1) and not GS.justtouchedrailthing
				local XYDIST = 250<<16
				searchBlockmap("objects", function(ref, t)
					if t.valid and (t.flags & MF_SPRING) and not ((t.GSgrind) and (t.GSgrind.railobject))
						GS.debugE1 = true
						if p==displayplayer
							RAIL.error("\x85"+"Warning!\x80 Touched unattached spring/booster while grinding!"+"\n"+
							"\x82"+"For proper rail interactions, give your rails an Attach Objects to Rail flag!")
						end return true --
					end
				end, s, s.x-XYDIST, s.x+XYDIST, s.y-XYDIST, s.y+XYDIST)
			end
			GS.badbalance = false
			RAIL.PlayerTilt(p,s, rail, GS, STATS, GRINDFLAGS, LOOPFLAGS, BALANCING)	
			if DOVIEWROLL==true
				local PLUS,ROLL = GS.VRplus, GS.viewrollangle or 0
				local TILT = GS.railtilt or 0 if abs(TILT) > 800 TILT = ($<0) and -800 or 800 end
				TILT = $*(ANG1/10)
				if not (PLUS==-1 and (TILT > ROLL))
				and not (PLUS==1 and (TILT < ROLL))
					local ADD = ANG10/3
					if (TILT > ROLL)
						GS.viewrollangle = min(ROLL+ADD, TILT)
					else
						GS.viewrollangle = max(ROLL-ADD, TILT)
					end
				end
				GS.VRplus = (TILT < (GS.viewrollangle or ROLL)) and -1 or 1
			end
		end
		if SKIN and SKIN["GrindThinker"]
			if SKIN["GrindThinker"](p, s, GS, rail or GS.LastRail) end
		end
		
		local VERTRAIL = (GS.MyRailStats) and GS.MyRailStats.VERTRAIL or 0
		if not GS or not (GS.myrail and GS.myrail.valid) --Aaand the rail is gone. Shit.
			RAIL.ExitRail(s, true, true) return --
		end
		local HANG =  (RAIL.GrindState(s, "hang")) and true or false
		
		--Grinding on a damaging Rail?
		if (GRINDFLAGS >= 2097152) and GS.myrail
		and not p.powers[pw_super] and not p.powers[pw_invulnerability] and not p.hypermysticsonic
		and not (p.xsonictable and p.xsonictable.darkphase==3) and not (p.solchar and p.solchar.istransformed)
			local STATE,SHIELD,DAMAGE  = s.state,(p.powers[pw_shield]&~SH_FIREFLOWER), 0		
			if (GRINDFLAGS & 4194304) and not (SHIELD and (SHIELD & SH_PROTECTELECTRIC)) --Electric Rail.
			and not ((s.skin=="xsonic" or s.skin=="xsonicserious") and p.xsonictable and p.xsonictable.PowerRing)
				P_DamageMobj(s, GS.myrail, GS.myrail, 1, DMG_ELECTRIC)
				if (s.state!=STATE) or p.powers[pw_flashing] or P_PlayerInPain(p)
					if p==displayplayer P_FlashPal(p, PAL_WHITE, 2) P_StartQuake(70<<16, 3) end
					S_StartSound(s, sfx_buzz2)
					DAMAGE = sfx_buzz1
					RAIL.SpawnFlameDust(s, SKINCOLOR_CYAN, s.scale*4)
				end
			end
			if (GRINDFLAGS & 2097152) and not (SHIELD and (SHIELD & SH_PROTECTFIRE)) --Burning Rail.
			and not (p.powers[pw_shield] & SH_FIREFLOWER) 
				P_DamageMobj(s, GS.myrail,GS.myrail, 1, DMG_FIRE)
				if (s.state!=STATE) or p.powers[pw_flashing] or P_PlayerInPain(p)
					DAMAGE = sfx_fire
					RAIL.SpawnFlameDust(s, nil, s.scale*3)
				end
			end
			if (DAMAGE!=0)
				S_StartSound(s, DAMAGE)  
				if s.health 
					s.state = S_PLAY_FALL
					p.pflags = $|PF_JUMPDOWN|PF_USEDOWN & ~(PF_STARTJUMP|PF_STARTDASH|PF_SPINNING|PF_JUMPED)
					if (p.powers[pw_flashing] > 100) and (p.powers[pw_flashing] < 200)
						p.powers[pw_flashing] = 88
					end
					GS.railtilt = ((GS.railtilt or 0)>0) and 800 or -800
					GS.railfalltimer = max(GS.railfalltimer or 0, 43)
					s.momz = $*2/3
				end
			end
		end
		
		--How should we get off the rail?
		if (GS.railfalltimer > 41) --Uhh, like a bumbling idiot? Got it.
			RAIL.FAILROLL(p,s,GS, true) return --
		else
			if P_PlayerInPain(p) or p.powers[pw_carry] and (p.powers[pw_carry]!=CR_GRINDRAIL) or GS.leftrail return end --Skip rest of the code.

			if rail and GS.grinding
				p.pflags = $|PF_JUMPDOWN & ~(PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_STARTJUMP|PF_STARTDASH|PF_NOJUMPDAMAGE|PF_SLIDING|PF_SHIELDABILITY)
				if (GRINDFLAGS & 131072) --We're in a cinematic part, apparently.
					if (abs(GS.railtilt) > 190)
						GS.railtilt = ($<0) and -190 or 190
					end
					GS.railWEAPNEXT,GS.railWEAPPREV = 999<<16,999<<16
					GS.railjump,GS.railspin,railc3 = 999<<16,999<<16,999<<16 return --		
				end
			end

			--Custom3 is the drop-off-rail-button
			if GS.railc3 == 1
			and not (STATS and STATS.EMERGENCYDROP==false) and not (JUMPFLAGS & 262144)
				if SKIN and SKIN["CustomDislodge"] and SKIN["CustomDislodge"](p, s, GS, rail)==true
					return --
				end
				local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
				if (abs(s.z-s.floorz) < 24*s.scale)
					s.z = $+5*s.scale*P_MobjFlip(s)
				end
				if HANG==true
					RAIL.ExitRail(s, "emergency", true)
					GS.cannotgrind = max($, 16)
					S_StartSound(s, sfx_s1b6)
					P_SetObjectMomZ(s, -4*FRACUNIT, true)
					s.spriteyoffset = -6*FRACUNIT
					s.spritexscale,s.spriteyscale = FRACUNIT-5000,FRACUNIT+9000
					GS.stretch = {timer = 5, Xspeed = 1700, Yspeed = 2000, air = true} return --No slope momentum on this.
				else
					RAIL.ExitRail(s, "emergency", true)
					s.spritexscale,s.spriteyscale = FRACUNIT+9000,FRACUNIT+9000
					GS.stretch = {timer = 3}
					s.momx = $/3 s.momy = $/3
					P_SetObjectMomZ(s, -1*FRACUNIT)
					GS.cannotgrind = max($, 60)
					S_StartSound(s, sfx_gsral5)

					local ghs = P_SpawnGhostMobj(s) 
					ghs.scale = s.scale
					ghs.scalespeed = s.scale/12
					ghs.momx,ghs.momy = s.momx/2,s.momy/2
					ghs.destscale = 99<<16 ghs.blendmode = AST_ADD
				end
				if (s.momz*P_MobjFlip(s) > 5*s.scale)
					s.momz = 5*s.scale*P_MobjFlip(s)
				end

			elseif not ( GS.flipanim and (GS.flipanim < 8) and HANG!=true and GS.animcall=="flipanim" )
			and not (GRINDFLAGS & 131072) --Controls ARE enabled, right?
			and not (LOOP and LOOP.jump==0) --This loop does allow jumps?
				local SIDEHOP = 0
				local HOPINPUT = (p.GSRO and p.GSRO.SideHold) and p.GSRO.SideHold or 0
				if GS.railWEAPNEXT==1 or GS.railWEAPPREV==1 --Do a sidejump like this?
					if HOPINPUT==2 or HOPINPUT==3 or HOPINPUT==4
						if (GS.TWOD!=true) and not (JUMPFLAGS & 131072) and not (LOOP and LOOP.sidehop==false)
							SIDEHOP = (GS.railWEAPNEXT==1) and 1 or -1
							GS.railjump = 1
							p.pflags = $|PF_JUMPDOWN
							p.cmd.buttons = $|BT_JUMP
						end
					end
				end

				if GS.railjump==1 or (SIDEHOP) --Jump off the rail? Let's call it a sidehop.
					local rail = GS.myrail or GS.lastrail
					local CANTSIDEHOP = false
					if (JUMPFLAGS & 131072) or (GS.TWOD==true) 
					or LOOP and LOOP.sidehop==false or (rail and rail.GSvertrail) or (LOOPFLAGS & 131072)
						CANTSIDEHOP = true
					end
					if (abs(SIDE) > 44) and (abs(FORWARD) < 18) and (HOPINPUT!=2)
						if HOPINPUT==0 or HOPINPUT==3 or GS.railspin and (GS.railspin < 99<<16)
							SIDEHOP = (SIDE < 0) and -1 or 1
						end
					end
					local RAMPJUMP = false
					if rail and rail.GSjumprail
						RAMPJUMP = (rail.GSjumprail==1) and true or "frontiers"
					end

					--Jump off the rail in a normal way!
					if GS.railjump==1 and (not (SIDEHOP) or CANTSIDEHOP==true) and not GS.flipanim
					and not (JUMPFLAGS & 65536)
						local RETURNVALUE = nil
						if SKIN and SKIN["CustomJump"]
							RETURNVALUE = SKIN["CustomJump"](p, s, GS, rail or GS.LastRail, RAMPJUMP)
							if RETURNVALUE==true 
							or RETURNVALUE=="customramp" and RAMPJUMP==true
								return --
							end
						end
						if STATS and STATS.JUMP==false --Character can't jump!
							return --
						end
						local ZDELTA = rail and rail.GSrailZDelta or 0
						local SLOPEFLING = RAIL.ExitRail(s, true, "jump")
						local OLDJUMPFACTOR = nil 
						local SCALE = max(FRACUNIT/4, s.scale)
						if (p.jumpfactor < 80350) and (p.jumpfactor > 9)
							if not (STATS and STATS.JUMPBOOST==false) --No jumpboost for this char.
								OLDJUMPFACTOR = p.jumpfactor  --There's an increased minimum jumpheight on rails.
								if ((SLOPEFLING or 0) >= 0) or RAMPJUMP==true
									p.jumpfactor = min(($*8/7)+2600, 80350)
								else
									p.jumpfactor = min(($*11/12)+400, 80350)
								end
							end
						end
						P_DoJump(p, true) --Apparently the SFX plays twice if PF_JUMPDOWN isn't enforced?
						if OLDJUMPFACTOR!=nil
							p.jumpfactor = OLDJUMPFACTOR
						end
						if RAIL.GrindState(s, "any") and s.health 
							s.state = S_PLAY_JUMP
						end
						GS.railswitchANG = p.drawangle --Useful for misc uses!
						p.pflags = $|PF_JUMPDOWN|PF_USEDOWN
						if GS.cannotgrind and ZDELTA and (abs(ZDELTA) > ANG10) and (s.momz*P_MobjFlip(s) > 12*SCALE)
							GS.cannotgrind = $+((abs(ZDELTA) > ANG20) and 2 or 1) --Extend it a bit when going down.
						end
					/*	if RAMPJUMP==true and RETURNVALUE!="norampjump"	
							if RETURNVALUE!="norampjump"
								
							end
						end */
						if (LOOPFLAGS & 131072)
						or RSTATS and RSTATS.VERTRAIL
						or GS.LastRail and GS.LastRail.GSVertRail
							GS.railfalltimer,GS.railtilt = 0,0
							if not (LOOP and LOOP.walljump) and s.health
								if GS.railcam and GS.railcam.valid
									GS.docam = max(GS.docam or 0, 12)
								end
								if (GS.failroll) or (s.state==S_PLAY_PAIN)
									if GS.LastRail and GS.LastRail.valid and (p.speed < 44*SCALE) and (GS.TWOD!=true)
										P_Thrust(s, R_PointToAngle2(s.x, s.y, GS.LastRail.x, GS.LastRail.y), -4*SCALE)
									end	
								else
									if (LOOPFLAGS & 131072)
										if GS.LastRail and GS.LastRail.GSVertRail
											if (p.speed < 24*SCALE) and GS.LastRail.valid
												P_InstaThrust(s, R_PointToAngle2(s.x, s.y, GS.LastRail.x, GS.LastRail.y), -24*SCALE)
											end
										else
											s.momz = -(abs($)+abs(SLOPEFLING))*P_MobjFlip(s)*3
										end
									end
									if LOOP and (LOOP.endwithspin!=false) 
										s.state = S_PLAY_ROLL
										if LOOP.endwithspin==true
											if p.charability2 == CA2_SPINDASH
												p.pflags = $|PF_SPINNING|PF_USEDOWN
											end
										elseif LOOP.endwithspin=="force" or LOOP.endwithspin=="always"
											p.pflags = $|PF_SPINNING|PF_USEDOWN
										end
									else
										s.state = (LOOP and LOOP.anim==SPR2_ROLL or GS.spinstate) and S_PLAY_ROLL or S_PLAY_JUMP
										p.pflags = $|PF_JUMPED|PF_JUMPDOWN
									end
								end
							end
						end	
						if ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
						and (p.cmd.forwardmove > 35) --Side camera!
						and (s.momx or s.momy) and not (p.pflags & PF_SPINNING)
							s.angle = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
							p.cmd.angleturn = s.angle/FRACUNIT
							if GS.railcam and GS.railcam.valid
								GS.railcam.angle = s.angle
							end
						end
						if JUMPFLAGS 		
							if (JUMPFLAGS & 2097152) or (JUMPFLAGS & 1048576) 
								S_StartSound(s, sfx_s1bb)
								S_StartSoundAtVolume(s, sfx_sprong, 88)
								local TYPE = RAILTYPE[RAIL.LearnRailType(GS.myrail or GS.LastRail)]
								if TYPE and not (TYPE.landSFX==sfx_none) --Clank! You hit a rail!
									local LANDSFX = DF.landSFX if (TYPE.landSFX!=nil) LANDSFX = TYPE.landSFX end
									if not (TYPE.spawnleaves) and not (TYPE.nosparks)
										S_StopSoundByID(s, sfx_gsralg)
										S_StartSoundAtVolume(s, sfx_gsralg, 160) --The heroes experience.
									else
										S_StopSoundByID(s, LANDSFX)
										S_StartSoundAtVolume(s, LANDSFX, 124)
									end
								end	
								for i = 1,5 P_SpawnSkidDust(p, 62<<16) end
								if not (JUMPFLAGS & 2097152) --Super JumpHeight!
									s.momz = $+(10*SCALE*P_MobjFlip(s))
								else --Mega jumpheight! Stackable with Super for Titan jumpheight!
									s.momz = $+(((JUMPFLAGS & 1048576) and 30 or 20)*SCALE*P_MobjFlip(s))
									S_StartSoundAtVolume(s, sfx_mswing, 210)
								end
							end		
						end
						
					elseif (SIDEHOP!=0) and (CANTSIDEHOP!=true) --Make rail switching leaps! 
						local rail = GS.myrail or GS.LastRail
						local CONVEYOR = 0 if rail and rail.GSconveyor CONVEYOR = rail.GSconveyor<<16 end
						local MOMANG = p.drawangle
					/*	if ((GS.railspeed or 0)+CONVEYOR < 0) and rail
							MOMANG  = RAIL.ANG(rail)
						else
							MOMANG  = RAIL.ANG(rail)+ANGLE_180
						end */
						
						
					--	print("Sidehop:\x83 "+SIDEHOP+"\x80 CONVEYOR: \x8c"+CONVEYOR/FRACUNIT+"\x80 GS.reversing:\x84"+GS.reversing)
						SIDEHOP = $*GS.invertinput --Quick and efficient, but you'll still want to use your brain and aim.
						
						local RESULT
						if SKIN and SKIN["CustomSideHop"] 
							RESULT = SKIN["CustomSideHop"](p, s, GS, rail or GS.LastRail, SIDEHOP, RAMPJUMP)
							if RESULT==true return end
						end
						local ZDELTA = GS.myrail and GS.myrail.GSrailZDelta or 0
						GS.preswitchspeed,GS.specialflip = GS.railspeed, nil --Save our previous speed!					

						local HOPSPEED = 35
						local SCALE = max(FRACUNIT/4, s.scale)
						if STATS
							if STATS.SIDEHOP==0 --Character can't sidehop!
								return --
							elseif (STATS.SIDEHOP)
								HOPSPEED = STATS.SIDEHOP+((HANG==true) and 2 or 0)
								if STATS.SIDEFLIP==7777
									if p.yusonictable p.yusonictable.noairdrag2 = min(p.yusonictable.noairdrag2 or 0, 10) end
									if GS.railtossflag or (GS.railWEAPPREV and GS.railWEAPNEXT)
										GS.specialflip = 13
										HOPSPEED = max(26, $-15)
									end
								end
							end
						end
						if HANG==true HOPSPEED = max($+5, 29) end	
						
						RAIL.ExitRail(s, true, "sidehop")--Get off the rail, using our current momentum.
						P_DoJump(p, true) --Jump.
						
						if RESULT!="nostretch"
							s.spriteyoffset = 9*FRACUNIT --Animation.
							s.spritexscale,s.spriteyscale = FRACUNIT+16000, FRACUNIT-8000
							GS.stretch = {timer = 4, Xspeed = 6000, Yspeed = 2800, air = true, OFFspeed = 2*FRACUNIT}
							if RESULT=="hanghop" or RESULT=="hangflip"
								local TIME = 13 if STATS and STATS.SIDEHOP TIME = STATS.SIDEHOP+1 end
								GS.hanghop = TIME
							end
						end
						if not (CONVEYOR)
							if (s.momx or s.momy)
								MOMANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
							end
							if GS.reversing==-1 MOMANG = $+ANGLE_180 end
						end
						GS.switchCAMang = p.cmd.angleturn
						p.drawangle = MOMANG

						local THRUST = HOPSPEED*SCALE*-SIDEHOP
						if GS.preswitchspeed
							local PRES = abs(GS.preswitchspeed)
							if (PRES > 16*SCALE) --Can gain slightly stronger sidehops as your speed increases.
								local ADD = PRES/(16*SCALE)
								THRUST = FixedDiv(FixedMul($, 100+ADD), 100)
							end
						end
						if not (abs(s.momx)+abs(s.momy) > max(38, HOPSPEED+1)*s.scale)
							P_Thrust(s, MOMANG-(ANGLE_90*SIDEHOP), abs(THRUST))
						else
							P_Thrust(s, MOMANG-(ANG2*(53*SIDEHOP)), abs(THRUST))
						end
						
						s.momz,s.pmomz = 1,0 --Only slope momentum!
						GS.railswitching = (STATS and STATS.SIDEHOPTIME) and STATS.SIDEHOPTIME or 9
						if GS.railcam and GS.railcam.valid
							GS.docam = max(GS.docam or 0, max(1, GS.railswitching-(GS.specialflip and 1 or 6)))
						end
						GS.justtouchedrailthing = max($, 2)
						GS.cannotgrind =  (HOPSPEED>50) and 1 or (HOPSPEED<20) and 5 or (HOPSPEED<35) and 3 or 2
						if (abs(ZDELTA or 0) > ANG10)
							GS.cannotgrind = $+((abs(ZDELTA) > ANG20) and 2 or 1)
						end
						
						GS.railswitchANG = p.drawangle
						p.powers[pw_nocontrol] = min($,2)
						S_StartSound(s, sfx_gsral9)
						if (HANG!=true)
							if GS.specialflip --Hyper-specialized flip!
								s.momz = $+(8*P_MobjFlip(s)*s.scale)
								GS.railswitching = max($, 13)
							elseif STATS and STATS.SIDEHOPARC 
								s.momz = $+(STATS.SIDEHOPARC*P_MobjFlip(s)*(s.scale/3))
							end
						end
						if RAIL.GrindState(s, "any") and s.health s.state = S_PLAY_JUMP end	
						p.pflags = $|PF_JUMPDOWN|PF_USEDOWN //|PF_SHIELDABILITY	
						if (s.scale < 50000)
							GS.cannotgrind = $+(5-(s.scale/10000)) --So they don't get stuck on the same rails for eternities.
						end
					end
				elseif p.GSRO and p.GSRO.HUDaid and (abs(SIDE) > 44) and (abs(FORWARD) < 18) and (HOPINPUT!=2)
					if not (LOOP and LOOP.sidehop==false) and not (STATS and STATS.SIDEHOP==0)
					and not (p.powers[pw_nocontrol]) and not GS.flipanim and (GS.TWOD!=true)
					and not ((HOPINPUT==1 or HOPINPUT==4) and not GS.railspin)
					and not (JUMPFLAGS & 131072) and not (rail and rail.GSVertRail)
					and not (s.GSarrow and s.GSarrow.valid and s.GSarrow.arrow)
						local ADDANG = ANG20*((p.cmd.sidemove*GS.invertinput > 0) and 4 or -4)
						RAIL.SpawnArrow(s, p.drawangle+ADDANG, SKINCOLOR_GOLDENROD, 6, ADDANG) --Sidehop tutorial arrow
					end
				end
			end
		end
			
		--Add manual slope momentum to our jumps!
		if not (GS.grinding) or not (GS.myrail and GS.myrail.valid) and not (p.powers[pw_justlaunched]==2)
			if (LOOPFLAGS & 131072) and LOOP
				if not (LOOP.walljump)
					if (VERTRAIL)
						s.momz = $*7/8
						s.momx,s.momy = $*7/6,$*7/6
					else
						s.momz = (-abs($)*3/4)*P_MobjFlip(s)
					end
				end
				s.momx,s.momy = $*4/3,$*4/3
				GS.airdrag = nil
			else
				RAIL.flingjump(s, GS, GRINDING, HANG)
			end
		else --Last possible things to do!
			if (p.charflags & SF_DASHMODE) and GS.grinding
				if ((GS.dashmode or 0) > 105)
					S_StopSoundByID(s, sfx_kc65)
				end
				if (GS.grinding > 5) and ((GS.grinding%2==0) or (GS.grinding%3==0))
					if ((GS.dashmode or 0) <= 105) --Even when grinding, you can burst into Dash Mode with alot of speed.
						local NEEDSPEED = DFSTATS.AUTODASHMODE*s.scale
						if STATS and (STATS.AUTODASHMODE!=nil)
							NEEDSPEED = STATS.AUTODASHMODE*s.scale
						end
						if (NEEDSPEED) and (abs(GS.railspeed) > NEEDSPEED) --Burst into dash mode!
							S_StartSound(s, sfx_kc4d)
							p.dashmode = max(p.dashmode or 0, 108)
							GS.dashmode = max(GS.dashmode or 0, p.dashmode)
							local NORMALSPEED = skins[s.skin].normalspeed or 35<<16
							GS.normalspeed = max(p.normalspeed, max(GS.normalspeed or 0, NORMALSPEED*2))
						end
					elseif GS.normalspeed
						RAIL.DashModeOnRails(p,s,GS,rail) --Quick recreation of dash mode effect. Don't care enough for perfect accuracy	
						if (abs(GS.railspeed) < 22*s.scale) --Oops, you lost it!
							p.dashmode,GS.dashmode,GS.normalspeed = 0, nil,nil
							p.normalspeed = skins[s.skin].normalspeed
						end
					end
				end
			end
		end
		
	else --if not GS.grinding
	
		if GS.flipanim or GS.flip
			GS.flipanim,GS.flip,GS.flipanimCD = nil
			s.mirrored = false
			s.spriteyoffset = 0
		end
		--Are we railswitching? 	
		if GS.railswitching
			GS.ANGswap,GS.JumpRamp = nil
			GS.railswitching = max($-1, 0)
			if not (GS.cannotgrind > 4)
				GS.cannotgrind = min($, 4)
			end
			GS.justtouchedrailthing = max($, 2)
			RAIL.RailCam(p,s, GS, STATS) --Always access railcam here.
			
			if GS.railswitching --If it's still here, then...
				if GS.railswitchANG != nil and (GS.TWOD!=true)
					p.drawangle = GS.railswitchANG	
					if p.GSRO and ((p.GSRO.cam_autoturnspeed or 0) > 25)
						s.angle = GS.railswitchANG	
					end	
					p.cmd.angleturn = GS.railswitchANG>>16
				end
				
				local SPAWNLINES,SPAWNGHOSTS = true,true
				--Do a sideflip? Add an arc to the sidehop?
				if STATS 
					if STATS.NOSIDEHOPGFX
						if STATS.NOSIDEHOPGFX==1 or STATS.NOSIDEHOPGFX==3
							SPAWNLINES = false
						end
						 if STATS.NOSIDEHOPGFX==2 or STATS.NOSIDEHOPGFX==3
							SPAWNGHOSTS = false
						end
					end
					if (p.pflags & (PF_NOJUMPDAMAGE|PF_JUMPED))
						if (GS.railswitching < 6)
							local TMOMZ = s.momz*P_MobjFlip(s)
							if (TMOMZ > -8*s.scale)
								local MIN = 0
								if GS.specialflip and (STATS.SIDEFLIP==7777)
									MIN = 2*s.scale
								elseif STATS.SIDEHOPARC
									MIN = STATS.SIDEHOPARC*(s.scale/((STATS.SIDEHOPARC>17) and 14 or 11))
								end					
								if (MIN)
									if (TMOMZ < -5*s.scale) or GS.railswitching==5
										MIN = (TMOMZ < -10*s.scale) and $/2 or (TMOMZ < -8*s.scale) and $*2/3 or $*3/4
									end
									s.momz = $-(MIN*P_MobjFlip(s))
								end
							end
						end
						if (STATS.SIDEFLIP) and not ((STATS.SIDEFLIP==7777) and not GS.specialflip)
							GS.animcall = "sideflip"
							if not (GS.railswitching > 6)
								SPAWNLINES = false
							end
						end
					end
				end

				local MOMANG = p.drawangle if s.momx or s.momy MOMANG=R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) end
				if SPAWNLINES==true
					RAIL.SpeedLines(s, GS, MOMANG, "leap")
				end

				local ghs
				for i = 1,2
					if (GS.railswitching > 4) or i==2 
						if SPAWNGHOSTS==true
							ghs = P_SpawnGhostMobj(s) --Sidehop after effects.
							P_SetObjectMomZ(ghs, 4<<16, true)
							
							ghs.scale = (s.scale*(9+min(0, GS.railswitching)))/10
							ghs.renderflags = $|RF_SEMIBRIGHT
							ghs.color,ghs.colorized = s.color or SKINCOLOR_YELLOW,true
							ghs.blendmode = AST_ADD
							ghs.fuse,ghs.tics = 7,8
							ghs.dispoffset = 77
							ghs.destscale = 1
							ghs.frame = ($ & ~FF_TRANSMASK) | min(7, max(1, 10-GS.railswitching))<<16
						end
						if i==2
							if SPAWNGHOSTS==true
								ghs.momx,ghs.momy = s.momx/-3,s.momy/-3
							end
							break --
						elseif SPAWNLINES==true
							RAIL.SpeedLines(s, GS, MOMANG+ANG20)			
						end
					end
					
					if s.health --Keep the right animation during all this!
						if not (p.pflags & PF_THOKKED)
							if p.panim==PA_ROLL or p.panim==PA_JUMP or p.panim==PA_SPRING
								if not (STATS and STATS.SIDEFLIP and (GS.railswitching > 1))
								or (STATS and STATS.SIDEFLIP==7777)
							--	(STATS.SIDEFLIP==SPR2_ROLL or STATS.SIDEFLIP==SPR2_JUMP or STATS.SIDEFLIP==SPR2_SPRING))
									s.state = S_PLAY_FALL
								end
							end
							if p.yusonictable p.yusonictable.sa2jumpdone = true end
						elseif p.panim==PA_FALL and (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE|PF_STARTJUMP)) 
							if (p.charability==CA_THOK or p.charability==CA_HOMINGTHOK) and not (p.charflags & SF_NOJUMPSPIN)
								s.state = S_PLAY_ROLL
							end
						end
					end
					if ghs or SPAWNLINES==false break end --
				end
			else
				if STATS and (STATS.SIDEFLIP) or GS.hanghop
					if not GS.JumpRamp s.rollangle = 0 end
				end
				if (p.charability==CA_THOK) or (p.charability==CA_HOMINGTHOK)
					if p.panim==PA_FALL and (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE))
					and not (p.pflags & PF_THOKKED) and not (p.charflags & SF_NOJUMPSPIN) and s.health
						s.state = S_PLAY_ROLL --Recurl when thokking or ready to thok!
					end
				end
			end
			
		else --if not GS.railswitching
		
			GS.ANGswap,GS.preswitchspeed,GS.specialflip = nil
			if GS.aimingreset and not (p.awayviewmobj and p.awayviewmobj.valid) --Gradually set p.aiming back to neutral! 
				if GS.OGAIM!=nil
					GS.aimingreset = 0
				else
					GS.aimingreset = max($-1,0)
					if not GS.aimingreset
						p.aiming = 0
					else
						local ABAIM = abs(p.aiming)
						if (ABAIM > ANG60)
							p.aiming = ($<0) and -ANG60 or ANG60
						elseif (ABAIM >= ANG10)
							p.aiming = (ABAIM < ANG20) and $*4/5 or (ABAIM < ANG20*2) and $*3/4 or $*2/3
						else
							p.aiming = $/2
						end
					end
				end
			end
			if GS.JumpRamp --Doing an SA2-style trick off a ramp!
				GS.JumpRamp.timer, GS.JumpRamp.init = $-1, $+1 
				local RAMP,INIT = GS.JumpRamp.timer,GS.JumpRamp.init
				if (INIT < 24) --Jump & cam properties.
					GS.airdrag = max(GS.airdrag or 0, 4)
					GS.aimingreset = max(GS.aimingreset or 0, 12)
					if (INIT < 15)
						if GS.OGAIM==nil
							if not (
							p.solcam and mapheaderinfo[gamemap].solcam=="on"
							and not (p.solcam.mode=="default" and not p.solcam.activetimer and not p.solcam.transitionrem)
							)						
								if (s.eflags & MFE_VERTICALFLIP)
									p.aiming = min($, -ANG20)
								else
									p.aiming = max($, ANG20)
								end
							end
						end
						if GS.JumpRamp.ropesnap
							GS.docam = 0
						else
							GS.docam = max(GS.docam or 0, 3)
						end
						p.pflags = $|PF_USEDOWN|PF_JUMPDOWN & ~PF_STARTJUMP	
						if not P_IsObjectOnGround(s)
							p.pflags = $|PF_THOKKED //PF_SHIELDABILITY|
						end
					end
				end
				if not RAMP or P_IsObjectOnGround(s) --Cancel conditions.
				or (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_GLIDING|PF_SLIDING))
				or (p.panim==PA_PAIN) or not s.health or p.powers[pw_carry] or (s.eflags & MFE_SPRUNG) or not (s.health)
					if (s.momx or s.momy) and (p.panim!=PA_PAIN) and (GS.TWOD!=true) and not GS.JumpRamp.ropesnap
						p.drawangle = R_PointToAngle(s.x, s.y, s.x+s.momx, s.y+s.momy)	
					end
					GS.JumpRamp,s.rollangle,GS.FrontiersLeap = nil,0,nil
					if (p.pflags & PF_JUMPED|PF_NOJUMPDAMAGE) and s.health
						p.pflags = $ & ~(PF_SHIELDABILITY|PF_THOKKED) 
						if (s.state==S_PLAY_SPRING or s.state==S_PLAY_FALL) and not P_IsObjectOnGround(s)
							s.state = S_PLAY_JUMP
						end
					end
				else --Actual trick.
					local ADDROLL = (INIT<6) and ANG2 or (INIT<12) and ANG1*6 or (INIT>30) and ANG1*16 or ANG1*12
					if (RAMP < 39)
						s.state = (RAMP > 35) and S_PLAY_RIDE or (RAMP < 5) and S_PLAY_FALL or S_PLAY_SPRING
						if (RAMP < 16)
							ADDROLL = (RAMP < 10) and $/2 or (RAMP < 14) and $*2/3 or $*3/4					
						elseif (RAMP > 20)
							ADDROLL = (RAMP<22 or RAMP>36) and ANGLE_22h or (RAMP > 31) and ANG1*33 or ANGLE_45
						end
					else
						if (INIT > 26) ADDROLL = $*3/2 end
						s.state = (INIT > 10) and S_PLAY_RIDE or S_PLAY_SPRING	
					end
					if (INIT < 16)
						if (INIT < 10) --Little sparkles to indicate it's happening?
							local ghs = P_SpawnMobjFromMobj(s, 0, 0, s.height/2, MT_IVSP)
							ghs.mirrored = (leveltime%3==0) and true or false
							ghs.color = (leveltime%4==0) and s.color or (leveltime%2==0) and SKINCOLOR_SUPERSKY4 or SKINCOLOR_GOLDENROD
							ghs.colorized = true
							ghs.scalespeed = s.scale/64
							ghs.destscale,ghs.fuse = 1,48
							ghs.momx,ghs.momy,ghs.momz = s.momx/2,s.momy/2,s.momz/2
							ghs.flags = $|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOBLOCKMAP						
							if INIT==1 --Starting leap!
								GS.rollangle,GS.animcall,GS.failroll,GS.hangswing = 0,nil
								if (p==displayplayer) P_StartQuake(46<<16, 4) end
								S_StartSoundAtVolume(s, sfx_mswing, 99)
								S_StartSound(ghs, sfx_hoop2)
								p.pflags = $|PF_THOKKED & ~PF_STARTJUMP //PF_SHIELDABILITY|
								if (s.momx or s.momy) and (GS.TWOD!=true) --Face momentum angle, duh.
									GS.JumpRamp.STARTANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
								end
								local TMOMZ = s.momz*P_MobjFlip(s)
								if (TMOMZ  < 64*s.scale)
									s.momz = $+(7*s.scale*P_MobjFlip(s))
									TMOMZ = s.momz*P_MobjFlip(s)
									if (TMOMZ  < 22*s.scale) --STILL tiny?? Okay, but put a limit on it!
										s.momz = FixedDiv(FixedMul($, 10), 9)+s.scale
									else
										S_StartSound(ghs, sfx_hoop3)
										if (TMOMZ > 48*s.scale) S_StartSoundAtVolume(s, sfx_nxbump, 210) end
									end
								end
								if (p.speed > 24*s.scale) and (TMOMZ > 32*s.scale) --Trade some speed for height!
									s.momx,s.momy = $*6/7,$*6/7
								end
							end
						end
						if (INIT%2==0)
							local ghs = P_SpawnGhostMobj(s)
							ghs.scale = (INIT%4==0) and s.scale or s.scale*5/4
							ghs.destscale = (INIT%4==0) and 1 or s.scale/2
							ghs.fuse,ghs.tics = 9,9
							ghs.blendmode = AST_ADD
							ghs.rollangle = s.rollangle
							ghs.color,ghs.colorized = (INIT%4==0) and SKINCOLOR_GOLDENROD or SKINCOLOR_SKY,true					
						end	
					end			
					GS.JumpRamp.rollangle = $-ADDROLL
					GS.JumpRamp.ANG = $-(ADDROLL*2)	
					
					if (RAMP < 17) --Lock player angle and maybe also camera angle
						p.drawangle = GS.JumpRamp.STARTANG
					else
						if (GS.TWOD!=true) and not GS.JumpRamp.ropesnap
							s.angle = GS.JumpRamp.STARTANG
							p.cmd.angleturn = GS.JumpRamp.STARTANG>>16
						end	
						p.drawangle = GS.JumpRamp.STARTANG+(((RAMP >= 8) and (GS.TWOD!=true)) and GS.JumpRamp.ANG-ANG60 or 0)
					end
					s.rollangle = GS.JumpRamp.rollangle
				--	print("RAMP:\x83"+RAMP+"\x80 INIT:\x82"+INIT+"\x80 ROLL:\x82"+GS.JumpRamp.rollangle/ANG1)
				end
				
			elseif GS.FrontiersLeap --Sonic Frontiers style jump time?
				local DIRECT = max(abs(p.cmd.sidemove), abs(p.cmd.forwardmove))
				if P_IsObjectOnGround(s) or (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_GLIDING|PF_SLIDING|PF_THOKKED))
				or (DIRECT < 5) and not p.powers[pw_nocontrol] or (s.eflags & MFE_SPRUNG)
				or p.powers[pw_carry] or p.panim==PA_PAIN or p.homing or (RAIL.busystates(s))
					GS.FrontiersLeap = nil
				else
					GS.FrontiersLeap,GS.airdrag = $-1,nil
					if (DIRECT > 30) and (p.cmd.buttons & BT_JUMP)
					and (p.speed > 21*s.scale) and (s.momz*P_MobjFlip(s) > s.scale) 
						if (p.speed <= 64*s.scale) and not (s.flags2&MF2_TWOD)
						and not (twodlevel) --Let's give some minor speeding-up potential
							P_Thrust(s,(p.cmd.angleturn<<16)+(R_PointToAngle2(0,0,p.cmd.forwardmove<<16,-(p.cmd.sidemove<<16))), DIRECT*(s.scale/48))
						end
						if (GS.FrontiersLeap > 3)
							local ghs = P_SpawnMobjFromMobj(s, 0, 0, -s.momz/2, MT_IVSP)
							ghs.mirrored = (leveltime%3==0) and true or false
							ghs.color = (leveltime%4==0) and s.color or (leveltime%2==0) and SKINCOLOR_SUPERSKY4 or SKINCOLOR_GOLDENROD
							ghs.colorized = true
							ghs.scalespeed,ghs.destscale = s.scale/64,1
							ghs.momx,ghs.momy = s.momx/3,s.momy/3
							P_Thrust(ghs, (leveltime*ANGLE_45), 48*s.scale)
							ghs.fuse = 33
							ghs.flags = $|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOBLOCKMAP
						end
					end
				end
			end
			if GS.airdrag --Since we're emulating Sonic Adventure, add a bit of airdrag after jumping off rails
				if P_IsObjectOnGround(s) or (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_GLIDING|PF_SLIDING|PF_THOKKED))
				or (p.isxmomentum and not p.noxmomchar) or p.xsonictable and ((p.xsonictable.darkphase or 0)>1)
				or p.powers[pw_carry] or (p.panim>=PA_ABILITY) or p.panim==PA_PAIN or p.homing or (RAIL.busystates(s))
				or (STATS and STATS.AIRDRAG==0) or (s.eflags & MFE_SPRUNG)
					GS.airdrag = nil
				else
					local SPEED = (((GS.JumpRamp or 0) > 30) and 39 or 51)*s.scale
					if (p.speed > SPEED)
						if (p.speed > (SPEED*24/15))
							s.momx = FixedDiv(FixedMul($,98),99)
							s.momy = FixedDiv(FixedMul($,98),99)				
						else
							s.momx = FixedDiv(FixedMul($,204),205)
							s.momy = FixedDiv(FixedMul($,204),205)
						end
						GS.airdrag = max($-1,0)
					else
						GS.airdrag = max($-3,0)
					end
				end
			end
			if GS.docam --Keep using the rail camera during all this?
				RAIL.RailCam(p,s, GS)
			end		
		end	
		--Determine if we're in a state capable of landing on rails.	
		if p.powers[pw_carry]
		or ((p.homing or 0) > 92) and not (s.eflags & MFE_SPRUNG) and not (s.tracer and s.tracer.GSrailobject)
		or RAIL.busystates(s)
	--	or p.panim==PA_ABILITY and (p.charability==CA_FLY or (p.pflags & PF_GLIDING))
			GS.cannotgrind = max($,2) --NEVER grind like this!
			if p.powers[pw_carry]==CR_GRINDRAIL --3888
				p.powers[pw_carry] = 0
			end	
			if GS.railswitching
				if RAIL.busystates(s)
				or p.panim==PA_ABILITY and (p.charability==CA_FLY or (p.pflags & PF_GLIDING))
					GS.railswitching = 0 --Oops!
					if GS.animcall=="sideflip"
						GS.animcall = nil
					end
				end
			end
		else --Good to go! Count down!
			GS.cannotgrind = max($-1, 0)
		end	
		if GS.crouchgrind --Reset spriteyscale.
			GS.crouchgrind = false
			s.spriteyscale,s.spritexscale = FRACUNIT,FRACUNIT
		end
	end
	if GS.OGAIM!=nil
		if not (LOOPFLAGS & (4194304|524288|262144))
		and not (GRINDFLAGS & 131072) and not (LOOP and not LOOP.walljump)
			p.aiming = GS.OGAIM
			if p.awayviewmobj==GS.railcam and GS.railcam and GS.railcam.valid
				p.awayviewaiming = GS.OGAIM
			end
		end
		GS.OGAIM = nil
	end		
	if GS.nocam --Disable custom camera for a while.
		GS.nocam = $-1
	end
	if GS.docam --Enable custom camera for a while. Nocam takes priority.
		GS.docam = $-1
	end
end

--Actual behaviour for player tilting when GS.railtilt is not nil.
--An extension of the playerthinker, so I'll keep it here instead of in MOBJFUNC.
RAIL.PlayerTilt = function(p,s,rail, GS, STATS, GRINDFLAGS, LOOPFLAGS, BALANCING)
	if (GS.TWOD==true) --No balancing in 2D, naturally.
	or GS.leftrail --Not even on a rail!
	or (GRINDFLAGS & 131072) --Disabled controls	
	or ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304)) --Sidecam
		GS.ANGswap,GS.camadd = nil
		GS.railtilt,GS.perfectcorner,GS.badbalance = 0,0,false
		if (GS.TWOD==true) or GS.leftrail
			GS.railfalltimer = 0
		end
		return false --
	elseif (LOOPFLAGS & 131072) --Upsidedown loop
		GS.railtilt,GS.perfectcorner,GS.badbalance = 0,0,false
	end
	local HANG = (RAIL.GrindState(s, "hang")) and true or false
	local SIDE,FORWARD = GS.side or 0, GS.forward or 0	
	local LOOP = RAIL.LearnLoopType(GS.myrail)
	local AUTOBALANCE = (STATS and STATS.AUTOBALANCE==true) and true or false
	if p.powers[pw_super] or p.hypermysticsonic
	or p.solchar and p.solchar.istransformed or p.xsonictable and p.xsonictable.darkphase==3
	or GRINDFLAGS and (GRINDFLAGS & (262144|524288)) --Controls not disabled, and momentum allowed?
		AUTOBALANCE = "super"
		BALANCING = false
	end
	--When you swap rails, balancing comes into play!
	if GS.ANGswap
		local SWAP = GS.ANGswap
		if not SWAP.timer or not (SWAP.ANG) or (SWAP.ANG < 0)
			GS.ANGswap = nil
		else
			SWAP.init = $+1
			SWAP.timer = max($-1,0)
			
			if (SWAP.ANG > ANG1*89) SWAP.ANG=ANG1*89 end
			SWAP.ANG = ($ < ANGLE_45) and $*3/4 or $*2/3
			
			local ADD = SWAP.ANG/ ((ANG1/11)*SWAP.PLUS)
			if ADD
				if (SWAP.timer > 8)
					if GS.camadd==nil GS.camadd=0 end 
					local DIV = 64
					if GS.ANGswap.consec
						DIV = $-min(61, (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1)) and GS.ANGswap.consec*7 or GS.ANGswap.consec*3)
					end
					local ADDCAM = (SWAP.ANG/DIV)*(SWAP.PLUS)
					if (SWAP.timer < 11) or (SWAP.init < 4)
						ADDCAM = ((ADD<0) and (GS.camadd>0) or (ADD<0) and (GS.camadd<0)) and $*2 or $
					else
						ADDCAM = ((ADD<0) and (GS.camadd>0) or (ADD<0) and (GS.camadd<0)) and $*4 or $*2
					end
					if (ADDCAM < 0)
						GS.camadd = max(-ADDCAM, min(ANGLE_22h, $-ADDCAM))
					else
						GS.camadd = min(-ADDCAM, max(-ANGLE_22h, $-ADDCAM))
					end
				end
				
				if HANG==true
					GS.hangswing = ANG1/3 --Prep this a bit.
					SWAP.ANG = $*2/3 --AGAIN!
					GS.railtilt = (GS.railtilt)+
					((abs(GS.railspeed or 0)>32*s.scale) and ADD*5 or (abs(GS.railspeed or 0)>32*s.scale) and ADD*4 or ADD*3)
					
					GS.badbalance,GS.railfalltimer = false,0	
					if (abs(GS.railtilt) >= 800)
						SWAP.timer = min(18, max($-6,0))
						GS.railtilt = ($ < 0) and -800 or 800
					else
						SWAP.timer = min(22, max($-2,0))
					end
				else
					local MAXROLL = (BALANCING==true or GS.crouchgrind) and 800 or 300
					if SIDE --You can save yourself by balancing.
						SIDE = $*GS.invertinput
						local MINS = -SIDE*2*(SWAP.PLUS or 1)
						if (MINS < 0)
							MAXROLL = 740
							if not (abs(SIDE) > 20)
								if SWAP.init==1
									GS.perfectcorner = max(GS.perfectcorner or 0, 2)
								end
							elseif not GS.flipanim --Gutsyness is rewarded!
								if SWAP.init==1 and GS.crouchbutton
									GS.perfectcorner = 7
								end
								if GS.crouchgrind or BALANCING==true
									if (abs(GS.railtilt) > 270) --Recover a bit!
										GS.railtilt = ($<0) and $+11 or $-11
									elseif GS.railtilt
										GS.railtilt = ($<0) and $+1 or $-1
									end
									if SWAP.init==1 and GS.railspeed and GS.LastGrindZ!=nil and (abs(GS.railtilt) < 464)
									and GS.crouchgrind
										local RLSPEED = abs(GS.railspeed)/s.scale
										if (RLSPEED < 280)
											if (RLSPEED > 120)
												local MAXB = RLSPEED
												GS.railspeed = FixedDiv(FixedMul($, MAXB), MAXB-1) --Perfect cornering!
											else
												if (RLSPEED < 42)
													GS.railspeed = ($<0) and $-2*s.scale or $+2*s.scale
												end
												GS.railspeed = FixedDiv(FixedMul($, 42), 41) 
											end
										end
										S_StopSoundByID(s, sfx_s1bc) S_StartSoundAtVolume(s, sfx_s1bc, 88)
										local ghs
										for i = 1,3
											ghs = P_SpawnMobjFromMobj(s,
											(i!=1) and FixedMul(-i*3*s.scale, cos(p.drawangle)) or 0,
											(i!=1) and FixedMul(-i*3*s.scale, sin(p.drawangle)) or 0, 0, MT_THOK)
											ghs.scale = (s.scale*6)/(i+4)
											ghs.fuse,ghs.tics = 7,9
											ghs.destscale = 99<<16
											ghs.scalespeed = s.scale/(5+i)
											ghs.spriteyoffset = -(11-(i*2))<<16
											ghs.blendmode = AST_ADD
											ghs.sprite = SPR_STAB
											ghs.frame = A|FF_PAPERSPRITE|FF_TRANS80
											ghs.angle = p.drawangle - (ANG10*(7-i))*(SWAP.PLUS or 1)
											ghs.flags = $|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING
											ghs.color,ghs.colorized = s.color,true
											if i != 1
												P_Thrust(ghs, p.drawangle-(ANG10*i)*(SWAP.PLUS or 1), -5*s.scale*(i*5))
											end
										end
									end
								end
							end
							ADD = max(0, $+MINS)
						end
					end
					if SWAP.init==1 and BALANCING==true and (MAXROLL != 740) and GS.railspeed and not GS.flipanim
						S_StopSoundByID(s, sfx_s1c0) S_StopSoundByID(s, sfx_s1bc) S_StartSoundAtVolume(s, sfx_s1c0, 130)
						GS.railspeed = FixedDiv(FixedMul($, 26), 27) --Bad cornering is punished!
						P_SpawnMobjFromMobj(s, 0, 0, s.height/3, MT_EXPLODE)
						for i = 1,3 P_SpawnSkidDust(p, 64<<16) end
					end
					if ADD
						GS.railtilt = $-ADD
						if (abs(GS.railtilt) > MAXROLL)
							GS.railtilt = ($ < 0) and -MAXROLL or MAXROLL
						end
					end				
				end
			end
			if GS.camadd and (SWAP.timer <= 8)
				if (SWAP.timer > 6) or (SWAP.timer < 3) or (LOOP)
					GS.camadd = ($<0) and min(0, $+ANG1) or ($>0) and max(0, $-ANG1) or 0	
				else
					GS.camadd = ($<0) and min(0, $+ANG2) or ($>0) and max(0, $-ANG2) or 0	
				end
			end
		--	print("\x80"+"SWAP.ANG added this tic:\x82"+ADD+"\x80 GS.railtilt is now:\x8a"+GS.railtilt+"\x80 Timer:\x8d"+SWAP.timer)
		end
	elseif GS.camadd
		local ANGX = (LOOP) and ANG2 or ANG1
		if GS.FreeAdd
			ANGX = $/(2+GS.FreeAdd)
			GS.FreeAdd = max($-1,0)
		end
		GS.camadd = ($<0) and min(0, $+ANGX) or ($>0) and max(0, $-ANGX) or 0	
	end
	if (HANG==true) or not GS.railtilt
		if GS.railfalltimer
			GS.railfalltimer = max((GS.railfalltimer>30) and $-6 or $-4, 0)	
		end
		if HANG==true --Swing about while hanging on stuff.
			if GS.rollangle and GS.ANGswap==nil
				if GS.hangswing==nil GS.hangswing = 0 end
				GS.hangswing = min(ANG20, (abs($)>ANG2*3) and $+ANG2 or (abs($)>ANG1*3) and $+ANG1 or (abs($)>ANG1) and $+ANG1/2 or $+(ANG1/3))
				
				--Realistic handswinging
				if (GS.rollangle < 0)
					GS.rollangle = $+GS.hangswing
					if (GS.rollangle >= 0)
						GS.hangswing = ((GS.grinding or 0) < 40) and $/-2 or $/-3
						if (abs(GS.hangswing) < ((abs(GS.rollangle) < ANG2) and (ANG1*3) or ANG1))
							GS.hangswing,GS.rollangle,GS.railtilt = 0,0,0
						end
					end
				else
					GS.rollangle = $-GS.hangswing
					if (GS.rollangle <= 0)
						GS.hangswing = ((GS.grinding or 0) < 40) and $/-2 or $/-3
						if (abs(GS.hangswing) < ((abs(GS.rollangle) < ANG2) and (ANG1*3) or ANG1))
							GS.hangswing,GS.rollangle,GS.railtilt = 0,0,0
						end
					end
				end
			else
				GS.hangswing = 0
				if GS.ANGswap==nil
					if GS.railtilt
						GS.railtilt,GS.hangswing = 0,0
					elseif ((GS.grinding or 0) < 12)
						GS.rollangle = ((SIDE>0) and -12 or (SIDE<0) and 12 or P_RandomRange(0,1)==1 and 12 or -12)*ANG1
						GS.hangswing = abs(GS.rollangle)
					end
				end
			end
			s.rollangle = GS.rollangle or 0
		--	print("HANGSWING:\x82"+(GS.hangswing/(ANG1))+"\x80 GS.rollangle:\x84"+GS.rollangle/ANG1+" lvltime:"+leveltime)
		end
	else
		--Tilting on rails! Mostly used for visual flair, but for characters with grinding sprites it can impact their speed!
		if (abs(GS.railtilt or 0) <= ((BALANCING==true or GS.crouchgrind) and 301 or 351)) 
	--		print("\x85 1"+abs(GS.railtilt))
	
			GS.railtilt = ($*7)/8 --We're not tilting much, so slowly auto-restabilize.
			GS.railfalltimer = (AUTOBALANCE!=false) and max($-2, 0) or 0
			if (abs(GS.railtilt) < 10) and (abs(SIDE) < 10)
				GS.railtilt = 0
			end

		else --Crap, crap, you'll fall at this rate!
			if AUTOBALANCE!=false
				GS.badbalance = false
				GS.railtilt = $*50/51
				GS.railfalltimer = 0
			else
			--	print("\x82 2"+abs(GS.railtilt))
				if (GS.railfalltimer > 30)
					if (leveltime%2)
						GS.railfalltimer = min($+1, 42)
					end
				elseif (GS.grinding > 8) and not (leveltime%3==0) 
					GS.railfalltimer = $+1
				end
				GS.badbalance = true
	
				if (GS.railfalltimer > 13)
					RAIL.FailDust(s, SKINCOLOR_EVENTIDE, 19, s.scale)
					if not S_SoundPlaying(s, sfx_s3kd1s) 
						S_StartSound(s, sfx_s3kd1s) 
					end
					if (leveltime%2==0)
						if (GS.railfalltimer > 25)
							s.flags2 = $|MF2_DONTDRAW
						end
						if (leveltime%4==0)
							for d = 24,12,-12
								if not (abs(GS.railspeed) < d*s.scale) break end --
								GS.railfalltimer = min($+1, 42)
							end
						end
					end
				end

				--Sweatdrops gfx.
				local RNG,RNG2 = P_RandomRange(-30,30),P_RandomRange(-31,31)
				local ghs = P_SpawnMobjFromMobj(s, 
				FixedMul(RNG*s.scale/2, cos(p.drawangle)),
				FixedMul(RNG*s.scale/2, sin(p.drawangle)), s.height+(abs(RNG)*15000), MT_GSRAILGFX)
				
				ghs.sprite = SPR_DRIP
				ghs.shadowscale = 58000
				ghs.frame = D|((leveltime%2) and FF_TRANS30 or FF_TRANS20)|FF_FULLBRIGHT
				ghs.rollangle = GS.rollangle+(RNG2*ANG1)
				ghs.scale = ($*15)/(30+abs(RNG))
				ghs.destscale = s.scale*9
				ghs.scalespeed = s.scale/12
				ghs.color,ghs.colorized = SKINCOLOR_SKY, true
				ghs.fuse,ghs.tics = 16,16

				ghs.sttfade = true
				ghs.GSgrinder = s
				P_SetObjectMomZ(ghs, (abs(RNG)*20000)+(s.z-GS.LastGrindZ), true)
				P_InstaThrust(ghs, p.drawangle+(RNG2*ANG2), (-GS.railspeed or 1)/2)
			end
		end	
	end	
	if GS.railtilt==nil return end 
	
	local MAXROLL = GS.maxroll or 0 
	if (abs(GS.railtilt) >= MAXROLL) MAXROLL = abs(GS.railtilt) end	
	
	--Now that we've determined automatic player tilts, get to the main tilting behaviour.
	local TILT = GS.railtilt
	if (abs(SIDE) > 4) and (abs(FORWARD) < 44) --Balancing!	
		local ADD = ((HANG==true) and SIDE/-2 or SIDE)
		if (GS.perfectcorner) and HANG!=true and ADD
			if (TILT < 0) and (TILT+ADD < TILT)
			or (TILT > 0) and (TILT+ADD > TILT)
				ADD = 0 --Perfect cornering means you can't make things worse for yourself for a moment.
			end
		end
		TILT = $+ADD
	end
	if AUTOBALANCE=="super"
		GS.railtilt = min(max(TILT, -299), 299) --Super Saiyan minmax values!
		GS.badbalance,GS.railfalltimer = false,0
		BALANCING = false
	else
		GS.railtilt = min(max(TILT, -MAXROLL), MAXROLL) --Minmax values!
	end	
	
	--Now set our final rollangle!	
	if not ((HANG==true) and GS.hangswing)
		if GS.badbalance and not (AUTOBALANCE)
			GS.rollangle = -GS.railtilt*(ANG1/15)
			if (leveltime%2==0)
				if (GS.rollangle < 0)
					GS.rollangle = $+ANG2*3
				else
					GS.rollangle = $-ANG2*3
				end
				P_SpawnSkidDust(p, 12<<16)
				if (p == displayplayer) and ((GS.railfalltimer or 0) > 33)
					P_StartQuake(min(800, abs(GS.railtilt))*420, 2)
				end
			end
		else
			GS.rollangle = -GS.railtilt*(ANG1/21) --Be more subtle if you're not in danger. 
		end
	end	
	if GS.rollangle 
		if ( ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
		or (LOOPFLAGS & 524288) or rail.GSVertRailor or GS.ropesnap) and not GS.flipanim
			s.spritexoffset,s.spriteyoffset = 0,0
		else
			local PLUS = (s.mirrored) and -1 or 1
		--	if (LOOPFLAGS & 4194304) 
		--		PLUS = $*-1
		--	end
			if HANG==true
				PLUS = $*-1
			end
			--Adjust xoffset since Lua sprite pivoting has special needs
			s.spritexoffset = max(-60, min(60, GS.rollangle/ANG1))*42000*PLUS
			if not ((GS.flipanim or 0) > 1)
				s.spriteyoffset = -(abs(s.spritexoffset or 1)/2)
			end
		end	
	end
	--Slow down according to your balance!
	if (abs(GS.railtilt) > 70) and (HANG!=true) and GS.railspeed and (AUTOBALANCE!="super")
		GS.balancepenalties = GS.balancepenalties and $+1 or 1
		if (GS.balancepenalties > ((BALANCING==true or GS.crouchgrind) and 4 or 1))
			local SMULT = 5950
			if (abs(GS.railspeed or 0) > 41*s.scale) and not (AUTOBALANCE) and (s.skin!="sms") 
				SMULT = (abs(GS.railspeed) > 70*s.scale) and 5250 or 5575
			end
			if GS.badbalance and not (AUTOBALANCE) --Huge penalty.
				GS.railspeed = FixedDiv( FixedMul($, SMULT), SMULT+abs(GS.railtilt/6) )
			elseif not GS.crouchgrind and not (BALANCING==true) --minor penalty if not GS.crouchgrind
				GS.railspeed = FixedDiv( FixedMul($, SMULT), max(SMULT, SMULT+abs(GS.railtilt/22)) )
			end
		end
	else
		GS.balancepenalties = 0
	end
end