-- this script attempts to make Kart more playable in Multiplayer when too many people are around by disabling some items and doing other stuff as the playercount increases
-- everything in this script is LOL XD! By VincyTM
-- Kanade is the most precious girl out there.

--local dbg_amount = 5

local function K_countplayers()

	if dbg_amount
		return dbg_amount
	end

	local count = 0
	for p in players.iterate do
		if not p.mo or not p.mo.valid continue end
		count = $+1
	end
	return count
end

-- item fall off:

--[ITEMTYPE] = {initial amount to go over of, num of players, remove 1 every n player, min it can lower to}

local itemfalloff = {
	[KITEM_BANANA] = {4, 5, 1, 3},	-- if we have >= 4 bananas with 5 or more players, remove 1 banana / player with a minimum of 3.
	[KITEM_ORBINAUT] = {2, 6, 2, 2},	-- remove 1 every 2 player, minimum 2 orbis, this is p fine!
	[KITEM_JAWZ]	= {2, 8, 1, 1},		-- remove dual jawz past 8 players, literally FUCK OFF.
}

local function K_handleitemtoggles(p, count)

	if not kmp_itemlimiter.value return end

	-- depending on the player count, we will reduce the amount of items you can obtain.
	-- for instance, with too many players, decabanana will just turn inuffto a triple banana.


	if p.kartstuff[k_itemblink] ~= (TICRATE) return end	-- only do it as we've JUST obtained the item at the end of the frame.

	local item = p.kartstuff[k_itemtype]
	if not item return end	-- no item, no need to continue
	if not itemfalloff[item] return end


	for k,v in pairs(itemfalloff)
		if item == k
			local amount = p.kartstuff[k_itemamount]

			if amount >= v[1]	-- we have too many items of that type
			and count >= v[2]	-- there are too many players for this item

				local diff = count - v[2]	-- player difference
				local loop_count = 0	-- how many players we've counted

				for i = 1, diff+1
					loop_count = $+1
					if not (loop_count%v[3])
					and amount > v[4]	-- > min item count for this
						amount = $-1	-- remove 1 item
					end
				end

				p.kartstuff[k_itemamount] = amount
			end
		end
	end
end

-- extend flashtics if there are too many players
local function K_handleflashing(p, count)

	if not kmp_extendflashtics.value return end

	local mo = p.mo

	if gametype ~= GT_RACE return end	-- not my fucking problem
	if leveltime < TICRATE*8 return end	-- don't touch ANYTHING at the start of the race.

	if not mo.K_fakeflashing
		mo.K_cansetflashtics = true
	end

	local flashcount = max(0, count-8)

	if p.powers[pw_flashing]
	and mo.K_cansetflashtics
	and not p.kartstuff[k_hyudorotimer]
		mo.K_fakeflashing = K_GetKartFlashing(p)*2
		mo.K_cansetflashtics = false
	end

	-- offset kart's weird change that prevents flashing if pw_flashing > getkartflashing

	local flashing = (p.powers[pw_flashing] or mo.K_fakeflashing) and not p.kartstuff[k_spinouttimer] and not p.kartstuff[k_wipeoutslow] and leveltime > TICRATE*6


	if flashing
	and not p.kartstuff[k_hyudorotimer]
		if not p.kartstuff[k_respawn]
			mo.K_fakeflashing = $-1
			if flashing and leveltime&1 and p.mo.health
				mo.flags2 = $|MF2_DONTDRAW
			elseif (flashing and not (leveltime&1) or not p.mo.health)
				mo.flags2 = $ & ~MF2_DONTDRAW
			end
		end
		p.powers[pw_flashing] = min(K_GetKartFlashing(p), mo.K_fakeflashing)
	end

	-- avoids us being immediately squished again by a growth player we pushed away
	if mo.K_squishimmubuffer
		mo.K_squishimmubuffer = $-1
	end
end

-- damage hooks:
local function K_flashspin(p, inf)
	local mo = p.mo
	if not kmp_extendflashtics.value return end
	if not mo return end

	if mo.K_fakeflashing
		K_DoInstashield(p)
		return false
	end
end

