--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════#
--║   	  PLAYER THINKERS      ║ Player Specific Thinkers! usually related to grinding, but not always.
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════#
local RAIL = GS_RAILS if RAIL.blockload return end --
local RAILTYPE = GS_RAILS_TYPES
local DFSTATS = GS_RAILS_SKINS["Default"] --The default character S_SKIN.
local SS2 = SPR2F_SUPER if SS2==nil SS2 = FF_SPR2SUPER or 0 end

--Settings table that carries over between levels.
RAIL.SetupPlayerTable = function(p)
	p.GSRO = {} local RO = p.GSRO
	
	--Default Camera Menu settings.
	RO.cam_vanilla = false
	RO.cam_shift = 10 --Sideways camera shifting amount.
	RO.cam_dist = 192
	RO.cam_height = 40
	RO.cam_turnspeed = 100
	RO.cam_autoturnspeed = 75
	RO.cam_centerspeed = 150
	
	--Default Gameplay Menu settings
	RO.devmode = 0 
	RO.backupgrindstate = SPR2_ROLL
	RO.duncegrindstate = SPR2_EDGE
	RO.SideHold = 0
	RO.HUDaid = 1 
	
	--Notification timer.
	RO.NewbieGrinder = 600
	RAIL.SaveLoad(p, false, true)
end

--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 rail = s.GSgrind and s.GSgrind.myrail or nil
	local UPSIDEDOWN,Z = 1,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
			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

