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

/* Freeslots */
freeslot(
	"MT_MINECART",
	"MT_MINECARTSHORT",
	"MT_MINECARTLONG",
	"MT_MINECARTSPAWNER"
)

-- Ported form of K_KartBouncing and K_GetMobjWeight used for empty kart 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 = P_AproxDistance(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 (P_AproxDistance(momdifx, momdify) < (25*mapobjectscale)) then
		local momdiflength = P_AproxDistance(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

/* Minecart Spawn Function */
local function SetObjectVars(thing)	
	/* Spawn papersprites */
	thing.FrontSide = P_SpawnMobj(thing.x, thing.y, thing.z, MT_MINECARTSHORT)
	thing.BackSide = P_SpawnMobj(thing.x, thing.y, thing.z, MT_MINECARTSHORT)
	thing.LeftSide = P_SpawnMobj(thing.x, thing.y, thing.z, MT_MINECARTLONG)
	thing.RightSide = P_SpawnMobj(thing.x, thing.y, thing.z, MT_MINECARTLONG)
	thing.flagsassigned = false	-- Set to true after 1 tic when object flags are changed
	thing.spinouttimer  = 0 -- Spinout timer for karts
	thing.fuse = 12
end -- Close function

/* Handle collision with another object */
local function BumpCode(thing, other)
	local bumpfuse = 6
	local objweight = thing.spawnpoint.extrainfo
	thing.shouldcollide = false
	if (thing.z <= other.z + other.height and other.z <= thing.z + thing.height) then -- Height check
		if (thing.state != S_KARTSQUISHED) then -- Squished karts never collide
			/* Check for players */
			if (other.player and not other.player.spectator and (other.player.kartstuff[k_justbumped] == 0) and (other.player.powers[pw_flashing] == 0) and not (thing.spawnpoint.options & (MTF_AMBUSH))) then
				if (objweight > 9) then -- Constrain weight to vanilla limits
					objweight = 9
				elseif (objweight < 1) then
					objweight = 1
				end
				/* Check for Grow */
				if (other.player.kartstuff[k_growshrinktimer] > 0) then
					KartBounce(other, thing, 0, false, 0<<FRACBITS)
					KartBounce(thing, other, 0, false, 9<<FRACBITS)
				/* Check for Invinc */
				elseif (other.player.kartstuff[k_invincibilitytimer] > 0)
					thing.spinouttimer = 36
					KartBounce(other, thing, 0, false, 0<<FRACBITS)
					KartBounce(thing, other, 0, false, 9<<FRACBITS)
				/* Player has no powers */
				else
					KartBounce(other, thing, 0, false, objweight<<FRACBITS)
					KartBounce(thing, other, 0, false, other.player.kartweight<<FRACBITS)
				end
				
				thing.fuse = bumpfuse -- Set Kart unable to collide for another <bumpfuse> tics
				thing.shouldcollide = true
			
			/* Check for solid karts */
			elseif (thing.spawnpoint.options & (MTF_AMBUSH)) then
				KartBounce(other, thing, 0, true, objweight<<FRACBITS) -- Only bounce *colliding* object– Object should remain stationary
				thing.fuse = bumpfuse
				thing.shouldcollide = true
			
			/* Check for other objects */
			elseif (not other.player and other.flags & (MF_SOLID) and thing.fuse == 0)
				KartBounce(thing, other, 0, true, 5<<FRACBITS)
				KartBounce(other, thing, 0, true, 5<<FRACBITS)
				
				thing.fuse = bumpfuse -- Set Kart unable to collide for another <bumpfuse> tics
				thing.shouldcollide = true
			end
		end
	end
	return thing.shouldcollide -- Check if objects should collide
end -- Close function

/* Minecart Sides thinker */
local function MinecartThinker(thing)
	local FrontBackOffset = 25*FRACUNIT
	local RightLeftOffset = 20*FRACUNIT

	/* Get minecart papersprite positions */
	/* Thanks Weeg for doing trig lol */
	local frontx = thing.x + FixedMul(FrontBackOffset, cos(thing.angle))
	local fronty = thing.y + FixedMul(FrontBackOffset, sin(thing.angle))

	local backx = thing.x - FixedMul(FrontBackOffset, cos(thing.angle))
	local backy = thing.y - FixedMul(FrontBackOffset, sin(thing.angle))

	local leftx = thing.x + FixedMul(RightLeftOffset, cos(thing.angle + ANGLE_90))
	local lefty = thing.y + FixedMul(RightLeftOffset, sin(thing.angle + ANGLE_90))

	local rightx = thing.x - FixedMul(RightLeftOffset, cos(thing.angle + ANGLE_90))
	local righty = thing.y - FixedMul(RightLeftOffset, sin(thing.angle + ANGLE_90))
	
	/* Update papersprite positions */
	P_TeleportMove(thing.FrontSide,	frontx, fronty, thing.z)
	P_TeleportMove(thing.BackSide,	backx,	backy, thing.z)
	P_TeleportMove(thing.LeftSide,	leftx, lefty, thing.z)
	P_TeleportMove(thing.RightSide,	rightx, righty, thing.z)
	
	/* Update papersprite angles */
	thing.FrontSide.angle, thing.BackSide.angle = thing.angle + ANGLE_90, thing.angle + ANGLE_90
	thing.LeftSide.angle, thing.RightSide.angle = thing.angle, thing.angle
	
	/* Update papersprite momentum */
	thing.FrontSide.momx, thing.BackSide.momx, thing.LeftSide.momx, thing.RightSide.momx = thing.momx, thing.momx, thing.momx, thing.momx
	thing.FrontSide.momy, thing.BackSide.momy, thing.LeftSide.momy, thing.RightSide.momy = thing.momy, thing.momy, thing.momy, thing.momy
	thing.FrontSide.momz, thing.BackSide.momz, thing.LeftSide.momz, thing.RightSide.momz = thing.momz, thing.momz, thing.momz, thing.momz
	
	/* Assign flags on first tic spawned */
	if (thing.flagsassigned == false) then
		if (thing.spawnpoint.options & (MTF_AMBUSH)) then
			thing.flags = $ & ~(MF_SPECIAL)
			thing.flags = $ & ~(MF_PUSHABLE)
		end
	thing.flagsassigned = true
	end
	/* Spin kart on invinc bump */
	if thing.spinouttimer then
		thing.angle = $ + ANG10
		thing.spinouttimer = max($ - 1, 0)
	end
	/* PEOPLE DIE WHEN THEY ARE KILLED */
	if P_CheckDeathPitCollide(thing)
		P_RemoveMobj(thing)
	end
end -- Close function

addHook("MobjSpawn",	SetObjectVars,	MT_MINECART)
addHook("MobjThinker",	MinecartThinker,MT_MINECART)
addHook("MobjCollide",	BumpCode,		MT_MINECART)
/* Override functions that potentially kill the Kart */
addHook("MobjFuse",		do return true end,	MT_MINECART)
addHook("TouchSpecial",	do return true end,	MT_MINECART)

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