-- Per-map customizable springs
-- By Ashnal

-- A level header soc can now include Lua variables listed here to affect the strength of springs on a per map basis
-- The default values are this, and they are in FRACUNITs, you can put this list at the bottom of your level header (without the -- Lua comment) and change the values to whatever you want
-- Lua.grehori_vforce = 0
-- Lua.grehori_hforce = 14
-- Lua.grediag_vforce = 12
-- Lua.grediag_hforce = 12
-- Lua.gresprg_vforce = 12
-- Lua.gresprg_hforce = 0
-- Lua.yelhori_vforce = 0
-- Lua.yelhori_hforce = 23
-- Lua.yeldiag_vforce = 20
-- Lua.yeldiag_hforce = 20
-- Lua.yelsprg_vforce = 20
-- Lua.yelsprg_hforce = 0
-- Lua.redhori_vforce = 0
-- Lua.redhori_hforce = 36
-- Lua.reddiag_vforce = 32
-- Lua.reddiag_hforce = 32
-- Lua.redsprg_vforce = 32
-- Lua.redsprg_hforce = 0
-- Lua.bluhori_vforce = 0
-- Lua.bluhori_hforce = 56
-- Lua.bludiag_vforce = 42
-- Lua.bludiag_hforce = 42
-- Lua.blusprg_vforce = 42
-- Lua.blusprg_hforce = 0

-- Springs now reduce player friction for a short time after being hit, which makes karts retain more of the momentum for longer after hitting it

