//rail grinding!!!
//this code specifically took me about a day to make, but this is actually my fourth or fifth attempt at rail grinding in srb2
//in total i would say this ability took at least a few (extremely stressful) days worth of hours to fully develop, but it was worth it!

//TODO: make all of this work while upside down

addHook("MobjLineCollide", function(mobj, line)
	if not mobj.player then return end
	if not (line.flags & ML_EFFECT4) then return end
	if not P_IsObjectOnGround(mobj) then return end
	if mobj and mobj.skin ~= "tempest" then return end
	if mobj.player.funkin ~= -1 then return end
	local p = mobj.player
	p.railthistic = $ or false
	if p.rail == nil
	or line ~= p.rail
	and min(R_PointToDist2(p.mo.x,p.mo.y,line.v1.x,line.v1.y),R_PointToDist2(p.mo.x,p.mo.y,line.v2.x,line.v2.y)) <= 24<<16 then
		local hasrail = false
		local bestrailheight = 128<<16 --prioritize the rail that is closest to the player
		for x = 1,8 do --check 1
			for y = 0,1 do
				local railside = line.frontside
				if y == 1 then
					railside = line.backside
				end
				if railside == nil then continue end
				if p.mo.z == railside.rowoffset + railside.sector.floorheight + (8*x<<16) --sidedef's midtexture position + sector height + sidedef's midtexture height
				and railside.rowoffset + (8*x<<16) < bestrailheight then
					hasrail = true
					bestrailheight = railside.rowoffset + (8*x<<16)
				end
			end
		end
		if not hasrail then --check 2
			for i = 0,3 do
				//check 4 spots around the player for a nearby surface, if one is close enough, the player is on a rail
				local zcheck = P_FloorzAtPos(p.mo.x + P_ReturnThrustX(p.mo,p.mo.angle + 90*i*ANG1, 32<<16),p.mo.y + P_ReturnThrustY(p.mo,p.mo.angle + 90*1*ANG1, 32<<16),p.mo.z,p.mo.height)
				if (p.mo.z - zcheck) >= 0
				and (p.mo.z - zcheck) < bestrailheight then
					hasrail = true
					bestrailheight = p.mo.z - zcheck
				else
					//summon 4 dummy objects around the player to check FOF surfaces on nearby sectors, if one is close enough, the player is on a rail
					local dummy = P_SpawnMobj(p.mo.x + P_ReturnThrustX(p.mo,p.mo.angle + 90*i*ANG1, 32<<16),p.mo.y + P_ReturnThrustY(p.mo,p.mo.angle + 90*i*ANG1, 32<<16),p.mo.z,MT_THOK)
					for fof in dummy.subsector.sector.ffloors() do
						if (p.mo.z - fof.topheight) >= 0
						and (p.mo.z - fof.topheight) < bestrailheight then
							hasrail = true
							bestrailheight = p.mo.z - fof.topheight
						end
					end
					P_KillMobj(dummy)
				end
			end
		end
		if hasrail
		and (p.cmd.buttons & BT_SPIN)
		and FixedHypot(p.mo.momx,p.mo.momy) >= 12<<16 then
			p.rail = line --if a rail was found, the player pressed spin and is moving, start the grind
		end
	end
	if p.rail ~= nil then
		p.railthistic = true --this is a failsafe, stopping the player from getting booted off the rail for one tic
	end
end)

addHook("MapChange", function(mapnum)
	for p in players.iterate
		p.rail = nil
		p.railthistic = nil
		p.normalnormalspeed = nil
	end
end)

