if (minecarts_initialized) then return end -- Check if the script has already been run

/* Freeslots */
freeslot(
	"SPR_MINE",
	
	"MT_MINECART", 
	"MT_MINECARTSHORT", 	
	"MT_MINECARTLONG",
	"MT_MINECARTSPAWNER",
	
	"S_MINECARTSH",
	"S_MINECART",
	"S_MINECARTLN"	
)

-- Object definitions
states[S_MINECART] = { NULL, A, -1, nil, 0, 0, S_NULL }
mobjinfo[MT_MINECART] = {
	--$Name Pushable Minecart
	--$Sprite MINEA0
	--$Category Community Resources
	doomednum = 2509, -- Thing number
	spawnstate = S_MINECART,
	spawnhealth = 1000,
	radius = 36*FRACUNIT,
	height = 36*FRACUNIT,
	flags = MF_SOLID|MF_PUSHABLE|MF_BOUNCE
}
states[S_MINECARTSH] = { SPR_MINE, FF_PAPERSPRITE|A, -1, nil, 0, 0, S_NULL }
mobjinfo[MT_MINECARTSHORT] = {
	spawnstate = S_MINECARTSH,
	radius = 25*FRACUNIT,
	height = 25*FRACUNIT
}
states[S_MINECARTLN]  = { SPR_MINE, FF_PAPERSPRITE|B, -1, nil, 0, 0, S_NULL }
mobjinfo[MT_MINECARTLONG] = {
	spawnstate = S_MINECARTLN,
	radius = 36*FRACUNIT,
	height = 36*FRACUNIT
}

-- Horrific optimizations lol
local FRACUNIT = FRACUNIT
local FRACBITS = FRACBITS
local ANGLE_90 = ANGLE_90
local FixedMul = FixedMul
local FixedDiv = FixedDiv
local sin = sin
local cos = cos
local DIR_FORWARD = 0
local DIR_LATERAL = 1