-- Marking a spring with the Ambush flag will have it force the colliding things angle int eh springs direction (including verticals' angles)
-- Marking a spring with the Special flag will have it cancel momentum not in its direction ... 
--	 so for verticals it causes a straight up bounce,
--   and for horizontals it causes falling or rising momentum to be reset to 0 

if AshnalSpringLua then return end -- Prevents this from loading twice
rawset(_G, "AshnalSpringLua", 1)

local FRACUNIT = FRACUNIT

local MFE_SPRUNG = 256

local MTF_EXTRA = 1 -- Doesnt exist as a real flag in source code, but exists in ZB
local MTF_SPECIAL = 4
local MTF_AMBUSH = 8

local MF_SPECIAL = 1
local MF_SOLID = 2

local HORIZONTAL, DIAGONAL, VERTICAL = 2, 1, 0 -- enums at home
local GREY, YELLOW, RED, BLUE = 2, 47, 22, 55

local springtypes = {
	[MT_SHGSPRG] = {socname = "GREHORI", color = GREY,   orientation = HORIZONTAL, vforce = mobjinfo[MT_SHGSPRG].mass, hforce = mobjinfo[MT_SHGSPRG].damage},
	[MT_SDGSPRG] = {socname = "GREDIAG", color = GREY,   orientation = DIAGONAL,   vforce = mobjinfo[MT_SDGSPRG].mass, hforce = mobjinfo[MT_SDGSPRG].damage},
	[MT_SVGSPRG] = {socname = "GRESPRG", color = GREY,   orientation = VERTICAL,   vforce = mobjinfo[MT_SVGSPRG].mass, hforce = mobjinfo[MT_SVGSPRG].damage},
	[MT_SHYSPRG] = {socname = "YELHORI", color = YELLOW, orientation = HORIZONTAL, vforce = mobjinfo[MT_SHYSPRG].mass, hforce = mobjinfo[MT_SHYSPRG].damage},
	[MT_SDYSPRG] = {socname = "YELDIAG", color = YELLOW, orientation = DIAGONAL,   vforce = mobjinfo[MT_SDYSPRG].mass, hforce = mobjinfo[MT_SDYSPRG].damage},
	[MT_SVYSPRG] = {socname = "YELSPRG", color = YELLOW, orientation = VERTICAL,   vforce = mobjinfo[MT_SVYSPRG].mass, hforce = mobjinfo[MT_SVYSPRG].damage},
	[MT_SHRSPRG] = {socname = "REDHORI", color = RED,    orientation = HORIZONTAL, vforce = mobjinfo[MT_SHRSPRG].mass, hforce = mobjinfo[MT_SHRSPRG].damage},
	[MT_SDRSPRG] = {socname = "REDDIAG", color = RED,    orientation = DIAGONAL,   vforce = mobjinfo[MT_SDRSPRG].mass, hforce = mobjinfo[MT_SDRSPRG].damage},
	[MT_SVRSPRG] = {socname = "REDSPRG", color = RED,    orientation = VERTICAL,   vforce = mobjinfo[MT_SVRSPRG].mass, hforce = mobjinfo[MT_SVRSPRG].damage},
	[MT_SHBSPRG] = {socname = "BLUHORI", color = BLUE,   orientation = HORIZONTAL, vforce = mobjinfo[MT_SHBSPRG].mass, hforce = mobjinfo[MT_SHBSPRG].damage},
	[MT_SDBSPRG] = {socname = "BLUDIAG", color = BLUE,   orientation = DIAGONAL,   vforce = mobjinfo[MT_SDBSPRG].mass, hforce = mobjinfo[MT_SDBSPRG].damage},
	[MT_SVBSPRG] = {socname = "BLUSPRG", color = BLUE,   orientation = VERTICAL,   vforce = mobjinfo[MT_SVBSPRG].mass, hforce = mobjinfo[MT_SVBSPRG].damage}
}

local vforce, hforce
for k,v in pairs(springtypes) do
	addHook("MobjSpawn", function(mo)
		mo.orientation = v.orientation
		vforce = mapheaderinfo[gamemap][v.socname:lower().."_vforce"] 
		hforce = mapheaderinfo[gamemap][v.socname:lower().."_hforce"]
		mo.info.mass = vforce and vforce*FRACUNIT or v.vforce -- if defined in the level header, we use that value
		mo.info.damage = hforce and hforce*FRACUNIT or v.hforce -- otherwise we revert it back to the original value
	end, k)
end

local options, z, momz
for k,v in pairs(springtypes) do
	addHook("MobjCollide", function(spring, tmthing)
		if tmthing.eflags & MFE_SPRUNG then return end
		if spring.z > tmthing.z + tmthing.height then return false end -- overhead
		if spring.z + spring.height < tmthing.z then return false end -- underneath

		options = spring.spawnpoint.options
		if options & MTF_SPECIAL then -- for forcing momentum
			if spring.orientation == HORIZONTAL and not P_IsObjectOnGround(tmthing) then -- reset falling momentum
				momz = P_MobjFlip(tmthing)*6*FRACUNIT -- small bit o arc
				tmthing.momz = 0 + momz
			elseif spring.orientation == VERTICAL then -- stop all horizontal mom and align with spring
				tmthing.momx = 0
				tmthing.momy = 0
				if P_MobjFlip(spring) > 0 then
					z = spring.z + spring.height - FRACUNIT
				else
					z = spring.z - tmthing.height + FRACUNIT
				end
				spring.flags = $ & ~(MF_SOLID|MF_SPECIAL) -- De-solidify before we move
				P_TeleportMove(tmthing, spring.x, spring.y, z)
				spring.flags = $ | (spring.info.flags & (MF_SPECIAL|MF_SOLID))
			end
		end
		if options & MTF_AMBUSH then -- Force angle to spring angle
			tmthing.angle = spring.angle
		else
			tmthing.springresetangle = true
		end
		if spring.orientation == HORIZONTAL then
			tmthing.springfrictionmult = FRACUNIT*3/4
		end
	end, k)
end

local startingfric, difference
addHook("MobjThinker", function(mo)
	if mo.springfrictionmult then
		if P_IsObjectOnGround(mo) then 
			difference = FRACUNIT - mo.friction
			mo.friction = $+FixedMul(difference, mo.springfrictionmult) 
		end
		mo.springfrictionmult = max(0, $-FRACUNIT/2/TICRATE) -- Recover in one second
	end
end, MT_PLAYER)

addHook("ThinkFrame", do
	for p in players.iterate do
		if p.mo and p.mo.valid then 
			if p.mo.springresetangle then
				p.mo.angle = p.mo.previousangle 
				p.mo.springresetangle = nil
			end
			p.mo.previousangle = p.mo.angle
		end
	end
end)