local function K_flashsquish(p, inf)
	local mo = p.mo
	if not mo return end

	if p.kartstuff[k_invincibilitytimer] or p.kartstuff[k_hyudorotimer] return end

	if mo.K_squishimmubuffer
		return false
	end

	if inf and inf.valid and inf.type == MT_PLAYER
	and kmp_growthbump.value
		-- someone rolled over a grandma
		if (not P_IsObjectOnGround(mo) and not P_IsObjectOnGround(inf)) -- we aren't on the floor, it makes NO FUCKING sense to get squished.
		or FixedHypot(inf.momx, inf.momy) < 8*inf.scale	-- inf is BARELY moving, we shouldn't get squished either, that's bullshit!]
		or mo.K_squishimmubuffer

			-- make weight articially higher for this bump
			local weight1, weight2 = mo.player.kartweight, inf.player.kartweight
			if P_IsObjectOnGround(mo) and P_IsObjectOnGround(inf)
				inf.player.kartweight = 32
			else
				inf.player.kartweight = mo.player.kartweight
			end

			K_KartBouncing(mo, inf)

			mo.player.kartweight = weight1
			inf.player.kartweight = weight2

			mo.K_squishimmubuffer = 16
			return false
		end
	end

	if mo.K_fakeflashing
	and kmp_extendflashtics.value
		K_DoInstashield(p)
		return false
	end
end

addHook("ShouldSpin", K_flashspin)
addHook("ShouldSquish", K_flashsquish)
-- bombs are shit and sadly we can't do anything with a Lua.
-- I need to fix them on my own in the source code, apologies...

local ver_11 = (VERSION == 110)
local ORIG_FRICTION = 62914

-- I swear banana friction is fine, that's the intended behavior :V :V :V
-- sad how a bug in 1.0.4 actually made it better don'tcha think? :P
local function K_fakebananafriction(p)

	if not kmp_slowspinout.value and not (kmp_offroadspinout.value and p.kartstuff[k_offroad]) return end

	-- fake friction here:
	local mo = p.mo
	if p.kartstuff[k_spinouttimer] and not p.kartstuff[k_wipeoutslow] and not p.kartstuff[k_sneakertimer]	 -- wipeout already works properly. wipeout is also a good game franchise, go play it nerd!
	and leveltime%3
	and P_IsObjectOnGround(mo)
	and not mo.ggz_frozen	-- let's support my own shit
		if p.kartstuff[k_offroad]
			mo.friction = $- 4912
		end
		local friction = max(0, mo.friction)

		mo.momx = FixedMul($, friction)
		mo.momy = FixedMul($, friction)	-- this is how P_XYFriction handles things. Actually pretty dumb, huh!
		mo.friction = ORIG_FRICTION

	end
end

-- orbiting item changes

local function K_KillKartItem(o, clash)
	o.flags = $|MF_NOCLIPTHING
	o.z = $ + o.height*P_MobjFlip(o)
	S_StartSound(o, o.info.deathsound)
	P_KillMobj(o)
	P_SetObjectMomZ(o, FRACUNIT*8)
	P_InstaThrust(o, P_RandomRange(0, 359)*ANG1, 16*FRACUNIT)
	if clash
		P_SpawnMobj(o.x, o.y, o.z, MT_ITEMCLASH)
	end
end

local ORBIT_TIME = TICRATE*9

local typelookup = {
	[MT_ORBINAUT_SHIELD] = MT_ORBINAUT,
	[MT_JAWZ_SHIELD] = MT_JAWZ_DUD,
}

local function K_handleorbit(p)

	if not kmp_orbitlimit.value return end

	-- orbiting item is stored in p.mo.hnext
	-- we just check if the type is mt_orbinaut_shield or mt_jawz_shield to make sure it's an orbiting item and not banana/eggbox

	local mo = p.mo
	local ob = mo.hnext

	if not ob or not ob.valid or (ob.type ~= MT_ORBINAUT_SHIELD and ob.type ~= MT_JAWZ_SHIELD) or p.kartstuff[k_itemtype] == 0
		mo.orbittimer = 0
		mo.orbitspdtimer = 0
		return
	end

	mo.orbittimer = $ and $-1 or ORBIT_TIME

	if not mo.orbitspdtimer
		mo.orbitspdtimer = ORBIT_TIME
	end
	mo.orbitspdtimer = max(1, $-1)	-- don't want this to reset mid use

	-- threw an item? this resets the fuse, not the speed
	if p.cmd.buttons & BT_ATTACK
		mo.kmp_attacktimer = $ and $+1 or 1
		if mo.kmp_attacktimer == 1
			mo.orbittimer = min(ORBIT_TIME, $+ORBIT_TIME/2)

			while ob	-- make them all visible again
				ob.flags2 = $ & ~MF2_DONTDRAW
				ob = ob.hnext
			end

		end
	else
		mo.kmp_attacktimer = 0
	end

	if mo.orbittimer == 1
		-- kill entire hnext list
		-- we actually have to spawn the dropped item and then kill it to have the desired visual effect. .....yyyeaaaaahhh....
		while ob
			local next = ob.hnext

			local t = P_SpawnMobj(ob.x, ob.y, ob.z, typelookup[ob.type])
			t.target = mo
			t.color = ob.color
			K_KillKartItem(t, true)
			if ob and ob.valid
				P_RemoveMobj(ob)
			end
			ob = next
		end
		p.kartstuff[k_itemtype] = 0
		mo.hnext = nil
		mo.orbittimer = 0
		return
	end

	local angadd = FixedAngle(max(0, mo.orbitspdtimer - ORBIT_TIME/2)<<FRACBITS)/8

	while ob
		-- make items spin faster after they've just been brought out
		ob.angle = $+angadd

		-- make items flash if the timer is low
		if mo.orbittimer < ORBIT_TIME/4
			if leveltime & 1
				ob.flags2 = $|MF2_DONTDRAW
			else
				ob.flags2 = $ & ~MF2_DONTDRAW
			end
		end
		ob = ob.hnext
	end