-- Ported form of K_KartBouncing and K_GetMobjWeight used for interactions.
local function KartBounce(mobj1, mobj2, bounce, solid, objweight)
	local momdifx, momdify
	local distx, disty
	local dot, force
	local mass1 
	local mass2
	
	if (not mobj1 or not mobj2)
		return
	end
	
	-- Don't bump when you're being reborn
	if ((mobj1.player and mobj1.player.playerstate != PST_LIVE)
		or (mobj2.player and mobj2.player.playerstate != PST_LIVE)) then
		return
	end

	if ((mobj1.player and mobj1.player.kartstuff[k_respawn])
		or (mobj2.player and mobj2.player.kartstuff[k_respawn])) then
		return
	end
	
	-- Don't bump if you're flashing
	local flash
	if(mobj1.player)
		flash = K_GetKartFlashing(mobj1.player);
		if (mobj1.player and mobj1.player.powers[pw_flashing] > 0 and mobj1.player.powers[pw_flashing] < flash) then
			if (mobj1.player.powers[pw_flashing] < flash-1)
				mobj1.player.powers[pw_flashing] = $1 + 1
				return
			end
		end
	end
	
	if(mobj2.player)
		flash = K_GetKartFlashing(mobj2.player);
		if (mobj2.player and mobj2.player.powers[pw_flashing] > 0 and mobj2.player.powers[pw_flashing] < flash) then
			if (mobj2.player.powers[pw_flashing] < flash-1) then
				mobj2.player.powers[pw_flashing] = $1 + 1
				return
			end
		end
	end
	
	if (mobj1.player and mobj1.player.kartstuff[k_justbumped]) then
		mobj1.player.kartstuff[k_justbumped] = 6
		return
	end
	
	if (mobj2.player and mobj2.player.kartstuff[k_justbumped]) then
		mobj2.player.kartstuff[k_justbumped] = 6
		return
	end

	if (mobj1.player) then
			if (mobj1.player.kartstuff[k_spinouttimer] > 0) then
				mass1 = 0
			else
				mass1 = (mobj1.player.kartweight)<<FRACBITS
				if (mobj1.player.speed > K_GetKartSpeed(mobj1.player, false)) then
					mass1 = $1 + (mobj1.player.speed - K_GetKartSpeed(mobj1.player, false))/8
				end
			end
	else
		mass1 = objweight
	end
	
	if (solid == true and mass1 > 0) then
		mass2 = mass1
	else
		if (mobj2.player) then
			if (mobj2.player.kartstuff[k_spinouttimer] > 0) then
				mass2 = 0
			else
				mass2 = (mobj2.player.kartweight)<<FRACBITS
				if (mobj2.player.speed > K_GetKartSpeed(mobj2.player, false)) then
					mass2 = $1 + (mobj2.player.speed - K_GetKartSpeed(mobj2.player, false))/8
				end
			end
		else
			mass2 = objweight
		end
	end
	
	momdifx = mobj1.momx - mobj2.momx
	momdify = mobj1.momy - mobj2.momy
	
	-- Adds the OTHER player's momentum times a bunch, for the best chance of getting the correct direction
	-- possibly Edit so it affects collided object.
	distx = (mobj1.x + mobj2.momx*3) - (mobj2.x + mobj1.momx*3)
	disty = (mobj1.y + mobj2.momy*3) - (mobj2.y + mobj1.momy*3)

	if (distx == 0 and disty == 0) then
		-- If there's no distance between the 2, they're directly on top of each other, don't run this
		return
	end

	-- Normalize distance to the sum of the two objects' radii, since in a perfect world that would be the distance at the point of collision...
		local dist = FixedHypot(distx, disty)
		local nx = FixedDiv(distx, dist)
		local ny = FixedDiv(disty, dist)
		
		-- dist = dist ? dist : 1;
		if not dist == dist
			dist = 1
		end
		
		distx = FixedMul(mobj1.radius+mobj2.radius, nx)
		disty = FixedMul(mobj1.radius+mobj2.radius, ny)

		if (momdifx == 0 and momdify == 0) then
			-- If there's no momentum difference, they're moving at exactly the same rate. Pretend they moved into each other.
			momdifx = -nx
			momdify = -ny
		end

	-- If the speed difference is less than this let's assume they're going proportionately faster from each other
	if (FixedHypot(momdifx, momdify) < (25*mapobjectscale)) then
		local momdiflength = FixedHypot(momdifx, momdify)
		local normalisedx = FixedDiv(momdifx, momdiflength)
		local normalisedy = FixedDiv(momdify, momdiflength)
		momdifx = FixedMul((25*mapobjectscale), normalisedx)
		momdify = FixedMul((25*mapobjectscale), normalisedy)
	end

	dot = FixedMul(momdifx, distx) + FixedMul(momdify, disty)

	if (dot >= 0)
		-- They're moving away from each other
		return
	end

	force = FixedDiv(dot, FixedMul(distx, distx)+FixedMul(disty, disty))
	if (bounce == 1 and mass2 > 0) -- Perform a Goomba Bounce.
		mobj1.momz = -mobj1.momz
	else
		local newz = mobj1.momz
		if (mass2 > 0) then
			mobj1.momz = mobj2.momz
		elseif (mass1 > 0 and solid == false) then
			mobj2.momz = newz
		end
	end

	if (mass2 > 0) then
		mobj1.momx = mobj1.momx - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), force), distx)
		mobj1.momy = mobj1.momy - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), force), disty)
	end

	if (mass1 > 0 and solid == false) then
		mobj2.momx = mobj2.momx - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), force), -distx)
		mobj2.momy = mobj2.momy - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), force), -disty)
	end
	
	S_StartSound(mobj1, sfx_s3k49)
	-- S_StartSound(mobj1, sfx_alarm) -- Debug
	local fx = P_SpawnMobj(mobj1.x/2 + mobj2.x/2, mobj1.y/2 + mobj2.y/2, mobj1.z/2 + mobj2.z/2, MT_BUMP);
	-- print(fx.type) -- Debug
	if (mobj1.eflags & MFE_VERTICALFLIP) then
		fx.eflags = $1 | MFE_VERTICALFLIP
	else
		fx.eflags = $1 & ~MFE_VERTICALFLIP
	end
	-- Because this is done during collision now, rmomx and rmomy need to be recalculated
	-- so that friction doesn't immediately decide to stop the player if they're at a standstill
	-- Also set justbumped here
	if (mobj1.player) then
		mobj1.player.rmomx = mobj1.momx - mobj1.player.cmomx
		mobj1.player.rmomy = mobj1.momy - mobj1.player.cmomy
		mobj1.player.kartstuff[k_justbumped] = 6 -- bumptime
		if (mobj1.player.kartstuff[k_spinouttimer])
			mobj1.player.kartstuff[k_wipeoutslow] = 20+1 -- wipeoutslowtime
			mobj1.player.kartstuff[k_spinouttimer] = max(20+1, mobj1.player.kartstuff[k_spinouttimer])
		end
	end
		
	if (mobj2.player) then
		mobj2.player.rmomx = -(mobj2.momx - mobj2.player.cmomx)
		mobj2.player.rmomy = -(mobj2.momy - mobj2.player.cmomy)
		mobj2.player.kartstuff[k_justbumped] = 6 -- bumptime
		if (mobj2.player.kartstuff[k_spinouttimer])
			mobj2.player.kartstuff[k_wipeoutslow] = 20+1 -- wipeoutslowtime
			mobj2.player.kartstuff[k_spinouttimer] = max(20+1, mobj2.player.kartstuff[k_spinouttimer])
		end
	end
	-- print("M1: "..mass1) -- Debug
	-- print("M2: "..mass2) -- Debug
end -- Close Function

