--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════╗
--║  RAIL SPAWNING & DIRECTOR  ║ This is where rails are set up, connected, animated, destroyed, etc. The rail's foundation!	 ║
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════║
--║												[DOCUMENTATION LINK]															 ║
--║			If you're here looking on how to USE rails, I've made much more comprehensible documentation here:					 ║
--║												  bit.ly/GSraildocs																 ║
--╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
local RAIL = GS_RAILS if RAIL.blockload return end

local Msa = {}
Msa["mapthing.stringargs"]  = true
Msa["mapthing_t.stringargs"]= true
Msa["line_t.stringargs"]	= 2 	--One of these is not like the other...

--The Rail Director tracks all the rails in the stage in its tables.
--That way we can avoid needing to iterate through mobjs and individual rail segments.
--It's also a very performance-friendly way to animate rails.
RAIL.DirectorThinker = function(s)
--	if not (s and type(s)=="userdata") return end --Be ACTUALLY valid!
	if (s.GSTIME==leveltime) and (s.GSTIME!=nil) return end --
	
	if not (s.GS_SetupRailDirector)
	--	print("DIRECTOR TIMER:\x83"+s.GS_SetupRailDirector)
		if ((RAIL.MapReady or 0) > 1)
		/*	if TEMPTABLE!=nil --SRB2 somehow still failed to properly load objects. Great. Just gGREAT SRB2.
				local MAXNO = #TEMPTABLE
				local DID, m = 0, nil
				for i = 1,MAXNO,1 do m = TEMPTABLE[i] print("TEMP:\x82"+i)
					if not (m and type(m)=="userdata" and m.valid and m.spawnpoint and m.GSrailnum==nil) continue end
						print("Going through\x81 temptable[\x82"+i+"\x81"+"]") 
					RAIL.MapThingSpawn(m, m.spawnpoint, true) --Now do it right, damn you.
					if m.GSrailnum!=nil DID, TEMPTABLE[i] = $+1,nil print("Success!\x83["+i+"]") end
				end 
				if (DID>=MAXNO) 
					TEMPTABLE = nil
				end
				return --
			end */
			if (RAIL.SetupRailDirector(s)!=false)
				s.GS_SetupRailDirector = 1
			end
		else
			RAIL.MapReady = (RAIL.MapReady or 0)+1 
		end
	else		
		s.GS_SetupRailDirector = $+1 --How long it's been setup.
		if (s.GS_SetupRailDirector < 244) and (s.GS_SetupRailDirector%9==0) and (s.GS_SetupRailDirector > 15) and (s.GSrailtable)
			for _,m in pairs(s.GSrailtable) --Undoing a superspecific color-bug on load, when rawget apparently won't work yet...
				if m and m.valid and not (m.GSraildye) and (m.type==MT_GSRAIL)
					local COLOR = RAIL.GrabRailColor(m)
					if (COLOR) and (COLOR!=0)
						m.GSraildye,m.color = COLOR,COLOR
						local SIDE = m.GSrailsideL
						if SIDE and SIDE.valid
							SIDE.GSraildye,SIDE.color,SIDE.colorized = m.GSraildye,m.color,m.colorized
						end
						if m.GSsegments --Look into all rail segments now.
							for _,seg in pairs(m.GSsegments)
								if seg and seg.valid
									seg.GSraildye,seg.color,seg.colorized = m.GSraildye,m.color,m.colorized
									SIDE = seg.GSrailsideL
									if SIDE and SIDE.valid
										SIDE.GSraildye,SIDE.color,SIDE.colorized = m.GSraildye,m.color,m.colorized
									end
								end
							end
						end
					end
				end
			end
		end
		if s.GSanimator --Oh,we animating some stuff tonight? Cool, cool.
			RAIL.RAILANIMATOR(s)
		end
	end
	s.GSTIME = leveltime
end

--Animates every rail in the stage that has animations attached. Also activates special rail effects.
RAIL.RAILANIMATOR = function(s)
	local TABLE = s.GSanimator
	local DO,FRAME,ADVANCE,ANIMSPEED = s.DO or 0, A, false, 0
	local CLOSETORAIL,SIDE,SEGS,RNG = nil
--	if displayplayer and (displayplayer==consoleplayer) and displayplayer.mo
--		ME = displayplayer
--	end
	if (s.GS_RNG!=nil)
		s.GS_RNG = P_RandomRange(-10000,10000) --Alternate RNG every frame once introduced!
		RNG = s.GS_RNG
	end

	for _,m in pairs(TABLE)
		if not (m and m.valid) or (m.GSdisabled or m.GSinvisiblerail) continue end --
		
		ANIMSPEED = max(1, m.GSinfo and m.GSinfo.animspeed or 0)
		ADVANCE = false
		
		if m.top_endframe or m.side_endframe
			if (ANIMSPEED<=1) or (leveltime%ANIMSPEED==0)
				ADVANCE = true --Let the others know they can advance a frame too.
				if m.top_endframe
					if ((m.frame & FF_FRAMEMASK) >= m.top_endframe)
						m.frame = ($ & ~FF_FRAMEMASK)|(m.top_startframe)
					else
						m.frame = $+1
					end
				end
				if m.side_endframe --Yes, the main rail object can have a side too!
					SIDE = m.GSrailsideL
					if SIDE and SIDE.valid
						FRAME = (SIDE.frame & FF_FRAMEMASK)
						if ((SIDE.frame & FF_FRAMEMASK) >= m.side_endframe)
							SIDE.frame = ($ & ~FF_FRAMEMASK)|(m.side_startframe)
						else
							SIDE.frame = $+1
						end
					end
				end
			end
		end
		if (ADVANCE) or m.GS_windrail
			SEGS = m.GSsegments
			if m.GS_windrail and m.GSnextrail and m.GSnextrail.valid --and ME
			/*	CLOSETORAIL = m.GSrailnum or 1
				if CLOSETORAIL!=nil
					if m.GSrailnum==nil or (abs(m.GSrailnum-CLOSETORAIL) > 9)
						CLOSETORAIL = nil
					end
				end
				if CLOSETORAIL==nil
					if RNG == nil
						s.GS_RNG = P_RandomRange(-10000,10000)
						RNG = s.GS_RNG
					end
					--Check if we're close to this rail.
					local LENGTH = max(5600<<16, (m.GSrailXYlength or 0)+(2500<<16))
					local X,Y = m.x, m.y
					if m.GSnextrail and m.GSnextrail.valid
						X = $-(($-m.GSnextrail.x)/2) --Use midpoint coordinates of the rail.
						Y = $-(($-m.GSnextrail.y)/2)
					end
					if (FixedHypot(X-ME.mo.x, Y-ME.mo.y) <= LENGTH) --We close to the middle of this rail?
						CLOSETORAIL = m.GSrailnum
					end
				end 
				if CLOSETORAIL!=nil */
					if RNG==nil
						s.GS_RNG = P_RandomRange(-10000,10000)
						RNG = s.GS_RNG
					end					
					RAIL.SpawnRailWind(DO, RNG, m, m, m.GSrailsideL or SIDE, true)
					DO = $+1
			--	end
			end
			if SEGS --Look into the rail object's individual segments now.
				local COUNTME = 0
				local SEGNUM = #SEGS or 0
				for _,seg in pairs(SEGS)
					if not (seg and seg.valid) continue end
					if (ADVANCE) --Animate the segments?
						if (m.top_endframe!=nil)
							FRAME = (seg.frame&FF_FRAMEMASK)
							if (FRAME >= m.top_endframe)
								seg.frame = ($&~FF_FRAMEMASK)|(m.top_startframe)
							else
								seg.frame = $+1
							end
						end
						if (m.side_endframe!=nil)
							SIDE = seg.GSrailsideL
							if SIDE and SIDE.valid
								FRAME = (SIDE.frame&FF_FRAMEMASK)
								if (FRAME >= m.side_endframe)
									SIDE.frame = ($&~FF_FRAMEMASK)|(m.side_startframe)
								else
									SIDE.frame = $+1
								end
							end
						end
					end
					if m.GS_windrail and m.GSnextrail and m.GSnextrail.valid --and CLOSETORAIL!=nil 
						COUNTME = $+1
						if (SEGNUM < 4) or not (COUNTME > SEGNUM-3)
							RAIL.SpawnRailWind(DO, RNG or 0, m, seg, seg.GSrailsideL or SIDE) --Add wind effects to reach segment
						end
						DO = $+1
					end
				end
			end
		end
	end
	s.DO = DO
end

--Takes the flags out of a number, returns them.
RAIL.GETFLAGS = function(NUM, STARTPOINT) if not (NUM) return 0 end
	local FLAGS, I = 0, STARTPOINT or FRACUNIT
	for i = 1,24
		if (NUM & I) FLAGS = $+I end
		I = $*2 if (I < 0) break end --
	end return FLAGS --
end

--Return rail's proper angle, rather than the angle accounting for curves.
RAIL.ANG = function(rail)
	if rail==nil RAIL.error("Warning! RAIL.ANG function: given rail is nil!") return 0 end
	if rail.GSmainrail rail = rail.GSmainrail end
	
	if rail.GSforceUDMFang!=nil and rail.GSVertRail
		return rail.GSforceUDMFang
	elseif rail.GSrailTrueAngle!=nil 
		return rail.GSrailTrueAngle --
	else
		return rail.angle or 0--
	end
end

/* --Experimental unused feature.
RAIL.Quake = function(INTENSITY, TIME, EPICENTER, RADIUS)
	INTENSITY = INTENSITY or 5<<16
	TIME = TIME or 2
	if type(EPICENTER)=="userdata" --Input an object, and it'll use the object's coordinates.
		EPICENTER = {EPICENTER.x, EPICENTER.y, EPICENTER.z}
	end
	if type(EPICENTER!="table") --If it's not a table, skip the epicenter entirely.
		P_StartQuake(INTENSITY, TIME)
	elseif displayplayer and displayplayer==consoleplayer and displayplayer.mo
		RADIUS = RADIUS or 128<<16
		local s = displayplayer.mo
		local X,Y,Z = EPICENTER
		local DIST = FixedHypot(FixedHypot(s.x-X, s.y-Y), s.z-Z)
		if (DIST > RADIUS) return end --Don't play a quake at all.
		if (DIST > RADIUS/2) --If we're barely within the radius, start reducing intensity a bit.
			INTENSITY = FixedDiv(FixedMul($, DIST), RADIUS*2)
		end
		P_StartQuake(INTENSITY, TIME, EPICENTER, RADIUS)
	end
end */

--Temporary debug
local print = function(text, NUM)
	if (consoleplayer) and consoleplayer==players[0] and consoleplayer.name=="Golden Shine" and not (players[1] and netgame==true)
		if NUM
			if type(NUM)=="number" and not ((NUM < 240) and (NUM > 198))
				return --
			end
		end
		CONS_Printf(consoleplayer, text)
	end
end