end

-- orbit timer hud

-- of course, fickle's replay system kind of sucks so splitscreen there will need an ADDITIONAL HACK.
-- thank you fickle.
local splitplayers = {}
local function splitnum(p)

	for i = 1, #splitplayers do
		if splitplayers[i] == p
			return i-1
		end
	end
end

local function hu_flushsplitplayers()
	if #splitplayers > splitscreen
		splitplayers = {}
	end
end

hud.add(function(v, p, c)

	splitplayers[#splitplayers+1] = p

	if not kmp_orbitlimit.value return end
	local mo = p.mo
	if not mo or not mo.valid return end
	local x, y = 5, 5
	local flags = V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS

	-- tsr coord:
	if tsr_ultimatemusic	-- yes, this is a valid check, fuck you
		x, y = 265, 5
		flags = V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS
	end

	flags = FUCK_returnsplitflags($, splitnum(p))

	-- credits to Snu for this wonderful port~

	-- 265, 5
	-- 276, 40
	-- timer bars
	if mo.orbittimer

		local l = mo.orbittimer

		local coords = {x+11, y+35}
		if (splitscreen)
			coords = {x+14, y+31}
			if (splitscreen) > 1
				coords = {x+35, 19}
			end
		end

		-- main bar
		v.draw(coords[1], coords[2], v.cachePatch(splitscreen and (splitscreen > 1 and "K_ISIMER") or "K_ITIMER"), flags)

		-- borders for 1p-2p
		local length = (splitscreen and (splitscreen > 1 and 12 or 19) or 26)
		if not (splitscreen > 1)
			v.drawFill(coords[1]+1, coords[2]+1, l*length/ORBIT_TIME, 1, 120|flags)
			v.drawFill(coords[1]+1, coords[2]+2, l*length/ORBIT_TIME, 1, 8|flags)
			v.drawFill(coords[1]+1, coords[2]+1, 1, 2, 12|flags)
		end

		if l*length/ORBIT_TIME > 0
			v.drawFill(coords[1]+l*length/ORBIT_TIME, coords[2]+1, 1, 2, 12|flags)
		end
	end
	hu_flushsplitplayers()
end)

local orbi_list = {}
-- synch
addHook("NetVars", function(net)
	orbi_list = net(orbi_list)
end)

addHook("MapLoad", do
	orbi_list = {}	-- reset
end)

-- orbit item fuse
addHook("MobjSpawn", function(mo)

	if not kmp_orbinautfuse.value return end

	if mo and mo.valid and mo.health
		orbi_list[#orbi_list+1] = mo
	end
end, MT_ORBINAUT)

local ORBI_FUSE = TICRATE*20

local function K_orbinautfuse()
	if not kmp_orbinautfuse.value
		orbi_list = {}
		return
	end

	local i = #orbi_list
	while i
		local mo = orbi_list[i]

		if not mo or not mo.valid
		or FixedHypot(mo.momx, mo.momy) == 0	-- Still orbinauts can stay on the track
			if mo and mo.valid
				mo.flags2 = $ & ~MF2_DONTDRAW	-- don't end up with invis shit
			end
			table.remove(orbi_list, i)
			i = $-1
			continue
		end

		if not FixedHypot(mo.momx, mo.momy)
			mo.flags2 = $ & ~MF2_DONTDRAW
			table.remove(orbi_list, i)
			i = $-1
			continue
		end

		-- mo is valid:
		mo.orbifuse = $ and $+1 or 1

		if mo.orbifuse > ORBI_FUSE*3/4
			if leveltime&1
				mo.flags2 = $|MF2_DONTDRAW
			else
				mo.flags2 = $ & ~MF2_DONTDRAW
			end
		end

		if mo.orbifuse >= ORBI_FUSE
			mo.flags2 = $ & ~MF2_DONTDRAW
			K_KillKartItem(mo, true)
			table.remove(orbi_list, i)
			i = $-1
			continue
		end

		i = $-1
	end
end

local kitemlist = {
	MT_BANANA,
	MT_ORBINAUT,
	MT_JAWZ,
}

-- fix shit not colliding when it should
local function item_collide(mo1, mo2)
	if mo1 and mo1.valid and mo2 and mo2.valid and mo1.health and mo2.health
	and mo1.z + mo1.height >= mo2.z
	and mo1.z <= mo2.z + mo2.height
	and not mo2.threshold

		local proceed
		for i = 1, #kitemlist
			if mo2.type == kitemlist[i]
				proceed = true
				break
			end
		end
		if not proceed return end

		if mo1.target ~= mo2.target return end	-- already handled fine

		-- item 1 damage:
		K_KillKartItem(mo1)
		K_KillKartItem(mo2, true)
		return true
	end
end

addHook("MobjCollide", item_collide, MT_ORBINAUT_SHIELD)
addHook("MobjMoveCollide", item_collide, MT_ORBINAUT_SHIELD)

addHook("MobjCollide", item_collide, MT_JAWZ_SHIELD)
addHook("MobjMoveCollide", item_collide, MT_JAWZ_SHIELD)

-- hard mode sneakers
local function K_handlehardsneaker(p)

	if not kmp_hardsneakers.value return end

	if p.kartstuff[k_sneakertimer]
		p.kartstuff[k_speedboost] = max($, FRACUNIT/2)
	end
end

local function K_handleDriftBump(p)
	local mo = p.mo
	if not mo return end

	if not kmp_bumpspark.value return end

	if mo.k_saveddriftcharge
	and p.kartstuff[k_driftcharge] == 0
	and mo.eflags & MFE_JUSTBOUNCEDWALL	-- bruh!
		-- fix wrongdrift when hitting a wall:
		p.kartstuff[k_driftend] = 0
		p.kartstuff[k_driftboost] = 0
		p.kartstuff[k_drift] = mo.k_saveddrift
		-- give me my sparks back!
		p.kartstuff[k_driftcharge] = mo.k_saveddriftcharge
	end

	mo.k_saveddriftcharge = p.kartstuff[k_driftcharge]
	mo.k_saveddrift = p.kartstuff[k_drift]
end

-- Credits to Tyron for the original Haste script
local function K_handleHaste(p)
	local mo = p.mo
	if not mo return end
	if not kmp_haste.value return end

	if p.NAlastdriftboost ~= nil

		-- Lawnmowering is already effective enough in a few edge cases, let's not let Omega get away with murder.
		-- This only activates if you spend too long offroad; no need to punish grazes on MKSC Sky Garden or something.
		if p.kartstuff[k_offroad] >= (FRACUNIT / 2) and p.kartstuff[k_driftboost] and p.NAlastadjust
			local mowdrop = max(p.kartstuff[k_driftboost] - p.NAlastadjust, 0)
			--dprint(p, "Deducting", p.NAlastadjust, "for offroad penalty! Was", p.kartstuff[k_driftboost], "F, is", mowdrop, "F")
			p.kartstuff[k_driftboost] = mowdrop
			p.NAlastadjust = 0
		end

		-- This is a sort of roundabout way of detecting drifts, but we can't rely on adjustments to k_driftboost alone...
		-- If we're a 1 speed character, a blue MT should boost us from 20F to 26F by default.
		-- However, if we chain a red into a blue with 23F left on the timer, the game will ignore it!
		-- This is because the base value of a blue MT, 20F, is less than the value we currently have...
		-- ...so in the base game, applying that MT would actually slow us down, even though Haste would boost it up again!
		if p.NAlastdriftcharge > p.kartstuff[k_driftcharge] then
			-- Our goal is to detect an MT one frame after it's happened, then retroactively apply the bonus time.
			local accel = max(0, (9 - p.kartspeed - p.kartweight/2))
			local target = (26*4 + p.kartspeed*2)*8 -- Taken from source.
			local LDC = p.NAlastdriftcharge
			local fakecharge = 20 -- Taken from source.
			local fakename = "blue"
			-- This is a bunch of redundant safety conditions because I'm kinda paranoid.
			-- At least the most common ones are at the top.
			if p.NAlastbounced then
				--dprint(p, "No adjustment: wallbonk")
				return
			elseif p.kartstuff[k_driftboost] < 19 then
				--dprint(p, "No adjustment: no existing MT timer")
				return
			elseif p.speed <= 10*FRACUNIT then
				--dprint(p, "No adjustment: you're too slow")
				return
			elseif p.kartstuff[k_spinouttimer] then
				--dprint(p, "No adjustment: you're spun out")
				return
			elseif p.kartstuff[k_squishedtimer] then
				--dprint(p, "No adjustment: you're squished")
				return
			elseif LDC < target then
				--dprint(p, "No adjustment: didn't charge enough")
				return
			elseif p.NAlastbuttons then
				--dprint(p, "No adjustment: Drift not released")
				return
			elseif LDC >= target*4 then -- Rainbow and red MT values taken from source.
				fakecharge = 125
				fakename = "rainbow"
				-- fallthrough
			elseif LDC >= target*2 then
				fakecharge = 50
				fakename = "red"
				-- fallthrough
			end

			-- Figure out how strong we're proportionally boosting MTs.
			local bonusfactor = FixedDiv((130 - 100)*FRACUNIT, 100*FRACUNIT)
			-- Figure out how much of that bonus a character is eligible for, based on their stats.
			-- 1 speed gets full bonus: 9 speed gets none! (In practice, 7+ speed gets 0F bonuses.)
			local diefactor = FRACUNIT + FixedMul(FixedDiv(bonusfactor, FRACUNIT*8), accel*FRACUNIT)
			-- Calculate the length of the target type of MT with bonus applied...
			-- ...minus 1 for the frame it's already been going.
			local holding = FixedInt(FixedMul(fakecharge*FRACUNIT, diefactor)) - 1
			-- Fixed point math is terrible.
			--dprint(p, "Correcting", fakename.." MT to", holding, "F, would be", p.kartstuff[k_driftboost], "F (scaled by", diefactor, ")")
			p.NAlastadjust = holding - p.kartstuff[k_driftboost]
			p.kartstuff[k_driftboost] = max(holding, $)
		end
	end

	p.NAlastdriftboost = p.kartstuff[k_driftboost]
	p.NAlastdriftcharge = p.kartstuff[k_driftcharge]
	p.NAlastdrift = p.kartstuff[k_drift]
	p.NAlastbuttons = p.cmd.buttons & BT_DRIFT
	p.NAlastbounced = p.mo.eflags & MFE_JUSTBOUNCEDWALL
	p.NAlastadjust = $ or 0

	if kmp_bumpspark.value
		p.NAlastbounced = 0	-- that never happened!
	end
end

addHook("MobjThinker", function(mo)

	if kmp_hyudoro.value and mo and mo.valid and mo.target and mo.target.valid and mo.target.player
	and mo.target.player.kartstuff[k_hyudorotimer]	-- !?
		mo.target = nil
	end
end, MT_JAWZ)

local function K_handleHyudoro(p)
	if not kmp_hyudoro.value return end

	if p.kartstuff[k_hyudorotimer]
		if not p.k_hyudoroextend
			p.kartstuff[k_hyudorotimer] = $*2
			p.k_hyudoroextend = true
		end
	else
		p.k_hyudoroextend = nil
	end

end

addHook("MapLoad", do
	for p in players.iterate do
		p.kmp_respawn = nil
	end
end)

-- remake the respawn command
-- if you use respawn, don't send you to the respawn point in case of GBJ.
-- also block cheeses.

if not kmp_debug
	COM_AddCommand("respawn", function(p)

		local mo = p.mo
		if not p.mo
			COM_BufInsertText(p, "You can only use this command while playing, doofus.")
			return
		end

		if not P_IsObjectOnGround(mo)
		or spbplace == p.kartstuff[k_position]
		or p.kartstuff[k_eggmanexplode]
			return
		end

		p.kmp_forcerespawn = true
		P_KillMobj(mo)
	end)
end

local PREDICTTICS = 5
local PREDICTSPD = 64

-- total dist = 32*8 = 256 safe units from pits etc

local function K_handleRespawn(p)
	if not kmp_respawnpoints.value return end

	local mo = p.mo

	-- every second, create respawn point if player is on ground, unless spun out.
	if leveltime%(TICRATE/2) == 0
	and P_IsObjectOnGround(mo)
	and not p.kartstuff[k_offroad]
	and not P_CheckDeathPitCollide(mo)
	and not p.kartstuff[k_spinouttimer]
	and not P_InQuicksand(mo)
	and mo.health	-- make sure we're alive and NOT on a death pit.
	and not mo.kmp_respawned
		local ang = mo.angle
		if p.speed
			ang = R_PointToAngle2(0, 0, mo.momx, mo.momy)
		end

		local x, y, z = mo.x, mo.y, mo.z
		local spd = PREDICTSPD*mapobjectscale
		local dummy = P_SpawnMobj(x, y, z, MT_GARGOYLE)
		dummy.flags2 = $|MF2_DONTDRAW
		dummy.flags = MF_RUNSPAWNFUNC

		local momx = FixedMul(spd, cos(ang))
		local momy = FixedMul(spd, sin(ang))

		for i = 1, PREDICTTICS do
			-- even without moving, apply a fake speed to the prediction:

			if not P_TryMove(dummy, x+momx, y+momy, true)
				break	-- wall, sure
			end

			if not P_IsObjectOnGround(dummy)
			or P_InQuicksand(dummy)	-- why would anyone do that
				return	-- drop? probably a pit...!
			end

			x = $+momx
			y = $+momy
		end
		p.kmp_respawn = {mo.x, mo.y, mo.z, ang, mo.eflags & MFE_VERTICALFLIP}
	end

	-- respawn timeout: spend 2 seconds on the ground to be able to generate spawns again.
	-- this to make it so a repeated death forces you to the last real checkpoint
	if mo.kmp_respawned
	and P_IsObjectOnGround(mo)
		mo.kmp_respawned = $-1
	end

	if p.kartstuff[k_respawn]
	and not mo.kmp_respawned
		if not mo.kmp_respawned
		and p.kmp_respawn

			if not p.kmp_forcerespawn

				local validrespawn = true

				local tt = p.kmp_respawn

				-- before we do anything, check if the z position we're supposed to respawn at is still valid
				local dummy = P_SpawnMobj(tt[1], tt[2], tt[3], MT_GARGOYLE)
				dummy.flags = MF_RUNSPAWNFUNC
				dummy.flags2 = $|MF2_DONTDRAW
				if tt[5]
					dummy.eflags = $|MFE_VERTICALFLIP
					if P_GetMobjGravity(dummy) < 0
						dummy.flags2 = $|MF2_OBJECTFLIP
					end
				end

				if not P_IsObjectOnGround(dummy)	-- YIKES
					validrespawn = nil
				end
				
				-- check for wind sectors 128 units above our position...
				-- to make for a quick easy hack, we'll teleport the player to that location and check using a built in func (lol)
				local currx, curry, currz = mo.x, mo.y, mo.z
				local cz = dummy.z + 128*mapobjectscale*P_MobjFlip(dummy)
				
				P_TeleportMove(mo, tt[1], tt[2], cz)
				if P_PlayerTouchingSectorSpecial(mo.player, 3, 2)	-- S3, E2, Wind/Current
					validrespawn = nil
				end
				
				P_TeleportMove(mo, currx, curry, currz)
				

				-- if respawn turned out valid, then the player should respawn there.
				if validrespawn

					P_TeleportMove(mo, tt[1], tt[2], tt[3])
					mo.angle = tt[4]
					if tt[5]

						mo.eflags = $|MFE_VERTICALFLIP
						if P_GetMobjGravity(mo) < 0
							mo.flags2 = $|MF2_OBJECTFLIP
						end
					end

					mo.z = $ + 128*mapobjectscale*P_MobjFlip(mo)
				end
			end
		end
		p.kmp_respawn = nil				-- remove this in case the respawn fucks up so we can use checkpoints as backup
		mo.kmp_respawned = TICRATE*2	-- while this timer is on, can't set a new respawn point.
		-- that way if you die right after a respawn somehow, just use the regular checkpoints.
		p.kmp_forcerespawn = nil	-- make sure to reset this
	end

	-- if we got to a checkpoint, remove the last point created:
	if p.starpostnum ~= mo.kmp_laststarpostnum
		p.kmp_respawn = nil
		mo.kmp_laststarpostnum = p.starpostnum
	end
end

if kmp_debug
	-- DEBUG: checkpoint dta

	hud.add(function(v, p, c)

		v.drawString(320, 0, "KMP DEBUG. DO NOT SHIP WITH THIS!", V_TRANSLUCENT, "right")
		v.drawString(320, 8, "'honestly i did not even need to test that respawn system to know it is crap the description of it was enough'", V_TRANSLUCENT, "small-right")
		-- thank you for your words of wisdom, lunarray

		-- draw respawn info:
		if not p.mo or not p.mo.valid return end

		local t = p.kmp_respawn

		local tnames = {"X", "Y", "Z", "A", "F"}
		if not t or not #t
			v.drawString(320, 32, "(CHECKPOINT RESPAWN)", V_TRANSLUCENT, "thin-right")
			return
		end

		for i = 1, #t do

			local val = t[i]
			if i < 4
				val = $/FRACUNIT
			end
			if i == 4	-- ang
				val = $/ANG1
			end

			v.drawString(320, 32 + (i-1)*8, tnames[i]..": "..val, V_TRANSLUCENT, "right")
		end
	end)
end


local function K_handleShrinkCleanse()

	if not kmp_shrinkcleanse.value return end

	server.shrinkholdtimer = $ or 0

	-- check if ANYONE has shrink:
	for p in players.iterate do
		if p and p.mo and p.mo.valid
		and p.kartstuff[k_itemtype] == KITEM_SHRINK

			server.shrinkplayer = p
			break	-- no one else should be allowed to have it :sus:
		end
	end

	if server.shrinkplayer and server.shrinkplayer.valid
	and not server.shrinkplayer.powers[pw_nocontrol]
	and not server.shrinkplayer.kartstuff[k_spinouttimer]
		if server.shrinkplayer.cmd.buttons & BT_ATTACK
			server.shrinkholdtimer = $+1
		else
			server.shrinkholdtimer = 0	-- i hate this hack too
		end

		if server.shrinkholdtimer == 1
		and server.shrinkplayer.kartstuff[k_itemblink] < TICRATE-1	-- make sure that doesn't happen in case you mash I guess?
		and not server.shrinkplayer.kartstuff[k_stealingtimer]		-- weird case where stealing shrink with hyu procs cleanse
			for m in thinkers.iterate("mobj")
				if m and m.valid

					-- I hate this.
					if m.type == MT_JAWZ
					or m.type == MT_JAWZ_DUD
					or m.type == MT_JAWZ_SHIELD
					or m.type == MT_ORBINAUT
					or m.type == MT_ORBINAUT_SHIELD
					or m.type == MT_SSMINE
					or m.type == MT_SSMINE_SHIELD
					or m.type == MT_BANANA
					or m.type == MT_BANANA_SHIELD
					or m.type == MT_EGGMANITEM
					or m.type == MT_EGGMANITEM_SHIELD
					or m.type == MT_FLOATINGITEM
					or m.type == MT_SPB

						m.target = server.shrinkplayer.mo	-- explosion colour

						-- stuff is a bit of a case by case scenario because items are kinda coded like shit.

						if m.type == MT_EGGMANITEM or m.type == MT_EGGMANITEM_SHIELD
							A_MineExplode(m)
						end

						if m.type == MT_FLOATINGITEM
							P_RemoveMobj(m)
							continue	-- don't continue past this point
						end
						if m.type == MT_SSMINE or m.type == MT_SSMINE_SHIELD
							P_KillMobj(m, server.shrinkplayer.mo, server.shrinkplayer.mo)
							continue
						end
						K_KillKartItem(m, true)
					end
				end
			end
			server.shrinkplayer = nil	-- remove this
		end
	else
		server.shrinkplayer = nil
	end
end

local baseshrinktimer = 16*TICRATE
local minshrinktimer = 4*TICRATE

local function K_handleShrinkDuration(p, nump)
	if not kmp_shrinkduration.value return end

	if not nump
		--print("No players...?")
		return
	end

	local shrinkslope = (baseshrinktimer - minshrinktimer) / nump
	local slopepos = nump - (p.kartstuff[k_position]-1)

	if p.kartstuff[k_growshrinktimer] < 0
		-- I've been shrunk...!
		-- But for how long ?
		local shrinkdur = - abs(minshrinktimer + shrinkslope*slopepos)

		if p.kartstuff[k_growshrinktimer] < shrinkdur
			p.kartstuff[k_growshrinktimer] = shrinkdur
			--print("P #"..(#p)..": Changed shrink duration to "..shrinkdur/TICRATE)
		end
	end
end

-- Aerial Sneaker buffer
local sneakertime = TICRATE + (TICRATE/3)

local function K_handleAirSneakerBuffer(p)

	-- holding attack button: (Do this regardless of asneakerbuf value because we don't want the timer to linger for some reason)
	if p.cmd.buttons & BT_ATTACK
		p.kmp_holdsneaker = $ and $+1 or 1
	else
		p.kmp_holdsneaker = 0
	end

	if not kmp_airsneakerbuf.value return end	-- bye

	-- check if I have a sneaker:
	if p.kartstuff[k_itemtype] == KITEM_SNEAKER
	and not p.powers[pw_nocontrol]
	and p.kartstuff[k_itemblink] < TICRATE-1	-- make sure that doesn't happen in case you mash I guess?
	and not p.kartstuff[k_stealingtimer]		-- weird case where stealing shrink with hyu procs cleanse
	and (not P_IsObjectOnGround(p.mo) or p.kartstuff[k_spinouttimer])	-- Buffer in mid-air or in spinout.
	and p.kmp_holdsneaker == 1
	and p.mo and p.mo.valid

		--print("Buffered Sneaker")
		p.mo.kmp_sneakerbuffer = $ and $+1 or 1
		p.mo.kmp_sneakerbuffertime = $ and $ + (sneakertime/p.mo.kmp_sneakerbuffer) or sneakertime
		-- duration stacks with diminitive returns.
		--print("Buffers: "..p.mo.kmp_sneakerbuffer.." | Duration: "..p.mo.kmp_sneakerbuffertime)

		-- use item:
		p.kartstuff[k_itemamount] = $-1
		if not p.kartstuff[k_itemamount]
			p.kartstuff[k_itemtype] = 0
		end

		S_StartSound(p.mo, sfx_s243)
		local img = P_SpawnGhostMobj(p.mo)
		img.destscale = p.mo.scale*8
		img.scalespeed = p.mo.scale/12
	end

	-- if sneakers have been buffered, use them asap.
	if p.mo.kmp_sneakerbuffer

		-- flash during buffer
		p.mo.colorized = leveltime&1 and true or false

		if (P_IsObjectOnGround(p.mo) and not p.kartstuff[k_spinouttimer])
			K_DoSneaker(p)
			p.kartstuff[k_sneakertimer] = p.mo.kmp_sneakerbuffertime

			p.mo.kmp_sneakerbuffer = 0
			p.mo.kmp_sneakerbuffertime = 0
			p.mo.colorized = false
			--print("Released buffer")
		end
	end
end

-- Battoru is soap.
-- I don't like soap, it's inedible.
-- Let's fix that.
local function K_handleBattleAccel(p)

	if not kmp_battleaccel.value or not G_BattleGametype() return end	-- bye

	p.kartstuff[k_accelboost] = max($, FRACUNIT/2)
end

-- duplicate for race mode
local function K_handleRaceAccel(p)

	if not kmp_raceaccel.value or not G_RaceGametype() return end	-- bye

	p.kartstuff[k_accelboost] = max($, FRACUNIT/2)
end

addHook("ThinkFrame", do

	-- global effects:
	K_handleShrinkCleanse()
	K_orbinautfuse()

	local count = K_countplayers()
	for p in players.iterate do
		if p.spectator or not p.mo or not p.mo.valid continue end

		-- player effects:
		K_handleitemtoggles(p, count)
		K_handleflashing(p, count)
		K_fakebananafriction(p)
		K_handleHaste(p)
		K_handleorbit(p)
		K_handlehardsneaker(p)
		K_handleDriftBump(p)
		K_handleHyudoro(p)
		K_handleRespawn(p)
		K_handleShrinkDuration(p, count)	-- playercount affects this
		K_handleAirSneakerBuffer(p)
		K_handleBattleAccel(p)
		K_handleRaceAccel(p)

		-- init player to kmp stuff:
		if not p.kmp_init
			COM_BufInsertText(p, "kmp_info")
			if kmp_prerelease
				chatprintf(p, "\x83".."*KartMP Pre-Release "..kmp.."\nDo not redistribute.")
			else
				chatprintf(p, "\x83".."*Server is running KartMP "..kmp.."\nType \'".."\x82".."kmp_info".."\x83".."\' in the console for details on the settings used.")
			end
			p.kmp_init = true
		end
	end
end)

-- make floating items despawn after a while, determined by lap count.
-- credits to Steel Titanium for the original script
addHook("MobjThinker", function(mo)

	if not kmp_floatingitemfuse.value return end

	if P_IsObjectOnGround(mo) and (not mo.fuse)

		local numlaps = mapheaderinfo[gamemap].numlaps
		mo.fuse = max(12*TICRATE, (60-(10*numlaps))*TICRATE)
	end

	if mo.fuse then
		mo.flags2 = (mo.fuse <= 5*TICRATE and leveltime % 2) and $ + MF2_DONTDRAW or $ & ~(MF2_DONTDRAW)
	end
end, MT_FLOATINGITEM)

addHook("MobjFuse", function(mo)
	P_RemoveMobj(mo)
end, MT_FLOATINGITEM)