--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 GS.chasecam==false or (STATS and STATS.AUTOVERTICALAIM==false)
			GS.forceVaim = max(GS.forceVaim or 0, 2) --Always apply this!
		end
	elseif GS.chasecam==false
		GS.forceVaim = max(GS.forceVaim or 0, 2)
	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
		if GS.myrail and GS.grinding and ((GS.myrail.GSloopflags or 0) & 33554432) --Force Horizontal cam angle.
			p.aiming,p.awayviewaiming = 0,0
		else
			p.aiming = GS.OGAIM	
		end
		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) and ((p.cmd.buttons & BT_JUMP) or GS.railjump) --Minor boost.
					P_SetObjectMomZ(s, ((abs(s.momz)<7*s.scale) and 7 or 5)<<16, true)
				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)
							if GS.chasecam==false p.cmd.sidemove = 0 end
							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 = max(s.friction or 0, 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 p.scoreadd and (GRINDING%121==0)
				p.scoreadd = max($-1,0) --Gradually lose scorecombo?
			end		
			if SKIN
				if STATS
					if STATS.MINSPEED
					and not (LOOP and LOOP.walljump) and not ((GS.myrail.GSgrindflags or 0) & 524288)
						GS.railminspeed = abs(STATS.MINSPEED)
					end
					if type(STATS.SPEEDTRAIL)=="number" and (STATS.SPEEDTRAIL > 0) and not GS.speedtrail 
					and (abs(GS.railspeed or 0) > STATS.SPEEDTRAIL*s.scale)
						GS.speedtrail = true
					end
				end	
				if SKIN["PreGrindThinker"] and (SKIN["PreGrindThinker"](p, s, GS, GS.myrail))==true return end --
			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|262144|524288|1048576|16777216))
						BALANCING = true --Full-range balancing is active?
					end
				end
			end
			
			s.movefactor = 0
			GS.movedfactor = true
			
			if GS.holdviewroll --If you're a camera mod, set GS.holdviewroll to 2 incase you need a viewrollangle.
				GS.holdviewroll = max($-1,0) --GS.holdviewroll is mostly for other camera mods incase they want to mess with it.
			elseif GS.viewrollangle --Gradually set the camera straight if it's rotated.
				if not (
				(GRINDFLAGS & 524288) 
				or (LOOPFLAGS & (524288|262144|33554432|16777216|8388608|4194304|131072))
				or not (abs(GS.viewrollangle) > ANG1/128)
				or (p.awayviewtics and (p.awayviewmobj!=GS.railcam) and p.awayviewmobj)
				)
					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 
					if not GS.cantcrouch
					and not (GRINDFLAGS & (65536|131072))
					and not (LOOP and (LOOP.anim!=0)) 
					and not (LOOPFLAGS & (131072|262144|524288|1048576|16777216))
					and not rail.GSVertRail
						BALANCING = true --Always balance if your character has "MUSTBALANCE" set to true!
					end
				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))
						and not (GS.myrail and GS.myrail.GSnorolling)
							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
							if GS.spinstate and GS.myrail and GS.myrail.GSnorolling
								GS.spinstate = false
							end
							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
				local CUSTOMHANG = nil
				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
					if STATS and (STATS.FORCEHANGSPRITE)
						CUSTOMHANG = STATS.FORCEHANGSPRITE
					end
					if SKIN["CustomHangAnim"] 
						SKIN["CustomHangAnim"](p, s, GS, GS and GS.myrail)
					end
				end
				if rail.GSforcerailanim
					local ANIM,NUM,DONE = rail.GSforcerailanim, (rail.GSforcerailanim2) and 2 or 1, false
					for d = 1,NUM if DONE==true break end --
						if (d!=1) ANIM = rail.GSforcerailanim2 end
						for i = 1,2
							if type(ANIM)=="number"
								if P_IsValidSprite2(s, ANIM)
									GS.forcesprite,GS.animcall = ANIM, "force" 
									CUSTOMHANG = nil DONE = true
								end break --
							elseif (spr2names[ANIM]) and type(spr2names[ANIM])=="number"	
								if d==2
									rail.GSforcerailanim2 = spr2names[ANIM]
								else
									rail.GSforcerailanim = spr2names[ANIM]
								end
							else
								break --
							end
						end
					end
				end
				if (CUSTOMHANG) and P_IsValidSprite2(s, CUSTOMHANG)
					GS.forcesprite,GS.animcall = CUSTOMHANG, "force"
				end
			else
				if s.health	and (s.state!=S_PLAY_GSGRINDING)
					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 (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)
					and not (rail.GSforcerollangle) --Looks buggy when rollangle is forced.
						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?
						local ANIM,NUM,DONE = rail.GSforcerailanim, (rail.GSforcerailanim2) and 2 or 1, false
						for d = 1,NUM if DONE==true break end --
							if (d!=1) ANIM = rail.GSforcerailanim2 end
							for i = 1,2
								if type(ANIM)=="number"
									if P_IsValidSprite2(s, ANIM)
										GS.forcesprite,GS.animcall = ANIM, "force" 
										DONE = true
									end break --
								elseif (spr2names[ANIM]) and type(spr2names[ANIM])=="number"	
									if (d!=1)
										rail.GSforcerailanim2 = spr2names[ANIM]
									else
										rail.GSforcerailanim = spr2names[ANIM]
									end
								else
									break --
								end
							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_GLIDING|PF_BOUNCING|PF_THOKKED|PF_STARTJUMP|PF_STARTDASH|PF_NOJUMPDAMAGE|PF_SLIDING|PF_SHIELDABILITY|PF_APPLYAUTOBRAKE)
			
			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)
				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<<16)
						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) and not (GS.LastRail and GS.LastRail.GSnorolling)
					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		
			--We're on a sidehop-only 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 now goes both ways.
			end
			--Some debugging for mappers.
			if RO and RO.devmode and (s.eflags & MFE_SPRUNG)
			and not s.GScollide and not (GS.debugE1) and not GS.justtouchedrailthing and ((GS.grinding or 0) > 2)
				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 and not (GRINDFLAGS & 524288) and not (LOOPFLAGS & (524288|262144|33554432|16777216|8388608|4194304|131072))
			and not (p.awayviewtics and (p.awayviewmobj!=GS.railcam) and p.awayviewmobj)
				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
		local VERTRAIL = ((GS.MyRailStats) and GS.MyRailStats.VERTRAIL or (rail) and rail.GSVertRail) or 0
		local HANG =  (RAIL.GrindState(s, "hang")) and true or false

		if SKIN and SKIN["GrindThinker"]
			if SKIN["GrindThinker"](p, s, GS, rail or GS.LastRail) end
		end
		if not GS or not (GS.myrail and GS.myrail.valid) --Aaand the rail is gone. Shit.
			RAIL.ExitRail(s, true, true) return --
		end
		
		--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
			if not (s.health or not p.playerstate==PST_LIVE)
				return --
			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.floorz
				if (s.eflags & MFE_VERTICALFLIP) FLOORZ = P_CeilingzAtPos(s.x, s.y, s.z, 0) else FLOORZ = P_FloorzAtPos(s.x, s.y, s.z, s.height) end
				if (abs(s.z-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<<16, true)
					s.spriteyoffset = -6<<16
					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,s.momy = $/3, $/3
					P_SetObjectMomZ(s, -1<<16)
					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 HANG!=true and GS.animcall=="flipanim" and (GS.flipanim < 8))
			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 if not (rail and rail.valid) rail = GS.LastRail end
					local CANTSIDEHOP = false
					if (JUMPFLAGS & 131072)
					or (LOOPFLAGS & 131072)
					or (GS.TWOD==true)
					or LOOP and (LOOP.sidehop==false or LOOP.walljump)
						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 RVERT = false
						if RSTATS and RSTATS.VERTRAIL
							RVERT = true
						end
						
						local DOWNJUMP = false --Let players jump down if they hold SPIN. I hope this won't kill too many players...
						if HANG==true and not (JUMPFLAGS & 4194304)
							if (JUMPFLAGS & 8388608) 
							or (p.cmd.buttons & BT_SPIN) and not (LOOPFLAGS & 131072)  //and not (JUMPFLAGS & (2097152|1048576))
							and not (LOOP and LOOP.walljump) and not (rail and rail.GSVertRail) and (RAMPJUMP!="frontiers") and (RVERT!=true)
								DOWNJUMP = true
							end
						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 not (rail and rail.valid) and GS.LastRail rail = GS.LastRail end
		--				print("JUMPSTART!")
						
						if (LOOPFLAGS & 131072)
						or RVERT==true or rail and rail.GSVertRail
						or DOWNJUMP==true
		--					print("\x82"+"1")
							GS.railfalltimer,GS.railtilt = 0,0
							if s.health
								if LOOP and LOOP.walljump
									if (s.eflags & MFE_VERTICALFLIP) and (s.momz > -19*s.scale) and (DOWNJUMP!=true)
										s.momz = -19*s.scale
									end
								else
									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 rail and rail.valid and (p.speed < 44*SCALE) and (GS.TWOD!=true)
											P_Thrust(s, R_PointToAngle2(s.x, s.y, rail.x, rail.y), -4*SCALE)
										end	
									else
										if (LOOPFLAGS & 131072) or DOWNJUMP==true
		--									print("Yeeeee...rail = \x82"+rail+"\x80 RSTATS:\x82"+RSTATS)
											if rail and rail.GSVertRail or RVERT==true
		--										print("Do\x83 this one:"+R_PointToAngle2(s.x, s.y, rail.x, rail.y)/ANG1)
												if (p.speed < 24*SCALE) and rail and rail.valid
													P_InstaThrust(s, R_PointToAngle2(s.x, s.y, rail.x, rail.y), -24*SCALE)
												end
											else
												s.momz = -(abs($)+abs(SLOPEFLING))*P_MobjFlip(s)*((DOWNJUMP==true) and 2 or 3)
												if GS.LTAB  
													GS.LTAB.downjump = true
												end
											end
										end
		--								print("Nope.")
										if LOOP and (LOOP.endwithspin!=false) and not (rail and rail.GSnorolling)
											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
						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|1048576))
								if (DOWNJUMP!=true)
									S_StartSound(s, sfx_s1bb)
									S_StartSoundAtVolume(s, sfx_sprong, 88)
									local TYPE = RAILTYPE[RAIL.LearnRailType(rail)]
									if TYPE and not (TYPE.landSFX==sfx_none) --Clank! You hit a rail!
										local LANDSFX = RAILTYPE[0].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		
						end
						
					elseif (SIDEHOP!=0) and (CANTSIDEHOP!=true) --Make rail switching leaps! 
					
						local WASFLIPPING = (GS.flipanim) and true or false
						local rail = GS.myrail if not (rail and rail.valid) rail = GS.LastRail end
						local CONVEYOR = 0 if rail and rail.GSconveyor CONVEYOR = rail.GSconveyor<<16 end
						local MOMANG = p.drawangle	
						local RSTATS = GS.MyRailStats
						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<<16 --Animation.
							s.spritexscale,s.spriteyscale = FRACUNIT+16000, FRACUNIT-8000
							GS.stretch = {timer = 4, Xspeed = 6000, Yspeed = 2800, air = true, OFFspeed = 2<<16}
							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 RSTATS and RSTATS.SIDEHOPFACEANG!=nil and RSTATS.VERTRAIL
								MOMANG = RSTATS.SIDEHOPFACEANG
							elseif (s.momx or s.momy)
								MOMANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
							elseif WASFLIPPING==true
								MOMANG = s.angle
							end
							if GS.reversing==-1 MOMANG = $+ANGLE_180 end
						end
						if p.awayviewtics and p.awayviewmobj==GS.railcam and GS.railcam and GS.railcam.valid and (GS.railcam.angle!=nil)
							GS.switchCAMang = GS.railcam.angle/FRACUNIT
						else
							GS.switchCAMang = p.cmd.angleturn
						end
						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
							if STATS and STATS.SIDEHOPCAMTIME
								GS.docam = max(GS.docam or 0, STATS.SIDEHOPCAMTIME)
							elseif GS.specialflip
								GS.docam = max(GS.docam or 0, max(3, GS.railswitching))
							else
								GS.docam = max(GS.docam or 0, max(1, GS.railswitching-4))
							end
						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
						if RSTATS and RSTATS.SIDEHOPFACEANG!=nil
							GS.railswitchANG = RSTATS.SIDEHOPFACEANG
						else
							GS.railswitchANG = p.drawangle
						end
						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 ANG = p.drawangle 
						if GS.MyRailStats and GS.MyRailStats.SIDEHOPFACEANG!=nil
							if ((GS.MyRailStats.ZSPEED or 0)<0)
								ANG = GS.MyRailStats.SIDEHOPFACEANG+ANGLE_180
							else
								ANG = GS.MyRailStats.SIDEHOPFACEANG
							end
						end
						ANG = $ + ( ANG20*((p.cmd.sidemove*GS.invertinput > 0) and 4 or -4) )
						RAIL.SpawnArrow(s, ANG, 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
			or GS.LTAB and (GS.LTAB.downjump)
				if not (LOOP and LOOP.walljump)
					if (VERTRAIL)
						s.momz = $*7/8
						s.momx,s.momy = $*3/2,$*3/2
					else
						s.momz = (-abs($)*3/4)*P_MobjFlip(s)
						if GS.LTAB and GS.LTAB.downjump
							P_SpawnMobjFromMobj(s, 0,0,s.height, MT_EXPLODE)
							GS.cannotgrind = min($, 4)
							GS.LTAB.downjump = nil
							S_StartSound(s, sfx_s3ka6) S_StartSoundAtVolume(s, sfx_kc55, 155)
							if (p.speed < 88<<16)
								P_Thrust(s, s.angle, 5*s.scale)
							end
						else
							s.momx,s.momy = $*4/3,$*4/3
						end
					end
					GS.airdrag = nil
				end
			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
						if STATS and (STATS.AUTODASHMODE!=nil)
							NEEDSPEED = STATS.AUTODASHMODE*s.scale
						else
							NEEDSPEED = DFSTATS.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 GS.MyRailStats and GS.MyRailStats.SIDEHOPFACEANG!=nil
					MOMANG = GS.MyRailStats.SIDEHOPFACEANG
				elseif 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 (s.eflags & MFE_VERTICALFLIP)
								p.aiming = min($, -ANG20)
							else
								p.aiming = max($, ANG20)
							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
				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)
				or p.powers[pw_justsprung]
					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 (LOOPFLAGS & 33554432) --Force Horizontal cam angle.
			p.aiming,p.awayviewaiming = 0,0
		elseif 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
		if not (GS.docam) and p==displayplayer and p.awayviewmobj==GS.railcam and p.awayviewtics and GS.railcam and (GS.chasecam!=false) 
		and GS.railcam.valid and p==consoleplayer and camera and camera.chase
			P_TeleportCameraMove(camera, GS.railcam.x, GS.railcam.y, GS.railcam.z)
			camera.angle = GS.railcam.angle
		end	
	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|16777216)) --Upsidedown loop or still camera.
		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 or 0) & (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 = max(GS.perfectcorner or 0, 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
		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
		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)) 
			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
				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.GSVertRail 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

