---- /!\ henlo /!\ ----
-- u are welcome to use this code for whatever if you'd like
-- BUT there's the caveat that I wrote this in the early days of slopes
-- before Lua could directly interface with them, and as such
-- this is 99% HACKY GARBAGE and I would tread with caution

local SPDFACTOR = ANG1/16

local SpinnySlopes = false
local TiltHelper = false
addHook("MapChange", do
	SpinnySlopes = false
	TiltHelper = false
end)

addHook("MapLoad", function()
	for l in lines.iterate do
		---------------------------------------
		-- LINEDEF TYPE 798 - Spin vector slope
		-- Default arguments:
		---- Line tag and front side offsets determine three vectors to spin
		---- Front sector floor height determines the level height
		---- Front sector height determines the vertical range of motion
		---- Line dx determines the speed of rotation in 1 angle/tic for every 16 FU
		-- Use ML_EFFECT2 to control an extra set of vertices/sectors
		---- Back sector brightness and back side offsets determine three extra vectors
		---- Line dy determines vertical offset of extra vertices
		-- Use ML_EFFECT1 to tag sectors instead of vertices
		---- ML_EFFECT3 will move the ceiling of the tagged sectors as well
		---- ML_EFFECT4 will only move the ceiling
		-- Use ML_NOCLIMB to allow for dynamic height, range, and speed
		---- In this case speed is controlled by back sector floor + ceiling height
		---- in 1 angle/tic for every 16 FU 
		---------------------------------------
		---------------------------------------
		-- LINEDEF TYPE 797 - Tilting slope
		-- Default arguments:
		---- Line tag and front side offsets tag three vectors to spin
		---- Front sector floor height determines the level height
		---- Front sector height determines the vertical range of motion
		---- Back sector floor height tags a vector marking the center of the slope
		---- Line dx determines radius
		-- Use ML_EFFECT2 to control an extra set of vertices/sectors
		---- Back sector brightness and back side offsets tag three extra vectors
		---- Line dy determines vertical offset of extra vertices
		-- Use ML_EFFECT1 to tag sectors instead of vertices
		---- ML_EFFECT3 will move the ceiling of the tagged sectors as well
		---- ML_EFFECT4 will only move the ceiling
		-- Use ML_NOCLIMB to allow for dynamic height, range
		---------------------------------------
		-- Used with a Call Lua Function linedef exec - TILTSLOP
		-- Default arguments:
		---- Line tag should tag the same center vector as line 797
		---- Front sector floor height determines weight of the slope
		------ 1000 is equivalent to an object
		------ 0 will have the slope snap back to level when no objects are on it
		---- Back sector floor height determines resistance of the slope
		------ 1000 is no resistance (not recommended)
		------ 0 is completely resisting
		---- The front-side y-offset determines how far
		------ past the default range the default range the slope will be allowed to go
		------ It's not recommended to change this from 0
		------ unless the slope weight is 0
		------ ML_NOCLIMB will bypass this check entirely
		---- ML_BOUNCY will reduce friction and make the slope "bouncier"
		---------------------------------------
		if l.special == 797 or l.special == 798  then
			local Spinner = {}

			if l.flags & ML_EFFECT1 then -- Tag sectors instead of vertices
				-- Find sectors by tag and offsets.
				-- Tagged sectors (s1) will always start at the highest point
				-- And we will spin in the direction that causes
				-- the x-offset sectors (s2) to be next at the top
				for s in sectors.iterate do
					if s.tag == l.tag then
						if not Spinner.s1 then do Spinner.s1 = {} end end
						Spinner.s1[#Spinner.s1 + 1] = s
					end
				end
				if not Spinner.s1 then
					print("Spinner on line " .. #l .. " doesn't have a tagged sector!")
					continue
				end
				for s in sectors.iterate do
					if s.tag == l.frontside.textureoffset/FRACUNIT then
						if not Spinner.s2 then do Spinner.s2 = {} end end
						Spinner.s2[#Spinner.s2 + 1] = s
					end
				end
				if not Spinner.s2 then
					print("Spinner on line " .. #l .. " doesn't have an x-offset sector!")
					continue
				end
				for s in sectors.iterate do
					if s.tag == l.frontside.rowoffset/FRACUNIT then
						if not Spinner.s3 then do Spinner.s3 = {} end end
						Spinner.s3[#Spinner.s3 + 1] = s
					end
				end
				if not Spinner.s3 then
					print("Spinner on line " .. #l .. " doesn't have a y-offset sector!")
					continue
				end

				if l.flags & ML_EFFECT2 and l.backsector then -- Control a second set of sectors as well
					-- Corresponds to the tagged vertex: back sector brightness
					for s in sectors.iterate do
						if s.tag == l.backsector.lightlevel then
							if not Spinner.s1p then do Spinner.s1p = {} end end
							Spinner.s1p[#Spinner.s1p + 1] = s
						end
					end
					if not Spinner.s1p then
						print("Spinner on line " .. #l .. " doesn't have a back brightness sector!")
						continue
					end
					-- And the back offsets are used for the other two sectors
					for s in sectors.iterate do
						if s.tag == l.backside.textureoffset/FRACUNIT then
							if not Spinner.s2p then do Spinner.s2p = {} end end
							Spinner.s2p[#Spinner.s2p + 1] = s
						end
					end
					if not Spinner.s2p then
						print("Spinner on line " .. #l .. " doesn't have a back x-offset sector!")
						continue
					end
					for s in sectors.iterate do
						if s.tag == l.backside.rowoffset/FRACUNIT then
							if not Spinner.s3p then do Spinner.s3p = {} end end
							Spinner.s3p[#Spinner.s3p + 1] = s
						end
					end
					if not Spinner.s3p then
						print("Spinner on line " .. #l .. " doesn't have a back y-offset sector!")
						continue
					end
					
					Spinner.extrasectors = true
					-- Height offset for the second set of sectors: dy
					Spinner.extraheight = l.dy
				end

				if l.flags & ML_EFFECT3 then -- Move the ceiling along with the floor
					-- We'll calculate the heights now and store them
					-- so they don't have to be recalculated every tic
					Spinner.s1h = {}
					Spinner.s2h = {}
					Spinner.s3h = {}
					if Spinner.extrasectors then
						Spinner.s1ph = {}
						Spinner.s2ph = {}
						Spinner.s3ph = {}
					end
					Spinner.plane = 3 -- Floor and Ceiling
					
					for s = 1, #Spinner.s1 do
						Spinner.s1h[s] = Spinner.s1[s].ceilingheight - Spinner.s1[s].floorheight
					end
					for s = 1, #Spinner.s2 do
						Spinner.s2h[s] = Spinner.s2[s].ceilingheight - Spinner.s2[s].floorheight
					end
					for s = 1, #Spinner.s3 do
						Spinner.s3h[s] = Spinner.s3[s].ceilingheight - Spinner.s3[s].floorheight
					end
					
					if Spinner.extrasectors then
						for s = 1, #Spinner.s1p do
							Spinner.s1ph[s] = Spinner.s1p[s].ceilingheight - Spinner.s1p[s].floorheight
						end
						for s = 1, #Spinner.s2p do
							Spinner.s2ph[s] = Spinner.s2p[s].ceilingheight - Spinner.s2p[s].floorheight
						end
						for s = 1, #Spinner.s3p do
							Spinner.s3ph[s] = Spinner.s3p[s].ceilingheight - Spinner.s3p[s].floorheight
						end
					end
				elseif l.flags & ML_EFFECT4 -- Move only the ceiling
					Spinner.plane = 2 -- Only ceiling
				else -- Default, move only the floor
					Spinner.plane = 1 -- Only floor
				end
			else
				-- Find vertices by tag and offsets.
				-- Tag vertex (v1) will always start at the highest point
				-- And we will spin in the direction that causes
				-- the x-offset vertex (v2) to be next at the top
				for mt in mapthings.iterate do
					if mt.type == 750 and mt.angle == l.tag then
						Spinner.v1 = mt
						break
					end
				end
				if not Spinner.v1 then
					print("Spinner on line " .. #l .. " doesn't have a tagged vertex!")
					continue
				end
				for mt in mapthings.iterate do
					if mt.type == 750 and mt.angle == l.frontside.textureoffset/FRACUNIT then
						Spinner.v2 = mt
						break
					end
				end
				if not Spinner.v2 then
					print("Spinner on line " .. #l .. " doesn't have an x-offset vertex!")
					continue
				end
				for mt in mapthings.iterate do
					if mt.type == 750 and mt.angle == l.frontside.rowoffset/FRACUNIT then
						Spinner.v3 = mt
						break
					end
				end
				if not Spinner.v3 then
					print("Spinner on line " .. #l .. " doesn't have a y-offset vertex!")
					continue
				end

				if l.flags & ML_EFFECT2 and l.backsector then -- Control a second set of vertices as well
					-- Corresponds to the tagged vertex: back sector brightness
					for mt in mapthings.iterate do
						if mt.type == 750 and mt.angle == l.backsector.lightlevel then
							Spinner.v1p = mt
							break
						end
					end
					if not Spinner.v1p then
						print("Spinner on line " .. #l .. " doesn't have a back brightness vertex!")
						continue
					end
					-- And the back offsets are used for the other two vectors
					for mt in mapthings.iterate do
						if mt.type == 750 and mt.angle == l.backside.textureoffset/FRACUNIT then
							Spinner.v2p = mt
							break
						end
					end
					if not Spinner.v2p then
						print("Spinner on line " .. #l .. " doesn't have a back x-offset vertex!")
						continue
					end
					for mt in mapthings.iterate do
						if mt.type == 750 and mt.angle == l.backside.rowoffset/FRACUNIT then
							Spinner.v3p = mt
							break
						end
					end
					if not Spinner.v3p then
						print("Spinner on line " .. #l .. " doesn't have a back y-offset vertex!")
						continue
					end
					Spinner.extravertices = true
					-- Height offset for the second set of vertices: dy
					Spinner.extraheight = l.dy/FRACUNIT
				end
			end

			if l.special == 797 then
				local tilt = {}
				
				for mt in mapthings.iterate do
					if mt.type == 750 and mt.angle == l.backsector.floorheight/FRACUNIT then
						-- Center of platform
						tilt = {}
						tilt.cx = mt.x*FRACUNIT
						tilt.cy = mt.y*FRACUNIT
						-- Radius
						tilt.r = abs(l.dx)
						-- Current position of weight relative to center
						tilt.x = 0
						tilt.y = 0
						-- Target position of weight relative to center
						tilt.tx = 0
						tilt.ty = 0
						-- Movement of weight
						tilt.mx = 0
						tilt.my = 0
						-- Number of objects
						tilt.numobj = 0
						-- Identifier
						Spinner.tilt = mt.angle
						break
					end
				end

				if not Spinner.tilt then
					print("Spinner on line " .. #l .. " doesn't have a center vertex!")
					continue
				end

				if not TiltHelper then do TiltHelper = {} end end
				
				TiltHelper[Spinner.tilt] = tilt
			end

			if l.flags & ML_NOCLIMB then -- Dynamic spinner, allows adjustable level, range, and speed
				Spinner.dynamic = true
				Spinner.fs = l.frontsector
				if not Spinner.tilt then
					Spinner.bs = l.backsector
					if not Spinner.bs then
						print("Spinner on line " .. #l .. " doesn't have a back sector!")
						continue
					end
					Spinner.angle = 0
				end
			else
				-- Level height: front sector's floor height
				Spinner.level = l.frontsector.floorheight
				-- Range: front sector height
				Spinner.range = l.frontsector.ceilingheight - Spinner.level
				if not Spinner.tilt then
					-- Speed: dx of the line, in degrees per tic, divided by 16
					-- Note that this can be negative depending on how the linedef is facing
					Spinner.speed = (l.dx/FRACUNIT)*SPDFACTOR
				end
			end
			
			if not SpinnySlopes then do SpinnySlopes = {} end end
			SpinnySlopes[#SpinnySlopes + 1] = Spinner
		end
	end
end)

addHook("LinedefExecute", function(l, mo)
	---------------------------------------
	-- Used with a Call Lua Function linedef exec - TILTSLOP
	-- Default arguments:
	---- Line tag should tag the same center vector as line 797
	---- Front sector floor height determines weight of the slope
	------ 1000 is equivalent to an object
	------ 0 will have the slope snap back to level when no objects are on it
	---- Back sector floor height determines resistance of the slope
	------ 1000 is no resistance (not recommended)
	------ 0 is completely resisting
	---- The front-side y-offset determines how far
	------ past the default range the default range the slope will be allowed to go
	------ It's not recommended to change this from 0
	------ unless the slope weight is 0
	------ ML_NOCLIMB will bypass this check entirely
	---- ML_BOUNCY will reduce friction and make the slope "bouncier"
	---------------------------------------

	--if not P_IsObjectOnGround(mo) do return end end
	if mo.tilt == leveltime or mo.momz > 0 or not TiltHelper do return end end

	local tilt = TiltHelper[l.tag]

	if not tilt.l do tilt.l = l end end

	tilt.tx = $1 + mo.x - tilt.cx
	tilt.ty = $1 + mo.y - tilt.cy
	tilt.numobj = $1 + 1

	mo.tilt = leveltime -- fixes bug where pushables would be counted twice

	TiltHelper[l.tag] = tilt
end, "TILTSLOP")

addHook("ThinkFrame", function()
	if not SpinnySlopes then return end
	for s = 1, #SpinnySlopes do
		local Spinner = SpinnySlopes[s]
		local angle = 0
		local range = 0

		if Spinner.dynamic then
				-- Level height: front sector's floor height
				Spinner.level = Spinner.fs.floorheight
				-- Range: front sector height
				range = Spinner.fs.ceilingheight - Spinner.level
				if leveltime and not Spinner.tilt then -- Don't do anything on the first tic, to keep it synchronized with static spinners
					-- Speed: back sector's floor + ceiling, in degrees per tic, divided by 16
					local speed = ((Spinner.bs.floorheight + Spinner.bs.ceilingheight)/FRACUNIT)*SPDFACTOR
					Spinner.angle = $1 + speed
					angle = Spinner.angle
				end
		elseif not Spinner.tilt -- Everything's pre-calculated so just find the angle
			angle = leveltime * Spinner.speed
			range = Spinner.range -- and set the range
		end

		if Spinner.tilt then
			local tilt = TiltHelper[Spinner.tilt]
			if tilt.l then
				local weight = tilt.l.frontsector.floorheight/1000
				local resistance = tilt.l.backsector.floorheight/1000

				tilt.tx = $1 + FixedMul(tilt.x, weight)
				tilt.ty = $1 + FixedMul(tilt.y, weight)
				tilt.numobj = $1*FRACUNIT + weight
				if tilt.numobj then
					tilt.tx = FixedDiv($1, tilt.numobj)
					tilt.ty = FixedDiv($1, tilt.numobj)
				end
				tilt.mx = FixedMul($1, FRACUNIT-resistance) + FixedMul((tilt.tx - tilt.x), resistance)
				tilt.my = FixedMul($1, FRACUNIT-resistance) + FixedMul((tilt.ty - tilt.y), resistance)
				if not (tilt.l.flags & ML_BOUNCY) then
					tilt.mx = FixedMul($1, resistance/2 + FRACUNIT/2)
					tilt.my = FixedMul($1, resistance/2 + FRACUNIT/2)
				end

				tilt.x = $1 + tilt.mx
				tilt.y = $1 + tilt.my
				angle = R_PointToAngle2(0, 0, tilt.x, tilt.y) + ANGLE_180
				local tiltstrength = FixedDiv(R_PointToDist2(0, 0, tilt.x, tilt.y), tilt.r)
				range = FixedMul(Spinner.range, tiltstrength)

				if not (tilt.l.flags & ML_NOCLIMB) then
					if range > (Spinner.range + tilt.l.frontside.rowoffset) then
						range = Spinner.range + tilt.l.frontside.rowoffset
						tiltstrength = FixedDiv(range, Spinner.range)
						local dist = FixedMul(tiltstrength, tilt.r)
						tilt.x = P_ReturnThrustX(nil, angle - ANGLE_180, dist)
						tilt.y = P_ReturnThrustY(nil, angle - ANGLE_180, dist)
					end
				end

				tilt.tx = 0
				tilt.ty = 0
				tilt.numobj = 0
				TiltHelper[Spinner.tilt] = tilt
			else -- Spinner hasn't been activated yet so we'll just leave it
				continue
			end
		end

		local z1 = FixedMul(range, cos(angle)) + Spinner.level
		local z2 = FixedMul(range, cos(angle - ANG60<<1)) + Spinner.level
		local z3 = FixedMul(range, cos(angle - ANG60<<2)) + Spinner.level
		if not Spinner.plane then
			Spinner.v1.z = z1/FRACUNIT
			Spinner.v2.z = z2/FRACUNIT
			Spinner.v3.z = z3/FRACUNIT
			if Spinner.extravertices then
				Spinner.v1p.z = Spinner.v1.z + Spinner.extraheight
				Spinner.v2p.z = Spinner.v2.z + Spinner.extraheight
				Spinner.v3p.z = Spinner.v3.z + Spinner.extraheight
			end
		elseif Spinner.plane & 1 -- Floor
			local doCeiling = Spinner.plane & 2
			for t = 1, #Spinner.s1 do
				Spinner.s1[t].floorheight = z1
				if doCeiling then
					Spinner.s1[t].ceilingheight = z1 + Spinner.s1h[t]
				end
			end
			for t = 1, #Spinner.s2 do
				Spinner.s2[t].floorheight = z2
				if doCeiling then
					Spinner.s2[t].ceilingheight = z2 + Spinner.s2h[t]
				end
			end
			for t = 1, #Spinner.s3 do
				Spinner.s3[t].floorheight = z3
				if doCeiling then
					Spinner.s3[t].ceilingheight = z3 + Spinner.s3h[t]
				end
			end

			if Spinner.extrasectors then
				for t = 1, #Spinner.s1p do
					Spinner.s1p[t].floorheight = z1 + Spinner.extraheight
					if doCeiling then
						Spinner.s1p[t].ceilingheight = Spinner.s1p[t].floorheight + Spinner.s1ph[t]
					end
				end
				for t = 1, #Spinner.s2p do
					Spinner.s2p[t].floorheight = z2 + Spinner.extraheight
					if doCeiling then
						Spinner.s2p[t].ceilingheight = Spinner.s2p[t].floorheight + Spinner.s2ph[t]
					end
				end
				for t = 1, #Spinner.s3p do
					Spinner.s3p[t].floorheight = z3 + Spinner.extraheight
					if doCeiling then
						Spinner.s3p[t].ceilingheight = Spinner.s3p[t].floorheight + Spinner.s3ph[t]
					end
				end
			end
		else -- Ceiling only
			for t = 1, #Spinner.s1 do
				Spinner.s1[t].ceilingheight = z1
			end
			for t = 1, #Spinner.s2 do
				Spinner.s2[t].ceilingheight = z2
			end
			for t = 1, #Spinner.s3 do
				Spinner.s3[t].ceilingheight = z3
			end
			
			if Spinner.extrasectors then
				for t = 1, #Spinner.s1p do
					Spinner.s1p[t].ceilingheight = z1 + Spinner.extraheight
				end
				for t = 1, #Spinner.s2p do
					Spinner.s2p[t].ceilingheight = z2 + Spinner.extraheight
				end
				for t = 1, #Spinner.s3p do
					Spinner.s3p[t].ceilingheight = z3 + Spinner.extraheight
				end
			end
		end
	end
end)