--Snags rail's color.
RAIL.GrabRailColor = function(s) 
	if not (s and (type(s)=="userdata") and s.valid) return 0 end --Not even valid..?
	
	if s.GSmainrail and s.GSmainrail.valid and (s.GSmainrail.spawnpoint) and (s.type!=MT_GSRAIL)
		s = s.GSmainrail --Use them instead.
	end
	local spn,SARG = s.spawnpoint, nil
	if spn and type(spn.stringargs)=="userdata" and Msa[userdataType(spn.stringargs)]==true and spn.stringargs[0]
		SARG = spn.stringargs
		local COLOR,NUM = SARG[0], nil --Custom rail color. Convenience feature for UDMF that doesn't require dye objects.
		if type(COLOR)=="number" -- and (COLOR <= #skincolors)
			return NUM --
		else
			NUM = rawget(_G, COLOR) --First, try converting SKINCOLOR_ constants. (Apparently _G needs a moment to load?)
			if not (NUM)
				NUM = R_GetColorByName(COLOR) --That didn't work. Try converting names?
				if not (NUM)
					NUM = R_GetSuperColorByName(COLOR) --SUPER name???
				end
			end
		end
		if not (NUM)
			NUM = tonumber(COLOR) --Did they input a number for some reason?
		end
		if (NUM) -- and type(NUM)=="number" and not (NUM > #skincolors)
		--	print("NUM:\x82"+NUM+" SARG:"+userdataType(SARG)+" SARG[0]:"+SARG[0], s.GSrailnum)
			return NUM --Final say!
		end
		
	end --print("\x85 No Late.\x80 SARG:\x82"+userdataType(SARG)+" SARG[0]:"+SARG[0], s.GSrailnum)
	return 0 --
end

local function TRUEorFALSE(m) 
	if not (m)
		return false --
	elseif m==true or type(m)=="string" and (string.lower(m))=="true"			
		return true --
	end return false --
end

--This is the main rail object that was actually placed in the map by a mapper. Here we set up its properties.
--These properties are passed onto the segments, sides, and possibly even the next rails, therefore this must be FIRST step!
--ActivateRail is called by the Rail Director, then chains into ConnectRails1>2>3>4, which make individual rail segments.
RAIL.ActivateRail = function(m)
	if not (m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid) return end --
	
	local spn = m.spawnpoint
	if not ((spn) and type(spn)=="userdata" and userdataType(spn)=="mapthing_t" and spn.valid) return end --	
	
	if m.GSrailsetup RAIL.error("Warning! Tried to activate rail["+m.GSrailnum+"] twice!") return false end
	
	if m.GSMapThinged=="TryAgain" --The fuck. MapThing. Do it!
		RAIL.MapThingSpawn(m, spn, true)
	/*	if m.GSMapThinged=="TryAgain"
			print("\x85"+"Failed TR:\x86 "+m.GSMapThinged) return "AbortMT"
		else
			print("\x83"+"YES THIS TIME! railnum: \x82"+m.GSrailnum)
		end */
	end
	local MAP = mapheaderinfo[gamemap]
	
	m.scale,m.spritexscale,m.spriteyscale = FRACUNIT,FRACUNIT,FRACUNIT --ALWAYS start with the same scale, then adjust as needed.
	m.GSXYZ = {X = m.x, Y = m.y, Z = m.z} --Original coordinates.
	m.momx,m.momy,m.momz,m.pmomz = 0,0,0,0,0
	m.state = S_GSRAIL --Mostly to ensure UZB didn't interfere somehow.
	m.sprite = SPR_GSRL
	m.frame = ($ & ~FF_FRAMEMASK)
	m.spritexoffset = m.GSspritewidth or 0
	
	m.GSrailsetup = true --this rail had been setup!

	m.flags = $|MF_SOLID & ~(MF_NOCLIP|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_NOTHINK)
	m.flags2 = $ & ~(MF2_DONTDRAW|MF2_OBJECTFLIP) --We can set the sprite to SPR_NULL for invisible grindable rails.
	m.eflags = $ & ~(MFE_VERTICALFLIP|MFE_APPLYPMOMZ|MFE_JUSTHITFLOOR|MFE_SPRUNG) --Never be flipped!
	
	m.GScantgrind = true --Rails cannot grind themselves, shockingly
	m.GSrailtophitbox = m.GSrailtophitbox or 0 --Vertical hitboxes are sometimes adjusted to feel more correct depending on slope angle.
	m.GSrailbottomhitbox = m.GSrailbottomhitbox or 0 
	m.GSrailTrueAngle = m.angle

	--Now check spawnpoint parameters!
	--#======================================Binary Map arguments====================================================#
	if (spn.pitch)
		m.GSpitch = spn.pitch --Temporary values for easier identification.
	end
	if (spn.roll)
		m.GSroll = spn.roll
	end	
	if MAP --Mapheader modifiers.
		if MAP.gsrailcam and type(MAP.gsrailcam)=="string"
			local CAM = string.lower(MAP.gsrailcam)
			if CAM=="force"
				m.GSforcecam = "force"
			elseif CAM=="always"
				m.GSforcecam = "always"
			elseif CAM=="off"
				m.GSforcecam = "off"
			end
		end
	end
	
	
	if (udmf)!=true --Binary map options! We can't modify it as much, unfortunately.
	
		local OPTIONS = spn.options
		if OPTIONS
			if (OPTIONS & MTF_EXTRA) -- Flag 1. Invisible rail. Useful for all kinds of stuff.
				m.sprite = SPR_NULL
				m.GSinvisiblerail = true
			end
			if (OPTIONS & MTF_OBJECTFLIP) -- Flag 2. Object Flip. Does nothing on rails, so I made it an SA2 jumpramp.
				m.GSjumprail = 1
			end		
			if (OPTIONS & MTF_OBJECTSPECIAL) --Flag 4. Hang style rail.
				m.GShangrail = true
				m.color = SKINCOLOR_LAVENDER
			end
			if (OPTIONS & MTF_AMBUSH) --Flag 8. Attach rings/bumpers/springs/boosters vertically alligned with the rail
				m.GSrailallignobjects = true
			end
		end
		if (spn.extrainfo) --Custom rails. Unfortunately binary can only support up to railtype 15.
			m.GStype = spn.extrainfo
			if m.GStype==15 --Always apply Frontiers leap property to Tropical Resort rails.
				if not (m.GSjumprail)
					m.GSjumprail = 2
				end
			elseif m.GStype==12 --Fences can become walljump rails when vertically stacked.
				if m.GSinvisiblerail and not m.GShangrail
					m.GScheckwalljump = true
				end
			elseif m.GStype==7 --Give Metal Harbor rails Mega Jump properties if the FLIP flag is applied.
				if m.GSjumprail
					m.GSjumprail = nil
				end
				m.GSjumpflags = 2097152
			elseif m.GStype==4 --Always apply a 78% speed modifier to City Escape rails
				m.GSrailspeed = 78
			elseif m.GStype==3 --City Escape handrails with poles have alot of special nonsense.
				if m.GSinvisiblerail==true --Apply loop properties.
					m.GStype = 42
					m.GSLoopType = 1 --Default loop.
					m.GSjumpflags = 393216 --Disable sidehop & C3.
					if m.GShangrail==true
						m.GSloopflags = 2228224
						m.GSjumprail = nil
					end
				elseif m.GShangrail==true --Make an '06 jumprope if it's not invisible.
					if not m.GSjumprail
						m.GSjumprail = 2
					end
					m.GStype = 18
					m.GSjumpflags = 2490368
					m.GSgrindflags = 1835008					
				end
				m.GShangrail = nil --Never ACTUALLY use hangrail properties.
				spn.options = $ & ~MTF_OBJECTSPECIAL
			elseif m.GStype==2
				if MAP and type(MAP.gsrailtypetwo)=="number" --Make all railtype 2's into a different one?
					m.GStype = MAP.gsrailtypetwo
				end				
			elseif m.GStype==1
				if MAP and type(MAP.gsrailtypeone)=="number" --Make all railtype 1's into a different one?
					m.GStype = MAP.gsrailtypeone
				end
			elseif not (m.GStype)
				if MAP and type(MAP.gsrailtypezero)=="number" --Make all railtype 1's into a different one?
					m.GStype = MAP.gsrailtypezero
				end				
			end
		end
		
	--#========================================UDMF Map arguments========================================================#	

	elseif type(spn.args)=="userdata" and (userdataType(spn.args)=="mapthing.args" or userdataType(spn.args)=="mapthing_t.args")
	
		if spn.scale and (spn.scale!=FRACUNIT) --Scaling rails isn't quite so simple...
			m.GSxMTscale = spn.scale --m.GSyMTscale = spn.scale
		else
			if spn.spritexscale and (spn.spritexscale!=FRACUNIT)
				m.GSxMTscale = spn.spritexscale
			elseif spn.spriteyscale and (spn.spriteyscale!=FRACUNIT)
				m.GSyMTscale = spn.spriteyscale
			end
		end
		local COLOR
		if spn.stringargs and Msa[userdataType(spn.stringargs)]==true
			COLOR = RAIL.GrabRailColor(m) if (COLOR) and (COLOR!=0) m.GSraildye,m.color = COLOR,COLOR end
			local WILDCARD = spn.stringargs[1]
			if (WILDCARD) and type(WILDCARD)=="string"
				if string.find(WILDCARD, "SPR2_") --They've input an animation!
					local SPRITE2 = string.sub(WILDCARD, 6)
					if SPRITE2=="RUN" or SPRITE2=="FLY" or SPRITE2=="FLT"
						SPRITE2 = $+"_" --Goddamnit, game.
					end
					m.GSforcerailanim = SPRITE2
				elseif string.find(WILDCARD, "onlyskin") --They've input an animation!
					local SKIN = string.sub(WILDCARD, 9)
					m.GSonlyskin = SKIN
				elseif string.find(WILDCARD, "notskin") --They've input an animation!
					local SKIN = string.sub(WILDCARD, 8)
					m.GSnotskin = SKIN
				end
			end
		end
	
		local ARG = spn.args		
		if ARG[0] --Turns rails invisible if 1.
			m.sprite = SPR_NULL 
			m.GSinvisiblerail = (ARG[0]==1) and true or ARG[0]
		end
		if ARG[1] and type(ARG[1])=="number" --Turns rail into a hangrail.
			m.GStouchflags = RAIL.GETFLAGS(ARG[1], FRACUNIT)
			local VAR = ARG[1]
			if m.GStouchflags
				VAR = $ & ~m.GStouchflags
			end
			if VAR==1
				m.GShangrail = (ARG[1]==1) and true or ARG[1]
				m.color = SKINCOLOR_LAVENDER
			end	
		end
		if ARG[2] --Automatically allign items with the rail?
			m.GSrailallignobjects = (ARG[2]==1) and true or ARG[2]
		end
		
		--ARG[3] is the Rail Number argument. It's called instantly in the MapThingSpawn hook.
		
		if ARG[4] and type(ARG[4])=="number" --Jump Settings! Such as an SA2 JumpRamp, or Frontiers style leaps!
			m.GSjumpflags = RAIL.GETFLAGS(ARG[4], FRACUNIT)
			if m.GSjumpflags
				m.GSjumprail = ARG[4]&~m.GSjumpflags
			else
				m.GSjumprail = ARG[4]
			end
			if m.GSjumprail==4
				m.GSjumprail = -1
			end
		end
		if ARG[5] and type(ARG[5])=="number" --Custom Rail Types!
			m.GStype = ARG[5]
		end
		
		if ARG[6] and type(ARG[6])=="number" --Forcibly Connect to Pitch.
			m.GSConnectToPitch = ARG[6]
		end
		if ARG[7] and type(ARG[7])=="number" --Grind Settings
			m.GSgrindflags = RAIL.GETFLAGS(ARG[7], FRACUNIT)
			local VAR = ARG[7]
			if m.GSgrindflags
				VAR = $ & ~m.GSgrindflags
			end
			if VAR==1
				m.GSfaceforward = 1 --Force forwards.
			elseif VAR==2
				m.GSfaceforward = -1 --Force backwards
			end
		end
		if ARG[8] and type(ARG[8])=="number" --Rail Speed Modifiers. The conveyors are kind of like Sonic Lost World rails!
			local SPEEDFLAGS = (RAIL.GETFLAGS(ARG[8], 16384)) --Speed Modifier flags.
			local VAR = ARG[8] --Actual input value.
			if SPEEDFLAGS
				VAR = (ARG[8]&~SPEEDFLAGS)
				if VAR
					if (SPEEDFLAGS & 16384) --Regular Conveyor.
						m.GSconveyor = VAR
					elseif (SPEEDFLAGS & 32768) --Reverse conveyor.
						m.GSconveyor = -VAR
					end
					if (SPEEDFLAGS  & 65536) --Railspeed %
						m.GSrailspeed = abs(VAR)
					end
					if (SPEEDFLAGS & 131072) --Start Cap
						m.GSrailstartcap = abs(VAR)
					end
				end
			elseif VAR --If no flag is picked, go with a conveyor.
				m.GSconveyor = min(500, max(-500, VAR))
			end
		end
		if ARG[9] --Loop Types/WallJumps/Cameras/Misc section.
			m.GSloopflags = RAIL.GETFLAGS(ARG[9], FRACUNIT)
			if m.GSloopflags
				m.GSLoopType = ARG[9]&~m.GSloopflags
			else
				m.GSLoopType = ARG[9]
			end
			if (m.GSloopflags & 1048576) and m.GSinvisiblerail==true
				m.GStype = 42 --For good measure.
			end
		end
		if m.GStype==2
			if MAP and type(MAP.gsrailtypetwo)=="number" --Make all railtype 2's into a different one?
				m.GStype = MAP.gsrailtypetwo
			end				
		elseif m.GStype==1
			if MAP and type(MAP.gsrailtypeone)=="number" --Make all railtype 1's into a different one?
				m.GStype = MAP.gsrailtypeone
			end
		elseif not (m.GStype)
			if MAP and type(MAP.gsrailtypezero)=="number" --Make all railtype 1's into a different one?
				m.GStype = MAP.gsrailtypezero
			end
		end
	end
	if (m.GStype) and ((RAIL.LearnRailType(m))!=0)
		local TYPE = GS_RAILS_TYPES[m.GStype]
		if not (m.GSraildye) and (TYPE.defaultcolor!=nil)
			m.color = TYPE.defaultcolor
		elseif (m.GSraildye)
			m.color = m.GSraildye
		end
		
		if TYPE.noSIDE 
			m.GSrenderstyle = "nosides"			
		elseif TYPE.noTOP
			m.GSrenderstyle = "notop" --Only 1 can be active at a time.
		end
		
		--Now make an INFO table. One I'm super inconsistent about actually using. Whoops.
		if type(m.GSinfo)!="table" m.GSinfo = {} end
		local INFO = m.GSinfo
		
		if TYPE.renderflags
			m.renderflags = $|(TYPE.renderflags)
			INFO.renderflags = TYPE.renderflags
		end
		if TYPE.blendmode
			m.blendmode = TYPE.blendmode
			INFO.blendmode = TYPE.blendmode
		end		
		if TYPE.hangZoffset
			INFO.hangZoffset = TYPE.hangZoffset or 0
		end
		if type(TYPE.TOPsprite)=="number" and (TYPE.TOPsprite) --Change sprite?
			if not m.GSinvisiblerail
				m.sprite = TYPE.TOPsprite
				m.frame = TYPE.TOPstartframe or A
			end
			INFO.TOPsprite = TYPE.TOPsprite
			INFO.TOPstartframe = TYPE.TOPstartframe or A			
			INFO.animspeed = max(TYPE.animspeed or 0, 1)
			if type(TYPE.TOPendframe)=="number"
				m.top_startframe = TYPE.TOPstartframe or A 
				m.top_endframe = max(m.top_startframe,TYPE.TOPendframe)
			end
		end
		if type(TYPE.SIDEsprite)=="number"
			INFO.SIDEsprite = TYPE.SIDEsprite
			INFO.SIDEstartframe = TYPE.SIDEstartframe or A
			INFO.animspeed = max(TYPE.animspeed or 0, 1)
			
			if type(TYPE.SIDEendframe)=="number"
				m.side_startframe = TYPE.SIDEstartframe or A
				m.side_endframe = max(m.side_startframe, TYPE.SIDEendframe)
			end
		end
		if TYPE.alwayscolorize
			if TYPE.alwayscolorize==2 --If it's 2, ALWAYS colorize no matter what.
				m.colorized = true
			elseif (m.color) and (m.color!=TYPE.defaultcolor)
				m.colorized = true --If it's anything else, only colorize if they're NOT the default color.
			end
		end
		if TYPE.animspeed and type((TYPE.animspeed))=="number"
			INFO.animspeed = abs(TYPE.animspeed)
		end		
		if (TYPE.translucent) and type((TYPE.translucent))=="number"
			INFO.translucent = TYPE.translucent
			m.frame = ($ & ~FF_TRANSMASK)|(TYPE.translucent)
		end
		if (TYPE.Xoffset) and type(TYPE.Xoffset)=="number"
			m.GSspritewidth = $+((TYPE.Xoffset)<<16)
			m.spritexoffset = m.GSspritewidth
			INFO.Xoffset = TYPE.Xoffset
		end
		if (TYPE.windrail) 
			if type(TYPE.windrail)=="number"
				m.GS_windrail = TYPE.windrail
			else
				m.GS_windrail = SKINCOLOR_GREY
			end
		end
		if TYPE.spawnleaves
			if type(TYPE.spawnleaves)=="number"
				INFO.spawnleaves = TYPE.spawnleaves
			else
				INFO.spawnleaves = SKINCOLOR_FOREST
			end
		end	
	end
	if MAP --Add properties that can't get overwritten.
		if TRUEorFALSE(MAP.gsrailautobalance)==true
			if not (m.GSgrindflags & 262144) --Add autobalance flag.
				m.GSgrindflags = $|262144
			end
		end
		if TRUEorFALSE(MAP.gsrailnopowers)==true
			if (m.GSgrindflags & 65536) --Add or remove TwistDrive+Focus Grind flags.
				m.GSgrindflags = $ & ~65536
			else
				m.GSgrindflags = $|65536
			end
		end
		if TRUEorFALSE(MAP.gsrailnodislodge)==true
			if (m.GSjumpflags & 262144) 
				m.GSjumpflags = $ & ~262144 --Remove no-dislodge flag.
			else
				m.GSjumpflags = $|262144 --Add no-dislodge flag.
			end			
		end
		if TRUEorFALSE(MAP.gsrailfrontiers)==true and not (m.GSjumprail)
			m.GSjumprail = 2 --All rails are frontiers rails
		end	
		if MAP.gsrailspeed and not (m.GSrailspeed) --grind speed % modifier
			local MAXSPEED = tonumber(MAP.gsrailspeed)
			if ((MAXSPEED or 0) > 0) and (MAXSPEED < 100)
				m.GSrailspeed = MAXSPEED
			end		
		end
		if MAP.gsrailmaxspeed --An absolute cap for how fast you can grind.
			local MAXSPEED = tonumber(MAP.gsrailmaxspeed)
			if ((MAXSPEED or 0) > 0)
				m.GSrailmaxspeed = MAXSPEED
			end
		end			
	/*	if MAP.gsrailcam and type(MAP.gsrailcam)=="string"
			local CAM = string.lower(MAP.gsrailcam)
			if CAM=="force"
				m.GSforcecam = "force"
			elseif CAM=="always"
				m.GSforcecam = "always"
			elseif CAM=="off"
				m.GSforcecam = "off"
			end
		end	 */	
	end	
	local FLOORZ = P_FloorzAtPos(m.x, m.y, m.z, 0)
	if m.z==FLOORZ or m.z==m.floorz
		m.z = $+2 --Try not to be technically touching the floor.
	elseif m.z==m.ceilingz
		m.z = $-2
	end 
end

--Connect the rails, and do optional special effects. Angle 0 to 359 is sequence 1. Angle 360 to 719 is sequence 2, etc. 
--t is the director. s is the rail we're connecting to the next one.
RAIL.ConnectRails1 = function(s, t, SameRailsOnly, PITCH)
	if (s.GSnextrail and s.GSprevrail) or s.GSrailnum==nil --If either is not confirmed already, then set up shop.
	or not (t and t.GSrailtable) -- and not s.GSSkipMoreChecks
		return --
	end
	if (PITCH!=nil) --Happens AFTER initial setup, so GSpitch is already set.
		for _,m in pairs(t.GSrailtable)
			if (s!=m) and m.valid and (m.GSpitch==PITCH) and m.GSrailnum!=nil
				s.GSnextrail = m
				if not (m.GSprevrail and m.GSprevrail.valid)
					m.GSprevrail = s --You can connect back to this if we never got another prevrail to prioritize.
				end
				RAIL.ConnectRails2(s, s.GSnextrail)	return true --
			end
		end	return false --
	elseif (s.GSConnectToPitch) --You're called up later.
		return "wait" --
	end

	local PrefRail --Closest rail with next angle. Will be prioritized!
	local dist --Comparing distance values for multiple next-numbered rails!
	local SameOptions --Can become a table of all possible rail connections we find.
	local totalsame = 0 --Amount of same-numbered rails we'll find.
	local num = s.GSrailnum or 0 --Convenience.
	
	for _,m in pairs(t.GSrailtable)
		if (s != m) and m.valid and m.GSrailnum != nil
			if m.GSrailnum==num --Closest rail with the same number. 
			and not m.GSprevrail and not (s.GSnextrail == m or s.GSprevrail == m)
		//	and not m.GSnextrail and not s.GSFurthestRail
				if SameOptions == nil SameOptions = {} end
				totalsame = $+1
				table.insert(SameOptions, totalsame, m)
			end
			if m.GSrailnum == (num+1) --The closest rail with the next number. Takes priority.
			and not m.GSprevrail and not (s.GSnextrail == m or s.GSprevrail == m)
		--	and not m.GSSkipMoreChecks and not s.GSFurthestRail
				local XYZDIST = FixedHypot(FixedHypot(s.x-m.x, s.y-m.y), s.z-m.z)
				if dist==nil or XYZDIST < dist
					dist = XYZDIST
					PrefRail = m
				end
			end
		end
	end
	
	
	--We found both a same-numbered rail AND a next-numbered rail? Time for stuff to get complicated!
	--SRB2's mobj ordering is based on the time of item placed, which doesn't help us.
	--We need to order them by distance to the real next rail, which is a hell of a process...
	if totalsame and SameOptions and (PrefRail and not PrefRail.GSConnectToPitch)
		RAIL.OrderRailMobjs(PrefRail, SameOptions, false) --Function to put the rails in order of numbers!
		local closest = nil
		--Now that they're all in the correct order, figure who should connect to the nextangle PrefRail!	
		for NotiPairs = 1,#SameOptions
		local m = SameOptions[NotiPairs]
			if m.GStemporder != nil
				if closest == nil 
				or (closest.valid and closest.GStemporder != nil and (closest.GStemporder > m.GStemporder))
					closest = m
				end
			end
		end 
		
		--Finally our answer
		if closest and closest.valid and closest.GStemporder!=nil 
			 if not SameRailsOnly
				closest.GSnextrail = PrefRail
				PrefRail.GSprevrail = closest
			--	PrefRail.GSSkipMoreChecks = true
				RAIL.ConnectRails2(closest, PrefRail)
			end
		end
		
		--Now repeat the stupid game of "who's closest?", and renew the re-ordered table.
		SameOptions = (RAIL.OrderRailMobjs(PrefRail, SameOptions, true))
		
		PrefRail,closest = nil --We're done with the next angled rail, remove it.
		local nextnumber = #SameOptions
		RAIL.print("\x85 Hey, so..."+#SameOptions, 15)
		
		--Now connect the next ones, from lowest to next.
		for NotiPairs = 1,totalsame
			local m = SameOptions[NotiPairs]
			for i = 1,totalsame
				local t = SameOptions[i]
			
				if t and m and (m != t) --and m.GStemporder == nextnumber						
				and m.GSrailnum == t.GSrailnum
				and not t.GSprevrail // or m.GSnextrail) 
					RAIL.print("Looking for... "+(m.GStemporder-1)+" Currently seeing: "+t.GStemporder, 15)	
					if t.GStemporder == m.GStemporder-1 --Higher temporder means further away from the PrefRail.
						m.GSnextrail = t
						t.GSprevrail = m
						RAIL.ConnectRails2(m, t)
						nextnumber = $-1
					end
				end
			end	
		end 	
	end

	--No same-numbered rails found! The default rail connection is so much simpler!
	if PrefRail	and not SameRailsOnly
		s.GSnextrail = PrefRail
		PrefRail.GSprevrail = s
		RAIL.print("CONNECTRAILS:\x8b Connecting rail\x82 "+num+"\x8b to nextrail\x82 "+(num+1)+"! Begin!")
		RAIL.ConnectRails2(s, s.GSnextrail) return true --We found the best suitable next rail, connect it!
	end
end

--s is the current rail, m is the next rail. Make sure BOTH are valid.
--ActivateRail determines rail connections, and activates rail if everything is valid. That leads to ConnectRails 1 > 2 > 3 > 4
--ConnectRails2 sets up the rail lenght/angle/slope properties, and decides where to put down the segments.
--ConnectRails3 sets up the connecting segments and hitboxes. Aesthetically those segments use floorsprites.
--ConnectRails4 sets up papersprites underneath the segments. They're purely aesthetic.
RAIL.ConnectRails2 = function(s, m)
	if not (s and s.valid and m and m.valid) return end --
	
	local X,Y,Z = s.x,s.y,s.z
	local MX,MY,MZ = m.x, m.y, m.z --Use the ORIGINAL coordinates for connections to prevent shenanigans
	if s.GSXYZ
		X,Y,Z = s.GSXYZ.X, s.GSXYZ.Y, s.GSXYZ.Z
	end
	if m.GSXYZ
		MX,MY,MZ = m.GSXYZ.X, m.GSXYZ.Y, m.GSXYZ.Z
	end

	s.GSrailXYlength = FixedHypot(X-MX, Y-MY) --XY distance only.
	s.GSrailZdist = Z-MZ --Z distance only.
	s.GSraillength = s.GSrailXYlength+abs(s.GSrailZdist) --True distance! Determine rail's distance to next rail.
	if s.GSrailnum==8400
		print("s.GSraillength:\x82"+s.GSraillength)
	end
	if not (s.GSraillength) or MZ==Z and MX==X and MY==Y
		RAIL.error("\x85"+"ERROR:\x80 rail[\x82"+s.GSrailnum+"\x85] and rail[\x82"+m.GSrailnum+
		"\x85] are sharing the exact same coordinates! \n"+"Spawned\x81 debug TOAD\x85 on location.")
		RAIL.SpawnDebug(s) P_RemoveMobj(s) P_RemoveMobj(m) return false --
	end
	local ZDELTA,TOPADD,BOTTOMADD = 0,0,0
	local UDMFANGLE = nil
	local RAILANG = R_PointToAngle2(X, Y, MX, MY)+ANGLE_180
	s.GSrailTrueAngle = RAILANG --Face the next rail
	s.angle = RAILANG
	
	local STARTZ,DESTZ,ZLIMIT = RAIL.VertRail(s, "check", m)
	if (ZLIMIT!=nil) or not (s.GSrailXYlength) --Purely vertical rail, then?
		if not (s.GSrailXYlength) 
			if s.GSUDMFang!=nil
				UDMFANGLE = s.GSUDMFang+ANGLE_180 --Specific case if UDMF's Rail Number field is used.
				s.GSforceUDMFang,s.GSrailTrueAngle,s.angle,RAILANG = UDMFANGLE,UDMFANGLE,UDMFANGLE,UDMFANGLE 
				s.GSrailXYlength = 0
			else
				RAIL.error("\x85"+"ERROR:\x80 Vertical rails can't share the exact same X & Y coordinates!\n RailNum was:\x82"
				+(s.GSrailnum or "unknown")+"\n"+"\x80"+"Move one of the stacked vertical rails at least 1 pixel!"+
				"\n"+"Spawned\x81 debug TOAD\x80 on location.")
				RAIL.SpawnDebug(s)
				P_RemoveMobj(s) P_RemoveMobj(m) return false --
			end
		end
		if s.floorspriteslope
			P_RemoveFloorSpriteSlope(s) --Should never be necessary, but just incase.
		end
		if s.GSrailZdist or (ZLIMIT!=nil)
			ZDELTA = ANGLE_90 	
			if (MZ > Z)
				TOPADD = $+max(0, ZLIMIT-s.height)
			else
				BOTTOMADD = $-ZLIMIT
			end
			s.GSrailXYlength = max(1<<16, s.GSrailXYlength or 0)
			s.GSVertRail = (MZ < Z) and -1 or 1 --Flag it as a downwards vertical rail or upwards?
			if UDMFANGLE!=nil
				s.GSVertRailDOWNANG = (UDMFANGLE+ANGLE_180)+((MZ > Z) and ANGLE_180 or 0)
			else
				s.GSVertRailDOWNANG = R_PointToAngle2(X, Y, MX, MY)+((MZ > Z) and ANGLE_180 or 0)
			end
			s.GSVertRailTopZ = MZ if (MZ < Z) s.GSVertRailTopZ = Z end
			s.GSVertRailBotZ = Z if (MZ < Z) s.GSVertRailBotZ = MZ end
			if s.GScheckwalljump==true
				s.GSLoopType = 4 --Become a walljump rail for binary.
				s.GSjumpflags = 393216
				s.GSgrindflags = 262144
				m.GSLoopType = s.GSLoopType
				m.GSjumpflags = s.GSjumpflags
				m.GSgrindflags = s.GSgrindflags 			
			end
		end	
	else
	
		s.frame = $ & ~FF_PAPERSPRITE
		s.flags2 = $|MF2_SPLAT
		s.renderflags = $|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_ABSOLUTEOFFSETS	& ~RF_PAPERSPRITE
			
		if s.GSrailZdist --If there's Z distance, slope the rail!
			--ZDELTA = FixedAngle(FixedDiv(FixedDiv(FixedMul(s.GSrailZdist, FRACUNIT), s.GSrailXYlength), 1200)) --WTF
			--local Hdist = R_PointToDist2(X, Y, MX, MY)
			ZDELTA = R_PointToAngle2(0, 0, s.GSrailXYlength or 0, s.GSrailZdist)
			
			if ( abs(ZDELTA) >=  ANG1/2) //((s.GSrailXYlength>64<<16) and ANG1 or ANG1/9) + (ANG1/6) )
				local zdelt = abs(ZDELTA/ANG1) 
--				RAIL.print("Slope angle for rail "+s.GSrailnum+" to "+m.GSrailnum+": "+(AngleFixed(ZDELTA)/FRACUNIT))
				s.renderflags = $|RF_SLOPESPLAT
				P_CreateFloorSpriteSlope(s)
				s.floorspriteslope.o = {x = X, y = Y, z = Z}
				s.floorspriteslope.xydirection = RAILANG --SRB2 generates the slope backwards? Uhh, did I reverse the sprite? Oh well.
				s.floorspriteslope.zangle = ZDELTA --Set the rail's slope angle
			else		
				ZDELTA = 0
				if s.floorspriteslope
					P_RemoveFloorSpriteSlope(s) --Should never be necessary, but just incase.
				end			
			end
			
			--Now modify the rail's vertical hitbox to allign better with the slope.
			--This method only works because we split them into segments no longer than 120 FRACUNITs.
			local ADDHITBOX = FixedInt(ZDELTA)*(FRACUNIT/-100) --If it works, it works!
			if (ADDHITBOX >= 0)
				if not s.GShangrail --Don't add a top hitbox if it's a hangrail! (Flipped rails do not exist, only flipped grind behaviour!)
					TOPADD = TOPADD and $+ADDHITBOX or ADDHITBOX
				end
			else
				BOTTOMADD = s.GSrailbottomhitbox and $+ADDHITBOX or ADDHITBOX
			end
			if (abs(s.GSrailZdist or 0) > (s.GSrailXYlength*3/2)+(31<<16)) --For vertical rails, massively stretch vertical hitboxes.
				if (m.z < s.z)
					BOTTOMADD = $-max(4<<16, abs(s.GSrailZdist)-s.height)
				else
					TOPADD = $+max(4<<16, (abs(s.GSrailZdist)-s.height))
				end
			end
		elseif s.floorspriteslope
			P_RemoveFloorSpriteSlope(s)
		end
	end
	s.GSrailTrueAngle,s.angle = RAILANG, RAILANG --Face the next rail! RAIL.ANG will try prioritizing GSrailTrueAngle
	s.GSrailtophitbox = TOPADD --Values to add and subtract for vertical hitbox.
	s.GSrailbottomhitbox = BOTTOMADD
	s.GSrailZDelta = ZDELTA --Angle value for the rail's "slope" if one exists. Might remain 0.
	s.radius = 64<<16 --Rail hitbox. Scale is irrelevant.
	if type(s.GSsegments)!="table"
		s.GSsegments = {} --Save all segments so we may mess with them later using Lua and the Rail Director.
	end
	local SEGS1,SEGSnum
	local DIVNO = 11520 --Bigger ends us with a shorter rail. Smaller gives a longer rail.

	--Connect them all with Rail Segments!	
	if ZLIMIT==nil
		SEGS1 = FixedDiv(FixedMul(s.GSrailXYlength, 100), DIVNO) --SEGS1  =R_PointToDist2(0, 0, s.GSrailXYlength, s.GSrailZdist)/120
		SEGS1 = max($,1) --At least be 1!
		SEGSnum = min(FixedInt(SEGS1)-1, 544) --No point adding more than 544 segments, that'd exceed the map limit.
	else
		SEGS1 = max(1, ZLIMIT/120) --Top sprites only, 120 pixels, soooo exactly this.
		SEGSnum = max(SEGS1/FRACUNIT, 1)
	end

	--Okay, so SRB2 just stops rendering sprites behind you if stretched out, so spriteyscale it out of the question.
	--s.spriteyscale = SEGS -- s.GSrailXYlength/120, basically. 120 is the rail's length in pixels.
	if (SEGSnum > 1) //or (SEGS1 > 75000) --If there's more than 1 segment needed, make a table.
		local lastseg,seg
		for i = 1,SEGSnum
			if (ZLIMIT!=nil) --Slightly altered version...
				seg = RAIL.ConnectRailsV3(s, i, lastseg)
			else
				seg = RAIL.ConnectRails3(s, i, lastseg) --Should I use SEGS or SEGS1?
			end
			if seg and seg.valid
				lastseg = seg
				table.insert(s.GSsegments, seg) --The first segment is the main rail, so only add the next segments to this table!
				if i==SEGSnum and ZLIMIT==nil --The final segment!
					seg.spriteyscale = FixedDiv(FixedMul(FixedHypot(seg.x-MX, seg.y-MY), 100), DIVNO)
					seg.spriteyscale = max(abs($),1)
					seg.color,seg.colorized = s.color, s.colorized
					if seg.GSrailsideL and seg.GSrailsideL.valid
						RAIL.ScaleRailRollAngle(s, seg, seg.GSrailsideL)
					end
				end	
			end
		end 
	else --If it's a VERY short rail with only 1 segment, then use spriteyscale to shorten it appropriately. 
		if (ZLIMIT!=nil) --Vertical rail?
			s.spriteyscale = ZLIMIT/120 --Simple enough...
		else
			s.spriteyscale = SEGS1	--Non vertical rail stretch/squash.
		end
	end	
	--Now attach the sides, assuming we're not using a vertical rail.
	if ZLIMIT==nil --We're doing it here so the scaling was already done, btw.
		s.GSrailsideL = RAIL.ScaleRailRollAngle(s, seg, RAIL.ConnectRails4(s, s, -1))
		
		if not (SEGSnum > 1) and (s.GSrailXYlength >= 280<<16) and (s.spriteyscale > 152000) --Is it stretched out? 
		and not (s.GSloopflags & 2097152) and not (s.flags & MF_NOCLIPTHING)
			local STRETCH = RAIL.ConnectRails3(s, 1, nil, "STRETCH") --Spawn an invisible hitbox segment.
			if STRETCH and STRETCH.valid
				STRETCH.state = S_INVISIBLE
				STRETCH.flags = $|MF_NOSECTOR
				STRETCH.flags2 = $|MF2_DONTDRAW
				STRETCH.sprite = SPR_NULL
				STRETCH.GSstretchhitbox = STRETCH --You are the stretchbox!
				STRETCH.GSmainrail = s
				
				P_SetOrigin(STRETCH, 
				s.x-FixedMul(s.GSrailXYlength/2, cos(RAILANG)), 
				s.y-FixedMul(s.GSrailXYlength/2, sin(RAILANG)), s.z+(s.GSrailZdist/2) )
				s.GSstretchhitbox = STRETCH
			end
		end		
	end
//	RAIL.print("\x82 CONNECTRAILS2:"+" Mission complete for rail:\x83 "+s.GSrailnum+" to rail "+s.GSnextrail.GSrailnum)
	return true --Done!
end

--How big is the difference from one angle to another? This function returns the answer!
RAIL.AngleDifference = function(ANG_1, ANG_2, Rformat, maxANG, divide)
	local diff = (ANG_1-ANG_2)
	if not diff return 0,1 end --The result's 0. ...that was easy.	
	
	diff = (diff > ANGLE_180) and InvAngle(InvAngle(diff)/((divide) or 1)) or diff/((divide) or 1)	
	
	--This function NEVER gives negative results. 
	--ANGLE_270 or -ANGLE_90 for example would return as ANGLE_90, with PLUS simply becoming -1.
	local PLUS = (diff < 0) and -1 or 1
	if diff < 0 diff = InvAngle(diff) end
	
	if maxANG --Return a maximum value?
		diff = min(maxANG, diff)
	end
	if Rformat != 2
		if Rformat == 1 --Turns the result into FRACUNITs. From 0 to 180*FRACUNIT
			diff = AngleFixed(diff)
		elseif Rformat == 0 --Turns result into regular numbers. From 0 to 180.
			diff = (diff >= ANG1) and diff/ANG1 or 0
		end
	end
	return diff, PLUS --
end

--Next, add looping segments that connect the rails! These act as the main visuals and also the hitboxes.
--ALL values must be entered for this to work!
RAIL.ConnectRails3 = function(rail, segnum, lastseg, STRETCHHB)
	if not (rail and rail.valid) 
		RAIL.print("Failed to put segment down. No rail.", 7) return --
	end 
	local RAILANG = RAIL.ANG(rail)
	--Spawn! One sprite is exactly 120 pixels, AKA 120 FRACUNITs in SRB2 dimensions.
	local m = P_SpawnMobjFromMobj(lastseg or rail, FixedMul(-120<<16, cos(RAILANG)), FixedMul(-120<<16, sin(RAILANG)), 0, MT_GSRAILSEG)
	if type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
	/*	if lastseg and lastseg.valid
			if lastseg.GSrailsideL and lastseg.GSrailsideL.valid
				P_RemoveMobj(lastseg.GSrailsideL)
			end
		elseif rail.GSrailsideL and rail.GSrailsideL.valid
			P_RemoveMobj(rail.GSrailsideL)
		end	*/
		m.state = S_GSRAIL
		m.GSmainrail = rail
		m.sprite = rail.sprite
		m.frame = rail.frame
		m.GSrailTrueAngle = rail.GSrailTrueAngle
		m.angle = RAILANG
		m.GSsegnum = segnum
		m.GSrailnum = rail.GSrailnum
		
		m.flags = ($|MF_SOLID|MF_SCENERY) & ~MF_NOCLIPTHING
		m.flags,m.flags2 = rail.flags, rail.flags2 --We can always set the sprite to SPR_NULL for invisible grindable rails.
		m.renderflags = rail.renderflags --1st time
		m.z = RAIL.GetRailZ(rail, m, 0)
		if (rail.floorspriteslope) --Mimmick slope values if needed.
			P_CreateFloorSpriteSlope(m)
			m.floorspriteslope.xydirection = rail.floorspriteslope.xydirection
			m.floorspriteslope.zangle = rail.floorspriteslope.zangle
			m.floorspriteslope.o = {x = rail.floorspriteslope.o.x, y = rail.floorspriteslope.o.y, z = rail.floorspriteslope.o.z}
		end
		m.GSrailtophitbox = rail.GSrailbottomhitbox or 0
		m.GSrailbottomhitbox = rail.GSrailbottomhitbox or 0
		m.GScantgrind = true --Any mobjs with GScantgrind cannot grind. Naturally, rails can't grind themselves.
		
		m.GSspritewidth,m.spritexoffset = rail.GSspritewidth, rail.GSspritewidth
		m.GShangrail = rail.GShangrail 
		if (STRETCHHB!="STRETCH")
			m.color,m.colorized = rail.color,rail.colorized
			m.renderflags = rail.renderflags --2nd time
			if rail.GSinvisiblerail or rail.sprite == SPR_NULL --Become invisible as well!
				m.sprite = SPR_NULL
				m.GSinvisiblerail = true
				m.GSrailtophitbox = m.GSrailtophitbox and $+4*FRACUNIT or 4*FRACUNIT
			end
			if m.GSrailsideL and m.GSrailsideL.valid --The rail can only have 1 side, obviously!
				P_RemoveMobj(m.GSrailsideL)
			end
			
			m.GSrailsideL = RAIL.ConnectRails4(rail, m, -1) --Now put in the sides!
			if rail.GSrenderstyle == "notop" --Don't draw the splat? (Segment needs to continue existing for its hitbox, though!)
				rail.flags2 = $|MF2_DONTDRAW
			end
			local INFO = rail.GSinfo
			if INFO
				if INFO.renderflags
					m.renderflags = $|(INFO.renderflags)
				end
				if INFO.blendmode
					m.blendmode = INFO.blendmode
				end			
				if INFO.TOPsprite and not (rail.GSinvisiblerail)
					m.sprite = INFO.TOPsprite
					m.frame = INFO.TOPstartframe or A --Just to be sure.
				end
				if INFO.translucent
					m.frame = ($ & ~FF_TRANSMASK)|(INFO.translucent)
				end
			end
		end
		if (rail.GSloopflags & 2097152)
			m.flags = ($|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP) & ~MF_SOLID
		end
	--	RAIL.print("Put Segment "+segnum+" down. Mainrailnum = "+rail.GSrailnum, 7)
		return m --
	end	
end

--V3 is a vertical variant of ConnectRails3.
RAIL.ConnectRailsV3 = function(rail, segnum, lastseg)
	if not (rail and rail.valid) RAIL.print("Failed to put segment down.", 7) return end --
	
	local RAILANG = RAIL.ANG(rail)
	
	--Spawn! One sprite is exactly 120 pixels, AKA 120 FRACUNITs in SRB2 dimensions.
	local m = P_SpawnMobjFromMobj(lastseg or rail, 0, 0, 0, MT_GSRAILSEG)
	if type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
		m.state = S_GSRAIL
		m.GSmainrail = rail
		m.sprite = rail.sprite
		m.frame = rail.frame
		m.GSrailTrueAngle = rail.GSrailTrueAngle
		m.angle = RAILANG
		
		m.GSsegnum = segnum
		m.GSrailnum = rail.GSrailnum
		
		m.GSrailtophitbox = rail.GSrailbottomhitbox or 0
		m.GSrailbottomhitbox = rail.GSrailbottomhitbox or 0
		m.GScantgrind = true --Any mobjs with GScantgrind cannot grind. Naturally, rails can't grind themselves.
		
		m.GSspritewidth,m.spritexoffset = rail.GSspritewidth, rail.GSspritewidth --Is this a BIG issue??
			
		m.flags = ($|MF_SOLID|MF_SCENERY|MF_NOBLOCKMAP) & ~MF_NOCLIPTHING
		m.flags,m.flags2 = rail.flags, rail.flags2 --We can always set the sprite to SPR_NULL for invisible grindable rails.
		
		m.color,m.colorized = rail.color,rail.colorized
		m.renderflags = rail.renderflags
		m.GShangrail = rail.GShangrail 
		m.GSVertRail = rail.GSVertRail
		if rail.GSinvisiblerail or rail.sprite==SPR_NULL --Become invisible as well!
			m.sprite = SPR_NULL
			m.GSinvisiblerail = true
			m.GSrailtophitbox = m.GSrailtophitbox and $+4*FRACUNIT or 4*FRACUNIT
		end
		if rail.GSinfo
			local INFO = rail.GSinfo
			if INFO.renderflags
				m.renderflags = $|(INFO.renderflags)
			end
			if INFO.blendmode
				m.blendmode = INFO.blendmode
			end			
			if INFO.TOPsprite and not (rail.GSinvisiblerail)
				m.sprite = INFO.TOPsprite
				m.frame = INFO.TOPstartframe or A --Just to be sure.
			end
			if INFO.translucent
				m.frame = ($ & ~FF_TRANSMASK)|(INFO.translucent)
			end
		end
		if m.floorspriteslope
			P_RemoveFloorSpriteSlope(m) --Should never be necessary, but just incase.
		end
		if (rail.GSloopflags & 2097152) --Cannot touch!
			m.flags = ($|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP) & ~MF_SOLID
		end
		if rail.GSnextrail and (rail.GSnextrail.z < rail.z)
			m.z = $-(120<<16)
		else
			m.z = $+(120<<16)
		end
		return m --
	end	
end

RAIL.ConnectRails4 = function(rail, segment, try)
	if not (segment and segment.valid and rail and rail.valid) --We're deep in, there's alot to check over now...
	or segment.GSrailsideL and segment.GSrailsideL.valid
	or not (rail.GSnextrail and rail.GSnextrail.valid)	
		RAIL.print("Failed to put railside down.", 11) return --
	elseif try==1
		return --Lets forget about double-sided rails for now.
	elseif rail.GSrenderstyle == "nosides" or rail.GSinvisiblerail or rail.sprite == SPR_NULL
		if segment.GSrailsideL and segment.GSrailsideL.valid --We only need one.
			P_RemoveMobj(segment.GSrailsideL)
		end			
		return --Don't create sides at all to save on resources if this flag is active.
	end
	
	local RAILANG = RAIL.ANG(rail)
	
--	Unused max quality version VVVV
--	local m = P_SpawnMobjFromMobj(segment,
--	FixedMul(segment.GSspritewidth/2, cos(RAILANG+(ANGLE_90*try))),
--	FixedMul(segment.GSspritewidth/2, sin(RAILANG+(ANGLE_90*try))), 0, MT_GSRAILSEG)	
	
	local m = P_SpawnMobjFromMobj(segment,0,0,0,MT_GSRAILSEG)
	if m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
		if segment.GSrailsideL and segment.GSrailsideL.valid --We only need one.
			P_RemoveMobj(segment.GSrailsideL) --Only one at a time.
		end
		m.state = S_GSRAIL
		m.GSmainrail = rail
		m.GScantgrind = true
		m.GSsegment = segment
		m.GSrailnum = rail.GSrailnum
		if segment.floorspriteslope and segment.floorspriteslope.zangle --Mimmick slope values with rollangle?
			if (abs(segment.floorspriteslope.zangle) >= ANG2) --First lag, then memory leaks, then 5 degree rotation limitations? SRB2 rollangle is such trash.
				if (abs(segment.floorspriteslope.zangle) >= ANG1*64)
					P_RemoveMobj(m) return --nvm.
				else
					m.rollangle = segment.floorspriteslope.zangle
				end
			end
		end
		m.GSrailTrueAngle = RAILANG
		m.angle = segment.angle
		m.color,m.colorized = segment.color,segment.colorized
		m.frame = ((m.sprite==SPR_GSRL) and B or A)|FF_PAPERSPRITE
		m.renderflags = RF_PAPERSPRITE
		m.flags2 = segment.flags2 & ~MF2_SPLAT --We are NOT a splat. We're a papersprite.
		m.flags = (segment.flags & ~(MF_SOLID|MF_SPECIAL))|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOTHINK
		local INFO = rail.GSinfo
		if INFO
			if INFO.renderflags
				m.renderflags = $|(INFO.renderflags)
			end
			if INFO.blendmode
				m.blendmode = INFO.blendmode
			end			
			if INFO.SIDEsprite
				m.sprite = INFO.SIDEsprite
				m.frame = INFO.SIDEstartframe or A --Actually important.
			end
			if INFO.translucent
				m.frame = ($ & ~FF_TRANSMASK)|(INFO.translucent)
			end
		end
		return m --
	end
end

--Vertical version of Connect Rails 4, just like with V3.
RAIL.ConnectRailsV4 = function(rail, segment)
	if not (segment and segment.valid and rail and rail.valid)
	or rail.GSrenderstyle == "nosides" or (rail.GSinvisiblerail) or (rail.sprite == SPR_NULL)
	or not (rail.GSnextrail and rail.GSnextrail.valid)
		return --
	end
	if segment.GSrailsideL and segment.GSrailsideL.valid --We only need one.
		P_RemoveMobj(segment.GSrailsideL)
	end
	
	local RAILANG = RAIL.ANG(rail)
	local m = P_SpawnMobjFromMobj(segment, 0, 0, 0, MT_GSRAILSEG)
	if type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
		m.GSrailTrueAngle = RAILANG
		m.angle = RAILANG
		m.dispoffset = segment.dispoffset
		m.color,m.colorized = segment.color,segment.colorized
		m.frame = ((m.sprite==SPR_GSRL) and B or A)|FF_PAPERSPRITE
		
		local ADD = -(6<<16)
		if rail.GSinfo
			local INFO = rail.GSinfo
			if INFO.renderflags
				m.renderflags = $|((INFO.renderflags)&~(FF_FLOORSPRITE|RF_SLOPESPLAT|RF_NOSPLATROLLANGLE|RF_NOSPLATBILLBOARD))
			end
			if INFO.blendmode
				m.blendmode = INFO.blendmode
			end			
			if INFO.SIDEsprite
				m.sprite = INFO.SIDEsprite
				m.frame = INFO.SIDEstartframe or A --Actually important.
			end
			if INFO.translucent
				m.frame = ($ & ~FF_TRANSMASK)|(INFO.translucent)
			end
			if INFO.hangZoffset
				ADD = $+(INFO.hangZoffset<<16) --For sideways allignment.
			end		
		end
		if m.floorspriteslope
			P_RemoveFloorSpriteSlope(m) --Should never be necessary, but just incase.
		end
		m.flags2 = segment.flags2 & ~MF2_SPLAT --We are NO splat. We're a papersprite!
		m.flags = (segment.flags & ~(MF_SOLID|MF_SPECIAL))|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOBLOCKMAP|MF_NOTHINK
		m.renderflags = $|RF_PAPERSPRITE
		m.spritexscale,m.spriteyscale = segment.spritexscale, segment.spriteyscale
		m.spritexoffset,m.spriteyoffset = 0,0	
		
		local SETX = FixedMul(ADD, cos(RAILANG))
		local SETY = FixedMul(ADD, sin(RAILANG))
		local SETZ = 0
		if rail.GSnextrail and (rail.GSnextrail.z > rail.z)
			SETZ = $-(99<<16)
			m.frame = $|FF_VERTICALFLIP|FF_HORIZONTALFLIP --Face the right way, pls.
		else
			SETX,SETY = -$,-$
			SETZ = $-(199<<16)
			m.frame = ($ & ~FF_HORIZONTALFLIP)|FF_VERTICALFLIP 
		end
		m.rollangle = ANGLE_90	
		P_SetOrigin(m, m.x+SETX, m.y+SETY, segment.z+SETZ)
		return m --
	end	
end

RAIL.ScaleRailRollAngle = function(rail, seg, side)
	if not (side and side.valid) return end
	local t = rail if seg and seg.valid t = seg end --Should "t" be the rail itself or a rail segment? Prefer segment here.
	if side.rollangle
		local ZDELTA = side.rollangle
		if t.floorspriteslope and t.floorspriteslope.zangle
			ZDELTA = t.floorspriteslope.zangle
		end
		if (abs(ZDELTA) < ANG20) --Initial calculation for unsteep slopes. Highly preferred method.
			local ogdiff = (FixedDiv(FixedMul(rail.GSrailZDelta, t.spriteyscale), FRACUNIT)-rail.GSrailZDelta) or 1 
			side.spritexscale = FixedDiv(FixedMul(t.spriteyscale, FRACUNIT), FRACUNIT)+abs(ogdiff/6100) //(side.rollangle<0) and 6300 or 
			side.rollangle = $+ogdiff //(($ < 0) and FixedDiv(FixedMul(ogdiff, 17), 20) or ogdiff)	
		else --For steeper slopes, scale the whole rail. Comes with downsides, but at least it connects...
			side.scale = R_PointToDist2(0,0, tan(t.floorspriteslope and t.floorspriteslope.zangle or rail.GSrailZDelta or side.rollangle), t.spriteyscale*61/60)
			side.spritexscale,side.spriteyscale = 1<<16, 1<<16
			side.GSslopescale = side.scale
			rail.GSslopescale = side.scale
		end	
	else
		side.spritexscale = t.spriteyscale
		side.spriteyoffset = $-FixedDiv(FixedMul(side.spritexscale, 19), 10)
	end
	side.flags = ($|MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOTHINK) & ~(MF_SOLID|MF_SPECIAL)
	return side --
end

--Reorder the given rail table from furthest to closest in relation to PrefRail.
RAIL.OrderRailMobjs = function(PrefRail, SameOptions, gimmetable)
	local UpdatedTable = {}
	
	--Setup! Calculate all rail distances to our preferred nextangle rail.
	for NotiPairs = 1,#SameOptions
		local m = SameOptions[NotiPairs]		
		m.PrefDist = FixedHypot(FixedHypot(m.x-PrefRail.x, m.y-PrefRail.y), m.z-PrefRail.z)
		m.GStemporder = nil
	end
	
	--Now EVERY rail goes through EVERY rail in the table.
	for i = #SameOptions,1,-1
	local m = SameOptions[i]
		m.GStemporder = #SameOptions	
		
		for kek = 1,#SameOptions
		local t = SameOptions[kek]
			if m.PrefDist < t.PrefDist
				m.GStemporder = $-1
			end
		end
		
		RAIL.print("\x88 m.GStemporder: "+m.GStemporder+" closest prefdist = "+FixedInt(m.PrefDist or 0), 15)		
		if gimmetable
			UpdatedTable[m.GStemporder] = m
			RAIL.print("\x8c INSERTING!  m.GStemporder: "+m.GStemporder+" UpdatedTable thus far = "+(#UpdatedTable), 15)	
		end	
	end
	if gimmetable
		return UpdatedTable --Now throw it back to ConnectRails1 if requested
	end
end

--Reorder the rail director's list of stuff 
/*
RAIL.OrderRailMobjs2 = function(director, oTable)
	local UpdatedTable = {}
	local Table = {}
	for i = 1,#oTable
	local m = oTable[i]
		table.insert(Table, 1, m)
	end
	--Set our order according to our railnumber.
	for i = 1,#Table --Now update the stupid table.
	local m = Table[i]
		if m.GSrailnum != nil
		m.tempnum = abs(#table)
			for kek = 1,#Table
				local t = Table[kek]
				if t and t.GSrailnum != nil 
					if m.GSrailnum < t.GSrailnum
						m.tempnum = $-1
						print("Table = "+#table+" New Tempnum = "+abs(m.tempnum))
					end
				end
			end
			UpdatedTable[abs(m.tempnum)] = m
			m.heyivebeenaddedcool = true
		end
	end
	--Squeeze stragglers without a railnum or the same railnum at the end of the table.
	for i = 1,#Table
		local m = Table[i]
		if m.GSrailnum == nil
		or not m.heyivebeenaddedcool
			table.insert(UpdatedTable, 1, m)
		end
	end
	print("Old table = "+#Table+"Final Table = "+#UpdatedTable)
	director.GSrailtable = UpdatedTable
end */

--Scan object "m", and either add it to a relevant table, or ignore it.
RAIL.DirectorScanObject = function(s, m, SEARCH) 
	if (m.type==MT_GSRAIL)
		RAIL.AddRail(s, m) --Add another rail to the table.
	elseif (m.type == MT_GSRAILLIGHT) or (m.type == MT_GSRAILDYE) or (m.type == MT_GSRAILPOLE) --Accessory objects!
		return m --	Add these after everything was set up.	
	elseif (m.flags & (MF_NOSECTOR|MF_AMBIENT|MF_NIGHTSITEM|MF_BOXICON))
	or (m.flags & (MF_MISSILE|MF_SPRING|MF_PAIN|MF_FIRE|MF_SCENERY)) and not (m.flags & (MF_ENEMY|MF_BOSS))		
		m.GScantgrind = true --Don't let guys with these properties grind!
	elseif not ((m.flags & (MF_ENEMY|MF_PUSHABLE|MF_SOLID|MF_BOSS)) or m.player) --Just so we don't have to do this later...
		m.GScantgrind = true --Don't let guys without these properties grind!
	else
		local spn = m.spawnpoint
		if (spn) and type(spn)=="userdata" and userdataType(spn)=="mapthing_t" and spn.valid
			if (udmf)~=true and (spn.options & MTF_EXTRA)
			or (udmf)==true and (spn.pitch == 7 or spn.roll == 7)  
				m.GScantgrind = true --You can manually prevent them from grinding too!
			end
		end
	end
end

--Scan for and attach everything found in Rail Director's extra stuff table. "s" must be the Rail Director.
RAIL.ScanAndAttach = function(s)
	RAIL.print("\x8a Going through all "+s.GStotalrails+" rails...")
	local TABLE = s.GSextrastufftable
	if not (TABLE and #TABLE) return end --

	--Apply extra stuff mappers placed down!
	for _,m in pairs(TABLE)
		local m = TABLE[i]
		if not (m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid) continue end --
		
		local REMOVEIT
		if (m.type == MT_GSRAILDYE) --This thing dyes your entire rail into any color!				
			REMOVEIT = true
			local XYDIST = 20*max(FRACUNIT, m.scale)
			if m.GSraildye != nil and not (m.GSraildye > #skincolors-1)
				searchBlockmap("objects", function(ref, t)
					if t.valid and (t.type == MT_GSRAIL) and (FixedHypot(t.x-m.x, t.y-m.y) < XYDIST)
						local GS = m.GSgrind if GS==nil RAIL.SetupMobjRailVars(m) GS=m.GSgrind end
						t.GSraildye = m.GSraildye RAIL.print("Turn color "+m.GSraildye)
						GS.raildye = t.GSraildye
						t.color,t.colorized = m.GSraildye,m.colorized
						GS.myrail,m.GSmyrail = t,t return true --
					end
				end, m, m.x-XYDIST, m.x+XYDIST, m.y-XYDIST, m.y+XYDIST)	
			end
			if m.valid --Thanks! You can go home now!
				m.fuse = 3
				m.flags2 = $|MF2_DONTDRAW
			end
		elseif (m.type == MT_GSRAILLIGHT)
			if m.GSlineoflights
				local XYDIST = 50*max(FRACUNIT, m.scale)
  				searchBlockmap("objects", function(ref, t)
					if t.valid and (t.type == MT_GSRAIL) and (FixedHypot(t.x-m.x, t.y-m.y) < XYDIST)
						local GS = m.GSgrind if GS==nil RAIL.SetupMobjRailVars(m) GS=m.GSgrind end
						GS.myrail,m.GSmyrail = t,t return true --
					end
				end, m, m.x-XYDIST, m.x+XYDIST, m.y-XYDIST, m.y+XYDIST)							
			end
		elseif (m.type == MT_GSRAILPOLE)
			local XYDIST = 28*max(FRACUNIT, m.scale)
  			searchBlockmap("objects", function(ref, t)
  				if t.valid and (t.type == MT_GSRAIL) and (FixedHypot(t.x-m.x, t.y-m.y) < XYDIST)
					local GS = m.GSgrind if GS==nil RAIL.SetupMobjRailVars(m) GS=m.GSgrind end
					if m.GSraildye != nil and not (m.GSraildye > #skincolors-1)
						local GS = m.GSgrind if GS==nil RAIL.SetupMobjRailVars(m) GS=m.GSgrind end
						t.GSraildye = m.GSraildye RAIL.print("Tur pole color:\x81 "+m.GSraildye)
						GS.raildye = t.GSraildye
						t.color,t.colorized = m.GSraildye,m.colorized
					end	
					GS.myrail,m.GSmyrail = t,t return true --
				end
			end, m, m.x-XYDIST, m.x+XYDIST, m.y-XYDIST, m.y+XYDIST)						
		end
		if REMOVEIT and m.valid
			if not (m.fuse and (m.fuse < 3))
				m.fuse = 3
			end
			m.flags2 = $|MF2_DONTDRAW
		--	table.remove(s.GSextrastufftable, m)
		end
	end
end

--Adds "m", a rail, to the Director Table. "s" must be the Rail Director.
RAIL.AddRail = function(s, m)
	if m and type(m)=="userdata" and userdataType(s)=="mobj_t" and not (m.GSrailsetup)
		s.GStotalrails = s.GStotalrails and $+1 or 1
		RAIL.ActivateRail(m)
		if m and m.valid --Still valid, I hope?
			table.insert(s.GSrailtable, m)
		end
	--	RAIL.print("\x89 DIRECTOR: Found Rail. TotalRails is now: "+s.GStotalrails)	
	end
end

--Adds "m", an accessory, to the Director's "ExtraStuff" Table. "s" must be the Rail Director.
RAIL.AddAccessory = function(s, m)
	s.GStotalextras = s.GStotalextras and $+1 or 1
	table.insert(s.GSextrastufftable, m)
	m.GScantgrind = true
end

--We got a brand new grind rail director, let's have it assemble every rail in the map and get this show started.
RAIL.SetupRailDirector = function(s)
	if not (s and type(s)=="userdata" and userdataType(s)=="mobj_t" and s.valid) 
		return false --
	end
	if s.GStotalrails==nil
		s.GStotalrails = 0  --Amount of rails we'll be counting
		if s.GStotalextras==nil
			s.GStotalextras = 0 --Extra stuff like rail lights and such.
		end
	end
	if s.GSrailtable == nil
		s.GSrailtable = {}
		if s.GSextrastufftable == nil s.GSextrastufftable = {} end
	end
	local EXTRAS, EXNUM = {}, 0 --Accesory count.
	GS_RAILS_DIRECTOR = s --Just incase this didn't come through.
	
	if s.GS_SetupRailDirector!=true
		--Start by adding all map-spawned rails to the Rail Director!
		--Also fill up the accesory object table, and add some properties to mobjs that shouldn't grind ever.
		for m in mobjs.iterate(mobjs)
			if m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid
				local ACC = RAIL.DirectorScanObject(s, m)
				if (ACC!=nil)
					EXNUM = $+1
					EXTRAS[EXNUM] = ACC
				end
			end
		end
	end
	local TOTALSEGS = 0
	local TOTALINVIS = 0
	local TOTALINVISSEGS = 0
	local TABLE = s.GSrailtable
	if TABLE and s.GStotalrails and (#TABLE) --Okay, everything's saved and set up. Now...connect the rails!
	
	--	RAIL.print("\x8a Going through all "+s.GStotalrails+" rails...")
	--	RAIL.OrderRailMobjs2(s, s.GSrailtable)
	--	for i,m in pairs(s.GSrailtable)
	--		RAIL.print("\x89 DIRECTOR: Got unconnected rail "+m.GSrailnum+". Attempting to find rail "+(m.GSrailnum+1)+" to connect with it.")		
	--		RAIL.ConnectRails1(m, s, true)
	--	end
	
	--	local TABLE2 = {}

		local DOPITCH = {}
		--Connect the rails! First wave!
		for _,m in pairs(TABLE)
			if m and m.valid
			--	RAIL.print("\x89 DIRECTOR: Got unconnected rail "+m.GSrailnum+". Attempting to find rail "+((m.GSrailnum or 0)+1)+" to connect with it.")
				RAIL.ConnectRails1(m, s)
				if m and m.valid
					if m.GSnextrail == nil
						if m.GSConnectToPitch
							table.insert(DOPITCH, m)
						else
							m.flags2 = $|MF2_DONTDRAW
							m.shadowscale = 0
							if m.GSraildye
								m.color = m.GSraildye
							end
						end  
					end
				--	if m.GSrailnum!=nil
				--		TABLE2[m.GSrailnum+(3<<16)] = m
				--	end 
				end 
			end
		end
				
		if DOPITCH and (#DOPITCH) --Second connect! DOPITCH == true --Second wave.
			for _,m in pairs(DOPITCH)
				if m and m.valid and (m.GSConnectToPitch)
					RAIL.ConnectRails1(m, s, nil, m.GSConnectToPitch)
					if m and m.valid and not (m.GSnextrail and m.GSnextrail.valid)
						m.flags2 = $|MF2_DONTDRAW
						m.shadowscale = 0
						if m.GSraildye m.color = m.GSraildye end
					end
				end
			end
		end
	/*	if TABLE2 -- and #TABLE2==#TABLE
			print("DONE and ORDERED! Now using TABLE 2.")
			s.GSrailtable = TABLE2
			TABLE = s.GSrailtable
			TABLE2 = nil
		end */
		
		--Go through the table again now that everything is properly connected. Third wave!
		local NUM = 0
		for _,m in pairs(TABLE)
			if not (m and type(m)=="userdata" and m.valid) continue end --
			TOTALSEGS = $+1
			m.GScheckwalljump = nil

			if m.GS_windrail or m.GS_elementrail --These are special cases added always.
				if s.GSanimator == nil s.GSanimator = {} end if s.DO==nil s.DO = P_RandomRange(0,4) end
				table.insert(s.GSanimator, m)
			end
			--Do we need to animate any rails? Put those in a special table.
			if m.GSnextrail and m.GSnextrail.valid
				NUM = $+1
				local NEXT = m.GSnextrail
				NEXT.colorized = m.colorized
				if m.GSraildye
					NEXT.GSraildye = m.GSraildye
					NEXT.color = m.color 
				end
				if m.GSrenderstyle
					NEXT.GSrenderstyle = m.GSrenderstyle
				end
				if m.GSrailtrans
					m.frame = ($ & ~FF_TRANSMASK)|m.GSrailtrans
					NEXT.GSrailtrans = m.GSrailtrans
					NEXT.frame = ($ & ~FF_TRANSMASK)|m.GSrailtrans
				end
				if m.top_endframe or m.side_endframe --Should we animate any rails? Put those in a special table.
					if not m.GSinvisiblerail
					and not m.GS_elementrail and not m.GS_windrail --These were added seperately earlier!
						if (m.sprite!=SPR_NULL) and (m.sprite!=SPR_GSRL)
							if s.GSanimator == nil s.GSanimator = {} end if s.DO==nil s.DO=P_RandomRange(0,3) end
							table.insert(s.GSanimator, m)
						end
					end
				end
				if m.GSVertRail --Vertical rail.
					if m.floorspriteslope P_RemoveFloorSpriteSlope(m) end
					m.frame = $|FF_PAPERSPRITE|FF_VERTICALFLIP & ~FF_FLOORSPRITE
					m.renderflags = $|RF_PAPERSPRITE & ~(RF_SLOPESPLAT|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_ABSOLUTEOFFSETS)
					m.flags2 = $ & ~MF2_SPLAT
					m.angle = $+ANGLE_90
					m.spritexoffset,m.spriteyoffset,m.dispoffset = 0,0,-444
					m.GSrailsideL = RAIL.ConnectRailsV4(m, m)
					if m.GSrailsideL and m.GSrailsideL.valid and (NEXT.z < m.z) and (m.GSrailsideL.z < m.z)
						m.z = m.GSrailsideL.z
					end
					for i = 1,2
						local NEXT = m.GSnextrail if i==2 NEXT = m.GSprevrail end
						if NEXT and NEXT.valid
							if i==1 and not (NEXT.GSnextrail and NEXT.GSnextrail.valid)
							or i==2 and not (NEXT.GSprevrail and NEXT.GSprevrail.valid)
								if NEXT.GSVertRail==nil
									NEXT.GSVertRail = m.GSVertRail
								end
								if NEXT.GSVertRailDOWNANG==nil
									NEXT.GSVertRailDOWNANG = m.GSVertRailDOWNANG
								end
								if NEXT.GSVertRailTopZ==nil
									NEXT.GSVertRailTopZ = m.GSVertRailTopZ
								end
								if NEXT.GSVertRailBotZ==nil						
									NEXT.GSVertRailBotZ = m.GSVertRailBotZ
								end
							end
						end
					end
				end

				local COLOR = RAIL.GrabRailColor(m) --Just to be sure, set color again.
				if (COLOR) or m.GSyMTscale 	--Main rail.
					if (COLOR) and (COLOR!=0) m.GSraildye,m.color = COLOR,COLOR end
					local SIDE = m.GSrailsideL
					if SIDE and SIDE.valid
						if (COLOR) SIDE.color,SIDE.GSraildye = COLOR,COLOR end
						if m.GSyMTscale
							SIDE.spriteyscale = FixedDiv(FixedMul($, m.GSyMTscale), FRACUNIT)
						end
					end
				end
				if m.GSxMTscale --Thinner/thicker rail top?
					m.spritexscale = FixedDiv(FixedMul($, m.GSxMTscale), FRACUNIT)
				end				
				local LOOP = RAIL.LearnLoopType(m)
				if LOOP
					if LOOP.anim==0 and (m.GSloopflags & 131072) and not (m.GSinvisiblerail) and not (m.flags2 & MF2_DONTDRAW)
						m.GSupsidedown = true --Extremely specific use case.
					end
					if LOOP.walljump
						if not (m.GSloopflags & (262144|524288))
							m.radius = $+(15<<16)//min($, 35<<16) --More precise attachment.
						else
							m.radius = $+(42<<16) --Easier attachment, then!
						end
						m.GSwalljumprail = true
						if m.GSnextrail and m.GSnextrail.valid and not (m.GSnextrail.GSnextrail and m.GSnextrail.GSnextrail.valid)
							m.GSnextrail.GSwalljumprail = m.GSwalljumprail
						end
					end
				end
				if (m.GSloopflags & 2097152)
			--	or ((m.GSconveyor or 0) > 0) and not (m.GSnextrail and m.GSnextrail.valid)
			--	or ((m.GSconveyor or 0) < 0) and not (m.GSprevrail and m.GSprevrail.valid)
					m.flags = ($|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_SCENERY|MF_NOTHINK) & ~MF_SOLID
				end
				local SEGS = m.GSsegments
				if SEGS and (#SEGS)
					for _,seg in pairs(SEGS)
						if not (seg and seg.valid) continue end --
						TOTALSEGS = $+1
						if (seg.GSstretchhitbox) continue end --
						local SIDE = seg.GSrailsideL
						if (COLOR) seg.color,seg.GSraildye = COLOR,COLOR end
						if SIDE and SIDE.valid
							if (COLOR) SIDE.color,SIDE.GSraildye = COLOR,COLOR end
							if m.GSyMTscale
								SIDE.spriteyscale = FixedDiv(FixedMul($, m.GSyMTscale), FRACUNIT)
							end
							if m.GSslopescale and not SIDE.GSslopescale 
								SIDE.spriteyscale = m.GSslopescale
								SIDE.scale = m.GSslopescale
								if m.GSrailsideL and m.GSrailsideL.valid and not (m.GSrailsideL.GSslopescale)
									m.GSrailsideL.scale = SIDE.GSslopescale
									m.GSrailsideL.GSslopescale = SIDE.GSslopescale
									m.GSrailsideL.spriteyscale = SIDE.GSslopescale
								end
							end
						end
						if m.GSxMTscale
							seg.spritexscale = FixedDiv(FixedMul($, m.GSxMTscale), FRACUNIT)
						end
						if m.GSVertRail
							if seg.floorspriteslope P_RemoveFloorSpriteSlope(seg) end						
							seg.frame = $|FF_PAPERSPRITE|FF_VERTICALFLIP & ~FF_FLOORSPRITE
							seg.flags2 = $ & ~MF2_SPLAT
							seg.renderflags = $|RF_PAPERSPRITE & ~(RF_SLOPESPLAT|RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_ABSOLUTEOFFSETS)
							seg.angle = $+ANGLE_90 --One of the many reasons we have RAIL.ANG...
							seg.spritexoffset,seg.spriteyoffset,seg.dispoffset = 0,0,-444
							if m.GSwalljumprail
								seg.GSwalljumprail = m.GSwalljumprail
								seg.radius = min($, 32<<16)
							end
							seg.GSrailsideL = RAIL.ConnectRailsV4(m, seg)
						end
						if not (m.GSnextrail and m.GSnextrail.valid) or m.GSrailXYlength==nil or m.GSnextrail==m
							seg.flags2 = $|MF2_DONTDRAW
							seg.sprite = SPR_NULL
							if seg.GSrailsideL and seg.GSrailsideL.valid
								seg.GSrailsideL.flags2 = $|MF2_DONTDRAW
								P_RemoveMobj(seg.GSrailsideL)
							end	
							P_RemoveMobj(seg)
						else
							seg.flags2 = $|MF2_DONTRESPAWN
							seg.momx,seg.momy,seg.momz,seg.pmomz = 0,0,0,0
							seg.tracer,seg.target = nil
							if (m.GSloopflags & 2097152)						
								seg.flags = ($|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_SCENERY|MF_NOTHINK) & ~MF_SOLID
							else --Ensure a couple of flags.
								seg.flags = $|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOGRAVITY
								if (seg.flags & MF_SOLID) and not (seg.flags & MF_NOCLIPTHING) and not (m.GSdisabled)
									seg.flags = $ & ~(MF_NOTHINK|MF_NOBLOCKMAP)
								end		
							end
						end
					end
				else
					m.GSsegments = nil --No need for a table if you've got no segments.
				end	
				if not (m.GSnextrail and m.GSnextrail.valid) or m.GSrailXYlength==nil or m.GSnextrail==m
					m.flags2 = $|MF2_DONTDRAW
					m.sprite = SPR_NULL
					if m.GSrailsideL and m.GSrailsideL.valid
						m.GSrailsideL.flags2 = $|MF2_DONTDRAW
						P_RemoveMobj(m.GSrailsideL)
					end
				elseif m.GSnextrail and m.GSnextrail.valid
					if m.GSrailallignobjects
						RAIL.AllignItemsWithRail(m) --Make sure this goes last. Attach suitable nearby objects to the rail if we were given this property!
					end
					if (m.GSgrindflags & 1048576) and not (m.GSloopflags & 2097152) and not (m.flags & MF_NOCLIPTHING)
						local ghs = P_SpawnMobj(m.x,m.y,m.z, MT_THOK)
						if ghs and ghs.valid
							local snapX = m.x-FixedMul(m.GSrailXYlength/2, cos(m.angle))
							local snapY = m.y-FixedMul(m.GSrailXYlength/2, sin(m.angle))	
							local snapZ = RAIL.GetRailZ(m, ghs, 0, nil, {X=snapX, Y=snapY})
							ghs.flags2 = $|MF2_DONTDRAW|MF2_INVERTAIMABLE
							ghs.GShomingtarget = m
							ghs.tics = -1
							P_MoveOrigin(ghs, snapX, snapY, snapZ-(10<<16)) 
							ghs.flags = ($|MF_NOSECTOR|MF_NOTHINK|MF_NOCLIP|MF_NOGRAVITY) & ~MF_SOLID
							if (m.GSgrindflags & 524288) and not (m.GSconveyor)
								m.height = max($, 60<<16)
								ghs.z = $+(30<<16)
							elseif (abs(m.z-m.floorz) < 42<<16) 
								ghs.z = $+ (((m.GSgrindflags & 524288) and 14 or 2)<<16)
							end
							ghs.scale = ($*3)/2
						end
					end
				end
				m.health = 0
				m.flags = ($|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOGRAVITY) & ~(MF_SHOOTABLE|MF_BOSS|MF_ENEMY)
				m.flags2 = ($|MF2_DONTRESPAWN|MF2_FRET|MF2_BOSSDEAD) & ~(MF2_INVERTAIMABLE|MF2_OBJECTFLIP)
				m.eflags = $ & ~(MFE_SPRUNG|MFE_APPLYPMOMZ)
				m.target,m.tracer,s.GSMapThinged = nil
				m.GSroll,m.GSpitch,m.GSrailsetup = nil
				m.momx,m.momy,m.momz,m.pmomz = 0,0,0,0
				if (m.flags & MF_SOLID) and not (m.flags & MF_NOCLIPTHING) and not (m.GSdisabled)
					m.flags = $ & ~(MF_NOTHINK|MF_NOBLOCKMAP)
				end
				if (m.flags2 & MF2_DONTDRAW) or m.sprite==SPR_NULL or m.GSinvisiblerail
					TOTALINVIS = $+1
				end
			elseif not (m.GSprevrail and m.GSprevrail.valid)
				RAIL.error("\x85"+"Warning:\x80 Rail number[\x82"+(m.GSrailnum or 0)+"\x80"+"] isn't connected to anything! (Spawned\x81 debug TOAD\x80 on location)")
				RAIL.SpawnDebug(m)
				if m.GSrailsideL and m.GSrailsideL.valid P_RemoveMobj(m.GSrailsideL) end P_RemoveMobj(m)
			end
		end
		if (EXNUM) --Now that this done, look to attach special freeslotted objects like rail lights and raildyes, and such.
			for i = 1,EXNUM
				local M = EXTRAS[i]
				if (M) and M.valid
					RAIL.AddAccessory(s, M) --Add accessories at this stage.
				end
			end
		end
		if s.GStotalextras 
			RAIL.ScanAndAttach(s) --Now apply extra stuff mappers placed down!
		end	
	end
/*	for _,m in pairs(TABLE)	--Cleanup phase! Fourth wave! Wave FOUR!!!
		if m and m.valid
			m.health = 0
			m.spawnpoint = nil
			m.flags = $|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOGRAVITY
			m.flags2 = $|MF2_DONTRESPAWN
			m.target,m.tracer = nil
			m.momx,m.momy,m.momz,m.pmomz = 0,0,0,0
		end
	end */
	GS_RAILS_DIRECTOR = s --Just incase this didn't come through.
	if (TOTALSEGS) --Report back the amount of rails and rail segments. Should help mappers keep track of their performance load.
		local TEXT = "\x82"+"GS GRINDRAILS\x80 SETUP COMPLETE! Total rails:\x83"+s.GStotalrails+"\x80"
		if TOTALINVIS
			TEXT = $+"(Invisible:\x88"+TOTALINVIS+"\x80"+")"
		end
		if consoleplayer and consoleplayer==displayplayer and consoleplayer.valid
		and consoleplayer.GSRO and (consoleplayer.GSRO.devmode)
			print("\x82"+"GS GRINDRAILS\x80 SETUP COMPLETE! Total rails:\x83"+s.GStotalrails+"\x80 Total segments:\x8c"+TOTALSEGS)
		end
	end
	return true --
end


--Put down a "Rail Director" on map load. It saves everything we want to remember, and every rail has a link to it.
--Every rail object will have rail.director.(rail being mo) If you want to find a rail, just ask ANY rail segment to do:
--for i,m in pairs(rail.director.GSrailtable)
RAIL.MapLoad = function(gamemap)
	RAIL.lastmap = gamemap
	RAIL.MapReady = 1
end

--Technically spawns before the MapThingAngle.
RAIL.RailMobjSpawn = function(m)
	if m
		if not (m.GStype)
			m.GStype = 0 --Start with a type of 0.
			if not (m.color)
				m.color = SKINCOLOR_RED --Default color	
			end
		end
		if m.GSspritewidth==nil
			m.GSspritewidth = 10<<16
		end
		if m.GSrailTrueAngle==nil
			m.GSrailTrueAngle = m.angle or 0
		end
		if m.GStouchflags==nil m.GStouchflags = 0 end
		if m.GSjumpflags==nil m.GSjumpflags = 0 end
		if m.GSgrindflags==nil m.GSgrindflags = 0 end
		if m.GSloopflags==nil m.GSloopflags = 0 end	
		
		m.GScantgrind = true --Rails cannot grind themselves, shockingly
		m.scale,m.spritexscale,m.spriteyscale = FRACUNIT,FRACUNIT,FRACUNIT --Never play into this, or things get screw quick.
	end
end

--This becomes your rail number. Thankfully, it's not actually saved in angle variants, but 1 to like...31000 or something.
RAIL.MapThingSpawn = function(s, mapthing, SKIPTABLE)
	if not (mapthing and type(mapthing)=="userdata" and userdataType(mapthing)=="mapthing_t" and s) return end --
	s.GSMapThinged = true
	if udmf==true
		local DOARG3 = false
		if userdataType(mapthing.args)=="mapthing.args" or userdataType(mapthing.args)=="mapthing_t.args"
			DOARG3 = true
		else
			local spn = s.spawnpoint
			if type(spn)=="userdata" and userdataType(spn)=="mapthing_t" 
			and (userdataType(spn.args)=="mapthing_t.args" or userdataType(spn.args)=="mapthing.args")
				mapthing,DOARG3 = spn, true print("BUT **THIS** ONE FUCKING WORKS,\x85 WHAT?!?")
			else
				s.GSMapThinged = "TryAgain"  -- print("\x89"+"Mamamia ["+mapthing.angle+"]")
				return --
			end
		end
	/*	or udmf==true and (not mapthing.args or userdataType(mapthing.args)!="mapthing_t.args")
		if s and (SKIPTABLE!=true)
			if TEMPTABLE==nil TEMPTABLE = {} end
			local NUM = max(1, #TEMPTABLE+1)
			table.insert(TEMPTABLE, NUM, m) print("ADDING TEMP:\x85"+"["+NUM+"]")
		end 
		if s s.GSMapThinged = "TryAgain" print("Added TryAgain"+((mapthing) and mapthing.angle or 0)) end return */
		
		if DOARG3==true and mapthing.args[3]
			s.GSrailnum = mapthing.args[3]
			s.GSUDMFang = mapthing.angle*ANG1
		else
			s.GSrailnum = mapthing.angle
		end	
	else
		s.GSrailnum = mapthing.angle
	end
	s.flags2 = $|MF2_DONTDRAW --To be a little prettier, be invisible first.
--	RAIL.print("\x8a Rail segment "+s.GSrailnum+" loaded!")
end

--Destroy the grind rail!! Normally, the rail's turned invisible and untouchable so we may reload it later.
RAIL.RailBusted = function(s,i,so,dmg,dmgtype)
	local mainrail = s.GSmainrail or s
	s.GSdisabled = true
	s.shadowscale,s.health = 0, 1
	s.flags = $|MF_NOCLIPTHING
	if (mainrail.flags2 & MF2_DONTDRAW) or not mainrail.GSraillength and not mainrail.GSinvisiblerail
		return true --
	end
	local RAILANG = RAIL.ANG(mainrail)
	local rng,debris = P_RandomRange(7,44)
	for d = 1,16 --Rail destruction debris
		debris = P_SpawnMobjFromMobj(s, 
		FixedMul(s.GSraillength/d, cos(RAILANG+ANGLE_180)),
		FixedMul(s.GSraillength/d, sin(RAILANG+ANGLE_180)), 0, MT_BOSSJUNK)
		if debris and debris.valid
			debris.renderflags = mainrail.renderflags
			debris.flags = $|MF_BOUNCE|MF_GRENADEBOUNCE|MF_NOCLIPTHING
			debris.sprite = SPR_BRKN
			debris.frame = I|((d%3==0) and FF_HORIZONTALFLIP or 0)
			if (mainrail.color)
				debris.color,debris.colorized = mainrail.color,true
			end
			debris.scale = $/max(1,rng/14)
			debris.rollangle = d*ANGLE_22h
			P_InstaThrust(debris, (ANGLE_45*d)-(d*ANGLE_22h), rng*FRACUNIT)
			P_SetObjectMomZ(debris, (10-rng/10)*FRACUNIT, false)
		end	
	end
	if mainrail==s and not S_SoundPlaying(s, sfx_bedeen)
	/*	for z in players.iterate if not (z and z.mo and z.mo.valid) continue end
			if ((FixedHypot(FixedHypot(mainrail.x-z.mo.x, mainrail.y-z.mo.y), mainrail.z-z.mo.z)) < 2200<<16)
				S_StartSound(nil, sfx_s3kb4, z) S_StartSound(nil, sfx_kc40, z) S_StartSound(nil, sfx_bedeen, z)
			end
		end	*/
		S_StartSound(s, sfx_s3kb4) S_StartSound(s, sfx_kc40) S_StartSound(s, sfx_bedeen)
	end
	s.flags2 = $|MF2_DONTDRAW return true --
end

--Sets and then returns the parameters according to ARG table given. ARG2 is for color swapping and is optional.
RAIL.EditParameters = function(ARG, ARG2)
	local TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE = nil
	if ARG!=nil local ARGS = ARG
		if (ARG[0]) --Toggle whether a rail is on or not?
			if ARG[0]==1
				TOGGLE = "off"
			elseif ARG[0]==2
				TOGGLE = "on"
			elseif ARG[0]==3
				TOGGLE = "toggle" --Toggles rails Off if they're On, or On if it's Off.
			elseif ARG[0]==4
				TOGGLE = "destroy" --Destroy the rail!
			end
		end
		if (ARG[1]) --Toggle Hangrail or not?
			if ARG[1]==1
				HANGTOGGLE = "on"
			elseif ARG[1]==2
				HANGTOGGLE = "off"
			elseif ARG[1]==3
				HANGTOGGLE = "toggle" --Toggles hangrail properties Off if they're On, or On if it's Off.
			end
		end
		if (ARG[2]) and type(ARG[2])=="number" --Raise/Lower tagged rail by a certain amount?
			DESTZ = (ARG[2])<<16 
		end
		if (ARG[3]) and type(ARG[3])=="number" --Change tagged rail's Custom Type? [NOT READY]
			NEWTYPE = ARG[3] 
		end
		if (ARG[4]) and type(ARG[4])=="number"  --Change the tagged rail's Loop Type? [NOT READY]
			NEWTYPE = ARG[4]
		end	
		if (ARG[5]) --If this is set, Z position is set to an absolute value, and "0" becomes a possible value.
			ABSOLUTE = ARG[5]
			if DESTZ==nil DESTZ = 0 end
		end
	--	print("DESTZ:"+DESTZ/FRACUNIT+" ABSOLUTE:\x82"+ABSOLUTE+"\x80 HANGTOGGLE:\x88"+HANGTOGGLE+"\x80 ARG[1]:\x8c"+ARG[1])
	end
	if ARG2!=nil 
		local COLOR,NUM = ARG2, nil
		if type(COLOR)=="string" --Custom rail color. Convenience feature for UDMF that doesn't require dye objects.
			NUM = rawget(_G, COLOR) --First, try converting SKINCOLOR_ constants.
			if not (NUM) 
				NUM = R_GetColorByName(COLOR) --That didn't work. Try converting names?
				if not (NUM) NUM = R_GetSuperColorByName(COLOR) end --SUPER names??
			end
		end
		if not (NUM) NUM = tonumber(COLOR) end --Did they input a number for some reason?
		if (NUM) and type(NUM)=="number" and not (NUM > #skincolors)
			COLORSWAP = NUM
		end
	end return TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE --Remember, EVERYTHING could return nil
end

--Actual part that edits the rail. Assumes m (the rail) is already confirmed valid.
RAIL.DoEdit = function(m, TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE)
	if TOGGLE!=nil		
		if TOGGLE=="off" or TOGGLE=="toggle" and not (m.GSdisabled)
			m.GSdisabled = true
			m.flags = $|MF_NOCLIPTHING
			if not m.GSinvisiblerail m.flags2 = $|MF2_DONTDRAW end
		elseif TOGGLE=="on" or TOGGLE=="toggle" and (m.GSdisabled) or TOGGLE=="destroy"
			if TOGGLE=="destroy" --Destroy the rail! (Cosmetic effect, it's still only just disabled, technically)	
				RAIL.RailBusted(m, nil, nil, 1, DMG_NUKE)
			end
			m.GSdisabled = false
			m.flags = $ & ~MF_NOCLIPTHING
			if not m.GSinvisiblerail and (m.GSnextrail and m.GSnextrail.valid) m.flags2 = $ & ~MF2_DONTDRAW end	
		end
	end		
	if HANGTOGGLE!=nil
		if HANGTOGGLE=="on"
			m.GShangrail = true 
		elseif HANGTOGGLE=="off"
			m.GShangrail = false
		elseif HANGTOGGLE=="toggle"
			m.GShangrail = not m.GShangrail
		end
	end
	if COLORSWAP!=nil
		m.color = COLORSWAP
	end

	local SEGS = m.GSsegments 
	if SEGS and (#SEGS) --Make sure the rail's inbetween segments also adjust.
		for _,seg in pairs(SEGS)
			if seg and seg.valid
				if TOGGLE!=nil
					seg.GSdisabled = m.GSdisabled
					if m.GSdisabled
						seg.flags = $|MF_NOCLIPTHING
						if not m.GSinvisiblerail seg.flags2 = $|MF2_DONTDRAW end
					else
						seg.flags = $ & ~MF_NOCLIPTHING
						if not m.GSinvisiblerail seg.flags2 = $ & ~MF2_DONTDRAW end
					end
				end
				if HANGTOGGLE!=nil
					seg.GShangrail = m.GShangrail
				end
				if DESTZ!=nil
					seg.z = ((not ABSOLUTE) and $ or 0)+DESTZ
					if seg.floorspriteslope and seg.floorspriteslope.o
						local X,Y,Z = seg.floorspriteslope.o.x, seg.floorspriteslope.o.y, seg.floorspriteslope.o.z
						seg.floorspriteslope.o = {x = X, y = Y, z = ((not ABSOLUTE) and Z or 0)+DESTZ}
					end
				end
				if COLORSWAP!=nil
					seg.color = COLORSWAP
				end
				local SIDE = seg.GSrailsideL --Change the paper sprite now.
				if SIDE and SIDE.valid
					if TOGGLE!=nil and not m.GSinvisiblerail
						SIDE.flags2 = ($ & ~MF2_DONTDRAW)|(seg.GSdisabled and MF2_DONTDRAW or 0)
					end
					if DESTZ!=nil
						SIDE.z = ((not ABSOLUTE) and $ or 0)+DESTZ
					end
					if COLORSWAP!=nil
						SIDE.color = COLORSWAP
					end
				end	
			end
		end
	end		
	
	--Back to the main rail object...!
	if DESTZ!=nil
		m.z = ((not ABSOLUTE) and $ or 0)+DESTZ
		if m.floorspriteslope and m.floorspriteslope.o
			local X,Y,Z = m.floorspriteslope.o.x, m.floorspriteslope.o.y, m.floorspriteslope.o.z
			m.floorspriteslope.o = {x = X, y = Y, z = ((not ABSOLUTE) and Z or 0)+DESTZ}
		end
	end
	local SIDE = m.GSrailsideL --The rail itself has a side too!
	if SIDE and SIDE.valid
		if TOGGLE!=nil and not m.GSinvisiblerail and (m.GSnextrail and m.GSnextrail.valid)
			SIDE.flags2 = ($ & ~MF2_DONTDRAW)|(m.GSdisabled and MF2_DONTDRAW or 0)
		end
		if DESTZ!=nil
			SIDE.z = ((not ABSOLUTE) and $ or 0)+DESTZ
		end
		if COLORSWAP!=nil
			SIDE.color = COLORSWAP
		end
	end	
end
local DIRECTOR = GS_RAILS_DIRECTOR

--Lua version of the linedef action's "rail edit" function, meant for advanced users!
--If TARGET is a number instead of a mobj, it'll look for the rail using that same GS.railnumber.
--ARG must be an array table, from 0 to 9 like in line.args. Check LUA_SHOW in SL_GSGrindRailTestMaps.wad for a demonstration.
--COLORSWAP is only needed if you want to swap the rail's color, otherwise make it nil.
--If ENTIRESEQUENCE is set to true, it'll edit ALL rails your given rail is connected to.
RAIL.EDIT = function(TARGET, ARG, COLORSWAP, ENTIRESEQUENCE)
	if not (type(GS_RAILS_DIRECTOR)=="userdata" and GS_RAILS_DIRECTOR.valid and GS_RAILS_DIRECTOR.GSrailtable)
		RAIL.error("\x85"+"____________________________________________"+"\n"+"\n"+"ERROR! Rail Director did not spawn yet!\x80"+"\n"+
		"If you tried using a\x82 'Level Load'\x80 trigger, set the\x82 executor delay\x80 to\x88 20\x80 on the\x89 Call Lua\x80 linedef!")
		return --
	end
	if ARG==nil 			RAIL.error("\x85"+"ARG is nil!") return end	--
	if (type(ARG)!="table")	RAIL.error("\x85"+"ARG must be an array table!") return end	--
	if TARGET==nil  		RAIL.error("\x85"+"TARGET is nil! Input either a rail number or a rail mobj.") return --
	elseif type(TARGET)=="number" --Try scanning for the railnumber?
		for _,m in pairs(GS_RAILS_DIRECTOR.GSrailtable)
			if m and m.valid and (m.type==MT_GSRAIL) and m.GSrailnum==TARGET --It's a rail with a matching rail number?
				TARGET = m,m break --
			end
		end
	end
	if not ((TARGET) and type(TARGET)=="userdata" and TARGET.valid and (TARGET.type==MT_GSRAIL)) return end --Couldn't find rail!
	local TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE = RAIL.EditParameters(ARG, COLORSWAP or nil) --Set properties...!
	RAIL.DoEdit(TARGET, TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE) return TARGET --Success!
end
	
--Make a custom Call Lua function that can edit the properties of all tagged rails!
--This is called using linedef action 443, when the function name's field says RAILEDIT
RAIL.LINE_RailEdit = function(line, s)
	if not (type(GS_RAILS_DIRECTOR)=="userdata" and GS_RAILS_DIRECTOR.valid and GS_RAILS_DIRECTOR.GSrailtable)
		RAIL.error("\x85"+"____________________________________________"+"\n"+"\n"+"ERROR! Rail Director did not spawn yet!\x80"+"\n"+
		"If you tried using a\x82 'Level Load'\x80 trigger, set the\x82 executor delay\x80 to\x88 20\x80 on the\x89 Call Lua\x80 linedef!")
		return --
	end
	local ARG, SARG = line.args, nil
	if type(line.stringargs)=="userdata" and Msa[userdataType(line.stringargs)]==2 and (line.stringargs[1])
		SARG = line.stringargs[1]
	end
	
	local TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE = RAIL.EditParameters(ARG or nil, SARG or nil) --Apply properties.
	local TAGS = {}
	if (line.taglist) and type(line.taglist)=="userdata" and userdataType(line.taglist)=="taglist"
		local LIST = line.taglist
		for i = 1,#(line.taglist) --Go through the list to see what to edit.
			if (LIST[i]) and (LIST[i]!=0)
				TAGS[i] = LIST[i] --print("Registered Tag:\x84"+LIST[i])
			end
		end
	elseif type(line.tag)=="number" and (line.tag!=0)
		TAGS[line.tag] = line.tag --print("Registered Line-Tag:\x84"+LIST[i])		
	end
	if not (#TAGS) RAIL.error("\x85"+"Error!\x82 No tags were input on linedef Identification!", 25) return end --

	for _,m in pairs(GS_RAILS_DIRECTOR.GSrailtable)
		if not (m and m.valid and (m.type==MT_GSRAIL) and m.spawnpoint and m.spawnpoint.valid) continue end --It's a rail?
		local spn,TAGLIST = m.spawnpoint,m.spawnpoint.taglist
		
		if TAGLIST and userdataType(TAGLIST)=="taglist"
			local EDITME = false
			for i = 1,#TAGLIST
				if TAGLIST[i] and (TAGLIST[i]!=0) and (EDITME!=true)
					for _,RailTag in pairs(TAGS)
						if (RailTag) and TAGLIST[i]==RailTag and (EDITME!=true)
							EDITME = TAGLIST[i] break --
						end
					end 
					if (EDITME!=false) break end --
				end
			end
			if (EDITME==false) continue end --print("Editing rail with\x88 "+EDITME)
		else
			if not (line.tag and line.tag==spn.tag) continue end --print("Editing rail with\x81 "+line.tag)
		end
--		print("\x81"+"PRE:\x80"+"DESTZ:"+DESTZ/FRACUNIT+" ABSOLUTE:\x82"+ABSOLUTE)
		RAIL.DoEdit(m, TOGGLE, DESTZ, NEWTYPE, HANGTOGGLE, COLORSWAP, ABSOLUTE)
	end return --
end
local function RAIL_LINE_EDIT(line, s) --Apparently doing this saves a joiner crash from ArchiveExtVars
	if not (line and line.valid) return end --Wha...no valid line?
	RAIL.LINE_RailEdit(line, s) --due to a gamestate becoming too big.
end
addHook("LinedefExecute", RAIL_LINE_EDIT, "RAILEDIT")

--When this mobj is destroyed, call the linedef executor that has their tag!
--By default applies to Jetty-Syns and Jetty-Gunners with the "Can Move" flag turned off.
--But feel free to apply this function to any enemy or object of your choosing!  
RAIL.Call_RailLineDef = function(s, i, so)
	if GS_RAILS==nil or not (s.spawnpoint and s.spawnpoint.valid) return end --
	local spn = s.spawnpoint
	local TAGS = {}
	if (spn.taglist) and type(spn.taglist)=="userdata"
		local LIST = spn.taglist
		for i = 0,#LIST
			if (LIST[i]) and LIST[i]!=0
				TAGS[i]=LIST[i]
			end
		end
	end
	local TAGS = {}
	if (spn.taglist) and type(spn.taglist)=="userdata" and userdataType(spn.taglist)=="taglist"
		local LIST = spn.taglist
		for i = 1,#LIST --Go through the list to see what to edit.
			if (LIST[i]) and (LIST[i]!=0)
				TAGS[i] = LIST[i]
			end
		end
	elseif type(spn.tag)=="number" and (spn.tag!=0)
		TAGS[1] = spn.tag 	
	end
	for _,LineTag in pairs(TAGS)
		if (LineTag) and (LineTag!=0)
			P_LinedefExecute(LineTag, nil, nil) --R_PointInSubsector(s.x, s.y).sector
		end
	end	
end

--Checks if object is valid and not junk data.
RAIL.IsValid = function(s, TIME)
	if type(s)=="userdata" and userdataType(s)=="mobj_t" and s.valid
	--	if not TIME or (leveltime >= TIME)
			return true
	--	end
	end
end

--Checks if object is valid and not junk data, INCLUDING it's MapThing.
RAIL.IsMTValid = function(s, spn)
	if type(s)=="userdata" and type(spn)=="userdata" and userdataType(s)=="mobj_t"
	and userdataType(spn)=="mapthing_t" and s.valid --and spn.valid
		return true --
	end
end

--When a map changes, then...!
RAIL.MapChange = function(mapnum)
	GS_RAILS_DIRECTOR = 0 --"reload"
	RAIL.LastErrorMessage,RAIL.LastMessage,RAIL.LastPrint = nil
	
	if RAIL.lastmap==mapnum
		RAIL.MapReady = 0 //print("Set to\x85 false(0)")
	else
		RAIL.MapReady = 1 //print("Set to\x83 true(1)")
	end
	for z in players.iterate
		if z and z.valid 
			if z.solcam and z.solcam.GSdisabled
				z.solcam.emergencydisable,z.solcam.GSdisabled = false, false
			end		
		--	if z.GS_RAILDIRECTOR
		--		z.GS_RAILDIRECTOR = 0
		--	end
		end
	end
end