--After losing balance on a rail.
RAIL.FAILROLL = function(p,s,GS, START)
	if (START)
		local TWOD, FORCEANG = GS.TWOD, nil
		local tilt = GS.railtilt if not (tilt) tilt = (leveltime%2) and -499 or 499 end GS.railtilt = tilt
		local ang = p.drawangle
		if (TWOD!=true)
			local rail = GS.myrail or GS.LastRail
			if rail and rail.GSVertRailDOWNANG!=nil
				FORCEANG = GS.LastRail.GSVertRailDOWNANG
				local CAMANG = p.cmd.angleturn<<16 if p.awayviewmobj and p.awayviewmobj.valid and p.awayviewtics CAMANG = p.awayviewmobj.angle end
				local ANGDIFF,PLUS = RAIL.AngleDifference(CAMANG, FORCEANG, 2, ANGLE_90, 2)
				tilt = abs($)*PLUS
				ang = FORCEANG
			end
		end
		local flingspeed = max(abs(GS.railspeed or 1)/4, 8<<16)
		p.cmd.sidemove,p.cmd.forwardmove = 0,0
		p.powers[pw_nocontrol] = max($+7, 8)
		
		RAIL.ExitRail(s, true, true) if not (s.health) return end --
		
		s.z = $+(16*s.scale*P_MobjFlip(s))
		if (TWOD!=true)
			if FORCEANG!=nil
				for i = 1,2
					P_TryMove(s, s.x+FixedMul(39*s.scale, cos(FORCEANG)), s.y+FixedMul(39*s.scale, sin(FORCEANG)), true)
				end
			else
				local sideang = ang + ( ((tilt or 0)>0) and -ANGLE_90 or ANGLE_90)
				for i = 1,2
					P_TryMove(s, s.x+FixedMul(32*s.scale, cos(sideang)), s.y+FixedMul(32*s.scale, sin(sideang)), true)
				end 			
			end
		end
		
		s.momx,s.momy = $/3,$/3
		GS.cannotgrind = max($, 24)
		S_StartSoundAtVolume(nil, sfx_kc55, 50, p) S_StartSoundAtVolume(nil, sfx_cdfm70, 50, p)
		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
			GS.failroll = 19*MULT
			if TWOD!=true
				if FORCEANG!=nil
					P_InstaThrust(s, FORCEANG, flingspeed)
					p.drawangle = FORCEANG+ANGLE_180
					GS.failrollang = FORCEANG+ANGLE_180
					if GS.LTAB
						GS.LTAB.FailRollANG = FORCEANG
						GS.LTAB.MOMANG = FORCEANG
						if p.yusonictable
							p.yusonictable.nosarun4 = max(p.yusonictable.nosarun4 or 0, 15)
						end
					end
					if (s.momz*P_MobjFlip(s) > -s.scale)
						P_SetObjectMomZ(s, -1<<16, false)
					end
				else
					GS.failrollang = ang+(ANGLE_90*MULT)
					P_Thrust(s, ang+ANGLE_90, -flingspeed*MULT)
				end
			end
			GS.failroll = 19*MULT
		else
			GS.failroll = 17
		end
		GS.railjump = 999<<16
		p.pflags = $|PF_JUMPDOWN|PF_USEDOWN & ~(PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_STARTDASH)
		s.spritexscale,s.spriteyscale = FRACUNIT+7600,FRACUNIT-7600
		GS.stretch = {timer = 4, 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)
			p.pflags = $|PF_JUMPDOWN|PF_USEDOWN & ~(PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_STARTDASH)
			if GS.failrollang != nil
				p.drawangle = GS.failrollang
			end
			GS.cannotgrind = max($, 3)
			if GS.LTAB.FailRollANG and (abs(GS.failroll) > 14)
				P_InstaThrust(s, GS.LTAB.FailRollANG, abs(s.momx)+abs(s.momy))
			end
		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