addHook("PostThinkFrame", do
	for p in players.iterate
		if p == nil then return end
		if p.mo == nil then return end
		if p.mo and p.mo.skin ~= "tempest" then return end
		if p.funkin ~= -1 then return end
		if p.normalnormalspeed == nil then
			p.normalnormalspeed = p.normalspeed
		end
		if (p.cmd.buttons & BT_JUMP)
		or (not (p.cmd.buttons & BT_SPIN))
		or (p.mo.state ~= S_TEMPEST_GRINDING and not p.railthistic)
		or FixedHypot(p.mo.momx,p.mo.momy) <= 12<<16 then
			p.rail = nil
			p.railthistic = false
			S_StopSoundByID(p.mo,sfx_grind)
			if p.mo.state == S_TEMPEST_GRINDING then
				p.mo.state = S_PLAY_SPRING
			end
		end
		if p.railthistic then
			if p.mo.state ~= S_TEMPEST_GRINDING then
				p.mo.state = S_TEMPEST_GRINDING
			end
			local momangle = R_PointToAngle2(p.mo.x,p.mo.y,p.mo.x + p.mo.momx,p.mo.y + p.mo.momy)
			local momspeed = R_PointToDist2(p.mo.x,p.mo.y,p.mo.x + p.mo.momx,p.mo.y + p.mo.momy)
			//hehe, mom speed
			if p.rail ~= nil then
				local gotox, gotoy = P_ClosestPointOnLine(p.mo.x + P_ReturnThrustX(p.mo,momangle,momspeed), p.mo.y + P_ReturnThrustY(p.mo,momangle,momspeed),p.rail)
				P_InstaThrust(p.mo,R_PointToAngle2(p.mo.x,p.mo.y,gotox,gotoy),momspeed)
			end
			if FixedHypot(p.mo.momx,p.mo.momy) >= 8<<16 then
				if not S_SoundPlaying(p.mo,sfx_grind) then
					S_StartSoundAtVolume(p.mo,sfx_grind,191)
				end
				if leveltime % 3 == 1 then
					local ptype = MT_CONDUIT_SPARK1
					local framediff = 0
					if P_RandomChance(FRACUNIT/2) then
						ptype = MT_CONDUIT_SPARK2
						framediff = 5
					end
					local particle = P_SpawnMobj(p.mo.x,p.mo.y,p.mo.z,ptype)
					particle.frame = (1+framediff)|FF_ANIMATE
					particle.scale = p.mo.scale*2
					particle.destscale = $/2
					particle.scalespeed = FRACUNIT/4
				end
			else
				S_StopSoundByID(p.mo,sfx_grind)
			end
			p.mo.momx = $*41/40
			p.mo.momy = $*41/40
			p.normalspeed = min(max($, FixedHypot(p.mo.momx,p.mo.momy)*4/3),p.normalnormalspeed*4/3)
		else
			if p.rail ~= nil then
				local gotrail = false
				for line in lines.iterate
					if line == p.rail then continue end
					if gotrail then continue end
					if not (line.flags & ML_EFFECT4) then continue end
					for x = 1,2 do
						local vertex = line.v1
						local vertexopp = line.v2
						if x == 2 then
							vertex = line.v2
							vertexopp = line.v1
						end
						if (vertex == p.rail.v1 or vertex == p.rail.v2)
						and R_PointToDist2(p.mo.x,p.mo.y,vertex.x,vertex.y) <= FixedHypot(p.mo.momx,p.mo.momy)*5 then
							p.rail = line
							local gotox, gotoy = P_ClosestPointOnLine(p.mo.x, p.mo.y,p.rail)
							P_TryMove(p.mo,gotox,gotoy,true)
							gotrail = true
						end
					end
				end
				if not gotrail then
					P_SetObjectMomZ(p.mo,max(3<<16,p.mo.momz))
					if p.mo.state == S_TEMPEST_GRINDING then
						p.mo.state = S_PLAY_ROLL
						p.mo.spintime = 10
					end
					p.pflags = $|PF_JUMPED
					if (p.pflags & PF_THOKKED) then
						p.pflags = $ - PF_THOKKED
					end
					if p.mo.conduitstate == 3 then
						p.mo.conduitstate = 0 --probably redundant but juuuust in case :p
					end
					p.rail = nil
				end
			end
			p.normalspeed = p.normalnormalspeed
		end
		p.railthistic = false
	end
end)