-- MINE Cart spawn func
local function SetObjectVars(mobj)	
	mobj.sides = { front = {}, back = {}, left = {}, right = {}}
	mobj.sides.front.mobj = P_SpawnMobj(mobj.x, mobj.y, mobj.z, MT_MINECARTSHORT)
	mobj.sides.front.facing = DIR_LATERAL
	mobj.sides.back.mobj = P_SpawnMobj(mobj.x, mobj.y, mobj.z, MT_MINECARTSHORT)
	mobj.sides.back.facing = DIR_LATERAL
	mobj.sides.left.mobj = P_SpawnMobj(mobj.x, mobj.y, mobj.z, MT_MINECARTLONG)
	mobj.sides.left.facing = DIR_FORWARD
	mobj.sides.right.mobj = P_SpawnMobj(mobj.x, mobj.y, mobj.z, MT_MINECARTLONG)
	mobj.sides.right.facing = DIR_FORWARD
	
	-- Randomize object color
	--mobj.color = P_RandomRange(1,MAXSKINCOLORS-1)
	--for k,v in pairs(mobj.sides) do
		--v.mobj.color = mobj.color
	--end
end -- Close function

-- Handle collisions with other objects
local bumpFuse = 6
local objWeight, cldWeight, shouldCollide
local function KartBumpCollide(mobj, cldr)
	objWeight = max(min(mobj.spawnpoint.extrainfo, 9), 1)<<FRACBITS -- Constrain to vanilla weight bounds
	cldWeight = (cldr.player ~= nil and max(cldr.player.kartweight, 2)<<FRACBITS or objWeight)
	shouldCollide = false
	-- Height check
	if not (mobj.z <= cldr.z + cldr.height and cldr.z <= mobj.z + mobj.height) then return false end
	-- If colliding with a player
	if (cldr.player and not cldr.player.spectator and (cldr.player.kartstuff[k_justbumped] == 0) and (cldr.player.powers[pw_flashing] == 0)) then
		if (cldr.state == S_KARTSQUISHED) then return false end -- Squished players never collide

		if (cldr.player.kartstuff[k_growshrinktimer] or cldr.player.kartstuff[k_invincibilitytimer]) then
			objWeight = 0
			cldWeight = 9<<FRACBITS
			mobj.spinouttimer = (cldr.player.kartstuff[k_invincibilitytimer] and 35 or $)
		end
		shouldCollide = true
	-- If colliding with another object
	elseif (not cldr.player and not mobj.fuse and cldr.flags & (MF_SOLID)) then
		shouldCollide = true
	end
	
	-- Handle collisions
	if (shouldCollide) then
		KartBounce(mobj, cldr, 0, false, objWeight)
		KartBounce(cldr, mobj, 0, false, cldWeight)
		S_StartSound(mobj1, sfx_s3k49)
		mobj.angle = $ + (P_RandomRange(-45, 45) * ANG1)
		mobj.fuse = bumpFuse -- Set Kart unable to collide for a brief time
	end
	return shouldCollide
end -- Close function

local forwardOffset, lateralOffset, forwardAngle, lateralAngle, mobX, mobY, mobZ, prevMobX, prevMobY, prevMobZ
local function PaperObjThinker(mobj)
	forwardOffset = 25<<FRACBITS
	lateralOffset = 20<<FRACBITS
	forwardAngle = mobj.angle
	lateralAngle = mobj.angle + ANGLE_90
	mobX, mobY, mobZ = mobj.x, mobj.y, mobj.z
	
	-- Only recalculate and update positions if the mobj has moved
	if (mobX ~= prevMobX or mobY ~= prevMobY or mobZ ~= prevMobZ) then
		-- Calculate papersprite positions
		mobj.sides.front.x = mobX + FixedMul(forwardOffset, cos(forwardAngle))
		mobj.sides.front.y = mobY + FixedMul(forwardOffset, sin(forwardAngle))
		mobj.sides.back.x = mobX - FixedMul(forwardOffset, cos(forwardAngle))
		mobj.sides.back.y = mobY - FixedMul(forwardOffset, sin(forwardAngle))
		mobj.sides.left.x = mobX + FixedMul(lateralOffset, cos(lateralAngle))
		mobj.sides.left.y = mobY + FixedMul(lateralOffset, sin(lateralAngle))
		mobj.sides.right.x = mobX - FixedMul(lateralOffset, cos(lateralAngle))
		mobj.sides.right.y = mobY - FixedMul(lateralOffset, sin(lateralAngle))
		
		-- Update papersprite positions
		for k,v in pairs(mobj.sides) do
			P_MoveOrigin(v.mobj, v.x, v.y, mobZ) -- Update position
			v.mobj.angle = (v.facing == DIR_FORWARD and forwardAngle or lateralAngle) -- Update angle
		end
	end
	
	-- Spin MINE cart on invinc bump
	if mobj.spinouttimer then
		mobj.angle = $ + ANG10
		mobj.spinouttimer = max($ - 1, 0)
	end
	-- PEOPLE DIE WHEN THEY ARE KILLED
	if P_CheckDeathPitCollide(mobj)
		P_RemoveMobj(mobj)
	end
	
	-- Set variables for next tic
	prevMobX, prevMobY, prevMobZ = mobX, mobY, mobZ
end -- Close function

addHook("MobjSpawn",	SetObjectVars, MT_MINECART)
addHook("MobjThinker",	PaperObjThinker, MT_MINECART)
addHook("MobjCollide",	KartBumpCollide, MT_MINECART)
addHook("MobjFuse",		do return true end,	MT_MINECART) -- Don't kill the Mine Cart with the fuse


rawset(_G, "minecarts_initialized", true) -- The script ran, don't run it again