--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 p.awayviewtics and p.awayviewmobj==GS.railcam and (p.awayviewmobj and p.awayviewmobj.valid)
					CAMANG = GS.railcam.angle
				end
				if 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	
				if (GS.reversing==-1) and GS.myrail and GS.myrail.GSVertRail
					GS.invertinput = $*-1
				end	
			end
		elseif not (p.cmd.sidemove)
			p.cmd.sidemove = (p.drawangle > ANGLE_90) and -1 or 1
		end
	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

--Returns DIRECT from 0 to 50, and STICK ANGLE based on camera. p and s must be valid.
RAIL.StickInput = function(p, s)
	local DIRECT,ANG = 0,s.angle
	if not (p.cmd.forwardmove or p.cmd.sidemove) --No input is pretty simple.
		DIRECT = 0
		if (s.flags2 & MF2_TWOD) or (twodlevel) --2D. Note that it doesn't include currentaxis in this instance.
			if s.momx
				ANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y)
			else
				ANG = s.angle
			end
		else --3D
			if s.momx or s.momy --Use our momentum angle in this case!
				ANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
			else
				ANG = p.drawangle
			end
		end
		return DIRECT,ANG --
	end
	
	local AWAYCAM = nil
	if p.awayviewtics and p.awayviewmobj and p.awayviewmobj.valid and p.awayviewmobj.angle!=nil
		AWAYCAM = p.awayviewmobj --Using an awayview camera! Use this mobj's angle, in that case!
	end
	
	--3D inputs. Also counts for alt cameras, even if that camera is pretending to be 2D.
	if not ((s.flags2 & MF2_TWOD) or (twodlevel) or (s.currentaxis)) or (AWAYCAM)
		local CAMANG
		if AWAYCAM
			CAMANG = AWAYCAM.angle 
		else
			CAMANG = p.cmd.angleturn<<16 
		end
		DIRECT = max(abs(p.cmd.forwardmove)*8/7, abs(p.cmd.sidemove)*8/7)
		DIRECT = min(50, DIRECT) --Decide how hard we're pressing the stick! From 0 to 50!
		ANG = CAMANG + R_PointToAngle2(0, 0, p.cmd.forwardmove<<16, -(p.cmd.sidemove<<16))				
	else --2D inputs.
		if (s.momx > 0) and (p.cmd.sidemove < 0)
		or (s.momx < 0) and (p.cmd.sidemove > 0)
			ANG = ANGLE_180 --"Backwards"
		else
			ANG = 0 --Forwards.
		end
		DIRECT = abs(p.cmd.sidemove) 
		if s.currentaxis and (DIRECT > 9) and p.cmd.forwardmove --Can add some of this as a modifier, why not.
			DIRECT = $+(abs(p.cmd.forwardmove)/3)
		end
		DIRECT = min(50, DIRECT)
	end	
	return DIRECT,ANG --
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_PAIN] = 3
ANS[SPR2_DEAD] = 5
ANS[SPR2_DRWN] = 5
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. It's the "CustomAnim" hook, basically.
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 = 0
	if (SPR2F_SUPER!=nil) and (s.sprite2&SPR2F_SUPER) --2.1.14+
		SUPER = SS2
	end
	if not (SUPER) and (p.powers[pw_super] or (s.eflags & MFE_FORCESUPER))
		SUPER = SS2
	end
	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 not GS.railswitching
						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
					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 = RAILTYPE[0].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	
	elseif STATS and STATS.NOFUNALLOWED==true and GS.forcesprite==nil and GS.animcall==1 and STATS.FORCESPRITE==nil
	and (GS.railtossflag and ((p.cmd.buttons & BT_TOSSFLAG) or (GS.railtossflag < 99<<16))) and not GS.flipanim and GS.grinding
		GS.animcall = 0 return --Uhh, some mistake?
	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)) --and not (SUPER and P_IsValidSprite2(s, (GS.curanim.SPRITE2|SUPER)))
				GS.curanim = nil --NO WAY!
			else
				s.sprite = GS.curanim.SPRITE
				s.sprite2 = GS.curanim.SPRITE2
				s.frame = GS.curanim.FRAME & ~(FF_SPR2MIDSTART|FF_ANIMATE|FF_RANDOMANIM|FF_GLOBALANIM|FF_SPR2ENDSTATE)
				s.anim_duration,s.tics = -1,-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
			else
				STANCE = RO.duncegrindstate or SPR2_EDGE
				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 = (s.scale/12)*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) --or SUPER and P_IsValidSprite2(s, (STANCE|SUPER))
						break --
					end 
				end
			end
			if GS.myrail.GSVertRailDOWNANG!=nil
				p.drawangle = GS.myrail.GSVertRailDOWNANG+ANGLE_180
			end	
		elseif not (P_IsValidSprite2(s, STANCE)) -- and not ( (SUPER) and (P_IsValidSprite2(s, (STANCE|SUPER))) )
			STANCE = SPR2_STND --Backup!
		end
		s.sprite2 = STANCE|SUPER
		s.frame = $ & ~(FF_SPR2MIDSTART|FF_ANIMATE|FF_RANDOMANIM|FF_GLOBALANIM|FF_SPR2ENDSTATE)
		s.tics,s.anim_duration = -1,-1
		if STANCE == SPR2_STND
			s.frame = $ & ~FF_FRAMEMASK
			GS.curanim = nil
		else
			SPRITE2 = skins[s.skin].sprites
			local NAME,MAXFRAME = nil
			if SPRITE2[STANCE] 
				NAME = SPRITE2[STANCE] --STANCE
				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)+1 >= (MAXFRAME or 0))
					s.frame = ($ & ~FF_FRAMEMASK)
					s.anim_duration = 1 
				else
					s.frame = $+1
					SPRITE2 = skins[s.skin].sprites --This can happen, annoyingly enough.
					if ((s.frame & FF_FRAMEMASK)+1 >= (skins[s.skin].sprites[s.sprite2].numframes or 0))
						s.frame = ($ & ~FF_FRAMEMASK) --THIS however, is pure paranoia.
					end
				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
	end return RAIL.DOSKIN(SKIN, p, s,GS, OGCALL)
end

/*
--Unused code I made for someone else. Returns "left" string if there's a nearby rail to your left, returns "right" string if there's one to your right.
--Returns "both" string if there's rails on both your left and right, returns false if nothing.
local function CheckSideRails(s, TIME)
	if not (s and s.valid and s.GSgrind and s.GSgrind.grinding)
		return false --
	end
	local p, GS = s.player, s.GSgrind
	TIME = (TIME) or 1
	if GS and GS.grinding and p and GS.grinddirection!=nil and GS.myrail and (leveltime%TIME==0) 
		local DIRECTOR = GS_RAILS_DIRECTOR
		if DIRECTOR and DIRECTOR.valid
			local TABLE = DIRECTOR.GSrailtable
			local ANG = p.drawangle
			local MAXDIST = 800*s.scale --Distance to check. Sidehops are pretty fast, so 800 is not actually THAT much.
			local RIGHT,LEFT = false,false
			
			for _,m in pairs(TABLE)		
				if not (m and m.valid) or (m.GSdisabled) continue end --
				if not ((m.type==MT_GSRAIL) or (m.type==MT_GSRAILSEG)) continue end --
				if (m==GS.myrail) or (m==GS.myrail.GSnextrail) or (m==GS.myrail.GSprevrail) or (m.flags & MF_NOCLIPTHING) continue end --
				
				local XYDIST = FixedHypot(s.x-m.x, s.y-m.y)
				if (XYDIST < MAXDIST) and (abs(s.z-m.z) < (MAXDIST*5/3))
					local ANGDIFF,LR = GS_RAILS.AngleDifference(ANG, R_PointToAngle2(s.x, s.y, m.x, m.y), 2, ANGLE_90, 2)
					if (ANGDIFF > ANG20) and (ANGDIFF < ANG60) --Remember, this is divided by 2!
						if LR==1
							RIGHT = true
						else
							LEFT = true
						end
						if LEFT==true and RIGHT==true  return "both" end
						continue --
					end
				end
				if m.GSsegments --Look into the rail object's individual segments now.
					local SEGS = m.GSsegments
					for _,seg in pairs(SEGS)
						if not (seg and seg.valid) or (seg.flags & MF_NOCLIPTHING) continue end
						XYDIST = FixedHypot(s.x-seg.x, s.y-seg.y)
						if (XYDIST < MAXDIST) and (abs(s.z-seg.z) < (MAXDIST*5/3))
							local ANGDIFF,LR = GS_RAILS.AngleDifference(ANG, R_PointToAngle2(s.x, s.y, seg.x, seg.y), 2, ANGLE_90, 2)
							if (ANGDIFF > ANG20) and (ANGDIFF < ANG60) --Remember, this is divided by 2!
								if LR==1
									RIGHT = true
								else
									LEFT = true 
								end
								if LEFT==true and RIGHT==true return "both" end  --
								continue --
							end
						end
					end
				end				
			end
			return (RIGHT==true) and "right" or (LEFT==true) and "left" or false
		end
	end return false --
end */