From 922f80b22f394e5fb59b1032d5ae70de74cb3f02 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 12 Jan 2019 04:30:41 -0500
Subject: [PATCH 01/73] Uncap player-count item distribution modifiers

Was an idea I had in development to make 16P less chaotic, but decided against it due to lack of testing and that it could backfire badly...
---
 src/k_kart.c | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 8189cd0f..2c68440f 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -631,15 +631,14 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 		}
 	}
 
-	if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB
+	if (first != -1 && second != -1 && !G_BattleGametype()) // calculate 2nd's distance from 1st, for SPB
 	{
 		secondist = P_AproxDistance(P_AproxDistance(players[first].mo->x - players[second].mo->x,
 													players[first].mo->y - players[second].mo->y),
 													players[first].mo->z - players[second].mo->z) / mapheaderinfo[gamemap-1]->mobj_scale;
 		if (franticitems)
 			secondist = (15*secondist/14);
-		if (pingame < 8 && !G_BattleGametype())
-			secondist = ((28+(8-pingame))*secondist/28);
+		secondist = ((28+(8-pingame))*secondist/28);
 	}
 
 	// POWERITEMODDS handles all of the "frantic item" related functionality, for all of our powerful items.
@@ -647,14 +646,13 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 	// Then, it multiplies it further if there's less than 5 players in game.
 	// This is done to make low player count races more fair & interesting. (1v1s are basically the same as franticitems false in a normal race)
 	// Lastly, it *divides* it by your mashed value, which was determined in K_KartItemRoulette, to punish those who are impatient.
-	// The last two are very fractional and complicated, very sorry!
 #define POWERITEMODDS(odds) \
 	if (franticitems) \
-		odds *= 2; \
-	if (pingame < 8 && !G_BattleGametype()) \
-		odds = FixedMul(odds*FRACUNIT, FRACUNIT+min((8-pingame)*(FRACUNIT/25), FRACUNIT))/FRACUNIT; \
+		odds <<= 1; \
+	if (!G_BattleGametype()) \
+		odds = FixedMul(odds<<FRACBITS, FRACUNIT + ((8-pingame) * (FRACUNIT/25))) >> FRACBITS; \
 	if (mashed > 0) \
-		odds = FixedDiv(odds*FRACUNIT, mashed+FRACUNIT)/FRACUNIT \
+		odds = FixedDiv(odds<<FRACBITS, FRACUNIT + mashed) >> FRACBITS \
 
 	switch (item)
 	{
@@ -837,8 +835,7 @@ static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT3
 
 		if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items
 			pdis = (15*pdis/14);
-		if (pingame < 8 && !G_BattleGametype())
-			pdis = ((28+(8-pingame))*pdis/28);
+		pdis = ((28+(8-pingame))*pdis/28);
 
 		if (pingame == 1 && oddsvalid[0])					// Record Attack, or just alone
 			useodds = 0;

From 24a32c2f517b20ae0ecbad6df16ac2f9bf40f1fc Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 12 Jan 2019 04:31:35 -0500
Subject: [PATCH 02/73] Allow player-count modifiers in Battle

---
 src/k_kart.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 2c68440f..b97a518f 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -649,8 +649,7 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 #define POWERITEMODDS(odds) \
 	if (franticitems) \
 		odds <<= 1; \
-	if (!G_BattleGametype()) \
-		odds = FixedMul(odds<<FRACBITS, FRACUNIT + ((8-pingame) * (FRACUNIT/25))) >> FRACBITS; \
+	odds = FixedMul(odds<<FRACBITS, FRACUNIT + ((8-pingame) * (FRACUNIT/25))) >> FRACBITS; \
 	if (mashed > 0) \
 		odds = FixedDiv(odds<<FRACBITS, FRACUNIT + mashed) >> FRACBITS \
 

From 650522fc59da39db4492095ddb0087cc0a21a500 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 12 Jan 2019 04:43:43 -0500
Subject: [PATCH 03/73] Don't increment ingame count without bumpers

---
 src/k_kart.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index b97a518f..92aafad5 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -613,21 +613,24 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 	{
 		if (!playeringame[i] || players[i].spectator)
 			continue;
-
-		pingame++;
+		if (!G_BattleGametype() || players[i].kartstuff[k_bumper])
+			pingame++;
 		if (players[i].exiting)
 			pexiting++;
 		if (players[i].mo)
 		{
-			if (players[i].kartstuff[k_position] == 1 && first == -1)
-				first = i;
-			if (players[i].kartstuff[k_position] == 2 && second == -1)
-				second = i;
 			if (players[i].kartstuff[k_itemtype] == KITEM_INVINCIBILITY
 				|| players[i].kartstuff[k_itemtype] == KITEM_GROW
 				|| players[i].kartstuff[k_invincibilitytimer]
 				|| players[i].kartstuff[k_growshrinktimer] > 0)
 				pinvin++;
+			if (!G_BattleGametype())
+			{
+				if (players[i].kartstuff[k_position] == 1 && first == -1)
+					first = i;
+				if (players[i].kartstuff[k_position] == 2 && second == -1)
+					second = i;
+			}
 		}
 	}
 

From 0ef5c2e388f86010da807ba8540eccb433a6d24c Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 12 Jan 2019 05:01:36 -0500
Subject: [PATCH 04/73] Some house-cleaning

---
 src/k_kart.c | 126 ++++++++++++++++++++-------------------------------
 1 file changed, 48 insertions(+), 78 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 92aafad5..049e3c54 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -603,6 +603,32 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 	UINT8 pingame = 0, pexiting = 0, pinvin = 0;
 	SINT8 first = -1, second = -1;
 	INT32 secondist = 0;
+	boolean itemenabled[NUMKARTRESULTS] = {
+		cv_sneaker.value,
+		cv_rocketsneaker.value,
+		cv_invincibility.value,
+		cv_banana.value,
+		cv_eggmanmonitor.value,
+		cv_orbinaut.value,
+		cv_jawz.value,
+		cv_mine.value,
+		cv_ballhog.value,
+		cv_selfpropelledbomb.value,
+		cv_grow.value,
+		cv_shrink.value,
+		cv_thundershield.value,
+		cv_hyudoro.value,
+		cv_kitchensink.value,
+		cv_triplesneaker.value,
+		cv_triplebanana.value,
+		cv_decabanana.value,
+		cv_tripleorbinaut.value,
+		cv_quadorbinaut.value,
+		cv_dualjawz.value
+	};
+
+	if (!itemenabled[item] && !modeattacking)
+		return 0;
 
 	if (G_BattleGametype())
 		newodds = K_KartItemOddsBattle[item-1][pos];
@@ -634,14 +660,14 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 		}
 	}
 
-	if (first != -1 && second != -1 && !G_BattleGametype()) // calculate 2nd's distance from 1st, for SPB
+	if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB
 	{
 		secondist = P_AproxDistance(P_AproxDistance(players[first].mo->x - players[second].mo->x,
 													players[first].mo->y - players[second].mo->y),
 													players[first].mo->z - players[second].mo->z) / mapheaderinfo[gamemap-1]->mobj_scale;
 		if (franticitems)
-			secondist = (15*secondist/14);
-		secondist = ((28+(8-pingame))*secondist/28);
+			secondist = (15 * secondist) / 14;
+		secondist = ((28 + (8-pingame)) * secondist) / 28;
 	}
 
 	// POWERITEMODDS handles all of the "frantic item" related functionality, for all of our powerful items.
@@ -658,97 +684,41 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 
 	switch (item)
 	{
-		case KITEM_SNEAKER:
-			if ((!cv_sneaker.value) && (!modeattacking)) newodds = 0;
-			break;
-		case KITEM_ROCKETSNEAKER:
-			POWERITEMODDS(newodds);
-			if (!cv_rocketsneaker.value) newodds = 0;
-			break;
 		case KITEM_INVINCIBILITY:
-			POWERITEMODDS(newodds);
-			if ((!cv_invincibility.value) || (pinvin >= 2)) newodds = 0;
-			break;
-		case KITEM_BANANA:
-			if (!cv_banana.value) newodds = 0;
-			break;
-		case KITEM_EGGMAN:
-			if (!cv_eggmanmonitor.value) newodds = 0;
-			break;
-		case KITEM_ORBINAUT:
-			if (!cv_orbinaut.value) newodds = 0;
-			break;
+		case KITEM_GROW:
+			if (pinvin >= 2)
+				newodds = 0;
+			else
+			/* FALLTHRU */
+		case KITEM_ROCKETSNEAKER:
 		case KITEM_JAWZ:
-			POWERITEMODDS(newodds);
-			if (!cv_jawz.value) newodds = 0;
-			break;
 		case KITEM_MINE:
-			POWERITEMODDS(newodds);
-			if (!cv_mine.value) newodds = 0;
-			break;
 		case KITEM_BALLHOG:
+		case KITEM_THUNDERSHIELD:
+		case KRITEM_TRIPLESNEAKER:
+		case KRITEM_TRIPLEBANANA:
+		case KRITEM_TENFOLDBANANA:
+		case KRITEM_TRIPLEORBINAUT:
+		case KRITEM_QUADORBINAUT:
+		case KRITEM_DUALJAWZ:
 			POWERITEMODDS(newodds);
-			if (!cv_ballhog.value) newodds = 0;
 			break;
 		case KITEM_SPB:
 			//POWERITEMODDS(newodds);
-			if ((!cv_selfpropelledbomb.value)
-				|| (indirectitemcooldown > 0)
-				|| (pexiting > 0)
-				|| (secondist/distvar < (4+gamespeed)))
+			if ((indirectitemcooldown > 0) || (pexiting > 0) || (secondist/distvar < 5))
 				newodds = 0;
-			newodds *= min((secondist/distvar)-(3+gamespeed), 3);
-			break;
-		case KITEM_GROW:
-			POWERITEMODDS(newodds);
-			if ((!cv_grow.value) || (pinvin >= 2)) newodds = 0;
+			else
+				newodds *= min((secondist/distvar)-4, 3);
 			break;
 		case KITEM_SHRINK:
 			POWERITEMODDS(newodds);
-			if ((!cv_shrink.value)
-				|| (indirectitemcooldown > 0)
-				|| (pingame-1 <= pexiting)) newodds = 0;
-			break;
-		case KITEM_THUNDERSHIELD:
-			POWERITEMODDS(newodds);
-			if (!cv_thundershield.value) newodds = 0;
-			break;
-		case KITEM_HYUDORO:
-			if (!cv_hyudoro.value) newodds = 0;
-			break;
-		case KITEM_POGOSPRING:
-			if (!cv_pogospring.value) newodds = 0;
-			break;
-		case KITEM_KITCHENSINK:
-			newodds = 0; // Not obtained via normal means.
-			break;
-		case KRITEM_TRIPLESNEAKER:
-			POWERITEMODDS(newodds);
-			if (!cv_triplesneaker.value) newodds = 0;
-			break;
-		case KRITEM_TRIPLEBANANA:
-			POWERITEMODDS(newodds);
-			if (!cv_triplebanana.value) newodds = 0;
-			break;
-		case KRITEM_TENFOLDBANANA:
-			POWERITEMODDS(newodds);
-			if (!cv_decabanana.value) newodds = 0;
-			break;
-		case KRITEM_TRIPLEORBINAUT:
-			POWERITEMODDS(newodds);
-			if (!cv_tripleorbinaut.value) newodds = 0;
-			break;
-		case KRITEM_QUADORBINAUT:
-			POWERITEMODDS(newodds);
-			if (!cv_quadorbinaut.value) newodds = 0;
-			break;
-		case KRITEM_DUALJAWZ:
-			POWERITEMODDS(newodds);
-			if (!cv_dualjawz.value) newodds = 0;
+			if ((indirectitemcooldown > 0) || (pingame-1 <= pexiting))
+				newodds = 0;
 			break;
 		default:
 			break;
 	}
+
 #undef POWERITEMODDS
 
 	return newodds;

From 5ed4d0256ee876cd352a2e38b7fc9764f422715e Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 12 Jan 2019 05:03:05 -0500
Subject: [PATCH 05/73] spacing & ()

---
 src/k_kart.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 049e3c54..ffe30eb5 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -806,8 +806,8 @@ static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT3
 		if (oddsvalid[8]) SETUPDISTTABLE(8,1);
 
 		if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items
-			pdis = (15*pdis/14);
-		pdis = ((28+(8-pingame))*pdis/28);
+			pdis = (15 * pdis) / 14;
+		pdis = ((28 + (8-pingame)) * pdis) / 28;
 
 		if (pingame == 1 && oddsvalid[0])					// Record Attack, or just alone
 			useodds = 0;

From 2416d7822409b69f6490e5c7d1df07d7654b528e Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sat, 12 Jan 2019 05:12:20 -0500
Subject: [PATCH 06/73] Update comment to be more accurate

---
 src/k_kart.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index ffe30eb5..63c9d454 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -673,7 +673,7 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 	// POWERITEMODDS handles all of the "frantic item" related functionality, for all of our powerful items.
 	// First, it multiplies it by 2 if franticitems is true; easy-peasy.
 	// Then, it multiplies it further if there's less than 5 players in game.
-	// This is done to make low player count races more fair & interesting. (1v1s are basically the same as franticitems false in a normal race)
+	// This is done to make low player count races more fair & interesting. (2P normal would be about halfway between 8P normal and 8P frantic)
 	// Lastly, it *divides* it by your mashed value, which was determined in K_KartItemRoulette, to punish those who are impatient.
 #define POWERITEMODDS(odds) \
 	if (franticitems) \

From c2aa6d4de6f4919ac9d4218844d3cf154cadd4e5 Mon Sep 17 00:00:00 2001
From: Latapostrophe <hyperclassic3@gmail.com>
Date: Sun, 13 Jan 2019 14:40:48 +0100
Subject: [PATCH 07/73] Expose some more Kart functions, freeplay and rankings
 bumpers are now hud stuff you can toggle, + experimental playercmd hook

---
 src/g_game.c        |  8 +++++
 src/k_kart.c        | 44 +++++++++++++++++---------
 src/k_kart.h        |  7 +++++
 src/lua_baselib.c   | 77 +++++++++++++++++++++++++++++++++++++++++++--
 src/lua_hook.h      |  6 ++++
 src/lua_hooklib.c   | 42 +++++++++++++++++++++++++
 src/lua_hud.h       |  2 ++
 src/lua_hudlib.c    | 17 ++++++++++
 src/lua_infolib.c   | 13 ++++++++
 src/lua_maplib.c    |  7 +++++
 src/lua_mobjlib.c   |  6 ++++
 src/lua_playerlib.c |  8 +++++
 12 files changed, 221 insertions(+), 16 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index 1e0c7e46..d93a2e9f 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1612,10 +1612,18 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 		}
 	}
 
+	// Lua: Allow this hook to overwrite ticcmd.
+	// Be aware that you can't actually write anything inside the player with this hook, only cmd may be altered.
+#ifdef HAVE_BLUA
+	if (playeringame[consoleplayer])	// safe to assume we can't do anything if consoleplayer isn't in the game.
+		LUAh_PlayerCmd(player, cmd);
+#endif
+
 	//Reset away view if a command is given.
 	if ((cmd->forwardmove || cmd->sidemove || cmd->buttons)
 		&& displayplayer != consoleplayer && ssplayer == 1)
 		displayplayer = consoleplayer;
+
 }
 
 // User has designated that they want
diff --git a/src/k_kart.c b/src/k_kart.c
index 320105ef..bd4e1394 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1548,7 +1548,7 @@ static void K_RegularVoiceTimers(player_t *player)
 		player->kartstuff[k_tauntvoices] = 4*TICRATE;
 }
 
-static void K_PlayAttackTaunt(mobj_t *source)
+void K_PlayAttackTaunt(mobj_t *source)
 {
 	sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons
 	boolean tasteful = (!source->player || !source->player->kartstuff[k_tauntvoices]);
@@ -1562,7 +1562,7 @@ static void K_PlayAttackTaunt(mobj_t *source)
 	K_TauntVoiceTimers(source->player);
 }
 
-static void K_PlayBoostTaunt(mobj_t *source)
+void K_PlayBoostTaunt(mobj_t *source)
 {
 	sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons
 	boolean tasteful = (!source->player || !source->player->kartstuff[k_tauntvoices]);
@@ -1576,7 +1576,7 @@ static void K_PlayBoostTaunt(mobj_t *source)
 	K_TauntVoiceTimers(source->player);
 }
 
-static void K_PlayOvertakeSound(mobj_t *source)
+void K_PlayOvertakeSound(mobj_t *source)
 {
 	boolean tasteful = (!source->player || !source->player->kartstuff[k_voices]);
 
@@ -1596,7 +1596,7 @@ static void K_PlayOvertakeSound(mobj_t *source)
 	K_RegularVoiceTimers(source->player);
 }
 
-static void K_PlayHitEmSound(mobj_t *source)
+void K_PlayHitEmSound(mobj_t *source)
 {
 	if (cv_kartvoices.value)
 		S_StartSound(source, sfx_khitem);
@@ -1606,7 +1606,7 @@ static void K_PlayHitEmSound(mobj_t *source)
 	K_RegularVoiceTimers(source->player);
 }
 
-static void K_PlayPowerGloatSound(mobj_t *source)
+void K_PlayPowerGloatSound(mobj_t *source)
 {
 	if (cv_kartvoices.value)
 		S_StartSound(source, sfx_kgloat);
@@ -4670,7 +4670,7 @@ static void K_KartDrift(player_t *player, boolean onground)
 			player->kartstuff[k_driftend] = 0;
 	}
 
-	
+
 
 	// Incease/decrease the drift value to continue drifting in that direction
 	if (player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_jmp] == 1 && onground && player->kartstuff[k_drift] != 0)
@@ -6933,15 +6933,23 @@ static boolean K_drawKartPositionFaces(void)
 				colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE);
 
 			V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, facerankprefix[players[rankplayer[i]].skin], colormap);
-			if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] > 0)
+
+#ifdef HAVE_BLUA
+			if (LUA_HudEnabled(hud_battlebumpers))
 			{
-				V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[0], colormap);
-				for (j = 1; j < players[rankplayer[i]].kartstuff[k_bumper]; j++)
+#endif
+				if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] > 0)
 				{
-					bumperx += 5;
-					V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[1], colormap);
+					V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[0], colormap);
+					for (j = 1; j < players[rankplayer[i]].kartstuff[k_bumper]; j++)
+					{
+						bumperx += 5;
+						V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[1], colormap);
+					}
 				}
-			}
+#ifdef HAVE_BLUA
+			}	// A new level of stupidity: checking if lua is enabled to close a bracket. :Fascinating:
+#endif
 		}
 
 		if (i == strank)
@@ -7641,7 +7649,10 @@ static void K_drawBattleFullscreen(void)
 				return;
 		}
 
-		K_drawKartFreePlay(leveltime);
+#ifdef HAVE_BLUA
+		if (LUA_HudEnabled(hud_freeplay))
+#endif
+			K_drawKartFreePlay(leveltime);
 	}
 }
 
@@ -8263,7 +8274,12 @@ void K_drawKartHUD(void)
 
 	// Draw FREE PLAY.
 	if (isfreeplay && !stplyr->spectator && timeinmap > 113)
-		K_drawKartFreePlay(leveltime);
+	{
+#ifdef HAVE_BLUA
+		if (LUA_HudEnabled(hud_freeplay))
+#endif
+			K_drawKartFreePlay(leveltime);
+	}
 
 	if (cv_kartdebugdistribution.value)
 		K_drawDistributionDebugger();
diff --git a/src/k_kart.h b/src/k_kart.h
index fed490db..aefc20bb 100644
--- a/src/k_kart.h
+++ b/src/k_kart.h
@@ -63,6 +63,13 @@ void K_CalculateBattleWanted(void);
 void K_CheckBumpers(void);
 void K_CheckSpectateStatus(void);
 
+// sound stuff for lua
+void K_PlayAttackTaunt(mobj_t *source);
+void K_PlayBoostTaunt(mobj_t *source);
+void K_PlayOvertakeSound(mobj_t *source);
+void K_PlayHitEmSound(mobj_t *source);
+void K_PlayPowerGloatSound(mobj_t *source);
+
 const char *K_GetItemPatch(UINT8 item, boolean tiny);
 INT32 K_calcSplitFlags(INT32 snapflags);
 void K_LoadKartHUDGraphics(void);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index c862ec26..b0231e39 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -31,9 +31,10 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "lua_hook.h" // hook_cmd_running
 
-#define NOHUD if (hud_running) return luaL_error(L, "HUD rendering code should not call this function!");
-
+#define NOHUD if (hud_running) return luaL_error(L, "HUD rendering code should not call this function!"); else if (hook_cmd_running) return luaL_error(L, "CMD Building code should not call this function!");
+// Yes technically cmd hook isn't a hud but whatever, this avoids having 2 defines for virtually the same thing.
 
 boolean luaL_checkboolean(lua_State *L, int narg) {
 	luaL_checktype(L, narg, LUA_TBOOLEAN);
@@ -2141,6 +2142,72 @@ static int lib_gTicsToMilliseconds(lua_State *L)
 // K_KART
 ////////////
 
+// Seriously, why weren't those exposed before?
+static int lib_kAttackSound(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	if (!mobj->player)
+		return luaL_error(L, "K_PlayAttackTaunt: mobj_t isn't a player object.");	//Nothing bad would happen if we let it run the func, but telling why it ain't doing anything is helpful.
+	K_PlayAttackTaunt(mobj);
+	return 0;
+}
+
+static int lib_kBoostSound(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	if (!mobj->player)
+		return luaL_error(L, "K_PlayBoostTaunt: mobj_t isn't a player object.");	//Nothing bad would happen if we let it run the func, but telling why it ain't doing anything is helpful.
+	K_PlayBoostTaunt(mobj);
+	return 0;
+}
+
+static int lib_kOvertakeSound(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	if (!mobj->player)
+		return luaL_error(L, "K_PlayOvertakeSound: mobj_t isn't a player object.");	//Nothing bad would happen if we let it run the func, but telling why it ain't doing anything is helpful.
+	K_PlayOvertakeSound(mobj);
+	return 0;
+}
+
+static int lib_kHitEmSound(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	if (!mobj->player)
+		return luaL_error(L, "K_PlayHitEmSound: mobj_t isn't a player object.");	//Nothing bad would happen if we let it run the func, but telling why it ain't doing anything is helpful.
+	K_PlayHitEmSound(mobj);
+	return 0;
+}
+
+static int lib_kGloatSound(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	if (!mobj->player)
+		return luaL_error(L, "K_PlayPowerGloatSound: mobj_t isn't a player object.");	//Nothing bad would happen if we let it run the func, but telling why it ain't doing anything is helpful.
+	K_PlayPowerGloatSound(mobj);
+	return 0;
+}
+
+static int lib_kLossSound(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));	// let's require a mobj for consistency with the other functions
+	sfxenum_t sfx_id;
+	NOHUD
+	if (!mobj->player)
+		return luaL_error(L, "K_PlayLossSound: mobj_t isn't a player object.");
+
+	sfx_id = ((skin_t *)mobj->skin)->soundsid[S_sfx[sfx_klose].skinsound];
+	S_StartSound(mobj, sfx_id);
+	return 0;
+}
+
+// Note: Pain, Death and Victory are already exposed.
+
 static int lib_kGetKartColorByName(lua_State *L)
 {
 	const char *name = luaL_checkstring(L, 1);
@@ -2708,6 +2775,12 @@ static luaL_Reg lib[] = {
 	{"G_TicsToMilliseconds",lib_gTicsToMilliseconds},
 
 	// k_kart
+	{"K_PlayAttackTaunt", lib_kAttackSound},
+	{"K_PlayBoostTaunt", lib_kBoostSound},
+	{"K_PlayPowerGloatSund", lib_kGloatSound},
+	{"K_PlayOvertakeSound", lib_kOvertakeSound},
+	{"K_PlayLossSound", lib_kLossSound},
+	{"K_PlayHitEmSound", lib_kHitEmSound},
 	{"K_GetKartColorByName",lib_kGetKartColorByName},
 	{"K_IsPlayerLosing",lib_kIsPlayerLosing},
 	{"K_IsPlayerWanted",lib_kIsPlayerWanted},
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 2c9cd346..126e7e40 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -50,11 +50,14 @@ enum hook {
 	hook_PlayerSpin,	//SRB2KART
 	hook_PlayerExplode,	//SRB2KART
 	hook_PlayerSquish,	//SRB2KART
+	hook_PlayerCmd,		//SRB2KART
 
 	hook_MAX // last hook
 };
 extern const char *const hookNames[];
 
+extern boolean hook_cmd_running;	// This is used by PlayerCmd and lua_playerlib to prevent anything from being wirtten to player while we run PlayerCmd.
+
 void LUAh_MapChange(INT16 mapnumber); // Hook for map change (before load)
 void LUAh_MapLoad(void); // Hook for map load
 void LUAh_PlayerJoin(int playernum); // Hook for Got_AddPlayer
@@ -93,4 +96,7 @@ UINT8 LUAh_ShouldSquish(player_t *player, mobj_t *inflictor, mobj_t *source); //
 boolean LUAh_PlayerSpin(player_t *player, mobj_t *inflictor, mobj_t *source); // SRB2KART: Hook for K_SpinPlayer. Allows Lua to execute code and/or overwrite its behavior.
 boolean LUAh_PlayerExplode(player_t *player, mobj_t *inflictor, mobj_t *source); // SRB2KART: Hook for K_ExplodePlayer. Allows Lua to execute code and/or overwrite its behavior.
 boolean LUAh_PlayerSquish(player_t *player, mobj_t *inflictor, mobj_t *source); // SRB2KART: Hook for K_SquishPlayer. Allows Lua to execute code and/or overwrite its behavior.
+
+boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd);	// Allows to write to player cmd before the game does anything with them.
+
 #endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 13ad03d3..1a1d4ed6 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -61,6 +61,7 @@ const char *const hookNames[hook_MAX+1] = {
 	"PlayerSpin",
 	"PlayerExplode",
 	"PlayerSquish",
+	"PlayerCmd",
 	NULL
 };
 
@@ -877,6 +878,47 @@ boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
 	return hooked;
 }
 
+// Hook for G_BuildTicCmd
+boolean hook_cmd_running = false;
+boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd)
+{
+	hook_p hookp;
+	boolean hooked = false;
+	if (!gL || !(hooksAvailable[hook_PlayerCmd/8] & (1<<(hook_PlayerCmd%8))))
+		return false;
+
+	lua_settop(gL, 0);
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_PlayerCmd)
+		{
+			hook_cmd_running = true;
+			if (lua_gettop(gL) == 0)
+			{
+				LUA_PushUserdata(gL, player, META_PLAYER);
+				LUA_PushUserdata(gL, cmd, META_TICCMD);
+			}
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -3);
+			lua_pushvalue(gL, -3);
+			if (lua_pcall(gL, 2, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
+			lua_pop(gL, 1);
+			hook_cmd_running = false;
+		}
+
+	lua_settop(gL, 0);
+	return hooked;
+}
+
 // Hook for B_BuildTailsTiccmd by skin name
 boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 {
diff --git a/src/lua_hud.h b/src/lua_hud.h
index 3216ab15..4fbbbace 100644
--- a/src/lua_hud.h
+++ b/src/lua_hud.h
@@ -20,8 +20,10 @@ enum hud {
 	hud_item,
 	hud_position,
 	hud_minirankings,	// Rankings to the left
+	hud_battlebumpers,	// mini rankings battle bumpers.
 	hud_wanted,
 	hud_speedometer,
+	hud_freeplay,
 	hud_rankings,		// Tab rankings
 
 	hud_MAX
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 353aebb2..ca952a00 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -45,8 +45,10 @@ static const char *const hud_disable_options[] = {
 	"item",
 	"position",
 	"minirankings",	// Gametype rankings to the left
+	"battlerankingsbumpers",	// bumper drawer for battle. Useful if you want to make a custom battle gamemode without bumpers being involved.
 	"wanted",
 	"speedometer",
+	"freeplay",
 	"rankings",
 	NULL};
 
@@ -482,6 +484,20 @@ static int libd_drawString(lua_State *L)
 	return 0;
 }
 
+static int libd_drawKartString(lua_State *L)
+{
+	fixed_t x = luaL_checkinteger(L, 1);
+	fixed_t y = luaL_checkinteger(L, 2);
+	const char *str = luaL_checkstring(L, 3);
+	INT32 flags = luaL_optinteger(L, 4, V_ALLOWLOWERCASE);
+
+	flags &= ~V_PARAMMASK; // Don't let crashes happen.
+
+	HUDONLY
+	V_DrawKartString(x, y, flags, str);
+	return 0;
+}
+
 static int libd_stringWidth(lua_State *L)
 {
 	const char *str = luaL_checkstring(L, 1);
@@ -593,6 +609,7 @@ static luaL_Reg lib_draw[] = {
 	{"drawFill", libd_drawFill},
 	{"fadeScreen", libd_fadeScreen},
 	{"drawString", libd_drawString},
+	{"drawKartString", libd_drawKartString},
 	{"stringWidth", libd_stringWidth},
 	{"getColormap", libd_getColormap},
 	{"width", libd_width},
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 9b22170f..d8659a7e 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -22,6 +22,7 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "lua_hook.h"	// cmd errors
 
 boolean LUA_CallAction(const char *action, mobj_t *actor);
 state_t *astate;
@@ -169,6 +170,8 @@ static int lib_setState(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter states in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter states in BuildCMD code!");
 
 	// clear the state to start with, in case of missing table elements
 	memset(state,0,sizeof(state_t));
@@ -378,6 +381,8 @@ static int state_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter states in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter states in BuildCMD code!");
 
 	if (fastcmp(field,"sprite")) {
 		value = luaL_checknumber(L, 3);
@@ -466,6 +471,8 @@ static int lib_setMobjInfo(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter mobjinfo in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter mobjinfo in BuildCMD code!");
 
 	// clear the mobjinfo to start with, in case of missing table elements
 	memset(info,0,sizeof(mobjinfo_t));
@@ -633,6 +640,8 @@ static int mobjinfo_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter mobjinfo in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter mobjinfo in BuildCMD code!");
 
 	I_Assert(info != NULL);
 	I_Assert(info >= mobjinfo);
@@ -755,6 +764,8 @@ static int lib_setSfxInfo(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter sfxinfo in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter sfxinfo in BuildCMD code!");
 
 	lua_pushnil(L);
 	while (lua_next(L, 1)) {
@@ -830,6 +841,8 @@ static int sfxinfo_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter S_sfx in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter S_sfx in BuildCMD code!");
 
 	I_Assert(sfx != NULL);
 
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 7599b261..0bb9a99d 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -24,6 +24,7 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "lua_hook.h"	// cmd errors
 
 #include "dehacked.h"
 #include "fastcmp.h"
@@ -484,6 +485,8 @@ static int sector_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter sector_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter sector_t in BuildCMD code!");
 
 	switch(field)
 	{
@@ -1174,6 +1177,8 @@ static int ffloor_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter ffloor_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter ffloor_t in BuildCMD code!");
 
 	switch(field)
 	{
@@ -1303,6 +1308,8 @@ static int slope_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter pslope_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter pslope_t in BuildCMD code!");
 
 	switch(field) // todo: reorganize this shit
 	{
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 34aba0a3..b56538d0 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -21,6 +21,7 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "lua_hook.h"	// cmd errors
 
 static const char *const array_opt[] ={"iterate",NULL};
 
@@ -391,6 +392,9 @@ static int mobj_set(lua_State *L)
 	if (hud_running)
 		return luaL_error(L, "Do not alter mobj_t in HUD rendering code!");
 
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter mobj_t in BuildCMD code!");
+
 	switch(field)
 	{
 	case mobj_valid:
@@ -756,6 +760,8 @@ static int mapthing_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter mapthing_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter mapthing_t in BuildCMD code!");
 
 	if(fastcmp(field,"x"))
 		mt->x = (INT16)luaL_checkinteger(L, 3);
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 067124ba..5f136fc1 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -21,6 +21,7 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "lua_hook.h"	// hook_cmd_running
 
 static int lib_iteratePlayers(lua_State *L)
 {
@@ -356,6 +357,9 @@ static int player_set(lua_State *L)
 	if (hud_running)
 		return luaL_error(L, "Do not alter player_t in HUD rendering code!");
 
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter player_t in BuildCMD code!");
+
 	if (fastcmp(field,"mo")) {
 		mobj_t *newmo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		plr->mo->player = NULL; // remove player pointer from old mobj
@@ -667,6 +671,8 @@ static int power_set(lua_State *L)
 		return luaL_error(L, LUA_QL("powertype_t") " cannot be %u", p);
 	if (hud_running)
 		return luaL_error(L, "Do not alter player_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter player_t in BuildCMD code!");
 	powers[p] = i;
 	return 0;
 }
@@ -699,6 +705,8 @@ static int kartstuff_set(lua_State *L)
 		return luaL_error(L, LUA_QL("kartstufftype_t") " cannot be %u", ks);
 	if (hud_running)
 		return luaL_error(L, "Do not alter player_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter player_t in BuildCMD code!");
 	kartstuff[ks] = i;
 	return 0;
 }

From 148bd140c23906c5fe6f8666392ab7410d64b206 Mon Sep 17 00:00:00 2001
From: Latapostrophe <hyperclassic3@gmail.com>
Date: Sun, 13 Jan 2019 20:16:53 +0100
Subject: [PATCH 08/73] hook_cmd_running around the loop rather than inside

---
 src/lua_hooklib.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 1a1d4ed6..5a95877e 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -889,10 +889,10 @@ boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd)
 
 	lua_settop(gL, 0);
 
+	hook_cmd_running = true;
 	for (hookp = roothook; hookp; hookp = hookp->next)
 		if (hookp->type == hook_PlayerCmd)
 		{
-			hook_cmd_running = true;
 			if (lua_gettop(gL) == 0)
 			{
 				LUA_PushUserdata(gL, player, META_PLAYER);
@@ -912,9 +912,9 @@ boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd)
 			if (lua_toboolean(gL, -1))
 				hooked = true;
 			lua_pop(gL, 1);
-			hook_cmd_running = false;
 		}
 
+	hook_cmd_running = false;
 	lua_settop(gL, 0);
 	return hooked;
 }

From 081872aa857cb57bf87670807597ee98b3009e91 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Tue, 15 Jan 2019 19:01:55 +0000
Subject: [PATCH 09/73] First steps at implementing a mechanism that allows you
 to load non-cheaty good-faith mods such as custom characters with no Lua
 scripting, and play record attack with them. Features a few bad hacks and a
 few more areas of improvement; I'll try to iron them out before they hit
 `next` or `master`.

---
 src/d_main.c     |  1 +
 src/d_netcmd.c   |  7 +++++--
 src/dehacked.c   | 28 +++++++++++++++++++++++++++-
 src/doomstat.h   |  3 +++
 src/g_game.c     | 44 ++++++++++++++++++++++++++++++++------------
 src/lua_script.c |  3 +++
 src/m_cheat.c    |  1 +
 src/m_cond.c     |  2 +-
 src/m_menu.c     |  4 ++--
 src/p_setup.c    | 14 +++++++++++---
 src/r_things.c   |  5 +++--
 src/r_things.h   |  2 +-
 src/y_inter.c    |  2 +-
 13 files changed, 91 insertions(+), 25 deletions(-)

diff --git a/src/d_main.c b/src/d_main.c
index 29a91686..f3539df7 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1253,6 +1253,7 @@ void D_SRB2Main(void)
 #endif //ifndef DEVELOP
 
 	mainwadstally = packetsizetally;
+	majormods = false;
 
 	cht_Init();
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index e15ed9aa..d6e78c91 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2214,10 +2214,13 @@ static void Command_Map_f(void)
 		return;
 	}
 
-	if (!(netgame || multiplayer) && (!modifiedgame || savemoddata))
+	if (!(netgame || multiplayer) && (!majormods || savemoddata))
 	{
 		if (COM_CheckParm("-force"))
+		{
 			G_SetGameModified(false);
+			majormods = true;
+		}
 		else
 		{
 			CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
@@ -4915,7 +4918,7 @@ static void Command_Isgamemodified_f(void)
 {
 	if (savemoddata)
 		CONS_Printf(M_GetText("modifiedgame is true, but you can save medal and record data in this mod.\n"));
-	else if (modifiedgame)
+	else if (/*modifiedgame*/ majormods)
 		CONS_Printf(M_GetText("modifiedgame is true, extras will not be unlocked\n"));
 	else
 		CONS_Printf(M_GetText("modifiedgame is false, you can unlock extras\n"));
diff --git a/src/dehacked.c b/src/dehacked.c
index e7e1ae69..49d9d107 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3417,18 +3417,21 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 			if (fastcmp(word, "FREESLOT"))
 			{
 				readfreeslots(f);
+				majormods = true;
 				continue;
 			}
 			else if (fastcmp(word, "MAINCFG"))
 			{
 				readmaincfg(f);
 				DEH_WriteUndoline(word, "", UNDO_HEADER);
+				majormods = true;
 				continue;
 			}
 			else if (fastcmp(word, "WIPES"))
 			{
 				readwipes(f);
 				DEH_WriteUndoline(word, "", UNDO_HEADER);
+				//majormods = true;
 				continue;
 			}
 			word2 = strtok(NULL, " ");
@@ -3449,6 +3452,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					ignorelines(f);
 				}
 				DEH_WriteUndoline(word, word2, UNDO_HEADER);
+				//majormods = true;
 				continue;
 			}
 			if (word2)
@@ -3462,12 +3466,14 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					// Read texture from spec file.
 					readtexture(f, word2);
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					//majormods = true;
 				}
 				else if (fastcmp(word, "PATCH"))
 				{
 					// Read patch from spec file.
 					readpatch(f, word2, wad);
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					//majormods = true;
 				}
 				else if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
 				{
@@ -3481,10 +3487,12 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					majormods = true;
 				}
 /*				else if (fastcmp(word, "ANIMTEX"))
 				{
 					readAnimTex(f, i);
+					//majormods = true;
 				}*/
 				else if (fastcmp(word, "LIGHT"))
 				{
@@ -3498,6 +3506,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					//majormods = true;
 #endif
 				}
 				else if (fastcmp(word, "SPRITE"))
@@ -3513,6 +3522,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					//majormods = true;
 #endif
 				}
 				else if (fastcmp(word, "LEVEL"))
@@ -3525,7 +3535,11 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						i = M_MapNumber(word2[0], word2[1]);
 
 					if (i > 0 && i <= NUMMAPS)
+					{
+						if (mapheaderinfo[i])
+							majormods = true; // only mark as a major mod if it replaces an already-existing mapheaderinfo
 						readlevelheader(f, i);
+					}
 					else
 					{
 						deh_warning("Level number %d out of range (1 - %d)", i, NUMMAPS);
@@ -3543,6 +3557,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					//majormods = true; -- might have to reconsider in a future update
 				}
 				else if (fastcmp(word, "FRAME") || fastcmp(word, "STATE"))
 				{
@@ -3556,6 +3571,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					majormods = true;
 				}
 				// <Callum> Added translations to this just in case its re-enabled
 /*				else if (fastcmp(word, "POINTER"))
@@ -3578,6 +3594,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					else
 						deh_warning("pointer (Frame %d) : missing ')'", i);
+					majormods = true;
 				}*/
 				else if (fastcmp(word, "SOUND"))
 				{
@@ -3591,6 +3608,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					//majormods = true; -- ...this won't bite me in the ass later, will it?
 				}
 /*				else if (fastcmp(word, "SPRITE"))
 				{
@@ -3611,6 +3629,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					else
 						deh_warning("Sprite %d doesn't exist",i);
+					//majormods = true;
 				}*/
 				else if (fastcmp(word, "HUDITEM"))
 				{
@@ -3624,6 +3643,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					//majormods = true;
 				}
 				else if (fastcmp(word, "EMBLEM"))
 				{
@@ -3644,6 +3664,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					majormods = true;
 				}
 				else if (fastcmp(word, "EXTRAEMBLEM"))
 				{
@@ -3664,6 +3685,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					majormods = true;
 				}
 				else if (fastcmp(word, "UNLOCKABLE"))
 				{
@@ -3680,6 +3702,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					majormods = true;
 				}
 				else if (fastcmp(word, "CONDITIONSET"))
 				{
@@ -3697,6 +3720,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					// no undo support for this insanity yet
 					//DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					majormods = true;
 				}
 				else if (fastcmp(word, "SRB2KART"))
 				{
@@ -3743,6 +3767,8 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 
 					if (clearall || fastcmp(word2, "LEVELS"))
 						clear_levels();
+
+					majormods = true;
 				}
 				else
 					deh_warning("Unknown word: %s", word);
@@ -9737,7 +9763,7 @@ static inline int lib_getenum(lua_State *L)
 		lua_pushboolean(L, devparm);
 		return 1;
 	} else if (fastcmp(word,"modifiedgame")) {
-		lua_pushboolean(L, modifiedgame && !savemoddata);
+		lua_pushboolean(L, /*modifiedgame*/ majormods && !savemoddata);
 		return 1;
 	} else if (fastcmp(word,"menuactive")) {
 		lua_pushboolean(L, menuactive);
diff --git a/src/doomstat.h b/src/doomstat.h
index 6d710e28..9ae2726d 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -54,6 +54,7 @@ extern boolean gamecomplete;
 
 // Set if homebrew PWAD stuff has been added.
 extern boolean modifiedgame;
+extern boolean majormods;
 extern UINT16 mainwads;
 extern boolean savemoddata; // This mod saves time/emblem data.
 extern boolean disableSpeedAdjust; // Don't alter the duration of player states if true
@@ -280,6 +281,8 @@ typedef struct
 #define LF2_NIGHTSATTACK   8 ///< Show this map in NiGHTS mode menu
 #define LF2_NOVISITNEEDED 16 ///< Available in time attack/nights mode without visiting the level
 
+#define LF2_EXISTSHACK   128 ///< Map lump exists; as noted, a single-bit hack that can be freely movable to other variables without concern.
+
 // Save override
 #define SAVE_NEVER   -1
 #define SAVE_DEFAULT  0
diff --git a/src/g_game.c b/src/g_game.c
index 1e0c7e46..28171e75 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -86,7 +86,8 @@ INT16 lastmapsaved = 0; // Last map we auto-saved at
 boolean gamecomplete = false;
 
 UINT16 mainwads = 0;
-boolean modifiedgame; // Set if homebrew PWAD stuff has been added.
+boolean modifiedgame = false; // Set if homebrew PWAD stuff has been added.
+boolean majormods = false; // Set if Lua/Gameplay SOC/replacement map has been added.
 boolean savemoddata = false;
 UINT8 paused;
 UINT8 modeattacking = ATTACKING_NONE;
@@ -5918,6 +5919,19 @@ void G_DoPlayDemo(char *defdemoname)
 		return;
 	}
 
+	// Skin not loaded?
+	if (!SetPlayerSkin(0, skin))
+	{
+		snprintf(msg, 1024, M_GetText("%s features a character that is not loaded.\n"), pdemoname);
+		CONS_Alert(CONS_ERROR, "%s", msg);
+		M_StartMessage(msg, NULL, MM_NOTHING);
+		Z_Free(pdemoname);
+		Z_Free(demobuffer);
+		demoplayback = false;
+		titledemo = false;
+		return;
+	}
+
 	Z_Free(pdemoname);
 
 	memset(&oldcmd,0,sizeof(oldcmd));
@@ -5949,9 +5963,6 @@ void G_DoPlayDemo(char *defdemoname)
 	P_SetRandSeed(randseed);
 	G_InitNew(false, G_BuildMapName(gamemap), true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer.
 
-	// Set skin
-	SetPlayerSkin(0, skin);
-
 	// Set color
 	for (i = 0; i < MAXSKINCOLORS; i++)
 		if (!stricmp(KartColor_Names[i],color))				// SRB2kart
@@ -6146,6 +6157,22 @@ void G_AddGhost(char *defdemoname)
 		return;
 	}
 
+	gh->oldmo->skin = &skins[0];
+	for (i = 0; i < numskins; i++)
+		if (!stricmp(skins[i].name,skin))
+		{
+			gh->oldmo->skin = &skins[i];
+			break;
+		}
+
+	if (i == numskins)
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid character.\n"), pdemoname);
+		Z_Free(pdemoname);
+		Z_Free(buffer);
+		return;
+	}
+
 	gh = Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL);
 	gh->next = ghosts;
 	gh->buffer = buffer;
@@ -6191,14 +6218,7 @@ void G_AddGhost(char *defdemoname)
 	gh->oldmo.z = gh->mo->z;
 
 	// Set skin
-	gh->mo->skin = &skins[0];
-	for (i = 0; i < numskins; i++)
-		if (!stricmp(skins[i].name,skin))
-		{
-			gh->mo->skin = &skins[i];
-			break;
-		}
-	gh->oldmo.skin = gh->mo->skin;
+	gh->mo.skin = gh->oldmo->skin;
 
 	// Set color
 	gh->mo->color = ((skin_t*)gh->mo->skin)->prefcolor;
diff --git a/src/lua_script.c b/src/lua_script.c
index 34a26052..d7c4a160 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -212,6 +212,9 @@ void LUA_LoadLump(UINT16 wad, UINT16 lump)
 
 	LUA_LoadFile(&f, name); // actually load file!
 
+	// Okay, we've modified the game beyond the point of no return.
+	majormods = true;
+
 	free(name);
 	Z_Free(f.data);
 }
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 9c53f901..499dac28 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -1268,6 +1268,7 @@ void Command_ObjectPlace_f(void)
 	REQUIRE_NOULTIMATE;
 
 	G_SetGameModified(multiplayer);
+	majormods = true;
 
 	// Entering objectplace?
 	if (!objectplacing)
diff --git a/src/m_cond.c b/src/m_cond.c
index 35eccd1c..b0e49a68 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -385,7 +385,7 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(boolean force)
 	char cechoText[992] = "";
 	UINT8 cechoLines = 0;
 
-	if (modifiedgame && !savemoddata
+	if (/*modifiedgame*/ majormods && !savemoddata
 		&& !force) // SRB2Kart: for enabling unlocks online in modified servers
 		return false;
 
diff --git a/src/m_menu.c b/src/m_menu.c
index 8c0e6079..03cc8002 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -2773,10 +2773,10 @@ boolean M_Responder(event_t *ev)
 				 || (currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_SUBMENU)
                  && (currentMenu->menuitems[itemOn].status & IT_CALLTYPE))
 				{
-					if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && modifiedgame && !savemoddata)
+					if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && /*modifiedgame*/ majormods && !savemoddata)
 					{
 						S_StartSound(NULL, sfx_menu1);
-						M_StartMessage(M_GetText("This cannot be done with add-ons\nor in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+						M_StartMessage(M_GetText("This cannot be done with complex add-ons\nor in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
 						return true;
 					}
 				}
diff --git a/src/p_setup.c b/src/p_setup.c
index 49b22184..0a59a2a9 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -234,7 +234,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	DEH_WriteUndoline("LEVELFLAGS", va("%d", mapheaderinfo[num]->levelflags), UNDO_NONE);
 	mapheaderinfo[num]->levelflags = 0;
 	DEH_WriteUndoline("MENUFLAGS", va("%d", mapheaderinfo[num]->menuflags), UNDO_NONE);
-	mapheaderinfo[num]->menuflags = 0;
+	mapheaderinfo[num]->menuflags = (mainwads ? 0 : LF2_EXISTSHACK); // see p_setup.c - prevents replacing maps in addons with easier versions
 	// TODO grades support for delfile (pfft yeah right)
 	P_DeleteGrades(num);
 	// SRB2Kart
@@ -1120,7 +1120,7 @@ static inline void P_SpawnEmblems(void)
 static void P_SpawnSecretItems(boolean loademblems)
 {
 	// Now let's spawn those funky emblem things! Tails 12-08-2002
-	if (netgame || multiplayer || (modifiedgame && !savemoddata)) // No cheating!!
+	if (netgame || multiplayer || (/*modifiedgame*/ majormods && !savemoddata)) // No cheating!!
 		return;
 
 	if (loademblems)
@@ -3272,7 +3272,7 @@ boolean P_SetupLevel(boolean skipprecip)
 	nextmapoverride = 0;
 	skipstats = false;
 
-	if (!(netgame || multiplayer) && (!modifiedgame || savemoddata))
+	if (!(netgame || multiplayer) && (/*!modifiedgame*/ !majormods || savemoddata))
 		mapvisited[gamemap-1] |= MV_VISITED;
 
 	levelloading = false;
@@ -3455,6 +3455,14 @@ boolean P_AddWadFile(const char *wadfilename)
 				continue;
 			num = (INT16)M_MapNumber(name[3], name[4]);
 
+			// we want to record whether this map exists. if it doesn't have a header, we can assume it's not relephant
+			if (num <= NUMMAPS && mapheaderinfo[num-1])
+			{
+				if (mapheaderinfo[num-1]->menuflags & LF2_EXISTSHACK)
+					majormods = true; // oops, double-defined - no record attack privileges for you
+				mapheaderinfo[num-1]->menuflags |= LF2_EXISTSHACK;
+			}
+
 			//If you replaced the map you're on, end the level when done.
 			if (num == gamemap)
 				replacedcurrentmap = true;
diff --git a/src/r_things.c b/src/r_things.c
index 1825d2d9..a4720063 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2635,7 +2635,7 @@ INT32 R_SkinAvailable(const char *name)
 }
 
 // network code calls this when a 'skin change' is received
-void SetPlayerSkin(INT32 playernum, const char *skinname)
+boolean SetPlayerSkin(INT32 playernum, const char *skinname)
 {
 	INT32 i;
 	player_t *player = &players[playernum];
@@ -2646,7 +2646,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
 		if (stricmp(skins[i].name, skinname) == 0)
 		{
 			SetPlayerSkinByNum(playernum, i);
-			return;
+			return true;
 		}
 	}
 
@@ -2656,6 +2656,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
 		CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) skin '%s' not found\n"), playernum, player_names[playernum], skinname);
 
 	SetPlayerSkinByNum(playernum, 0);
+	return false;
 }
 
 // Same as SetPlayerSkin, but uses the skin #.
diff --git a/src/r_things.h b/src/r_things.h
index a8635034..bc51f711 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -194,7 +194,7 @@ typedef struct drawnode_s
 extern INT32 numskins;
 extern skin_t skins[MAXSKINS + 1];
 
-void SetPlayerSkin(INT32 playernum,const char *skinname);
+boolean SetPlayerSkin(INT32 playernum,const char *skinname);
 void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
 INT32 R_SkinAvailable(const char *name);
 void R_AddSkins(UINT16 wadnum);
diff --git a/src/y_inter.c b/src/y_inter.c
index 021519e3..046d6d6d 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -786,7 +786,7 @@ void Y_StartIntermission(void)
 		}
 		case int_race: // (time-only race)
 		{
-			if ((!modifiedgame || savemoddata) && !multiplayer && !demoplayback) // remove this once we have a proper time attack screen
+			if ((/*!modifiedgame*/ !majormods || savemoddata) && !multiplayer && !demoplayback) // remove this once we have a proper time attack screen
 			{
 				// Update visitation flags
 				mapvisited[gamemap-1] |= MV_BEATEN;

From a7445a7b71b70c51f17a0e5acedb5dd2125e3847 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Wed, 16 Jan 2019 15:27:23 +0000
Subject: [PATCH 10/73] Woops, didn't commit the most up-to-date g_game.c
 originally (old one didn't compile)

---
 src/g_game.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index 28171e75..a1ca4e73 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -5922,7 +5922,7 @@ void G_DoPlayDemo(char *defdemoname)
 	// Skin not loaded?
 	if (!SetPlayerSkin(0, skin))
 	{
-		snprintf(msg, 1024, M_GetText("%s features a character that is not loaded.\n"), pdemoname);
+		snprintf(msg, 1024, M_GetText("%s features a character that is not currently loaded.\n"), pdemoname);
 		CONS_Alert(CONS_ERROR, "%s", msg);
 		M_StartMessage(msg, NULL, MM_NOTHING);
 		Z_Free(pdemoname);
@@ -6157,11 +6157,11 @@ void G_AddGhost(char *defdemoname)
 		return;
 	}
 
-	gh->oldmo->skin = &skins[0];
+	gh->oldmo.skin = &skins[0];
 	for (i = 0; i < numskins; i++)
 		if (!stricmp(skins[i].name,skin))
 		{
-			gh->oldmo->skin = &skins[i];
+			gh->oldmo.skin = &skins[i];
 			break;
 		}
 
@@ -6218,7 +6218,7 @@ void G_AddGhost(char *defdemoname)
 	gh->oldmo.z = gh->mo->z;
 
 	// Set skin
-	gh->mo.skin = gh->oldmo->skin;
+	gh->mo->skin = gh->oldmo.skin;
 
 	// Set color
 	gh->mo->color = ((skin_t*)gh->mo->skin)->prefcolor;

From 2f2d3768d55c2551017fca4fc4ef57803d06f2ff Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Thu, 17 Jan 2019 20:51:41 +0000
Subject: [PATCH 11/73] * Increased leniency for SOC wrt "major mods". 	* If
 your file defines state and object freeslots in SOC, you are allowed to
 modify those freeslots IN ANY SOC SCRIPT IN THE SAME FILE without being
 marked as a "major mod". 	* If your file contains broken
 unlockables/emblems that don't actually have effect for any reason, it's not
 counted as a "major mod". * Added add-ons menu message for adding a "major
 mod".

---
 src/dehacked.c | 36 +++++++++++++++++++++++++++++-------
 src/dehacked.h |  2 ++
 src/m_menu.c   | 15 +++++++++++++--
 src/w_wad.c    |  2 ++
 4 files changed, 46 insertions(+), 9 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 49d9d107..2e8ce562 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -602,6 +602,14 @@ done:
 	Z_Free(s);
 }
 
+static int freeslotusage[2][2] = {{0, 0}, {0, 0}}; // [S_, MT_][max, previous .wad's max]
+
+void DEH_UpdateMaxFreeslots(void)
+{
+	freeslotusage[0][1] = freeslotusage[0][0];
+	freeslotusage[1][1] = freeslotusage[1][0];
+}
+
 // TODO: Figure out how to do undolines for this....
 // TODO: Warnings for running out of freeslots
 static void readfreeslots(MYFILE *f)
@@ -664,6 +672,7 @@ static void readfreeslots(MYFILE *f)
 					if (!FREE_STATES[i]) {
 						FREE_STATES[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
 						strcpy(FREE_STATES[i],word);
+						freeslotusage[0][0]++;
 						break;
 					}
 			}
@@ -673,6 +682,7 @@ static void readfreeslots(MYFILE *f)
 					if (!FREE_MOBJS[i]) {
 						FREE_MOBJS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
 						strcpy(FREE_MOBJS[i],word);
+						freeslotusage[1][0]++;
 						break;
 					}
 			}
@@ -3417,7 +3427,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 			if (fastcmp(word, "FREESLOT"))
 			{
 				readfreeslots(f);
-				majormods = true;
+				//majormods = true;
 				continue;
 			}
 			else if (fastcmp(word, "MAINCFG"))
@@ -3480,14 +3490,17 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					if (i == 0 && word2[0] != '0') // If word2 isn't a number
 						i = get_mobjtype(word2); // find a thing by name
 					if (i < NUMMOBJTYPES && i >= 0)
+					{
+						if (i < (MT_FIRSTFREESLOT+freeslotusage[1][1]))
+							majormods = true; // affecting something earlier than the first freeslot allocated in this .wad? DENIED
 						readthing(f, i);
+					}
 					else
 					{
 						deh_warning("Thing %d out of range (0 - %d)", i, NUMMOBJTYPES-1);
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					majormods = true;
 				}
 /*				else if (fastcmp(word, "ANIMTEX"))
 				{
@@ -3564,14 +3577,17 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					if (i == 0 && word2[0] != '0') // If word2 isn't a number
 						i = get_state(word2); // find a state by name
 					if (i < NUMSTATES && i >= 0)
+					{
+						if (i < (S_FIRSTFREESLOT+freeslotusage[0][1]))
+							majormods = true; // affecting something earlier than the first freeslot allocated in this .wad? DENIED
 						readframe(f, i);
+					}
 					else
 					{
 						deh_warning("Frame %d out of range (0 - %d)", i, NUMSTATES-1);
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					majormods = true;
 				}
 				// <Callum> Added translations to this just in case its re-enabled
 /*				else if (fastcmp(word, "POINTER"))
@@ -3657,6 +3673,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						if (numemblems < i)
 							numemblems = i;
 						reademblemdata(f, i);
+						majormods = true;
 					}
 					else
 					{
@@ -3664,7 +3681,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					majormods = true;
 				}
 				else if (fastcmp(word, "EXTRAEMBLEM"))
 				{
@@ -3678,6 +3694,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						if (numextraemblems < i)
 							numextraemblems = i;
 						readextraemblemdata(f, i);
+						majormods = true;
 					}
 					else
 					{
@@ -3685,7 +3702,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					majormods = true;
 				}
 				else if (fastcmp(word, "UNLOCKABLE"))
 				{
@@ -3695,14 +3711,16 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					else if (i > 0 && i <= MAXUNLOCKABLES)
+					{
 						readunlockable(f, i - 1);
+						majormods = true;
+					}
 					else
 					{
 						deh_warning("Unlockable number %d out of range (1 - %d)", i, MAXUNLOCKABLES);
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					majormods = true;
 				}
 				else if (fastcmp(word, "CONDITIONSET"))
 				{
@@ -3712,7 +3730,10 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					else if (i > 0 && i <= MAXCONDITIONSETS)
+					{
 						readconditionset(f, (UINT8)i);
+						majormods = true;
+					}
 					else
 					{
 						deh_warning("Condition set number %d out of range (1 - %d)", i, MAXCONDITIONSETS);
@@ -3720,7 +3741,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					// no undo support for this insanity yet
 					//DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					majormods = true;
 				}
 				else if (fastcmp(word, "SRB2KART"))
 				{
@@ -9381,6 +9401,7 @@ static inline int lib_freeslot(lua_State *L)
 					CONS_Printf("State S_%s allocated.\n",word);
 					FREE_STATES[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
 					strcpy(FREE_STATES[i],word);
+					freeslotusage[0][0]++;
 					lua_pushinteger(L, i);
 					r++;
 					break;
@@ -9396,6 +9417,7 @@ static inline int lib_freeslot(lua_State *L)
 					CONS_Printf("MobjType MT_%s allocated.\n",word);
 					FREE_MOBJS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
 					strcpy(FREE_MOBJS[i],word);
+					freeslotusage[1][0]++;
 					lua_pushinteger(L, i);
 					r++;
 					break;
diff --git a/src/dehacked.h b/src/dehacked.h
index 683fe7d9..0d6cc902 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -37,6 +37,8 @@ void DEH_UnloadDehackedWad(UINT16 wad);
 void DEH_LoadDehackedLump(lumpnum_t lumpnum);
 void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump);
 
+void DEH_UpdateMaxFreeslots(void);
+
 void DEH_Check(void);
 
 fixed_t get_number(const char *word);
diff --git a/src/m_menu.c b/src/m_menu.c
index 03cc8002..e68027c6 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4551,9 +4551,14 @@ static char *M_AddonsHeaderPath(void)
 #define CLEARNAME Z_Free(refreshdirname);\
 					refreshdirname = NULL
 
+static boolean prevmajormods = false;
+
 static void M_AddonsClearName(INT32 choice)
 {
-	CLEARNAME;
+	if (majormods == prevmajormods || savemoddata)
+	{
+		CLEARNAME;
+	}
 	M_StopMessage(choice);
 }
 
@@ -4566,7 +4571,7 @@ static boolean M_AddonsRefresh(void)
 		return true;
 	}
 
-	if (refreshdirmenu & REFRESHDIR_ADDFILE)
+	if ((refreshdirmenu & REFRESHDIR_ADDFILE) || (majormods != prevmajormods && !savemoddata))
 	{
 		char *message = NULL;
 
@@ -4583,6 +4588,12 @@ static boolean M_AddonsRefresh(void)
 			S_StartSound(NULL, sfx_s224);
 			message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings"));
 		}
+		else if (majormods != prevmajormods && !savemoddata)
+		{
+			S_StartSound(NULL, sfx_s221);
+			message = va("%c%s\x80\nGameplay has now been modified.\nIf you want to play record attack mode, restart the game to clear existing add-ons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
+			prevmajormods = majormods;
+		}
 
 		if (message)
 		{
diff --git a/src/w_wad.c b/src/w_wad.c
index 63bee97d..58a65191 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -799,6 +799,8 @@ UINT16 W_InitFile(const char *filename)
 		break;
 	}
 
+	DEH_UpdateMaxFreeslots();
+
 	W_InvalidateLumpnumCache();
 	return wadfile->numlumps;
 }

From 66273898b7233bb33ce6145563c2b93361643903 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Thu, 17 Jan 2019 22:01:28 +0000
Subject: [PATCH 12/73] * Refactor all instances of `majormods = true;` to
 route through G_SetGameModified, and catch a few spots I missed in the
 process. * Make G_SetGameModified only console-print for major mods. * Add
 amnesty to "major mod" detection while loading files with custom savedatas. *
 Improved the console prints for command `isgamemodified`.

---
 src/d_main.c     |  4 +--
 src/d_netcmd.c   | 23 ++++++++---------
 src/d_netfil.c   |  2 +-
 src/dehacked.c   | 66 +++++++++++++++++++++++-------------------------
 src/filesrch.h   |  3 ++-
 src/g_game.c     | 12 ++++++---
 src/g_game.h     |  2 +-
 src/lua_script.c |  2 +-
 src/m_cheat.c    | 13 +++++-----
 src/m_menu.c     | 14 ++++++----
 src/p_setup.c    |  4 ++-
 src/w_wad.c      |  3 +++
 12 files changed, 80 insertions(+), 68 deletions(-)

diff --git a/src/d_main.c b/src/d_main.c
index f3539df7..fd85770c 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1162,7 +1162,7 @@ void D_SRB2Main(void)
 				if (s) // Check for NULL?
 				{
 					if (!W_VerifyNMUSlumps(s))
-						G_SetGameModified(true);
+						G_SetGameModified(true, false);
 					D_AddFile(s);
 				}
 			}
@@ -1189,7 +1189,7 @@ void D_SRB2Main(void)
 		else
 		{
 			if (!M_CheckParm("-server"))
-				G_SetGameModified(true);
+				G_SetGameModified(true, true);
 			autostart = true;
 		}
 	}
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index d6e78c91..244e3058 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2218,8 +2218,7 @@ static void Command_Map_f(void)
 	{
 		if (COM_CheckParm("-force"))
 		{
-			G_SetGameModified(false);
-			majormods = true;
+			G_SetGameModified(false, true);
 		}
 		else
 		{
@@ -3789,7 +3788,7 @@ static void Command_RunSOC(void)
 		if (!P_RunSOC(fn))
 			CONS_Printf(M_GetText("Could not find SOC.\n"));
 		else
-			G_SetGameModified(multiplayer);
+			G_SetGameModified(multiplayer, false);
 		return;
 	}
 
@@ -3843,7 +3842,7 @@ static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum)
 	}
 
 	P_RunSOC(filename);
-	G_SetGameModified(true);
+	G_SetGameModified(true, false);
 }
 
 /** Adds a pwad at runtime.
@@ -3880,7 +3879,7 @@ static void Command_Addfile(void)
 			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 			return;
 		}
-		G_SetGameModified(multiplayer);
+		G_SetGameModified(multiplayer, false);
 	}
 
 	// Add file on your client directly if it is trivial, or you aren't in a netgame.
@@ -4126,7 +4125,7 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
-	G_SetGameModified(true);
+	G_SetGameModified(true, false);
 }
 
 static void Command_ListWADS_f(void)
@@ -4483,7 +4482,7 @@ static void Ringslinger_OnChange(void)
 	}
 
 	if (cv_ringslinger.value) // Only if it's been turned on
-		G_SetGameModified(multiplayer);
+		G_SetGameModified(multiplayer, true);
 }
 
 static void Gravity_OnChange(void)
@@ -4504,7 +4503,7 @@ static void Gravity_OnChange(void)
 #endif
 
 	if (!CV_IsSetToDefault(&cv_gravity))
-		G_SetGameModified(multiplayer);
+		G_SetGameModified(multiplayer, true);
 	gravity = cv_gravity.value;
 }
 
@@ -4900,7 +4899,7 @@ static void Fishcake_OnChange(void)
 	// so don't make modifiedgame always on!
 	if (cv_debug)
 	{
-		G_SetGameModified(multiplayer);
+		G_SetGameModified(multiplayer, true);
 	}
 
 	else if (cv_debug != cv_fishcake.value)
@@ -4917,11 +4916,11 @@ static void Fishcake_OnChange(void)
 static void Command_Isgamemodified_f(void)
 {
 	if (savemoddata)
-		CONS_Printf(M_GetText("modifiedgame is true, but you can save medal and record data in this mod.\n"));
+		CONS_Printf(M_GetText("The game is modified, but you can save medal and record data in this add-on.\n"));
 	else if (/*modifiedgame*/ majormods)
-		CONS_Printf(M_GetText("modifiedgame is true, extras will not be unlocked\n"));
+		CONS_Printf(M_GetText("Major add-ons have been loaded, so you cannot play record attack.\n"));
 	else
-		CONS_Printf(M_GetText("modifiedgame is false, you can unlock extras\n"));
+		CONS_Printf(M_GetText("No major add-ons are loaded. You can play record attack, earn medals and unlock extras.\n"));
 }
 
 static void Command_Cheats_f(void)
diff --git a/src/d_netfil.c b/src/d_netfil.c
index c7cfdbc1..99a05840 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -426,7 +426,7 @@ void CL_LoadServerFiles(void)
 		else if (fileneeded[i].status == FS_FOUND)
 		{
 			P_AddWadFile(fileneeded[i].filename);
-			G_SetGameModified(true);
+			G_SetGameModified(true, false);
 			fileneeded[i].status = FS_OPEN;
 		}
 		else if (fileneeded[i].status == FS_MD5SUMBAD)
diff --git a/src/dehacked.c b/src/dehacked.c
index 2e8ce562..4edeb987 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -21,6 +21,7 @@
 #include "w_wad.h"
 #include "m_menu.h"
 #include "m_misc.h"
+#include "filesrch.h" // for refreshdirmenu
 #include "f_finale.h"
 #include "dehacked.h"
 #include "st_stuff.h"
@@ -79,8 +80,6 @@ static powertype_t get_power(const char *word);
 boolean deh_loaded = false;
 static int dbg_line;
 
-static boolean gamedataadded = false;
-
 #ifdef DELFILE
 typedef struct undehacked_s
 {
@@ -3149,6 +3148,7 @@ static void readmaincfg(MYFILE *f)
 				strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
 				strlwr(gamedatafilename);
 				savemoddata = true;
+				majormods = false;
 
 				// Also save a time attack folder
 				filenamelen = strlen(gamedatafilename)-4;  // Strip off the extension
@@ -3161,7 +3161,7 @@ static void readmaincfg(MYFILE *f)
 				// can't use sprintf since there is %u in savegamename
 				strcatbf(savegamename, srb2home, PATHSEP);
 
-				gamedataadded = true;
+				refreshdirmenu |= REFRESHDIR_GAMEDATA;
 			}
 			else if (fastcmp(word, "RESETDATA"))
 			{
@@ -3392,8 +3392,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 	for (i = 0; i < NUMSFX; i++)
 		savesfxnames[i] = S_sfx[i].name;
 
-	gamedataadded = false;
-
 	// it doesn't test the version of SRB2 and version of dehacked file
 	dbg_line = -1; // start at -1 so the first line is 0.
 	while (!myfeof(f))
@@ -3427,21 +3425,21 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 			if (fastcmp(word, "FREESLOT"))
 			{
 				readfreeslots(f);
-				//majormods = true;
+				//G_SetGameModified(multiplayer, true);
 				continue;
 			}
 			else if (fastcmp(word, "MAINCFG"))
 			{
+				G_SetGameModified(multiplayer, true);
 				readmaincfg(f);
 				DEH_WriteUndoline(word, "", UNDO_HEADER);
-				majormods = true;
 				continue;
 			}
 			else if (fastcmp(word, "WIPES"))
 			{
 				readwipes(f);
 				DEH_WriteUndoline(word, "", UNDO_HEADER);
-				//majormods = true;
+				//G_SetGameModified(multiplayer, true);
 				continue;
 			}
 			word2 = strtok(NULL, " ");
@@ -3462,7 +3460,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					ignorelines(f);
 				}
 				DEH_WriteUndoline(word, word2, UNDO_HEADER);
-				//majormods = true;
+				//G_SetGameModified(multiplayer, true);
 				continue;
 			}
 			if (word2)
@@ -3476,14 +3474,14 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					// Read texture from spec file.
 					readtexture(f, word2);
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//majormods = true;
+					//G_SetGameModified(multiplayer, true);
 				}
 				else if (fastcmp(word, "PATCH"))
 				{
 					// Read patch from spec file.
 					readpatch(f, word2, wad);
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//majormods = true;
+					//G_SetGameModified(multiplayer, true);
 				}
 				else if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
 				{
@@ -3492,7 +3490,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					if (i < NUMMOBJTYPES && i >= 0)
 					{
 						if (i < (MT_FIRSTFREESLOT+freeslotusage[1][1]))
-							majormods = true; // affecting something earlier than the first freeslot allocated in this .wad? DENIED
+							G_SetGameModified(multiplayer, true); // affecting something earlier than the first freeslot allocated in this .wad? DENIED
 						readthing(f, i);
 					}
 					else
@@ -3505,7 +3503,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 /*				else if (fastcmp(word, "ANIMTEX"))
 				{
 					readAnimTex(f, i);
-					//majormods = true;
+					//G_SetGameModified(multiplayer, true);
 				}*/
 				else if (fastcmp(word, "LIGHT"))
 				{
@@ -3519,7 +3517,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//majormods = true;
+					//G_SetGameModified(multiplayer, true);
 #endif
 				}
 				else if (fastcmp(word, "SPRITE"))
@@ -3535,7 +3533,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//majormods = true;
+					//G_SetGameModified(multiplayer, true);
 #endif
 				}
 				else if (fastcmp(word, "LEVEL"))
@@ -3550,7 +3548,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					if (i > 0 && i <= NUMMAPS)
 					{
 						if (mapheaderinfo[i])
-							majormods = true; // only mark as a major mod if it replaces an already-existing mapheaderinfo
+							G_SetGameModified(multiplayer, true); // only mark as a major mod if it replaces an already-existing mapheaderinfo
 						readlevelheader(f, i);
 					}
 					else
@@ -3570,7 +3568,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//majormods = true; -- might have to reconsider in a future update
+					//G_SetGameModified(multiplayer, true); -- might have to reconsider in a future update
 				}
 				else if (fastcmp(word, "FRAME") || fastcmp(word, "STATE"))
 				{
@@ -3579,7 +3577,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					if (i < NUMSTATES && i >= 0)
 					{
 						if (i < (S_FIRSTFREESLOT+freeslotusage[0][1]))
-							majormods = true; // affecting something earlier than the first freeslot allocated in this .wad? DENIED
+							G_SetGameModified(multiplayer, true); // affecting something earlier than the first freeslot allocated in this .wad? DENIED
 						readframe(f, i);
 					}
 					else
@@ -3610,7 +3608,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					else
 						deh_warning("pointer (Frame %d) : missing ')'", i);
-					majormods = true;
+					G_SetGameModified(multiplayer, true);
 				}*/
 				else if (fastcmp(word, "SOUND"))
 				{
@@ -3624,7 +3622,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//majormods = true; -- ...this won't bite me in the ass later, will it?
+					//G_SetGameModified(multiplayer, true); -- ...this won't bite me in the ass later, will it?
 				}
 /*				else if (fastcmp(word, "SPRITE"))
 				{
@@ -3645,7 +3643,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					else
 						deh_warning("Sprite %d doesn't exist",i);
-					//majormods = true;
+					//G_SetGameModified(multiplayer, true);
 				}*/
 				else if (fastcmp(word, "HUDITEM"))
 				{
@@ -3659,11 +3657,11 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//majormods = true;
+					//G_SetGameModified(multiplayer, true);
 				}
 				else if (fastcmp(word, "EMBLEM"))
 				{
-					if (!gamedataadded)
+					if (!(refreshdirmenu & REFRESHDIR_GAMEDATA))
 					{
 						deh_warning("You must define a custom gamedata to use \"%s\"", word);
 						ignorelines(f);
@@ -3673,7 +3671,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						if (numemblems < i)
 							numemblems = i;
 						reademblemdata(f, i);
-						majormods = true;
+						G_SetGameModified(multiplayer, true);
 					}
 					else
 					{
@@ -3684,7 +3682,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 				}
 				else if (fastcmp(word, "EXTRAEMBLEM"))
 				{
-					if (!gamedataadded)
+					if (!(refreshdirmenu & REFRESHDIR_GAMEDATA))
 					{
 						deh_warning("You must define a custom gamedata to use \"%s\"", word);
 						ignorelines(f);
@@ -3694,7 +3692,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						if (numextraemblems < i)
 							numextraemblems = i;
 						readextraemblemdata(f, i);
-						majormods = true;
+						G_SetGameModified(multiplayer, true);
 					}
 					else
 					{
@@ -3705,7 +3703,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 				}
 				else if (fastcmp(word, "UNLOCKABLE"))
 				{
-					if (!gamedataadded)
+					if (!(refreshdirmenu & REFRESHDIR_GAMEDATA))
 					{
 						deh_warning("You must define a custom gamedata to use \"%s\"", word);
 						ignorelines(f);
@@ -3713,7 +3711,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					else if (i > 0 && i <= MAXUNLOCKABLES)
 					{
 						readunlockable(f, i - 1);
-						majormods = true;
+						G_SetGameModified(multiplayer, true);
 					}
 					else
 					{
@@ -3724,7 +3722,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 				}
 				else if (fastcmp(word, "CONDITIONSET"))
 				{
-					if (!gamedataadded)
+					if (!(refreshdirmenu & REFRESHDIR_GAMEDATA))
 					{
 						deh_warning("You must define a custom gamedata to use \"%s\"", word);
 						ignorelines(f);
@@ -3732,7 +3730,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					else if (i > 0 && i <= MAXCONDITIONSETS)
 					{
 						readconditionset(f, (UINT8)i);
-						majormods = true;
+						G_SetGameModified(multiplayer, true);
 					}
 					else
 					{
@@ -3761,7 +3759,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 				{
 					boolean clearall = (fastcmp(word2, "ALL"));
 
-					if (!gamedataadded)
+					if (!(refreshdirmenu & REFRESHDIR_GAMEDATA))
 					{
 						deh_warning("You must define a custom gamedata to use \"%s\"", word);
 						continue;
@@ -3788,7 +3786,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					if (clearall || fastcmp(word2, "LEVELS"))
 						clear_levels();
 
-					majormods = true;
+					G_SetGameModified(multiplayer, true);
 				}
 				else
 					deh_warning("Unknown word: %s", word);
@@ -3800,8 +3798,8 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 			deh_warning("No word in this line: %s", s);
 	} // end while
 
-	if (gamedataadded)
-		G_LoadGameData();
+	/*if (gamedataadded) -- REFRESHDIR_GAMEDATA murdered this
+		G_LoadGameData();*/
 
 	dbg_line = -1;
 	if (deh_num_warning)
diff --git a/src/filesrch.h b/src/filesrch.h
index 4186271b..01a52848 100644
--- a/src/filesrch.h
+++ b/src/filesrch.h
@@ -88,7 +88,8 @@ typedef enum
 	REFRESHDIR_WARNING = 4,
 	REFRESHDIR_ERROR = 8,
 	REFRESHDIR_NOTLOADED = 16,
-	REFRESHDIR_MAX = 32
+	REFRESHDIR_MAX = 32,
+	REFRESHDIR_GAMEDATA = 64
 } refreshdir_enum;
 
 void closefilemenu(boolean validsize);
diff --git a/src/g_game.c b/src/g_game.c
index a1ca4e73..789c5cc5 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -16,6 +16,7 @@
 #include "d_main.h"
 #include "d_player.h"
 #include "f_finale.h"
+#include "filesrch.h" // for refreshdirmenu
 #include "p_setup.h"
 #include "p_saveg.h"
 #include "i_system.h"
@@ -753,16 +754,21 @@ void G_SetNightsRecords(void)
 }*/
 
 // for consistency among messages: this modifies the game and removes savemoddata.
-void G_SetGameModified(boolean silent)
+void G_SetGameModified(boolean silent, boolean major)
 {
-	if (modifiedgame && !savemoddata)
+	if ((majormods && modifiedgame && !savemoddata) || (refreshdirmenu & REFRESHDIR_GAMEDATA)) // new gamedata amnesty?
 		return;
 
 	modifiedgame = true;
 	savemoddata = false;
 
+	if (!major)
+		return;
+
+	majormods = true;
+
 	if (!silent)
-		CONS_Alert(CONS_NOTICE, M_GetText("Game must be restarted to record statistics.\n"));
+		CONS_Alert(CONS_NOTICE, M_GetText("Game must be restarted to play record attack.\n"));
 
 	// If in record attack recording, cancel it.
 	if (modeattacking)
diff --git a/src/g_game.h b/src/g_game.h
index 14dc12d0..793cfe95 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -227,7 +227,7 @@ boolean G_GetRetryFlag(void);
 void G_LoadGameData(void);
 void G_LoadGameSettings(void);
 
-void G_SetGameModified(boolean silent);
+void G_SetGameModified(boolean silent, boolean major);
 
 void G_SetGamestate(gamestate_t newstate);
 
diff --git a/src/lua_script.c b/src/lua_script.c
index d7c4a160..1f87d33e 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -213,7 +213,7 @@ void LUA_LoadLump(UINT16 wad, UINT16 lump)
 	LUA_LoadFile(&f, name); // actually load file!
 
 	// Okay, we've modified the game beyond the point of no return.
-	majormods = true;
+	G_SetGameModified(multiplayer, true);
 
 	free(name);
 	Z_Free(f.data);
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 499dac28..acb53b22 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -121,7 +121,7 @@ static UINT8 cheatf_devmode(void)
 	S_StartSound(0, sfx_itemup);
 
 	// Just unlock all the things and turn on -debug and console devmode.
-	G_SetGameModified(false);
+	G_SetGameModified(false, false); // might need to revist the latter later
 	for (i = 0; i < MAXUNLOCKABLES; i++)
 		unlockables[i].unlocked = true;
 	devparm = true;
@@ -295,7 +295,7 @@ void Command_CheatNoClip_f(void)
 	plyr->pflags ^= PF_NOCLIP;
 	CONS_Printf(M_GetText("No Clipping %s\n"), plyr->pflags & PF_NOCLIP ? M_GetText("On") : M_GetText("Off"));
 
-	G_SetGameModified(multiplayer);
+	G_SetGameModified(multiplayer, true);
 }
 
 void Command_CheatGod_f(void)
@@ -310,7 +310,7 @@ void Command_CheatGod_f(void)
 	plyr->pflags ^= PF_GODMODE;
 	CONS_Printf(M_GetText("Sissy Mode %s\n"), plyr->pflags & PF_GODMODE ? M_GetText("On") : M_GetText("Off"));
 
-	G_SetGameModified(multiplayer);
+	G_SetGameModified(multiplayer, true);
 }
 
 void Command_CheatNoTarget_f(void)
@@ -325,7 +325,7 @@ void Command_CheatNoTarget_f(void)
 	plyr->pflags ^= PF_INVIS;
 	CONS_Printf(M_GetText("SEP Field %s\n"), plyr->pflags & PF_INVIS ? M_GetText("On") : M_GetText("Off"));
 
-	G_SetGameModified(multiplayer);
+	G_SetGameModified(multiplayer, true);
 }
 
 void Command_Scale_f(void)
@@ -727,7 +727,7 @@ void Command_Devmode_f(void)
 		return;
 	}
 
-	G_SetGameModified(multiplayer);
+	G_SetGameModified(multiplayer, true);
 }
 
 /*void Command_Setrings_f(void)
@@ -1267,8 +1267,7 @@ void Command_ObjectPlace_f(void)
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_NOULTIMATE;
 
-	G_SetGameModified(multiplayer);
-	majormods = true;
+	G_SetGameModified(multiplayer, true);
 
 	// Entering objectplace?
 	if (!objectplacing)
diff --git a/src/m_menu.c b/src/m_menu.c
index e68027c6..b1d9ba09 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4555,7 +4555,7 @@ static boolean prevmajormods = false;
 
 static void M_AddonsClearName(INT32 choice)
 {
-	if (majormods == prevmajormods || savemoddata)
+	if (!majormods || prevmajormods)
 	{
 		CLEARNAME;
 	}
@@ -4568,10 +4568,14 @@ static boolean M_AddonsRefresh(void)
 	if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true))
 	{
 		UNEXIST;
+		CLEARNAME;
 		return true;
 	}
 
-	if ((refreshdirmenu & REFRESHDIR_ADDFILE) || (majormods != prevmajormods && !savemoddata))
+	if (!majormods && prevmajormods)
+		prevmajormods = false;
+
+	if ((refreshdirmenu & REFRESHDIR_ADDFILE) || (majormods && !prevmajormods))
 	{
 		char *message = NULL;
 
@@ -4588,7 +4592,7 @@ static boolean M_AddonsRefresh(void)
 			S_StartSound(NULL, sfx_s224);
 			message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings"));
 		}
-		else if (majormods != prevmajormods && !savemoddata)
+		else if (majormods && !prevmajormods && !savemoddata)
 		{
 			S_StartSound(NULL, sfx_s221);
 			message = va("%c%s\x80\nGameplay has now been modified.\nIf you want to play record attack mode, restart the game to clear existing add-ons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
@@ -5141,7 +5145,7 @@ static void M_GetAllEmeralds(INT32 choice)
 	emeralds = ((EMERALD7)*2)-1;
 	M_StartMessage(M_GetText("You now have all 7 emeralds.\nUse them wisely.\nWith great power comes great ring drain.\n"),NULL,MM_NOTHING);
 
-	G_SetGameModified(multiplayer);
+	G_SetGameModified(multiplayer, true);
 }
 
 static void M_DestroyRobotsResponse(INT32 ch)
@@ -5152,7 +5156,7 @@ static void M_DestroyRobotsResponse(INT32 ch)
 	// Destroy all robots
 	P_DestroyRobots();
 
-	G_SetGameModified(multiplayer);
+	G_SetGameModified(multiplayer, true);
 }
 
 static void M_DestroyRobots(INT32 choice)
diff --git a/src/p_setup.c b/src/p_setup.c
index 0a59a2a9..fcb1ac78 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -3459,7 +3459,7 @@ boolean P_AddWadFile(const char *wadfilename)
 			if (num <= NUMMAPS && mapheaderinfo[num-1])
 			{
 				if (mapheaderinfo[num-1]->menuflags & LF2_EXISTSHACK)
-					majormods = true; // oops, double-defined - no record attack privileges for you
+					G_SetGameModified(multiplayer, true); // oops, double-defined - no record attack privileges for you
 				mapheaderinfo[num-1]->menuflags |= LF2_EXISTSHACK;
 			}
 
@@ -3489,6 +3489,8 @@ boolean P_AddWadFile(const char *wadfilename)
 			SendNetXCmd(XD_EXITLEVEL, NULL, 0);
 	}
 
+	refreshdirmenu &= ~REFRESHDIR_GAMEDATA; // Under usual circumstances we'd wait for REFRESHDIR_GAMEDATA to disappear the next frame, but it's a bit too dangerous for that...
+
 	return true;
 }
 
diff --git a/src/w_wad.c b/src/w_wad.c
index 58a65191..efa09ce4 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -34,6 +34,7 @@
 #include "z_zone.h"
 #include "fastcmp.h"
 
+#include "g_game.h" // G_LoadGameData
 #include "filesrch.h"
 
 #include "i_video.h" // rendermode
@@ -799,6 +800,8 @@ UINT16 W_InitFile(const char *filename)
 		break;
 	}
 
+	if (refreshdirmenu & REFRESHDIR_GAMEDATA)
+		G_LoadGameData();
 	DEH_UpdateMaxFreeslots();
 
 	W_InvalidateLumpnumCache();

From dda94e4498321a2c5e70da0c51d52f706a361677 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Thu, 17 Jan 2019 22:12:59 +0000
Subject: [PATCH 13/73] Remove irrelevant attempts at majormod setting for SOC
 events which explicitly can only happen if a gamedata is created.

---
 src/dehacked.c | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 4edeb987..7820928d 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3671,7 +3671,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						if (numemblems < i)
 							numemblems = i;
 						reademblemdata(f, i);
-						G_SetGameModified(multiplayer, true);
 					}
 					else
 					{
@@ -3692,7 +3691,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						if (numextraemblems < i)
 							numextraemblems = i;
 						readextraemblemdata(f, i);
-						G_SetGameModified(multiplayer, true);
 					}
 					else
 					{
@@ -3709,10 +3707,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					else if (i > 0 && i <= MAXUNLOCKABLES)
-					{
 						readunlockable(f, i - 1);
-						G_SetGameModified(multiplayer, true);
-					}
 					else
 					{
 						deh_warning("Unlockable number %d out of range (1 - %d)", i, MAXUNLOCKABLES);
@@ -3728,10 +3723,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					else if (i > 0 && i <= MAXCONDITIONSETS)
-					{
 						readconditionset(f, (UINT8)i);
-						G_SetGameModified(multiplayer, true);
-					}
 					else
 					{
 						deh_warning("Condition set number %d out of range (1 - %d)", i, MAXCONDITIONSETS);
@@ -3785,8 +3777,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 
 					if (clearall || fastcmp(word2, "LEVELS"))
 						clear_levels();
-
-					G_SetGameModified(multiplayer, true);
 				}
 				else
 					deh_warning("Unknown word: %s", word);

From 15aafb00a9f3ddf3156778f903698ae7d32cb5a9 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Fri, 18 Jan 2019 13:01:40 +0000
Subject: [PATCH 14/73] Turns out modifiedgame was getting set during startup
 because of making it use G_SetGameModified. Uh, woops?

---
 src/d_main.c | 1 -
 src/g_game.c | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/d_main.c b/src/d_main.c
index fd85770c..5cf95f4b 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1253,7 +1253,6 @@ void D_SRB2Main(void)
 #endif //ifndef DEVELOP
 
 	mainwadstally = packetsizetally;
-	majormods = false;
 
 	cht_Init();
 
diff --git a/src/g_game.c b/src/g_game.c
index 789c5cc5..1b583e17 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -756,7 +756,7 @@ void G_SetNightsRecords(void)
 // for consistency among messages: this modifies the game and removes savemoddata.
 void G_SetGameModified(boolean silent, boolean major)
 {
-	if ((majormods && modifiedgame && !savemoddata) || (refreshdirmenu & REFRESHDIR_GAMEDATA)) // new gamedata amnesty?
+	if ((majormods && modifiedgame && !savemoddata) || !mainwads || (refreshdirmenu & REFRESHDIR_GAMEDATA)) // new gamedata amnesty?
 		return;
 
 	modifiedgame = true;

From 4b493b81a68b440f551598ce39394df4d874336d Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Fri, 18 Jan 2019 13:04:12 +0000
Subject: [PATCH 15/73] Add warning message when attempting to use the master
 server browser while `modifiedgame` is true.

(We assume that if you're using an IP address connection, you're more of a power user.)
---
 src/m_menu.c | 31 ++++++++++++++++++++++---------
 1 file changed, 22 insertions(+), 9 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index b1d9ba09..d0386887 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -274,14 +274,13 @@ static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef;
 #ifndef NONET
 static void M_StartServerMenu(INT32 choice);
 static void M_ConnectMenu(INT32 choice);
-#endif
-static void M_StartOfflineServerMenu(INT32 choice);
-static void M_StartServer(INT32 choice);
-#ifndef NONET
+static void M_ConnectMenuModChecks(INT32 choice);
 static void M_Refresh(INT32 choice);
 static void M_Connect(INT32 choice);
 static void M_ChooseRoom(INT32 choice);
 #endif
+static void M_StartOfflineServerMenu(INT32 choice);
+static void M_StartServer(INT32 choice);
 static void M_SetupMultiPlayer(INT32 choice);
 static void M_SetupMultiPlayer2(INT32 choice);
 static void M_SetupMultiPlayer3(INT32 choice);
@@ -969,11 +968,11 @@ static menuitem_t MP_MainMenu[] =
 
 	{IT_HEADER, NULL, "Join a game", NULL, 132-24},
 #ifndef NONET
-	{IT_STRING|IT_CALL,       NULL, "Internet server browser...",M_ConnectMenu,            142-24},
+	{IT_STRING|IT_CALL,       NULL, "Internet server browser...",M_ConnectMenuModChecks,   142-24},
 	{IT_STRING|IT_KEYHANDLER, NULL, "Specify IPv4 address:",     M_HandleConnectIP,        150-24},
 #else
-	{IT_GRAYEDOUT,            NULL, "Internet server browser...",M_ConnectMenu,            142-24},
-	{IT_GRAYEDOUT,            NULL, "Specify IPv4 address:",     M_HandleConnectIP,        150-24},
+	{IT_GRAYEDOUT,            NULL, "Internet server browser...",NULL,                     142-24},
+	{IT_GRAYEDOUT,            NULL, "Specify IPv4 address:",     NULL,                     150-24},
 #endif
 	//{IT_HEADER, NULL, "Player setup", NULL, 80},
 	//{IT_STRING|IT_CALL,       NULL, "Name, character, color...", M_SetupMultiPlayer,       90},
@@ -4583,7 +4582,7 @@ static boolean M_AddonsRefresh(void)
 		{
 			S_StartSound(NULL, sfx_s26d);
 			if (refreshdirmenu & REFRESHDIR_MAX)
-				message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nIf you want to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
+				message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nif you wish to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 			else
 				message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 		}
@@ -4595,7 +4594,7 @@ static boolean M_AddonsRefresh(void)
 		else if (majormods && !prevmajormods && !savemoddata)
 		{
 			S_StartSound(NULL, sfx_s221);
-			message = va("%c%s\x80\nGameplay has now been modified.\nIf you want to play record attack mode, restart the game to clear existing add-ons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
+			message = va("%c%s\x80\nGameplay has now been modified.\nif you wish to play record attack mode, restart the game to clear existing add-ons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 			prevmajormods = majormods;
 		}
 
@@ -7423,6 +7422,20 @@ static void M_ConnectMenu(INT32 choice)
 	M_Refresh(0);
 }
 
+static void M_ConnectMenuModChecks(INT32 choice)
+{
+	(void)choice;
+	// okay never mind we want to COMMUNICATE to the player pre-emptively instead of letting them try and then get confused when it doesn't work
+
+	if (modifiedgame)
+	{
+		M_StartMessage(M_GetText("Add-ons are currently loaded.\nYou will only be able to join a server if\nit has the same ones loaded in the same order.\nIf you wish to play on other servers,\nrestart the game to clear existing add-ons.\n(Press a key)\n"),M_ConnectMenu,MM_EVENTHANDLER);
+		return;
+	}
+
+	M_ConnectMenu(-1);
+}
+
 static UINT32 roomIds[NUM_LIST_ROOMS];
 
 static void M_RoomMenu(INT32 choice)

From d119c711e59fa345028a9a8e616dcb35c6802f5c Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Fri, 18 Jan 2019 14:11:59 +0000
Subject: [PATCH 16/73] Improve readability of server browser add-ons message.

---
 src/m_menu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index d0386887..9534a456 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -7429,7 +7429,7 @@ static void M_ConnectMenuModChecks(INT32 choice)
 
 	if (modifiedgame)
 	{
-		M_StartMessage(M_GetText("Add-ons are currently loaded.\nYou will only be able to join a server if\nit has the same ones loaded in the same order.\nIf you wish to play on other servers,\nrestart the game to clear existing add-ons.\n(Press a key)\n"),M_ConnectMenu,MM_EVENTHANDLER);
+		M_StartMessage(M_GetText("Add-ons are currently loaded.\n\nYou will only be able to join a server if\nit has the same ones loaded in the same order, which may be unlikely.\n\nIf you wish to play on other servers,\nrestart the game to clear existing add-ons.\n\n(Press a key)\n"),M_ConnectMenu,MM_EVENTHANDLER);
 		return;
 	}
 

From f8229b9dad37480ffff2be430cd7e88aacdd52c1 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Fri, 18 Jan 2019 14:53:43 +0000
Subject: [PATCH 17/73] whoopsie doodle, forgot gh wasn't allocated until after
 this section

---
 src/g_game.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index 1b583e17..327a96ba 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -6018,6 +6018,7 @@ void G_AddGhost(char *defdemoname)
 	UINT8 *buffer,*p;
 	mapthing_t *mthing;
 	UINT16 count, ghostversion;
+	skin_t *ghskin = &skins[0];
 
 	name[16] = '\0';
 	skin[16] = '\0';
@@ -6163,11 +6164,10 @@ void G_AddGhost(char *defdemoname)
 		return;
 	}
 
-	gh->oldmo.skin = &skins[0];
 	for (i = 0; i < numskins; i++)
 		if (!stricmp(skins[i].name,skin))
 		{
-			gh->oldmo.skin = &skins[i];
+			ghskin = &skins[i];
 			break;
 		}
 
@@ -6224,7 +6224,7 @@ void G_AddGhost(char *defdemoname)
 	gh->oldmo.z = gh->mo->z;
 
 	// Set skin
-	gh->mo->skin = gh->oldmo.skin;
+	gh->mo->skin = gh->oldmo.skin = ghskin;
 
 	// Set color
 	gh->mo->color = ((skin_t*)gh->mo->skin)->prefcolor;

From 1850123f8bf49ad4fabb11e71d8ec5724b5e5fb7 Mon Sep 17 00:00:00 2001
From: Latapostrophe <hyperclassic3@gmail.com>
Date: Sun, 20 Jan 2019 18:50:07 +0100
Subject: [PATCH 18/73] Prevent the use of respawn to cheese SPB and other
 items

---
 src/d_netcmd.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 41f88ab9..8272b75d 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2489,6 +2489,12 @@ static void Command_Respawn(void)
 	UINT8 *cp = buf;
 
 	WRITEINT32(cp, consoleplayer);
+	
+	if (players[consoleplayer].kartstuff[k_spinouttimer])	// KART: Nice try, but no, you won't be cheesing spb anymore.
+	{	
+		CONS_Printf(M_GetText("Cannot use this while hurt.\n"));
+		return;
+	}
 
 	if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING))
 	{

From e7d730e3b230d9072fc79543b4b1e725ab31298e Mon Sep 17 00:00:00 2001
From: Latapostrophe <hyperclassic3@gmail.com>
Date: Sun, 20 Jan 2019 18:51:45 +0100
Subject: [PATCH 19/73] prevent the use of respawn to cheese items like SPB

---
 src/d_netcmd.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 8272b75d..8bedb526 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2489,9 +2489,9 @@ static void Command_Respawn(void)
 	UINT8 *cp = buf;
 
 	WRITEINT32(cp, consoleplayer);
-	
+
 	if (players[consoleplayer].kartstuff[k_spinouttimer])	// KART: Nice try, but no, you won't be cheesing spb anymore.
-	{	
+	{
 		CONS_Printf(M_GetText("Cannot use this while hurt.\n"));
 		return;
 	}

From fdbf750f48eb1849d54d49fc53ee9c5d55ebe73d Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 21 Jan 2019 21:48:52 +0000
Subject: [PATCH 20/73] Fix incorrect condition for setting savemoddata to
 false.

---
 src/g_game.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/g_game.c b/src/g_game.c
index 327a96ba..60fd56cb 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -760,11 +760,11 @@ void G_SetGameModified(boolean silent, boolean major)
 		return;
 
 	modifiedgame = true;
-	savemoddata = false;
 
 	if (!major)
 		return;
 
+	savemoddata = false;
 	majormods = true;
 
 	if (!silent)

From e9d95f07c184f637078a664a39342718b2075ec5 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 21 Jan 2019 21:49:39 +0000
Subject: [PATCH 21/73] Fix everything Sal wanted changed.

---
 src/d_netcmd.c | 10 ++++++----
 src/dehacked.c |  5 ++++-
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 244e3058..617ff78f 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -4916,11 +4916,13 @@ static void Fishcake_OnChange(void)
 static void Command_Isgamemodified_f(void)
 {
 	if (savemoddata)
-		CONS_Printf(M_GetText("The game is modified, but you can save medal and record data in this add-on.\n"));
-	else if (/*modifiedgame*/ majormods)
-		CONS_Printf(M_GetText("Major add-ons have been loaded, so you cannot play record attack.\n"));
+		CONS_Printf("The game has been modified with an add-on with its own save data, so you can play Record Attack and earn medals.\n");
+	else if (majormods)
+		CONS_Printf("The game has been modified with major add-ons, so you cannot play Record Attack.\n");
+	else if (modifiedgame)
+		CONS_Printf("The game has been modified with only minor add-ons. You can play Record Attack, earn medals and unlock extras.\n");
 	else
-		CONS_Printf(M_GetText("No major add-ons are loaded. You can play record attack, earn medals and unlock extras.\n"));
+		CONS_Printf("The game has not been modified. You can play Record Attack, earn medals and unlock extras.\n");
 }
 
 static void Command_Cheats_f(void)
diff --git a/src/dehacked.c b/src/dehacked.c
index 7820928d..8bb2d567 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -9773,7 +9773,10 @@ static inline int lib_getenum(lua_State *L)
 		lua_pushboolean(L, devparm);
 		return 1;
 	} else if (fastcmp(word,"modifiedgame")) {
-		lua_pushboolean(L, /*modifiedgame*/ majormods && !savemoddata);
+		lua_pushboolean(L, modifiedgame && !savemoddata);
+		return 1;
+	} else if (fastcmp(word,"majormods")) {
+		lua_pushboolean(L, majormods);
 		return 1;
 	} else if (fastcmp(word,"menuactive")) {
 		lua_pushboolean(L, menuactive);

From 72133b74985f9862669ab8dab46ed80cd7b45343 Mon Sep 17 00:00:00 2001
From: Latapostrophe <hyperclassic3@gmail.com>
Date: Sat, 26 Jan 2019 16:55:26 +0100
Subject: [PATCH 22/73] No more ghetto check, and some more descriptive
 comments on the Hook

---
 src/g_game.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index d93a2e9f..58787495 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1612,10 +1612,18 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 		}
 	}
 
-	// Lua: Allow this hook to overwrite ticcmd.
-	// Be aware that you can't actually write anything inside the player with this hook, only cmd may be altered.
+	/* 	Lua: Allow this hook to overwrite ticcmd.
+		We check if we're actually in a level because for some reason this Hook would run in menus and on the titlescreen otherwise.
+		Be aware that within this hook, nothing but this player's cmd can be edited (otherwise we'd run in some pretty bad synching problems since this is clientsided, or something)
+
+		Possible usages for this are:
+			-Forcing the player to perform an action, which could otherwise require terrible, terrible hacking to replicate.
+			-Preventing the player to perform an action, which would ALSO require some weirdo hacks.
+			-Making some galaxy brain autopilot Lua if you're a masochist
+			-Making a Mario Kart 8 Deluxe tier baby mode that steers you away from walls and whatnot. You know what, do what you want!
+	*/
 #ifdef HAVE_BLUA
-	if (playeringame[consoleplayer])	// safe to assume we can't do anything if consoleplayer isn't in the game.
+	if (gamestate == GS_LEVEL)
 		LUAh_PlayerCmd(player, cmd);
 #endif
 

From 9028783190380b1084e6cbc63fbcd3285a9fec2a Mon Sep 17 00:00:00 2001
From: Latapostrophe <hyperclassic3@gmail.com>
Date: Sat, 26 Jan 2019 17:40:05 +0100
Subject: [PATCH 23/73] Only prevent respawn if spun out in midair and added a
 Got_Respawn check to kick cheaters

---
 src/d_netcmd.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 8bedb526..41886d70 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2490,18 +2490,18 @@ static void Command_Respawn(void)
 
 	WRITEINT32(cp, consoleplayer);
 
-	if (players[consoleplayer].kartstuff[k_spinouttimer])	// KART: Nice try, but no, you won't be cheesing spb anymore.
-	{
-		CONS_Printf(M_GetText("Cannot use this while hurt.\n"));
-		return;
-	}
-
 	if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING))
 	{
 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
 		return;
 	}
 
+	if (players[consoleplayer].kartstuff[k_spinouttimer] && !P_IsObjectOnGround(players[consoleplayer].mo))	// KART: Nice try, but no, you won't be cheesing spb anymore.
+	{
+		CONS_Printf(M_GetText("Cannot use this while hurt.\n"));
+		return;
+	}
+
 	/*if (!G_RaceGametype()) // srb2kart: not necessary, respawning makes you lose a bumper in battle, so it's not desirable to use as a way to escape a hit
 	{
 		CONS_Printf(M_GetText("You may only use this in co-op, race, and competition!\n"));
@@ -2523,8 +2523,8 @@ static void Got_Respawn(UINT8 **cp, INT32 playernum)
 {
 	INT32 respawnplayer = READINT32(*cp);
 
-	// You can't respawn someone else.  Nice try, there.
-	if (respawnplayer != playernum) // srb2kart: "|| (!G_RaceGametype())"
+	// You can't respawn someone else or cheat your way by removing the send checks above :)  Nice try, there.
+	if ((respawnplayer != playernum) || (players[respawnplayer].mo && players[respawnplayer].kartstuff[k_spinouttimer] && !P_IsObjectOnGround(players[respawnplayer].mo))) // srb2kart: "|| (!G_RaceGametype())"
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal respawn command received from %s\n"), player_names[playernum]);
 		if (server)

From 1a21c5efbec1328b28c51ccb0637124275336008 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sat, 26 Jan 2019 16:58:45 +0000
Subject: [PATCH 24/73] Code cleanup as requested by Sal and Sryder. *
 majormods and savemoddata cannot coexist as true values, so going through and
 making situations that involve both only reference one. * Clean up comments
 in `dehacked.c`.

---
 src/d_netcmd.c |  2 +-
 src/dehacked.c | 22 +++++++++++-----------
 src/g_game.c   |  2 +-
 src/m_cond.c   |  3 +--
 src/m_menu.c   |  4 ++--
 src/p_setup.c  |  4 ++--
 src/y_inter.c  |  2 +-
 7 files changed, 19 insertions(+), 20 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 1da2c523..67f8c3a2 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2209,7 +2209,7 @@ static void Command_Map_f(void)
 		return;
 	}
 
-	if (!(netgame || multiplayer) && (!majormods || savemoddata))
+	if (!(netgame || multiplayer) && !majormods)
 	{
 		if (COM_CheckParm("-force"))
 		{
diff --git a/src/dehacked.c b/src/dehacked.c
index 83550282..0ad67ff0 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3425,7 +3425,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 			if (fastcmp(word, "FREESLOT"))
 			{
 				readfreeslots(f);
-				//G_SetGameModified(multiplayer, true);
+				// This is not a major mod.
 				continue;
 			}
 			else if (fastcmp(word, "MAINCFG"))
@@ -3439,7 +3439,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 			{
 				readwipes(f);
 				DEH_WriteUndoline(word, "", UNDO_HEADER);
-				//G_SetGameModified(multiplayer, true);
+				// This is not a major mod.
 				continue;
 			}
 			word2 = strtok(NULL, " ");
@@ -3460,7 +3460,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					ignorelines(f);
 				}
 				DEH_WriteUndoline(word, word2, UNDO_HEADER);
-				//G_SetGameModified(multiplayer, true);
+				// This is not a major mod.
 				continue;
 			}
 			if (word2)
@@ -3474,14 +3474,14 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					// Read texture from spec file.
 					readtexture(f, word2);
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//G_SetGameModified(multiplayer, true);
+					// This is not a major mod.
 				}
 				else if (fastcmp(word, "PATCH"))
 				{
 					// Read patch from spec file.
 					readpatch(f, word2, wad);
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//G_SetGameModified(multiplayer, true);
+					// This is not a major mod.
 				}
 				else if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
 				{
@@ -3503,7 +3503,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 /*				else if (fastcmp(word, "ANIMTEX"))
 				{
 					readAnimTex(f, i);
-					//G_SetGameModified(multiplayer, true);
+					// This is not a major mod.
 				}*/
 				else if (fastcmp(word, "LIGHT"))
 				{
@@ -3517,7 +3517,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//G_SetGameModified(multiplayer, true);
+					// This is not a major mod.
 #endif
 				}
 				else if (fastcmp(word, "SPRITE"))
@@ -3533,7 +3533,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//G_SetGameModified(multiplayer, true);
+					// This is not a major mod.
 #endif
 				}
 				else if (fastcmp(word, "LEVEL"))
@@ -3622,7 +3622,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//G_SetGameModified(multiplayer, true); -- ...this won't bite me in the ass later, will it?
+					// This is not a major mod.
 				}
 /*				else if (fastcmp(word, "SPRITE"))
 				{
@@ -3643,7 +3643,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					else
 						deh_warning("Sprite %d doesn't exist",i);
-					//G_SetGameModified(multiplayer, true);
+					// This is not a major mod.
 				}*/
 				else if (fastcmp(word, "HUDITEM"))
 				{
@@ -3657,7 +3657,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-					//G_SetGameModified(multiplayer, true);
+					// This is not a major mod.
 				}
 				else if (fastcmp(word, "EMBLEM"))
 				{
diff --git a/src/g_game.c b/src/g_game.c
index e347cf7c..1e0744f4 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -756,7 +756,7 @@ void G_SetNightsRecords(void)
 // for consistency among messages: this modifies the game and removes savemoddata.
 void G_SetGameModified(boolean silent, boolean major)
 {
-	if ((majormods && modifiedgame && !savemoddata) || !mainwads || (refreshdirmenu & REFRESHDIR_GAMEDATA)) // new gamedata amnesty?
+	if ((majormods && modifiedgame) || !mainwads || (refreshdirmenu & REFRESHDIR_GAMEDATA)) // new gamedata amnesty?
 		return;
 
 	modifiedgame = true;
diff --git a/src/m_cond.c b/src/m_cond.c
index b0e49a68..b777e7d2 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -385,8 +385,7 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(boolean force)
 	char cechoText[992] = "";
 	UINT8 cechoLines = 0;
 
-	if (/*modifiedgame*/ majormods && !savemoddata
-		&& !force) // SRB2Kart: for enabling unlocks online in modified servers
+	if (majormods && !force) // SRB2Kart: for enabling unlocks online in modified servers
 		return false;
 
 	M_CheckUnlockConditions();
diff --git a/src/m_menu.c b/src/m_menu.c
index 1edb1cdf..2ea7234c 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -2738,7 +2738,7 @@ boolean M_Responder(event_t *ev)
 				 || (currentMenu->menuitems[itemOn].status & IT_TYPE)==IT_SUBMENU)
                  && (currentMenu->menuitems[itemOn].status & IT_CALLTYPE))
 				{
-					if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && /*modifiedgame*/ majormods && !savemoddata)
+					if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && majormods)
 					{
 						S_StartSound(NULL, sfx_menu1);
 						M_StartMessage(M_GetText("This cannot be done with complex add-ons\nor in a cheated game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
@@ -4567,7 +4567,7 @@ static boolean M_AddonsRefresh(void)
 			S_StartSound(NULL, sfx_s224);
 			message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings"));
 		}
-		else if (majormods && !prevmajormods && !savemoddata)
+		else if (majormods && !prevmajormods)
 		{
 			S_StartSound(NULL, sfx_s221);
 			message = va("%c%s\x80\nGameplay has now been modified.\nif you wish to play record attack mode, restart the game to clear existing add-ons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
diff --git a/src/p_setup.c b/src/p_setup.c
index fcb1ac78..912791cf 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1120,7 +1120,7 @@ static inline void P_SpawnEmblems(void)
 static void P_SpawnSecretItems(boolean loademblems)
 {
 	// Now let's spawn those funky emblem things! Tails 12-08-2002
-	if (netgame || multiplayer || (/*modifiedgame*/ majormods && !savemoddata)) // No cheating!!
+	if (netgame || multiplayer || majormods) // No cheating!!
 		return;
 
 	if (loademblems)
@@ -3272,7 +3272,7 @@ boolean P_SetupLevel(boolean skipprecip)
 	nextmapoverride = 0;
 	skipstats = false;
 
-	if (!(netgame || multiplayer) && (/*!modifiedgame*/ !majormods || savemoddata))
+	if (!(netgame || multiplayer) && !majormods)
 		mapvisited[gamemap-1] |= MV_VISITED;
 
 	levelloading = false;
diff --git a/src/y_inter.c b/src/y_inter.c
index 046d6d6d..795f7f1b 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -786,7 +786,7 @@ void Y_StartIntermission(void)
 		}
 		case int_race: // (time-only race)
 		{
-			if ((/*!modifiedgame*/ !majormods || savemoddata) && !multiplayer && !demoplayback) // remove this once we have a proper time attack screen
+			if (!majormods && !multiplayer && !demoplayback) // remove this once we have a proper time attack screen
 			{
 				// Update visitation flags
 				mapvisited[gamemap-1] |= MV_BEATEN;

From f8fd2c5190962f424fc0db84fabbc22f28104038 Mon Sep 17 00:00:00 2001
From: Latapostrophe <hyperclassic3@gmail.com>
Date: Sat, 26 Jan 2019 19:40:12 +0100
Subject: [PATCH 25/73] Changed condition to being mid-air, changed print and
 changed the kick to a return

---
 src/d_netcmd.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 41886d70..a015b8e7 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2496,9 +2496,9 @@ static void Command_Respawn(void)
 		return;
 	}
 
-	if (players[consoleplayer].kartstuff[k_spinouttimer] && !P_IsObjectOnGround(players[consoleplayer].mo))	// KART: Nice try, but no, you won't be cheesing spb anymore.
+	if (players[consoleplayer].mo && !P_IsObjectOnGround(players[consoleplayer].mo))	// KART: Nice try, but no, you won't be cheesing spb anymore.
 	{
-		CONS_Printf(M_GetText("Cannot use this while hurt.\n"));
+		CONS_Printf(M_GetText("You must be on the floor to use this.\n"));
 		return;
 	}
 
@@ -2523,8 +2523,8 @@ static void Got_Respawn(UINT8 **cp, INT32 playernum)
 {
 	INT32 respawnplayer = READINT32(*cp);
 
-	// You can't respawn someone else or cheat your way by removing the send checks above :)  Nice try, there.
-	if ((respawnplayer != playernum) || (players[respawnplayer].mo && players[respawnplayer].kartstuff[k_spinouttimer] && !P_IsObjectOnGround(players[respawnplayer].mo))) // srb2kart: "|| (!G_RaceGametype())"
+	// You can't respawn someone else. Nice try, there.
+	if (respawnplayer != playernum) // srb2kart: "|| (!G_RaceGametype())"
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal respawn command received from %s\n"), player_names[playernum]);
 		if (server)
@@ -2538,6 +2538,10 @@ static void Got_Respawn(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
+	// incase the above checks were modified to allow sending a respawn on these occasions:
+	if (players[respawnplayer].mo && !P_IsObjectOnGround(players[respawnplayer].mo))
+		return;
+
 	if (players[respawnplayer].mo)
 		P_DamageMobj(players[respawnplayer].mo, NULL, NULL, 10000);
 }

From ce09566e119525e6ee4fc7cef920d202a71e46e9 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sat, 26 Jan 2019 19:12:53 +0000
Subject: [PATCH 26/73] Do a bit more cleanup. Notably, prevent skins setting a
 bunch of things in preperation for this branch hitting the mainstream.

Also, make SF_RUNONWATER set off majormods. I was under strong pressure to remove it and almost did but honestly it's kind of endearing and I think like one character in Releases uses it..?
---
 src/p_map.c    | 235 +------------------------------------------------
 src/p_mobj.c   |   3 +-
 src/p_spec.c   |  19 ++--
 src/p_user.c   |  68 --------------
 src/r_things.c |  29 +++---
 5 files changed, 24 insertions(+), 330 deletions(-)

diff --git a/src/p_map.c b/src/p_map.c
index c307e572..051a1e6f 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -3231,129 +3231,6 @@ isblocking:
 	return false; // stop
 }
 
-//
-// P_IsClimbingValid
-//
-// Unlike P_DoClimbing, don't use when up against a one-sided linedef.
-//
-static boolean P_IsClimbingValid(player_t *player, angle_t angle)
-{
-	fixed_t platx, platy;
-	subsector_t *glidesector;
-	fixed_t floorz, ceilingz;
-
-	platx = P_ReturnThrustX(player->mo, angle, player->mo->radius + FixedMul(8*FRACUNIT, player->mo->scale));
-	platy = P_ReturnThrustY(player->mo, angle, player->mo->radius + FixedMul(8*FRACUNIT, player->mo->scale));
-
-	glidesector = R_PointInSubsector(player->mo->x + platx, player->mo->y + platy);
-
-#ifdef ESLOPE
-	floorz = glidesector->sector->f_slope ? P_GetZAt(glidesector->sector->f_slope, player->mo->x, player->mo->y) : glidesector->sector->floorheight;
-	ceilingz = glidesector->sector->c_slope ? P_GetZAt(glidesector->sector->c_slope, player->mo->x, player->mo->y) : glidesector->sector->ceilingheight;
-#else
-	floorz = glidesector->sector->floorheight;
-	ceilingz = glidesector->sector->ceilingheight;
-#endif
-
-	if (glidesector->sector != player->mo->subsector->sector)
-	{
-		boolean floorclimb = false;
-		fixed_t topheight, bottomheight;
-
-		if (glidesector->sector->ffloors)
-		{
-			ffloor_t *rover;
-			for (rover = glidesector->sector->ffloors; rover; rover = rover->next)
-			{
-				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER))
-					continue;
-
-				topheight = *rover->topheight;
-				bottomheight = *rover->bottomheight;
-
-#ifdef ESLOPE
-				if (*rover->t_slope)
-					topheight = P_GetZAt(*rover->t_slope, player->mo->x, player->mo->y);
-				if (*rover->b_slope)
-					bottomheight = P_GetZAt(*rover->b_slope, player->mo->x, player->mo->y);
-#endif
-
-				floorclimb = true;
-
-				if (player->mo->eflags & MFE_VERTICALFLIP)
-				{
-					if ((topheight < player->mo->z + player->mo->height) && ((player->mo->z + player->mo->height + player->mo->momz) < topheight))
-					{
-						floorclimb = true;
-					}
-					if (topheight < player->mo->z) // Waaaay below the ledge.
-					{
-						floorclimb = false;
-					}
-					if (bottomheight > player->mo->z + player->mo->height - FixedMul(16*FRACUNIT,player->mo->scale))
-					{
-						floorclimb = false;
-					}
-				}
-				else
-				{
-					if ((bottomheight > player->mo->z) && ((player->mo->z - player->mo->momz) > bottomheight))
-					{
-						floorclimb = true;
-					}
-					if (bottomheight > player->mo->z + player->mo->height) // Waaaay below the ledge.
-					{
-						floorclimb = false;
-					}
-					if (topheight < player->mo->z + FixedMul(16*FRACUNIT,player->mo->scale))
-					{
-						floorclimb = false;
-					}
-				}
-
-				if (floorclimb)
-					break;
-			}
-		}
-
-		if (player->mo->eflags & MFE_VERTICALFLIP)
-		{
-			if ((floorz <= player->mo->z + player->mo->height)
-				&& ((player->mo->z + player->mo->height - player->mo->momz) <= floorz))
-				floorclimb = true;
-
-			if ((floorz > player->mo->z)
-				&& glidesector->sector->floorpic == skyflatnum)
-				return false;
-
-			if ((player->mo->z + player->mo->height - FixedMul(16*FRACUNIT,player->mo->scale) > ceilingz)
-				|| (player->mo->z + player->mo->height <= floorz))
-				floorclimb = true;
-		}
-		else
-		{
-			if ((ceilingz >= player->mo->z)
-				&& ((player->mo->z - player->mo->momz) >= ceilingz))
-				floorclimb = true;
-
-			if ((ceilingz < player->mo->z+player->mo->height)
-				&& glidesector->sector->ceilingpic == skyflatnum)
-				return false;
-
-			if ((player->mo->z + FixedMul(16*FRACUNIT,player->mo->scale) < ceilingz)
-				|| (player->mo->z >= ceilingz))
-				floorclimb = true;
-		}
-
-		if (!floorclimb)
-			return false;
-
-		return true;
-	}
-
-	return false;
-}
-
 //
 // PTR_SlideTraverse
 //
@@ -3407,117 +3284,7 @@ isblocking:
 			P_ProcessSpecialSector(slidemo->player, slidemo->subsector->sector, li->polyobj->lines[0]->backsector);
 	}
 
-	if (slidemo->player && (slidemo->player->pflags & PF_GLIDING || slidemo->player->climbing)
-		&& slidemo->player->charability == CA_GLIDEANDCLIMB)
-	{
-		line_t *checkline = li;
-		sector_t *checksector;
-		ffloor_t *rover;
-		fixed_t topheight, bottomheight;
-		boolean fofline = false;
-		INT32 side = P_PointOnLineSide(slidemo->x, slidemo->y, li);
-
-		if (!side && li->backsector)
-			checksector = li->backsector;
-		else
-			checksector = li->frontsector;
-
-		if (checksector->ffloors)
-		{
-			for (rover = checksector->ffloors; rover; rover = rover->next)
-			{
-				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || (rover->flags & FF_BUSTUP))
-					continue;
-
-				topheight = *rover->topheight;
-				bottomheight = *rover->bottomheight;
-
-#ifdef ESLOPE
-				if (*rover->t_slope)
-					topheight = P_GetZAt(*rover->t_slope, slidemo->x, slidemo->y);
-				if (*rover->b_slope)
-					bottomheight = P_GetZAt(*rover->b_slope, slidemo->x, slidemo->y);
-#endif
-
-				if (topheight < slidemo->z)
-					continue;
-
-				if (bottomheight > slidemo->z + slidemo->height)
-					continue;
-
-				// Got this far, so I guess it's climbable. // TODO: Climbing check, also, better method to do this?
-				if (rover->master->flags & ML_TFERLINE)
-				{
-					size_t linenum = li-checksector->lines[0];
-					checkline = rover->master->frontsector->lines[0] + linenum;
-					fofline = true;
-				}
-
-				break;
-			}
-		}
-
-		// see about climbing on the wall
-		if (!(checkline->flags & ML_NOCLIMB))
-		{
-			boolean canclimb;
-			angle_t climbangle, climbline;
-			INT32 whichside = P_PointOnLineSide(slidemo->x, slidemo->y, li);
-
-			climbangle = climbline = R_PointToAngle2(li->v1->x, li->v1->y, li->v2->x, li->v2->y);
-
-			if (whichside) // on second side?
-				climbline += ANGLE_180;
-
-			climbangle += (ANGLE_90 * (whichside ? -1 : 1));
-
-			canclimb = (li->backsector ? P_IsClimbingValid(slidemo->player, climbangle) : true);
-
-			if (((!slidemo->player->climbing && abs((signed)(slidemo->angle - ANGLE_90 - climbline)) < ANGLE_45)
-			|| (slidemo->player->climbing == 1 && abs((signed)(slidemo->angle - climbline)) < ANGLE_135))
-			&& canclimb)
-			{
-				slidemo->angle = climbangle;
-				if (!demoplayback || P_AnalogMove(slidemo->player))
-				{
-					if (slidemo->player == &players[consoleplayer])
-						localangle = slidemo->angle;
-					else if (slidemo->player == &players[secondarydisplayplayer])
-						localangle2 = slidemo->angle;
-					else if (slidemo->player == &players[thirddisplayplayer])
-						localangle3 = slidemo->angle;
-					else if (slidemo->player == &players[fourthdisplayplayer])
-						localangle4 = slidemo->angle;
-				}
-
-				if (!slidemo->player->climbing)
-				{
-					S_StartSound(slidemo->player->mo, sfx_s3k4a);
-					slidemo->player->climbing = 5;
-				}
-
-				slidemo->player->pflags &= ~(PF_GLIDING|PF_SPINNING|PF_JUMPED|PF_THOKKED);
-				slidemo->player->glidetime = 0;
-				slidemo->player->secondjump = 0;
-
-				if (slidemo->player->climbing > 1)
-					slidemo->momz = slidemo->momx = slidemo->momy = 0;
-
-				if (fofline)
-					whichside = 0;
-
-				if (!whichside)
-				{
-					slidemo->player->lastsidehit = checkline->sidenum[whichside];
-					slidemo->player->lastlinehit = (INT16)(checkline - lines);
-				}
-
-				P_Thrust(slidemo, slidemo->angle, FixedMul(5*FRACUNIT, slidemo->scale));
-			}
-		}
-	}
-
-	if (in->frac < bestslidefrac && (!slidemo->player || !slidemo->player->climbing))
+	if (in->frac < bestslidefrac)
 	{
 		secondslidefrac = bestslidefrac;
 		secondslideline = bestslideline;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index f71ff209..d380b97a 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3233,8 +3233,7 @@ boolean P_CanRunOnWater(player_t *player, ffloor_t *rover)
 #endif
 		*rover->topheight;
 
-	if (!(player->pflags & PF_NIGHTSMODE) && !player->homing
-		&& (((player->charability == CA_SWIM) || player->powers[pw_super] || player->charflags & SF_RUNONWATER) && player->mo->ceilingz-topheight >= player->mo->height)
+	if (((player->charflags & SF_RUNONWATER) && player->mo->ceilingz-topheight >= player->mo->height)
 		&& (rover->flags & FF_SWIMMABLE) && !(player->pflags & PF_SPINNING) && player->speed > FixedMul(player->runspeed, player->mo->scale)
 		&& !(player->pflags & PF_SLIDING)
 		&& abs(player->mo->z - topheight) < FixedMul(30*FRACUNIT, player->mo->scale))
diff --git a/src/p_spec.c b/src/p_spec.c
index ca4967ce..24f56c43 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -1758,12 +1758,12 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 
 	switch (specialtype)
 	{
-		case 305: // continuous
+		/*case 305: // continuous
 		case 306: // each time
 		case 307: // once
 			if (!(actor && actor->player && actor->player->charability == dist/10))
 				return false;
-			break;
+			break;*/
 		case 309: // continuous
 		case 310: // each time
 			// Only red team members can activate this.
@@ -3864,14 +3864,6 @@ DoneSection2:
 
 				P_InstaThrust(player->mo, player->mo->angle, linespeed);
 
-				/*if (GETSECSPECIAL(sector->special, 3) == 6 && (player->charability2 == CA2_SPINDASH)) // SRB2kart
-				{
-					if (!(player->pflags & PF_SPINNING))
-						player->pflags |= PF_SPINNING;
-
-					//P_SetPlayerMobjState(player->mo, S_PLAY_ATK1);
-				}*/
-
 				player->kartstuff[k_dashpadcooldown] = TICRATE/3;
 				player->kartstuff[k_drift] = 0;
 				player->kartstuff[k_driftcharge] = 0;
@@ -5781,7 +5773,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 				lines[i].special = 0;
 				continue;
 			}
-			/*else -- commented out because irrelevant to kart
+			/*else -- commented out because irrelevant to kart. keeping here because we can use these flags for something else now
 			{
 				if ((players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
 				|| (players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
@@ -7997,12 +7989,13 @@ static void P_SearchForDisableLinedefs(void)
 			}
 			else if ((lines[i].flags & ML_NETONLY) == ML_NETONLY)
 				continue; // Net-only never triggers in single player
-			else if (players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
+			// commented out because irrelevant to kart. keeping here because we can use these flags for something else now
+			/*else if (players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
 				continue;
 			else if (players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
 				continue;
 			else if (players[consoleplayer].charability == CA_GLIDEANDCLIMB && (lines[i].flags & ML_NOKNUX))
-				continue;
+				continue;*/
 
 			// Disable any linedef specials with our tag.
 			for (j = -1; (j = P_FindLineFromLineTag(&lines[i], j)) >= 0;)
diff --git a/src/p_user.c b/src/p_user.c
index d7423d80..ce411d2d 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -7323,74 +7323,6 @@ static void P_MovePlayer(player_t *player)
 	if (CheckForBustableBlocks)
 		P_CheckBustableBlocks(player);
 
-	// Special handling for
-	// gliding in 2D mode
-	if ((twodlevel || player->mo->flags2 & MF2_TWOD) && player->pflags & PF_GLIDING && player->charability == CA_GLIDEANDCLIMB
-		&& !(player->mo->flags & MF_NOCLIP))
-	{
-		msecnode_t *node; // only place it's being used in P_MovePlayer now
-		fixed_t oldx;
-		fixed_t oldy;
-		fixed_t floorz, ceilingz;
-
-		oldx = player->mo->x;
-		oldy = player->mo->y;
-
-		P_UnsetThingPosition(player->mo);
-		player->mo->x += player->mo->momx;
-		player->mo->y += player->mo->momy;
-		P_SetThingPosition(player->mo);
-
-		for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
-		{
-			if (!node->m_sector)
-				break;
-
-			if (node->m_sector->ffloors)
-			{
-				ffloor_t *rover;
-				fixed_t topheight, bottomheight;
-
-				for (rover = node->m_sector->ffloors; rover; rover = rover->next)
-				{
-					if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER))
-						continue;
-
-					topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
-					bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
-					if (topheight > player->mo->z && bottomheight < player->mo->z)
-					{
-						P_ResetPlayer(player);
-						S_StartSound(player->mo, sfx_s3k4a);
-						player->climbing = 5;
-						player->mo->momx = player->mo->momy = player->mo->momz = 0;
-						break;
-					}
-				}
-			}
-
-			floorz = P_GetFloorZ(player->mo, node->m_sector, player->mo->x, player->mo->y, NULL);
-			ceilingz = P_GetCeilingZ(player->mo, node->m_sector, player->mo->x, player->mo->y, NULL);
-
-			if (player->mo->z+player->mo->height > ceilingz
-				&& node->m_sector->ceilingpic == skyflatnum)
-				continue;
-
-			if (floorz > player->mo->z || ceilingz < player->mo->z)
-			{
-				P_ResetPlayer(player);
-				S_StartSound(player->mo, sfx_s3k4a);
-				player->climbing = 5;
-				player->mo->momx = player->mo->momy = player->mo->momz = 0;
-				break;
-			}
-		}
-		P_UnsetThingPosition(player->mo);
-		player->mo->x = oldx;
-		player->mo->y = oldy;
-		P_SetThingPosition(player->mo);
-	}
-
 	// Check for a BOUNCY sector!
 	if (CheckForBouncySector)
 		P_CheckBouncySectors(player);
diff --git a/src/r_things.c b/src/r_things.c
index c0a71b02..135ae6a2 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2895,27 +2895,27 @@ void R_AddSkins(UINT16 wadnum)
 #define FULLPROCESS(field) else if (!stricmp(stoken, #field)) skin->field = get_number(value);
 			// character type identification
 			FULLPROCESS(flags)
-			FULLPROCESS(ability)
-			FULLPROCESS(ability2)
+			//FULLPROCESS(ability)
+			//FULLPROCESS(ability2)
 
-			FULLPROCESS(thokitem)
-			FULLPROCESS(spinitem)
-			FULLPROCESS(revitem)
+			//FULLPROCESS(thokitem)
+			//FULLPROCESS(spinitem)
+			//FULLPROCESS(revitem)
 #undef FULLPROCESS
 
 #define GETSPEED(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value)<<FRACBITS;
-			GETSPEED(normalspeed)
+			//GETSPEED(normalspeed)
 			GETSPEED(runspeed)
-			GETSPEED(mindash)
-			GETSPEED(maxdash)
-			GETSPEED(actionspd)
+			//GETSPEED(mindash)
+			//GETSPEED(maxdash)
+			//GETSPEED(actionspd)
 #undef GETSPEED
 
-#define GETINT(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value);
+/*#define GETINT(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value);
 			GETINT(thrustfactor)
 			GETINT(accelstart)
 			GETINT(acceleration)
-#undef GETINT
+#undef GETINT*/
 
 #define GETKARTSTAT(field) \
 	else if (!stricmp(stoken, #field)) \
@@ -2934,8 +2934,8 @@ void R_AddSkins(UINT16 wadnum)
 
 			else if (!stricmp(stoken, "prefcolor"))
 				skin->prefcolor = K_GetKartColorByName(value);
-			else if (!stricmp(stoken, "jumpfactor"))
-				skin->jumpfactor = FLOAT_TO_FIXED(atof(value));
+			//else if (!stricmp(stoken, "jumpfactor"))
+				//skin->jumpfactor = FLOAT_TO_FIXED(atof(value));
 			else if (!stricmp(stoken, "highresscale"))
 				skin->highresscale = FLOAT_TO_FIXED(atof(value));
 			else
@@ -3045,6 +3045,9 @@ next_token:
 			HWR_AddPlayerMD2(numskins);
 #endif
 
+		if (skin->flags & SF_RUNONWATER) // this is literally the only way a skin can be a major mod... this might be a bit heavy handed
+			G_SetGameModified(multiplayer, true);
+
 		numskins++;
 	}
 	return;

From 55c6ab2581af730748bfcb0d7bbba5f777c470f4 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 27 Jan 2019 17:51:46 -0500
Subject: [PATCH 27/73] Scale number of allowed invuln items w/ player count

0-5 players: 1 invincibility/grow allowed out at once
6-9 players: 2 invincibilities (how it was before)
10-13 players: 3 invincibilities
14+ players: 4 invincibilites
---
 src/k_kart.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 18165a7f..bc5e53ca 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -700,7 +700,7 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 	{
 		case KITEM_INVINCIBILITY:
 		case KITEM_GROW:
-			if (pinvin >= 2)
+			if (pinvin >= max(1, (pingame+2) / 4))
 				newodds = 0;
 			else
 			/* FALLTHRU */

From 1f90a04623db5ccdefab5243d6909bed830fcb70 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 27 Jan 2019 17:52:53 -0500
Subject: [PATCH 28/73] Thin names for 9+ player intermission

---
 src/y_inter.c | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/src/y_inter.c b/src/y_inter.c
index 021519e3..a8b1f740 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -474,16 +474,12 @@ void Y_IntermissionDrawer(void)
 					V_DrawScaledPatch(x+16, y-4, 0, W_CachePatchName(va("K_CHILI%d", cursorframe+1), PU_CACHE));
 				}
 
-				if (!gutter)
-					strlcpy(strtime, data.match.name[i], 6);
-				else
-					STRBUFCPY(strtime, data.match.name[i]);
+				STRBUFCPY(strtime, data.match.name[i]);
 
-				V_DrawString(x+36, y,
-					((data.match.num[i] == whiteplayer)
-						? hilicol|V_ALLOWLOWERCASE
-						: V_ALLOWLOWERCASE),
-					strtime);
+				if (!gutter)
+					V_DrawThinString(x+36, y, ((data.match.num[i] == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, strtime);
+				else
+					V_DrawString(x+36, y, ((data.match.num[i] == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE, strtime);
 
 				if (data.match.rankingsmode)
 				{

From e9887b267503f3a90e46583ae32b5c17d99fd7fb Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 27 Jan 2019 21:11:49 -0500
Subject: [PATCH 29/73] Apply to TAB rankings too

---
 src/k_kart.c | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index ee50cfdd..0410a931 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -7097,19 +7097,15 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 			continue; //ignore them.
 
 		if (netgame // don't draw it offline
-        && tab[i].num != serverplayer)
+		&& tab[i].num != serverplayer)
 			HU_drawPing(x + ((i < 8) ? -19 : rightoffset + 13), y+2, playerpingtable[tab[i].num], false);
 
-		if (scorelines > 8)
-			strlcpy(strtime, tab[i].name, 6);
-		else
-			STRBUFCPY(strtime, tab[i].name);
+		STRBUFCPY(strtime, tab[i].name);
 
-		V_DrawString(x + 20, y,
-			((tab[i].num == whiteplayer)
-				? hilicol|V_ALLOWLOWERCASE
-				: V_ALLOWLOWERCASE),
-			strtime);
+		if (scorelines > 8)
+			V_DrawThinString(x + 20, y, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, strtime);
+		else
+			V_DrawString(x + 20, y, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE, strtime);
 
 		if (players[tab[i].num].mo->color)
 		{

From dd2b895e800951436f881e345ba76aa9e5b578fc Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 27 Jan 2019 21:57:18 -0500
Subject: [PATCH 30/73] Remove offroad leniency bias

Everyone now has flat, equal 1-second leniency
---
 src/k_kart.c | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index f01f0d0b..5de534bb 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1264,7 +1264,6 @@ static UINT8 K_CheckOffroadCollide(mobj_t *mo, sector_t *sec)
 */
 static void K_UpdateOffroad(player_t *player)
 {
-	fixed_t kartweight = player->kartweight;
 	fixed_t offroad;
 	sector_t *nextsector = R_PointInSubsector(
 		player->mo->x + player->mo->momx*2, player->mo->y + player->mo->momy*2)->sector;
@@ -1284,13 +1283,11 @@ static void K_UpdateOffroad(player_t *player)
 	if (offroadstrength)
 	{
 		if (K_CheckOffroadCollide(player->mo, player->mo->subsector->sector) && player->kartstuff[k_offroad] == 0)
-			player->kartstuff[k_offroad] = 16;
+			player->kartstuff[k_offroad] = (TICRATE/2);
 
 		if (player->kartstuff[k_offroad] > 0)
 		{
-			// 1872 is the magic number - 35 frames adds up to approximately 65536. 1872/4 = 468/3 = 156
-			// A higher kart weight means you can stay offroad for longer without losing speed
-			offroad = (1872 + 5*156 - kartweight*156)*offroadstrength;
+			offroad = (FRACUNIT * offroadstrength) / TICRATE;
 
 			//if (player->kartstuff[k_growshrinktimer] > 1) // grow slows down half as fast
 			//	offroad /= 2;

From 8a97b28936d3d8ace0b7bd03b367ad736f8589bb Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 27 Jan 2019 21:58:17 -0500
Subject: [PATCH 31/73] Wipeout slowdown timer is set to 20 tics if below,
 instead of adding up per bump.

---
 src/k_kart.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 5de534bb..b7029452 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1211,8 +1211,8 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid)
 		mobj1->player->kartstuff[k_justbumped] = bumptime;
 		if (mobj1->player->kartstuff[k_spinouttimer])
 		{
-			mobj1->player->kartstuff[k_wipeoutslow] += wipeoutslowtime+1;
-			mobj1->player->kartstuff[k_spinouttimer] += wipeoutslowtime+1;
+			mobj1->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1;
+			mobj1->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj1->player->kartstuff[k_spinouttimer]);
 		}
 	}
 
@@ -1223,8 +1223,8 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid)
 		mobj2->player->kartstuff[k_justbumped] = bumptime;
 		if (mobj2->player->kartstuff[k_spinouttimer])
 		{
-			mobj2->player->kartstuff[k_wipeoutslow] += wipeoutslowtime+1;
-			mobj2->player->kartstuff[k_spinouttimer] += wipeoutslowtime+1;
+			mobj2->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1;
+			mobj2->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj2->player->kartstuff[k_spinouttimer]);
 		}
 	}
 }

From c53144ecd67d296534a42930dbff003644285ee6 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 27 Jan 2019 21:58:44 -0500
Subject: [PATCH 32/73] Wipeout slowdown is x2 strength

---
 src/k_kart.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index b7029452..12da394a 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -5429,6 +5429,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 		player->mo->friction += 4608;
 	if (player->speed > 0 && cmd->forwardmove < 0 && player->mo->friction == 59392)
 		player->mo->friction += 1608;
+
+	// Karma ice physics
 	if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)
 	{
 		player->mo->friction += 1228;
@@ -5448,11 +5450,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 		if (player->mo->movefactor < 32)
 			player->mo->movefactor = 32;
 	}
+
+	// Wipeout slowdown
 	if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow])
 	{
-		player->mo->friction -= FixedMul(1228, player->kartstuff[k_offroad]);
+		player->mo->friction -= FixedMul(2456, player->kartstuff[k_offroad]);
 		if (player->kartstuff[k_wipeoutslow] == 1)
-			player->mo->friction -= 4912;
+			player->mo->friction -= 9824;
 	}
 
 	K_KartDrift(player, onground);

From 5518a19945f39533c5873219c224dfd06c62c024 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 27 Jan 2019 22:28:44 -0500
Subject: [PATCH 33/73] Wipeout slowdown in offroad is static

---
 src/k_kart.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 12da394a..d3685638 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -5454,7 +5454,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 	// Wipeout slowdown
 	if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow])
 	{
-		player->mo->friction -= FixedMul(2456, player->kartstuff[k_offroad]);
+		if (player->kartstuff[k_offroad])
+			player->mo->friction -= 4912;
 		if (player->kartstuff[k_wipeoutslow] == 1)
 			player->mo->friction -= 9824;
 	}

From 0ef442c004d81496ee480522c0c1106a40adfc02 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 27 Jan 2019 23:18:40 -0500
Subject: [PATCH 34/73] Change offroad leniency from 1 second to 0.5 seconds,
 don't make friction more forgiving in offroad

---
 src/k_kart.c | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index d3685638..a54f8ef5 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1287,7 +1287,7 @@ static void K_UpdateOffroad(player_t *player)
 
 		if (player->kartstuff[k_offroad] > 0)
 		{
-			offroad = (FRACUNIT * offroadstrength) / TICRATE;
+			offroad = (FRACUNIT * offroadstrength) / (TICRATE/2);
 
 			//if (player->kartstuff[k_growshrinktimer] > 1) // grow slows down half as fast
 			//	offroad /= 2;
@@ -5425,10 +5425,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 	}
 
 	// Friction
-	if (player->speed > 0 && cmd->forwardmove == 0 && player->mo->friction == 59392)
-		player->mo->friction += 4608;
-	if (player->speed > 0 && cmd->forwardmove < 0 && player->mo->friction == 59392)
-		player->mo->friction += 1608;
+	if (!player->kartstuff[k_offroad])
+	{
+		if (player->speed > 0 && cmd->forwardmove == 0 && player->mo->friction == 59392)
+			player->mo->friction += 4608;
+		if (player->speed > 0 && cmd->forwardmove < 0 && player->mo->friction == 59392)
+			player->mo->friction += 1608;
+	}
 
 	// Karma ice physics
 	if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)

From 38ca2ab98a1e595ff97a312e486b5c61e5097ca9 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 27 Jan 2019 23:25:03 -0500
Subject: [PATCH 35/73] Sparks can't be started in offroad

---
 src/k_kart.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index a54f8ef5..118da2c5 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -4667,8 +4667,6 @@ static void K_KartDrift(player_t *player, boolean onground)
 			player->kartstuff[k_driftend] = 0;
 	}
 
-
-
 	// Incease/decrease the drift value to continue drifting in that direction
 	if (player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_jmp] == 1 && onground && player->kartstuff[k_drift] != 0)
 	{
@@ -4700,7 +4698,7 @@ static void K_KartDrift(player_t *player, boolean onground)
 		// Disable drift-sparks until you're going fast enough
 		if (player->kartstuff[k_getsparks] == 0)
 			driftadditive = 0;
-		if (player->speed > minspeed*2)
+		if (player->speed > minspeed*2 && !player->kartstuff[k_offroad])
 			player->kartstuff[k_getsparks] = 1;
 
 		// This spawns the drift sparks

From ada4ce622f793023b58b8132228e2e9040a7167b Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 28 Jan 2019 16:32:07 +0000
Subject: [PATCH 36/73] * Did some reviewing, turns out there's literally no
 reason to disable savemoddata when majormods gets tripped and it's just a
 stupid thing vanilla did for modifiedgame for some reason that we almost
 inherited with our new solution? * Adjusted the save system to acknowledge
 the new status quo. Instead of trying to save modifiedgame in the file like
 some sort of extremely boneheaded honour system everyone and their mothers
 hacks around, we just use it to determine whether the save is for a mod with
 savedata or not (this keeps backwards compatibility based on how we were
 using it, anyways, especially with the *force* parameter...) * Added a menu
 message for attempting to play a demo set on a map that isn't loaded, as
 opposed to letting it I_Error. * Minor tweaks to addons menu representing
 modded status.

---
 src/d_netcmd.c |  6 +++---
 src/g_game.c   | 25 ++++++++++++++++---------
 src/m_menu.c   |  7 +++++--
 3 files changed, 24 insertions(+), 14 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 67f8c3a2..1c90d181 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -4910,10 +4910,10 @@ static void Fishcake_OnChange(void)
   */
 static void Command_Isgamemodified_f(void)
 {
-	if (savemoddata)
-		CONS_Printf("The game has been modified with an add-on with its own save data, so you can play Record Attack and earn medals.\n");
-	else if (majormods)
+	if (majormods)
 		CONS_Printf("The game has been modified with major add-ons, so you cannot play Record Attack.\n");
+	else if (savemoddata)
+		CONS_Printf("The game has been modified with an add-on with its own save data, so you can play Record Attack and earn medals.\n");
 	else if (modifiedgame)
 		CONS_Printf("The game has been modified with only minor add-ons. You can play Record Attack, earn medals and unlock extras.\n");
 	else
diff --git a/src/g_game.c b/src/g_game.c
index 1e0744f4..8dffb7d5 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -764,7 +764,7 @@ void G_SetGameModified(boolean silent, boolean major)
 	if (!major)
 		return;
 
-	savemoddata = false;
+	//savemoddata = false; -- there is literally no reason to do this anymore.
 	majormods = true;
 
 	if (!silent)
@@ -3933,7 +3933,6 @@ void G_LoadGameData(void)
 // Saves the main data file, which stores information such as emblems found, etc.
 void G_SaveGameData(boolean force)
 {
-	const boolean wasmodified = modifiedgame;
 	size_t length;
 	INT32 i, j;
 	UINT8 btemp;
@@ -3950,9 +3949,7 @@ void G_SaveGameData(boolean force)
 		return;
 	}
 
-	if (force) // SRB2Kart: for enabling unlocks online, even if the game is modified
-		modifiedgame = savemoddata; // L-let's just sort of... hack around the cheat protection, because I'm too worried about just removing it @@;
-	else if (modifiedgame && !savemoddata)
+	if (majormods && !force)
 	{
 		free(savebuffer);
 		save_p = savebuffer = NULL;
@@ -3965,7 +3962,7 @@ void G_SaveGameData(boolean force)
 	WRITEUINT32(save_p, totalplaytime);
 	WRITEUINT32(save_p, matchesplayed);
 
-	btemp = (UINT8)(savemoddata || modifiedgame);
+	btemp = (UINT8)(savemoddata); // what used to be here was profoundly dunderheaded
 	WRITEUINT8(save_p, btemp);
 
 	// TODO put another cipher on these things? meh, I don't care...
@@ -4051,9 +4048,6 @@ void G_SaveGameData(boolean force)
 	FIL_WriteFile(va(pandf, srb2home, gamedatafilename), savebuffer, length);
 	free(savebuffer);
 	save_p = savebuffer = NULL;
-
-	if (force) // Eeeek, I'm sorry for my sins!
-		modifiedgame = wasmodified;
 }
 
 #define VERSIONSIZE 16
@@ -5925,6 +5919,19 @@ void G_DoPlayDemo(char *defdemoname)
 		return;
 	}
 
+	// ...*map* not loaded?
+	if (!gamemap || (gamemap > NUMMAPS) || !mapheaderinfo[gamemap-1] || !(mapheaderinfo[gamemap-1]->menuflags & LF2_EXISTSHACK))
+	{
+		snprintf(msg, 1024, M_GetText("%s features a course that is not currently loaded.\n"), pdemoname);
+		CONS_Alert(CONS_ERROR, "%s", msg);
+		M_StartMessage(msg, NULL, MM_NOTHING);
+		Z_Free(pdemoname);
+		Z_Free(demobuffer);
+		demoplayback = false;
+		titledemo = false;
+		return;
+	}
+
 	Z_Free(pdemoname);
 
 	memset(&oldcmd,0,sizeof(oldcmd));
diff --git a/src/m_menu.c b/src/m_menu.c
index 2ea7234c..9e51cb50 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4543,7 +4543,10 @@ static boolean M_AddonsRefresh(void)
 	if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true))
 	{
 		UNEXIST;
-		CLEARNAME;
+		if (refreshdirname)
+		{
+			CLEARNAME;
+		}
 		return true;
 	}
 
@@ -4723,7 +4726,7 @@ static void M_DrawAddons(void)
 	V_DrawSmallScaledPatch(x, y + 4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+3]);
 
 	x = BASEVIDWIDTH - x - 16;
-	V_DrawSmallScaledPatch(x, y + 4, ((!modifiedgame || savemoddata) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]);
+	V_DrawSmallScaledPatch(x, y + 4, ((!majormods) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]);
 
 	if (modifiedgame)
 		V_DrawSmallScaledPatch(x, y + 4, 0, addonsp[NUM_EXT+2]);

From d3bf1d55ef1a3f28ace0ae159582ede647bff851 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Mon, 28 Jan 2019 17:28:15 +0000
Subject: [PATCH 37/73] You will never have Lua consoleplayer.

(This is a quick, cheap hack to make splitscreen not almost impossible to support in major mods like Wipezones.)
---
 src/lua_hudlib.c | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index ca952a00..cd8e0392 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -34,6 +34,8 @@ static UINT8 hud_enabled[(hud_MAX/8)+1];
 
 static UINT8 hudAvailable; // hud hooks field
 
+static UINT8 camnum = 1;
+
 // must match enum hud in lua_hud.h
 static const char *const hud_disable_options[] = {
 	"stagetitle",
@@ -134,7 +136,8 @@ enum cameraf {
 	camera_height,
 	camera_momx,
 	camera_momy,
-	camera_momz
+	camera_momz,
+	camera_pnum
 };
 
 
@@ -153,6 +156,7 @@ static const char *const camera_opt[] = {
 	"momx",
 	"momy",
 	"momz",
+	"pnum",
 	NULL};
 
 static int lib_getHudInfo(lua_State *L)
@@ -308,6 +312,9 @@ static int camera_get(lua_State *L)
 	case camera_momz:
 		lua_pushinteger(L, cam->momz);
 		break;
+	case camera_pnum:
+		lua_pushinteger(L, camnum);
+		break;
 	}
 	return 1;
 }
@@ -772,13 +779,25 @@ void LUAh_GameHUD(player_t *stplayr)
 	LUA_PushUserdata(gL, stplayr, META_PLAYER);
 
 	if (splitscreen > 2 && stplayr == &players[fourthdisplayplayer])
+	{
 		LUA_PushUserdata(gL, &camera4, META_CAMERA);
+		camnum = 4;
+	}
 	else if (splitscreen > 1 && stplayr == &players[thirddisplayplayer])
+	{
 		LUA_PushUserdata(gL, &camera3, META_CAMERA);
+		camnum = 3;
+	}
 	else if (splitscreen && stplayr == &players[secondarydisplayplayer])
+	{
 		LUA_PushUserdata(gL, &camera2, META_CAMERA);
+		camnum = 2;
+	}
 	else
+	{
 		LUA_PushUserdata(gL, &camera, META_CAMERA);
+		camnum = 1;
+	}
 
 	lua_pushnil(gL);
 	while (lua_next(gL, -5) != 0) {

From 80d1c303e3dc4612c84d3bdbeedd2cfadede03b0 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Mon, 28 Jan 2019 12:47:23 -0500
Subject: [PATCH 38/73] Reduce frequency of Eggman items, give some of those
 points to Orbinaut or Banana

---
 src/k_kart.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index bc5e53ca..619f2b1e 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -499,9 +499,9 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS][10] =
 			   /*Sneaker*/ {20, 0, 0, 4, 6, 6, 0, 0, 0, 0 }, // Sneaker
 		/*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 1, 3, 5, 3, 0 }, // Rocket Sneaker
 		 /*Invincibility*/ { 0, 0, 0, 0, 0, 1, 4, 6,14, 0 }, // Invincibility
-				/*Banana*/ { 0, 9, 4, 2, 1, 0, 0, 0, 0, 0 }, // Banana
-		/*Eggman Monitor*/ { 0, 4, 3, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor
-			  /*Orbinaut*/ { 0, 6, 5, 3, 2, 0, 0, 0, 0, 0 }, // Orbinaut
+				/*Banana*/ { 0,10, 4, 2, 1, 0, 0, 0, 0, 0 }, // Banana
+		/*Eggman Monitor*/ { 0, 3, 2, 1, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor
+			  /*Orbinaut*/ { 0, 8, 6, 4, 2, 0, 0, 0, 0, 0 }, // Orbinaut
 				  /*Jawz*/ { 0, 0, 3, 2, 1, 1, 0, 0, 0, 0 }, // Jawz
 				  /*Mine*/ { 0, 0, 2, 2, 1, 0, 0, 0, 0, 0 }, // Mine
 			   /*Ballhog*/ { 0, 0, 0, 2, 1, 0, 0, 0, 0, 0 }, // Ballhog

From 1e61cc6152061643833ada1c720d0b07ebcdb259 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Mon, 28 Jan 2019 14:12:08 -0500
Subject: [PATCH 39/73] Better cpusleep

- Default to 1, which means potential for a 1-frame loss every once in a while but no longer a complete cpu hog
- New minimum is 0, since -1 just did the exact same thing as 0.
---
 src/d_netcmd.c        | 4 ++--
 src/sdl/i_system.c    | 2 +-
 src/sdl12/i_system.c  | 2 +-
 src/win32/win_sys.c   | 2 +-
 src/win32ce/win_sys.c | 2 +-
 5 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 41f88ab9..775f2225 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -214,7 +214,7 @@ static CV_PossibleValue_t autobalance_cons_t[] = {{0, "MIN"}, {4, "MAX"}, {0, NU
 static CV_PossibleValue_t teamscramble_cons_t[] = {{0, "Off"}, {1, "Random"}, {2, "Points"}, {0, NULL}};
 
 static CV_PossibleValue_t startingliveslimit_cons_t[] = {{1, "MIN"}, {99, "MAX"}, {0, NULL}};
-static CV_PossibleValue_t sleeping_cons_t[] = {{-1, "MIN"}, {1000/TICRATE, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t sleeping_cons_t[] = {{0, "MIN"}, {1000/TICRATE, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t competitionboxes_cons_t[] = {{0, "Normal"}, {1, "Random"}, {2, "Teleports"},
 	{3, "None"}, {0, NULL}};
 
@@ -447,7 +447,7 @@ consvar_t cv_runscripts = {"runscripts", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL
 consvar_t cv_pause = {"pausepermission", "Server", CV_NETVAR, pause_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_mute = {"mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_sleep = {"cpusleep", "-1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL, NULL, 0, 0, NULL};
+consvar_t cv_sleep = {"cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL, NULL, 0, 0, NULL};
 
 INT16 gametype = GT_RACE; // SRB2kart
 boolean forceresetplayers = false;
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 2154a070..f360072a 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -3018,7 +3018,7 @@ void I_StartupTimer(void)
 
 void I_Sleep(void)
 {
-	if (cv_sleep.value != -1)
+	if (cv_sleep.value > 0)
 		SDL_Delay(cv_sleep.value);
 }
 
diff --git a/src/sdl12/i_system.c b/src/sdl12/i_system.c
index 0e5adbb4..d055a4ca 100644
--- a/src/sdl12/i_system.c
+++ b/src/sdl12/i_system.c
@@ -2914,7 +2914,7 @@ void I_StartupTimer(void)
 void I_Sleep(void)
 {
 #if !(defined (_arch_dreamcast) || defined (_XBOX))
-	if (cv_sleep.value != -1)
+	if (cv_sleep.value > 0)
 		SDL_Delay(cv_sleep.value);
 #endif
 }
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index 75aca68d..fa9d6d64 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -261,7 +261,7 @@ tic_t I_GetTime(void)
 
 void I_Sleep(void)
 {
-	if (cv_sleep.value != -1)
+	if (cv_sleep.value > 0)
 		Sleep(cv_sleep.value);
 }
 
diff --git a/src/win32ce/win_sys.c b/src/win32ce/win_sys.c
index 3b6a4725..091171b5 100644
--- a/src/win32ce/win_sys.c
+++ b/src/win32ce/win_sys.c
@@ -265,7 +265,7 @@ tic_t I_GetTime(void)
 
 void I_Sleep(void)
 {
-	if (cv_sleep.value != -1)
+	if (cv_sleep.value > 0)
 		Sleep(cv_sleep.value);
 }
 

From e118ec939968579ec1eb311ef0fe0dc5fd9740f0 Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Tue, 29 Jan 2019 13:27:43 -0500
Subject: [PATCH 40/73] Remove CV_HIDEN flag for serversort

---
 src/m_menu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index f0831a17..f337f1b4 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -443,7 +443,7 @@ static CV_PossibleValue_t serversort_cons_t[] = {
 	{5,"Gametype"},
 	{0,NULL}
 };
-consvar_t cv_serversort = {"serversort", "Ping", CV_HIDEN | CV_CALL, serversort_cons_t, M_SortServerList, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_serversort = {"serversort", "Ping", CV_CALL, serversort_cons_t, M_SortServerList, 0, NULL, NULL, 0, 0, NULL};
 
 // autorecord demos for time attack
 static consvar_t cv_autorecord = {"autorecord", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};

From c1fde7bfb7c2692b2cf81ef3e869a8e8e5f2e286 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 30 Jan 2019 01:21:20 -0500
Subject: [PATCH 41/73] Thin the rest of this screen's stuff too

---
 src/k_kart.c  | 24 ++++++++++++++++++------
 src/y_inter.c | 30 ++++++++++++++++++++++--------
 2 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 0410a931..dde4849e 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -7145,12 +7145,24 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 		if (G_RaceGametype())
 		{
 #define timestring(time) va("%i'%02i\"%02i", G_TicsToMinutes(time, true), G_TicsToSeconds(time), G_TicsToCentiseconds(time))
-			if (players[tab[i].num].exiting)
-				V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime));
-			else if (players[tab[i].num].pflags & PF_TIMEOVER)
-				V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "NO CONTEST.");
-			else if (circuitmap)
-				V_DrawRightAlignedString(x+rightoffset, y, 0, va("Lap %d", tab[i].count));
+			if (scorelines > 8)
+			{
+				if (players[tab[i].num].exiting)
+					V_DrawRightAlignedThinString(x+rightoffset, y-1, hilicol|V_6WIDTHSPACE, timestring(players[tab[i].num].realtime));
+				else if (players[tab[i].num].pflags & PF_TIMEOVER)
+					V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, "NO CONTEST.");
+				else if (circuitmap)
+					V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, va("Lap %d", tab[i].count));
+			}
+			else
+			{
+				if (players[tab[i].num].exiting)
+					V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime));
+				else if (players[tab[i].num].pflags & PF_TIMEOVER)
+					V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "NO CONTEST.");
+				else if (circuitmap)
+					V_DrawRightAlignedString(x+rightoffset, y, 0, va("Lap %d", tab[i].count));
+			}
 #undef timestring
 		}
 		else
diff --git a/src/y_inter.c b/src/y_inter.c
index a8b1f740..31960a08 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -432,7 +432,7 @@ void Y_IntermissionDrawer(void)
 		if (data.match.encore)
 			V_DrawCenteredString(-4 + x + BASEVIDWIDTH/2, 12-8, hilicol, "ENCORE MODE");
 
-		if (!gutter)
+		if (data.match.numplayers > NUMFORNEWCOLUMN)
 		{
 			V_DrawFill(x+156, 24, 1, 158, 0);
 			V_DrawFill((x-3) - duptweak, 182, dupadjust-2, 1, 0);
@@ -476,8 +476,8 @@ void Y_IntermissionDrawer(void)
 
 				STRBUFCPY(strtime, data.match.name[i]);
 
-				if (!gutter)
-					V_DrawThinString(x+36, y, ((data.match.num[i] == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, strtime);
+				if (data.match.numplayers > NUMFORNEWCOLUMN)
+					V_DrawThinString(x+36, y-1, ((data.match.num[i] == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, strtime);
 				else
 					V_DrawString(x+36, y, ((data.match.num[i] == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE, strtime);
 
@@ -490,17 +490,23 @@ void Y_IntermissionDrawer(void)
 						else
 							snprintf(strtime, sizeof strtime, "(+  %d)", data.match.increase[data.match.num[i]]);
 
-						V_DrawRightAlignedString(x+120+gutter, y, 0, strtime);
+						if (data.match.numplayers > NUMFORNEWCOLUMN)
+							V_DrawRightAlignedThinString(x+135+gutter, y-1, V_6WIDTHSPACE, strtime);
+						else
+							V_DrawRightAlignedString(x+120+gutter, y, 0, strtime);
 					}
 
 					snprintf(strtime, sizeof strtime, "%d", data.match.val[i]);
 
-					V_DrawRightAlignedString(x+152+gutter, y, 0, strtime);
+					if (data.match.numplayers > NUMFORNEWCOLUMN)
+						V_DrawRightAlignedThinString(x+152+gutter, y-1, V_6WIDTHSPACE, strtime);
+					else
+						V_DrawRightAlignedString(x+152+gutter, y, 0, strtime);
 				}
 				else
 				{
 					if (data.match.val[i] == (UINT32_MAX-1))
-						V_DrawRightAlignedThinString(x+152+gutter, y-1, 0, "NO CONTEST.");
+						V_DrawRightAlignedThinString(x+152+gutter, y-1, (data.match.numplayers > NUMFORNEWCOLUMN ? V_6WIDTHSPACE : 0), "NO CONTEST.");
 					else
 					{
 						if (intertype == int_race)
@@ -509,10 +515,18 @@ void Y_IntermissionDrawer(void)
 							G_TicsToSeconds(data.match.val[i]), G_TicsToCentiseconds(data.match.val[i]));
 							strtime[sizeof strtime - 1] = '\0';
 
-							V_DrawRightAlignedString(x+152+gutter, y, 0, strtime);
+							if (data.match.numplayers > NUMFORNEWCOLUMN)
+								V_DrawRightAlignedThinString(x+152+gutter, y-1, V_6WIDTHSPACE, strtime);
+							else
+								V_DrawRightAlignedString(x+152+gutter, y, 0, strtime);
 						}
 						else
-							V_DrawRightAlignedString(x+152+gutter, y, 0, va("%i", data.match.val[i]));
+						{
+							if (data.match.numplayers > NUMFORNEWCOLUMN)
+								V_DrawRightAlignedThinString(x+152+gutter, y-1, V_6WIDTHSPACE, va("%i", data.match.val[i]));
+							else
+								V_DrawRightAlignedString(x+152+gutter, y, 0, va("%i", data.match.val[i]));
+						}
 					}
 				}
 

From bc4832aa40d20838e92286f4b10f632cc6152de8 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 30 Jan 2019 14:14:24 -0500
Subject: [PATCH 42/73] Slower orbit, always have 1 orbit item deploy behind
 you, and higher gravity for Orbinaut/Jawz

---
 src/info.c   | 4 ++--
 src/k_kart.c | 6 +++---
 src/p_mobj.c | 9 ++++++---
 3 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/src/info.c b/src/info.c
index ea9eac9d..ccdfa3cf 100644
--- a/src/info.c
+++ b/src/info.c
@@ -15449,7 +15449,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_ORBINAUT_SHIELDDEAD, // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		10*FRACUNIT,	// speed
+		4*FRACUNIT,     // speed
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
@@ -15530,7 +15530,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_JAWZ_DEAD1,   // deathstate
 		S_JAWZ_DEAD2,   // xdeathstate
 		sfx_None,       // deathsound
-		10*FRACUNIT,    // speed
+		4*FRACUNIT,     // speed
 		16*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
diff --git a/src/k_kart.c b/src/k_kart.c
index dbc9b740..9d3a12c5 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -3646,7 +3646,7 @@ static void K_MoveHeldObjects(player_t *player)
 					cur->angle += FixedAngle(cur->info->speed);
 
 					if (cur->extravalue1 < radius)
-						cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12);
+						cur->extravalue1 += P_AproxDistance(cur->extravalue1, radius) / 12;
 					if (cur->extravalue1 > radius)
 						cur->extravalue1 = radius;
 
@@ -5109,7 +5109,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 
 						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
 						{
-							newangle = FixedAngle(((360/player->kartstuff[k_itemamount])*moloop)*FRACUNIT) + ANGLE_90;
+							newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90;
 							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD);
 							if (!mo)
 							{
@@ -5150,7 +5150,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 
 						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
 						{
-							newangle = FixedAngle(((360/player->kartstuff[k_itemamount])*moloop)*FRACUNIT) + ANGLE_90;
+							newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90;
 							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD);
 							if (!mo)
 							{
diff --git a/src/p_mobj.c b/src/p_mobj.c
index d380b97a..ed53f647 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1354,7 +1354,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 		if (wasflip == !(mo->eflags & MFE_VERTICALFLIP)) // note!! == ! is not equivalent to != here - turns numeric into bool this way
 			P_PlayerFlip(mo);
 		if (mo->player->kartstuff[k_pogospring])
-			gravityadd = FixedMul(gravityadd, 5*FRACUNIT/2);
+			gravityadd = (5*gravityadd)/2;
 	}
 	else
 	{
@@ -1404,11 +1404,14 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 					break;
 				case MT_BANANA:
 				case MT_EGGMANITEM:
+				case MT_ORBINAUT:
+				case MT_JAWZ:
+				case MT_JAWZ_DUD:
 				case MT_SSMINE:
-					gravityadd = FixedMul(gravityadd, 5*FRACUNIT/2);
+					gravityadd = (5*gravityadd)/2;
 					break;
 				case MT_SINK:
-					gravityadd = FixedMul(gravityadd, 5*FRACUNIT); // Double gravity
+					gravityadd = (5*gravityadd); // Double gravity
 					break;
 				case MT_SIGN:
 					gravityadd /= 8;

From e63b6aee00d48d9f9d2f580fe86d6498a6811ad2 Mon Sep 17 00:00:00 2001
From: Sryder <sryder13@gmail.com>
Date: Wed, 30 Jan 2019 19:18:51 +0000
Subject: [PATCH 43/73] Fix Connection Timeouts during Wipes Keep the
 connection alive with a specific packet to say we haven't timed out

---
 src/d_clisrv.c | 112 +++++++++++++++++++++++++++++++++++++++++--------
 src/d_clisrv.h |   2 +
 src/d_net.c    |   3 ++
 src/f_wipe.c   |   3 ++
 4 files changed, 103 insertions(+), 17 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index c84faa72..71fdf6fb 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -4083,6 +4083,21 @@ FILESTAMP
 			else if (resynch_score[node])
 				--resynch_score[node];
 			break;
+		case PT_WIPETIME:
+			if (client)
+				break;
+
+			// This should probably still timeout though, as the node should always have a player 1 number
+			if (netconsole == -1)
+				break;
+
+			// If a client sends this it should mean they are done receiving the savegame
+			sendingsavegame[node] = false;
+
+			// As long as clients send keep alives, the server can keep running, so reset the timeout
+			/// \todo Use a separate cvar for that kind of timeout?
+			freezetimeout[node] = I_GetTime() + connectiontimeout;
+			break;
 		case PT_TEXTCMD:
 		case PT_TEXTCMD2:
 		case PT_TEXTCMD3:
@@ -4586,6 +4601,15 @@ static INT16 Consistancy(void)
 	return (INT16)(ret & 0xFFFF);
 }
 
+// confusing, but this DOESN'T send PT_NODEKEEPALIVE, it sends PT_WIPETIME
+// used during wipes to tell the server that a node is still connected
+static void CL_SendClientKeepAlive(void)
+{
+	netbuffer->packettype = PT_WIPETIME;
+
+	HSendPacket(servernode, false, 0, 0);
+}
+
 // send the client packet to the server
 static void CL_SendClientCmd(void)
 {
@@ -5032,9 +5056,77 @@ static inline void PingUpdate(void)
 }
 #endif
 
+static tic_t gametime = 0;
+
+#ifdef NEWPING
+static void UpdatePingTable(void)
+{
+	INT32 i;
+	if (server)
+	{
+		if (netgame && !(gametime % 255))
+			PingUpdate();
+		// update node latency values so we can take an average later.
+		for (i = 0; i < MAXPLAYERS; i++)
+			if (playeringame[i])
+				realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
+		pingmeasurecount++;
+	}
+}
+#endif
+
+// Handle timeouts to prevent definitive freezes from happenning
+static void HandleNodeTimeouts(void)
+{
+	INT32 i;
+	if (server)
+		for (i = 1; i < MAXNETNODES; i++)
+			if (nodeingame[i] && freezetimeout[i] < I_GetTime())
+				Net_ConnectionTimeout(i);
+}
+
+// Keep the network alive while not advancing tics!
+void NetKeepAlive(void)
+{
+	tic_t nowtime;
+	INT32 realtics;
+
+	nowtime = I_GetTime();
+	realtics = nowtime - gametime;
+
+	// return if there's no time passed since the last call
+	if (realtics <= 0) // nothing new to update
+		return;
+
+#ifdef NEWPING
+	UpdatePingTable();
+#endif
+
+	if (server)
+		CL_SendClientKeepAlive();
+
+// Sryder: What is FILESTAMP???
+FILESTAMP
+	GetPackets();
+FILESTAMP
+
+	MasterClient_Ticker();
+
+	if (client)
+	{
+		// send keep alive
+		CL_SendClientKeepAlive();
+		// No need to check for resynch because we aren't running any tics
+	}
+	// No else because no tics are being run and we can't resynch during this
+
+	Net_AckTicker();
+	HandleNodeTimeouts();
+	SV_FileSendTicker();
+}
+
 void NetUpdate(void)
 {
-	static tic_t gametime = 0;
 	static tic_t resptime = 0;
 	tic_t nowtime;
 	INT32 i;
@@ -5056,16 +5148,7 @@ void NetUpdate(void)
 	gametime = nowtime;
 
 #ifdef NEWPING
-	if (server)
-	{
-		if (netgame && !(gametime % 255))
-			PingUpdate();
-		// update node latency values so we can take an average later.
-		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i])
-				realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
-		pingmeasurecount++;
-	}
+	UpdatePingTable();
 #endif
 
 	if (client)
@@ -5133,12 +5216,7 @@ FILESTAMP
 		}
 	}
 	Net_AckTicker();
-	// Handle timeouts to prevent definitive freezes from happenning
-	if (server)
-		for (i = 1; i < MAXNETNODES; i++)
-			if (nodeingame[i] && freezetimeout[i] < I_GetTime())
-				Net_ConnectionTimeout(i);
-	nowtime /= NEWTICRATERATIO;
+	HandleNodeTimeouts();
 	if (nowtime > resptime)
 	{
 		resptime = nowtime;
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 39cb8c4d..af507739 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -71,6 +71,7 @@ typedef enum
 	PT_CLIENT3MIS,
 	PT_CLIENT4CMD,    // 4P
 	PT_CLIENT4MIS,
+	PT_WIPETIME,      // Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called
 
 	PT_CANFAIL,       // This is kind of a priority. Anything bigger than CANFAIL
 	                  // allows HSendPacket(*, true, *, *) to return false.
@@ -538,6 +539,7 @@ void SendNetXCmd3(netxcmd_t id, const void *param, size_t nparam); // splitsreen
 void SendNetXCmd4(netxcmd_t id, const void *param, size_t nparam); // splitsreen4 player
 
 // Create any new ticcmds and broadcast to other players.
+void NetKeepAlive(void);
 void NetUpdate(void);
 
 void SV_StartSinglePlayerServer(void);
diff --git a/src/d_net.c b/src/d_net.c
index 62301dc1..7c8fc956 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -903,6 +903,9 @@ static void DebugPrintpacket(const char *header)
 				(UINT32)ExpandTics(netbuffer->u.clientpak.client_tic),
 				(UINT32)ExpandTics (netbuffer->u.clientpak.resendfrom));
 			break;
+		case PT_WIPETIME:
+			fprintf(debugfile, "    wipetime\n");
+			break;
 		case PT_TEXTCMD:
 		case PT_TEXTCMD2:
 		case PT_TEXTCMD3:
diff --git a/src/f_wipe.c b/src/f_wipe.c
index f7a5992a..eaa5a013 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -26,6 +26,7 @@
 #include "console.h"
 #include "d_main.h"
 #include "m_misc.h" // movie mode
+#include "d_clisrv.h" // So the network state can be updated during the wipe
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -375,6 +376,8 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 
 		if (moviemode)
 			M_SaveFrame();
+
+		NetKeepAlive(); // Update the network so we don't cause timeouts
 	}
 	WipeInAction = false;
 #endif

From a81546177d9c1906707ecd4dc0d9921d96676d9d Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 30 Jan 2019 14:24:04 -0500
Subject: [PATCH 44/73] Speed updates based on number of orbiting items

---
 src/k_kart.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 9d3a12c5..a9e1f9be 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -3626,6 +3626,7 @@ static void K_MoveHeldObjects(player_t *player)
 		case MT_JAWZ_SHIELD:
 			{
 				mobj_t *cur = player->mo->hnext;
+				fixed_t speed = ((8 - min(4, player->kartstuff[k_itemamount])) * cur->info->speed) / 7;
 
 				player->kartstuff[k_bananadrag] = 0; // Just to make sure
 
@@ -3643,7 +3644,7 @@ static void K_MoveHeldObjects(player_t *player)
 					cur->color = player->skincolor;
 
 					cur->angle -= ANGLE_90;
-					cur->angle += FixedAngle(cur->info->speed);
+					cur->angle += FixedAngle(speed);
 
 					if (cur->extravalue1 < radius)
 						cur->extravalue1 += P_AproxDistance(cur->extravalue1, radius) / 12;

From 9d51ef8e82848b93f750a8f93c53425ed9cce28c Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 30 Jan 2019 14:42:24 -0500
Subject: [PATCH 45/73] Play hit confirm sound for shields

---
 src/p_map.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/src/p_map.c b/src/p_map.c
index 051a1e6f..41e5a455 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -752,9 +752,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			// Player Damage
 			P_DamageMobj(thing, tmthing, tmthing->target, 1);
 			K_KartBouncing(thing, tmthing, false, false);
-
-			if (tmthing->type == MT_ORBINAUT || tmthing->type == MT_JAWZ || tmthing->type == MT_JAWZ_DUD)
-				S_StartSound(thing, sfx_s3k7b);
+			S_StartSound(thing, sfx_s3k7b);
 
 			// This Item Damage
 			if (tmthing->eflags & MFE_VERTICALFLIP)
@@ -1035,9 +1033,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			// Player Damage
 			P_DamageMobj(tmthing, thing, thing->target, 1);
 			K_KartBouncing(tmthing, thing, false, false);
-
-			if (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_JAWZ_DUD)
-				S_StartSound(tmthing, sfx_s3k7b);
+			S_StartSound(tmthing, sfx_s3k7b);
 
 			// Other Item Damage
 			if (thing->eflags & MFE_VERTICALFLIP)

From a3cd1310171ac68472b23a636c669902445eabfc Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 30 Jan 2019 15:02:24 -0500
Subject: [PATCH 46/73] Don't bump while flashing

---
 src/k_kart.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/src/k_kart.c b/src/k_kart.c
index a9e1f9be..d09e3b60 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1068,6 +1068,21 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid)
 		|| (mobj2->player && mobj2->player->kartstuff[k_respawn]))
 		return;
 
+	// Don't bump if you're flashing
+	if (mobj1->player && mobj1->player->powers[pw_flashing] > 0
+		&& mobj1->player->powers[pw_flashing] < K_GetKartFlashing(mobj1->player)-1)
+	{
+		mobj1->player->powers[pw_flashing]++;
+		return;
+	}
+
+	if (mobj2->player && mobj2->player->powers[pw_flashing] > 0
+		&& mobj2->player->powers[pw_flashing] < K_GetKartFlashing(mobj2->player)-1)
+	{
+		mobj2->player->powers[pw_flashing]++;
+		return;
+	}
+
 	// Don't bump if you've recently bumped
 	if (mobj1->player && mobj1->player->kartstuff[k_justbumped])
 	{

From 3cb468aec8ba403be14c445736a680ebc159b3bb Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 30 Jan 2019 15:39:29 -0500
Subject: [PATCH 47/73] Add delay to Jawz target switching, set Jawz target
 when fired

---
 src/d_player.h |  3 ++-
 src/dehacked.c |  3 ++-
 src/k_kart.c   | 28 +++++++++++++++++++++++++---
 src/p_enemy.c  |  4 ++--
 4 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/src/d_player.h b/src/d_player.h
index 27fdef8d..5ce9066b 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -348,10 +348,11 @@ typedef enum
 	k_wanted, 			// Timer for determining WANTED status, lowers when hitting people, prevents the game turning into Camp Lazlo
 	k_yougotem, 		// "You Got Em" gfx when hitting someone as a karma player via a method that gets you back in the game instantly
 
-	// v1.0.2 vars
+	// v1.0.2+ vars
 	k_itemblink,		// Item flashing after roulette, prevents Hyudoro stealing AND serves as a mashing indicator
 	k_itemblinkmode,	// Type of flashing: 0 = white (normal), 1 = red (mashing), 2 = rainbow (enhanced items)
 	k_getsparks,		// Disable drift sparks at low speed, JUST enough to give acceleration the actual headstart above speed
+	k_jawztargetdelay,	// Delay for Jawz target switching, to make it less twitchy
 
 	NUMKARTSTUFF
 } kartstufftype_t;
diff --git a/src/dehacked.c b/src/dehacked.c
index 0ad67ff0..b03530a4 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -8333,7 +8333,8 @@ static const char *const KARTSTUFF_LIST[] = {
 
 	"ITEMBLINK",
 	"ITEMBLINKMODE",
-	"GETSPARKS"
+	"GETSPARKS",
+	"JAWZTARGETDELAY"
 };
 
 static const char *const HUDITEMS_LIST[] = {
diff --git a/src/k_kart.c b/src/k_kart.c
index d09e3b60..f2490c9b 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -2517,7 +2517,17 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I
 			break;
 		case MT_JAWZ:
 			if (source && source->player)
+			{
+				INT32 lasttarg = source->player->kartstuff[k_lastjawztarget];
 				th->cvmem = source->player->skincolor;
+				if ((lasttarg >= 0 && lasttarg < MAXPLAYERS)
+					&& playeringame[lasttarg]
+					&& !players[lasttarg].spectator
+					&& players[lasttarg].mo)
+				{
+					P_SetTarget(&th->tracer, players[lasttarg].mo);
+				}
+			}
 			else
 				th->cvmem = SKINCOLOR_KETCHUP;
 			/* FALLTHRU */
@@ -4454,12 +4464,22 @@ void K_KartPlayerAfterThink(player_t *player)
 	// Jawz reticule (seeking)
 	if (player->kartstuff[k_itemtype] == KITEM_JAWZ && player->kartstuff[k_itemheld])
 	{
-		player_t *targ = K_FindJawzTarget(player->mo, player);
+		INT32 lasttarg = player->kartstuff[k_lastjawztarget];
+		player_t *targ;
 		mobj_t *ret;
 
-		if (!targ)
+		if (player->kartstuff[k_jawztargetdelay] && playeringame[lasttarg] && !players[lasttarg].spectator)
+		{
+			targ = &players[lasttarg];
+			player->kartstuff[k_jawztargetdelay]--;
+		}
+		else
+			targ = K_FindJawzTarget(player->mo, player);
+
+		if (!targ || !targ->mo || P_MobjWasRemoved(targ->mo))
 		{
 			player->kartstuff[k_lastjawztarget] = -1;
+			player->kartstuff[k_jawztargetdelay] = 0;
 			return;
 		}
 
@@ -4469,7 +4489,7 @@ void K_KartPlayerAfterThink(player_t *player)
 		ret->tics = 1;
 		ret->color = player->skincolor;
 
-		if (targ-players != player->kartstuff[k_lastjawztarget])
+		if (targ-players != lasttarg)
 		{
 			if (P_IsLocalPlayer(player) || P_IsLocalPlayer(targ))
 				S_StartSound(NULL, sfx_s3k89);
@@ -4477,11 +4497,13 @@ void K_KartPlayerAfterThink(player_t *player)
 				S_StartSound(targ->mo, sfx_s3k89);
 
 			player->kartstuff[k_lastjawztarget] = targ-players;
+			player->kartstuff[k_jawztargetdelay] = 5;
 		}
 	}
 	else
 	{
 		player->kartstuff[k_lastjawztarget] = -1;
+		player->kartstuff[k_jawztargetdelay] = 0;
 	}
 }
 
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 8a6d7597..9d3aa951 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -8264,8 +8264,8 @@ void A_JawzChase(mobj_t *actor)
 
 	if (actor->tracer)
 	{
-		if (G_RaceGametype()) // Stop looking after first target in race
-			actor->extravalue1 = 1;
+		/*if (G_RaceGametype()) // Stop looking after first target in race
+			actor->extravalue1 = 1;*/
 
 		if (actor->tracer->health)
 		{

From f3644505bebfe07041ddd19495f37c51a0457e04 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 30 Jan 2019 15:41:15 -0500
Subject: [PATCH 48/73] Increase target cone for Race

---
 src/k_kart.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index f2490c9b..426ce2f6 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -3932,7 +3932,7 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source)
 		if (thisang > ANGLE_180)
 			thisang = InvAngle(thisang);
 
-		if (thisang > ANGLE_45) // Don't go for people who are behind you
+		if (thisang > (G_RaceGametype() ? ANGLE_67h : ANGLE_45)) // Don't go for people who are behind you
 			continue;
 
 		// Jawz only go after the person directly ahead of you in race... sort of literally now!

From 9984f0e217e632d3fcb6eb6e29e4fc9a753b880c Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 30 Jan 2019 15:43:45 -0500
Subject: [PATCH 49/73] Reorganize these checks

---
 src/k_kart.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 426ce2f6..9a8adb22 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -3932,13 +3932,14 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source)
 		if (thisang > ANGLE_180)
 			thisang = InvAngle(thisang);
 
-		if (thisang > (G_RaceGametype() ? ANGLE_67h : ANGLE_45)) // Don't go for people who are behind you
-			continue;
-
 		// Jawz only go after the person directly ahead of you in race... sort of literally now!
 		if (G_RaceGametype())
 		{
-			if (player->kartstuff[k_position] >= source->kartstuff[k_position]) // Don't pay attention to people behind you
+			// Don't go for people who are behind you
+			if (thisang > ANGLE_67h)
+				continue;
+			// Don't pay attention to people who aren't above your position
+			if (player->kartstuff[k_position] >= source->kartstuff[k_position])
 				continue;
 			if ((best == -1) || (player->kartstuff[k_position] > best))
 			{
@@ -3951,6 +3952,11 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source)
 			fixed_t thisdist;
 			fixed_t thisavg;
 
+			// Don't go for people who are behind you
+			if (thisang > ANGLE_45)
+				continue;
+
+			// Don't pay attention to dead players
 			if (player->kartstuff[k_bumper] <= 0)
 				continue;
 

From 34b027e8ba7457f595b685c695572a48f180e347 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 30 Jan 2019 15:57:46 -0500
Subject: [PATCH 50/73] Shift around flashing bump stuff

---
 src/k_kart.c | 29 +++++++++++++++++------------
 1 file changed, 17 insertions(+), 12 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 9a8adb22..850b703a 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1068,19 +1068,24 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid)
 		|| (mobj2->player && mobj2->player->kartstuff[k_respawn]))
 		return;
 
-	// Don't bump if you're flashing
-	if (mobj1->player && mobj1->player->powers[pw_flashing] > 0
-		&& mobj1->player->powers[pw_flashing] < K_GetKartFlashing(mobj1->player)-1)
-	{
-		mobj1->player->powers[pw_flashing]++;
-		return;
-	}
+	{ // Don't bump if you're flashing
+		INT32 flash;
 
-	if (mobj2->player && mobj2->player->powers[pw_flashing] > 0
-		&& mobj2->player->powers[pw_flashing] < K_GetKartFlashing(mobj2->player)-1)
-	{
-		mobj2->player->powers[pw_flashing]++;
-		return;
+		flash = K_GetKartFlashing(mobj1->player);
+		if (mobj1->player && mobj1->player->powers[pw_flashing] > 0 && mobj1->player->powers[pw_flashing] < flash)
+		{
+			if (mobj1->player->powers[pw_flashing] < flash-1)
+				mobj1->player->powers[pw_flashing]++;
+			return;
+		}
+
+		flash = K_GetKartFlashing(mobj2->player);
+		if (mobj2->player && mobj2->player->powers[pw_flashing] > 0 && mobj2->player->powers[pw_flashing] < flash)
+		{
+			if (mobj2->player->powers[pw_flashing] < flash-1)
+				mobj2->player->powers[pw_flashing]++;
+			return;
+		}
 	}
 
 	// Don't bump if you've recently bumped

From 9ddea94590167a82d9303ec992e8eef7865de5e3 Mon Sep 17 00:00:00 2001
From: wolfy852 <wolfy852@hotmail.com>
Date: Thu, 31 Jan 2019 19:15:28 -0600
Subject: [PATCH 51/73] Potentially fix 64-bit builds crashing on certain
 replays

---
 src/g_game.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index d8022e08..dcd67b40 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -5141,15 +5141,13 @@ void G_GhostTicker(void)
 				for (i = 0; i < count; i++)
 				{
 					g->p += 4; // reserved
-					type = READUINT32(g->p);
+					demo_p += 4; // backwards compat.
 					health = READUINT16(g->p);
 					x = READFIXED(g->p);
 					y = READFIXED(g->p);
 					z = READFIXED(g->p);
 					angle = READANGLE(g->p);
-					if (!(mobjinfo[type].flags & MF_SHOOTABLE)
-					|| !(mobjinfo[type].flags & (MF_ENEMY|MF_MONITOR))
-					|| health != 0 || i >= 4) // only spawn for the first 4 hits per frame, to prevent ghosts from splode-spamming too bad.
+					if (health != 0 || i >= 4) // only spawn for the first 4 hits per frame, to prevent ghosts from splode-spamming too bad.
 						continue;
 					poof = P_SpawnMobj(x, y, z, MT_GHOST);
 					poof->angle = angle;

From 7c18d130f23da29b8d07bb89b3da83df6723b605 Mon Sep 17 00:00:00 2001
From: wolfy852 <wolfy852@hotmail.com>
Date: Thu, 31 Jan 2019 23:21:00 -0600
Subject: [PATCH 52/73] Fix replay weirdness

Sryder's Pleasure Castle ghost tries to return to England with this line.
---
 src/g_game.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/g_game.c b/src/g_game.c
index dcd67b40..58f8b0d9 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -5141,7 +5141,7 @@ void G_GhostTicker(void)
 				for (i = 0; i < count; i++)
 				{
 					g->p += 4; // reserved
-					demo_p += 4; // backwards compat.
+					type = READUINT32(g->p);
 					health = READUINT16(g->p);
 					x = READFIXED(g->p);
 					y = READFIXED(g->p);

From b5b4e1fcc88b417f3054fd2332a6c8bcbda363e5 Mon Sep 17 00:00:00 2001
From: wolfy852 <wolfy852@hotmail.com>
Date: Fri, 1 Feb 2019 15:42:29 -0600
Subject: [PATCH 53/73] Skip over type

CORRECTLY, this time.
---
 src/g_game.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/g_game.c b/src/g_game.c
index 58f8b0d9..7cddad08 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -5141,7 +5141,7 @@ void G_GhostTicker(void)
 				for (i = 0; i < count; i++)
 				{
 					g->p += 4; // reserved
-					type = READUINT32(g->p);
+					g->p += 4; // backwards compat., type used to be here
 					health = READUINT16(g->p);
 					x = READFIXED(g->p);
 					y = READFIXED(g->p);

From 85f3be58895aea1d68adf31fd126d849db601627 Mon Sep 17 00:00:00 2001
From: wolfy852 <wolfy852@hotmail.com>
Date: Fri, 1 Feb 2019 15:45:53 -0600
Subject: [PATCH 54/73] Comment out type

---
 src/g_game.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/g_game.c b/src/g_game.c
index 7cddad08..df7119c3 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -5134,7 +5134,7 @@ void G_GhostTicker(void)
 			if (ziptic & EZT_HIT)
 			{ // Spawn hit poofs for killing things!
 				UINT16 i, count = READUINT16(g->p), health;
-				UINT32 type;
+				//UINT32 type;
 				fixed_t x,y,z;
 				angle_t angle;
 				mobj_t *poof;

From b332a3e16355301fb4b12ceeab08d3fe09a13c8a Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Fri, 1 Feb 2019 18:51:15 -0500
Subject: [PATCH 55/73] Tweak incorrect capitalisation.

---
 src/m_menu.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index ee94ab3c..92318687 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4561,7 +4561,7 @@ static boolean M_AddonsRefresh(void)
 		{
 			S_StartSound(NULL, sfx_s26d);
 			if (refreshdirmenu & REFRESHDIR_MAX)
-				message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nif you wish to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
+				message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nIf you wish to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 			else
 				message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 		}
@@ -4573,7 +4573,7 @@ static boolean M_AddonsRefresh(void)
 		else if (majormods && !prevmajormods)
 		{
 			S_StartSound(NULL, sfx_s221);
-			message = va("%c%s\x80\nGameplay has now been modified.\nif you wish to play record attack mode, restart the game to clear existing add-ons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
+			message = va("%c%s\x80\nGameplay has now been modified.\nIf you wish to play Record Attack mode, restart the game to clear existing add-ons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 			prevmajormods = majormods;
 		}
 

From 6a14d79cf01d2961f5d600d48857492e57fd8373 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sat, 2 Feb 2019 00:58:51 -0500
Subject: [PATCH 56/73] Offroad spark fix & sink

---
 src/k_kart.c | 10 ++++------
 src/p_mobj.c |  4 +---
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 850b703a..aa6a0cc3 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1637,10 +1637,8 @@ static void K_GetKartBoostPower(player_t *player)
 		&& player->kartstuff[k_offroad] >= 0)
 		boostpower = FixedDiv(boostpower, player->kartstuff[k_offroad] + FRACUNIT);
 
-	if (player->kartstuff[k_itemtype] == KITEM_KITCHENSINK)
-		boostpower = max((TICRATE/2), (5*TICRATE)-(player->kartstuff[k_bananadrag]/2))*boostpower/(5*TICRATE);
-	else if (player->kartstuff[k_bananadrag] > TICRATE)
-		boostpower = 4*boostpower/5;
+	if (player->kartstuff[k_bananadrag] > TICRATE)
+		boostpower = (4*boostpower)/5;
 
 	// Banana drag/offroad dust
 	if (boostpower < FRACUNIT
@@ -4716,9 +4714,9 @@ static void K_KartDrift(player_t *player, boolean onground)
 		}
 
 		// Disable drift-sparks until you're going fast enough
-		if (player->kartstuff[k_getsparks] == 0)
+		if (player->kartstuff[k_getsparks] == 0 || player->kartstuff[k_offroad])
 			driftadditive = 0;
-		if (player->speed > minspeed*2 && !player->kartstuff[k_offroad])
+		if (player->speed > minspeed*2)
 			player->kartstuff[k_getsparks] = 1;
 
 		// This spawns the drift sparks
diff --git a/src/p_mobj.c b/src/p_mobj.c
index ed53f647..746fc1af 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1408,10 +1408,8 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 				case MT_JAWZ:
 				case MT_JAWZ_DUD:
 				case MT_SSMINE:
-					gravityadd = (5*gravityadd)/2;
-					break;
 				case MT_SINK:
-					gravityadd = (5*gravityadd); // Double gravity
+					gravityadd = (5*gravityadd)/2;
 					break;
 				case MT_SIGN:
 					gravityadd /= 8;

From d9f16d2aaf4249a30b0771a05051f2aecf39473c Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sat, 2 Feb 2019 01:43:32 -0500
Subject: [PATCH 57/73] Offroad code cleanup

---
 src/k_kart.c | 25 +++++++------------------
 1 file changed, 7 insertions(+), 18 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index aa6a0cc3..5a45d5ff 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1239,9 +1239,8 @@ static UINT8 K_CheckOffroadCollide(mobj_t *mo, sector_t *sec)
 	for (i = 2; i < 5; i++)
 	{
 		if ((sec2 && GETSECSPECIAL(sec2->special, 1) == i)
-			|| (P_IsObjectOnRealGround(mo, sec)
-			&& GETSECSPECIAL(sec->special, 1) == i))
-			return i;
+			|| (P_IsObjectOnRealGround(mo, sec) && GETSECSPECIAL(sec->special, 1) == i))
+			return i-1;
 	}
 
 	return 0;
@@ -1258,19 +1257,9 @@ static void K_UpdateOffroad(player_t *player)
 	fixed_t offroad;
 	sector_t *nextsector = R_PointInSubsector(
 		player->mo->x + player->mo->momx*2, player->mo->y + player->mo->momy*2)->sector;
+	UINT8 offroadstrength = K_CheckOffroadCollide(player->mo, nextsector);
 
-	fixed_t offroadstrength = 0;
-
-	if (K_CheckOffroadCollide(player->mo, nextsector) == 2)	// Weak Offroad
-		offroadstrength = 1;
-	else if (K_CheckOffroadCollide(player->mo, nextsector) == 3)	// Mid Offroad
-		offroadstrength = 2;
-	else if (K_CheckOffroadCollide(player->mo, nextsector) == 4)	// Strong Offroad
-		offroadstrength = 3;
-
-	// If you are offroad, a timer starts. Depending on your weight value, the timer increments differently.
-	//if ((nextsector->special & 256) && nextsector->special != 768
-	//	&& nextsector->special != 1024 && nextsector->special != 4864)
+	// If you are in offroad, a timer starts.
 	if (offroadstrength)
 	{
 		if (K_CheckOffroadCollide(player->mo, player->mo->subsector->sector) && player->kartstuff[k_offroad] == 0)
@@ -1278,7 +1267,7 @@ static void K_UpdateOffroad(player_t *player)
 
 		if (player->kartstuff[k_offroad] > 0)
 		{
-			offroad = (FRACUNIT * offroadstrength) / (TICRATE/2);
+			offroad = (offroadstrength << FRACBITS) / (TICRATE/2);
 
 			//if (player->kartstuff[k_growshrinktimer] > 1) // grow slows down half as fast
 			//	offroad /= 2;
@@ -1286,8 +1275,8 @@ static void K_UpdateOffroad(player_t *player)
 			player->kartstuff[k_offroad] += offroad;
 		}
 
-		if (player->kartstuff[k_offroad] > FRACUNIT*offroadstrength)
-			player->kartstuff[k_offroad] = FRACUNIT*offroadstrength;
+		if (player->kartstuff[k_offroad] > (offroadstrength << FRACBITS))
+			player->kartstuff[k_offroad] = (offroadstrength << FRACBITS);
 	}
 	else
 		player->kartstuff[k_offroad] = 0;

From dff5cb049f221e474e8f4339bc3df8cb9b01e944 Mon Sep 17 00:00:00 2001
From: Sryder <sryder13@gmail.com>
Date: Sat, 2 Feb 2019 21:53:27 +0000
Subject: [PATCH 58/73] rename PT_WIPETIME to PT_BASICKEEPALIVE to be more
 obvious what it does

---
 src/d_clisrv.c | 6 +++---
 src/d_clisrv.h | 2 +-
 src/d_net.c    | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 71fdf6fb..961c1e59 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -4083,7 +4083,7 @@ FILESTAMP
 			else if (resynch_score[node])
 				--resynch_score[node];
 			break;
-		case PT_WIPETIME:
+		case PT_BASICKEEPALIVE:
 			if (client)
 				break;
 
@@ -4601,11 +4601,11 @@ static INT16 Consistancy(void)
 	return (INT16)(ret & 0xFFFF);
 }
 
-// confusing, but this DOESN'T send PT_NODEKEEPALIVE, it sends PT_WIPETIME
+// confusing, but this DOESN'T send PT_NODEKEEPALIVE, it sends PT_BASICKEEPALIVE
 // used during wipes to tell the server that a node is still connected
 static void CL_SendClientKeepAlive(void)
 {
-	netbuffer->packettype = PT_WIPETIME;
+	netbuffer->packettype = PT_BASICKEEPALIVE;
 
 	HSendPacket(servernode, false, 0, 0);
 }
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index af507739..62bd8bc1 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -71,7 +71,7 @@ typedef enum
 	PT_CLIENT3MIS,
 	PT_CLIENT4CMD,    // 4P
 	PT_CLIENT4MIS,
-	PT_WIPETIME,      // Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called
+	PT_BASICKEEPALIVE,// Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called
 
 	PT_CANFAIL,       // This is kind of a priority. Anything bigger than CANFAIL
 	                  // allows HSendPacket(*, true, *, *) to return false.
diff --git a/src/d_net.c b/src/d_net.c
index 7c8fc956..7e16297b 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -903,7 +903,7 @@ static void DebugPrintpacket(const char *header)
 				(UINT32)ExpandTics(netbuffer->u.clientpak.client_tic),
 				(UINT32)ExpandTics (netbuffer->u.clientpak.resendfrom));
 			break;
-		case PT_WIPETIME:
+		case PT_BASICKEEPALIVE:
 			fprintf(debugfile, "    wipetime\n");
 			break;
 		case PT_TEXTCMD:

From 59730c5db61c763d75f8262bb4b755871e7fb552 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 3 Feb 2019 16:43:11 -0500
Subject: [PATCH 59/73] In-game player cap & spectator queue

Force everyone beyond a certain point to spectate -- spectators get to queue up. In response to me doing a 1v1 match, tons of people wanting to join to watch, and just relying on honor system to prevent mid-joiners. Spectators are prioritized by how long they've been waiting. I'm thinking of hijacking base SRB2's team scramble for a scramble option later.
---
 src/d_netcmd.c |  4 ++++
 src/d_netcmd.h |  2 +-
 src/d_player.h |  1 +
 src/dehacked.c |  3 ++-
 src/k_kart.c   | 64 +++++++++++++++++++++++++++++++++++++++++---------
 src/p_user.c   |  2 ++
 src/st_stuff.c | 39 +++++++++++++++++-------------
 7 files changed, 86 insertions(+), 29 deletions(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 775f2225..5773d590 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -236,6 +236,9 @@ static consvar_t cv_dummyconsvar = {"dummyconsvar", "Off", CV_CALL|CV_NOSHOWHELP
 consvar_t cv_restrictskinchange = {"restrictskinchange", "No", CV_NETVAR|CV_CHEAT, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_allowteamchange = {"allowteamchange", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
+static CV_PossibleValue_t ingamecap_cons_t[] = {{0, "MIN"}, {MAXPLAYERS-1, "MAX"}, {0, NULL}};
+consvar_t cv_ingamecap = {"ingamecap", "0", CV_NETVAR, ingamecap_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 consvar_t cv_startinglives = {"startinglives", "3", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, startingliveslimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t respawntime_cons_t[] = {{0, "MIN"}, {30, "MAX"}, {0, NULL}};
@@ -642,6 +645,7 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_allowexitlevel);
 	CV_RegisterVar(&cv_restrictskinchange);
 	CV_RegisterVar(&cv_allowteamchange);
+	CV_RegisterVar(&cv_ingamecap);
 	CV_RegisterVar(&cv_respawntime);
 	CV_RegisterVar(&cv_killingdead);
 
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 2269996f..c590eee6 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -93,7 +93,7 @@ extern consvar_t cv_mute;
 extern consvar_t cv_killingdead;
 extern consvar_t cv_pause;
 
-extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_respawntime;
+extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_ingamecap, cv_respawntime;
 
 /*extern consvar_t cv_teleporters, cv_superring, cv_supersneakers, cv_invincibility;
 extern consvar_t cv_jumpshield, cv_watershield, cv_ringshield, cv_forceshield, cv_bombshield;
diff --git a/src/d_player.h b/src/d_player.h
index 27fdef8d..58d2df4b 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -352,6 +352,7 @@ typedef enum
 	k_itemblink,		// Item flashing after roulette, prevents Hyudoro stealing AND serves as a mashing indicator
 	k_itemblinkmode,	// Type of flashing: 0 = white (normal), 1 = red (mashing), 2 = rainbow (enhanced items)
 	k_getsparks,		// Disable drift sparks at low speed, JUST enough to give acceleration the actual headstart above speed
+	k_spectatewait,		// How long have you been waiting as a spectator
 
 	NUMKARTSTUFF
 } kartstufftype_t;
diff --git a/src/dehacked.c b/src/dehacked.c
index b9e29bc4..67080bd7 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -8299,7 +8299,8 @@ static const char *const KARTSTUFF_LIST[] = {
 
 	"ITEMBLINK",
 	"ITEMBLINKMODE",
-	"GETSPARKS"
+	"GETSPARKS",
+	"SPECTATEWAIT"
 };
 
 static const char *const HUDITEMS_LIST[] = {
diff --git a/src/k_kart.c b/src/k_kart.c
index dde4849e..8cb0941e 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -5744,9 +5744,22 @@ void K_CheckBumpers(void)
 void K_CheckSpectateStatus(void)
 {
 	UINT8 respawnlist[MAXPLAYERS];
-	UINT8 i, numingame = 0, numjoiners = 0;
+	UINT8 i, j, numingame = 0, numjoiners = 0;
 
-	if (!cv_allowteamchange.value) return;
+	// Maintain spectate wait timer
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+		if (players[i].spectator && (players[i].pflags & PF_WANTSTOJOIN))
+			players[i].kartstuff[k_spectatewait]++;
+		else
+			players[i].kartstuff[k_spectatewait] = 0;
+	}
+
+	// No one's allowed to join
+	if (!cv_allowteamchange.value)
+		return;
 
 	// Get the number of players in game, and the players to be de-spectated.
 	for (i = 0; i < MAXPLAYERS; i++)
@@ -5757,16 +5770,18 @@ void K_CheckSpectateStatus(void)
 		if (!players[i].spectator)
 		{
 			numingame++;
+			if (cv_ingamecap.value && numingame >= cv_ingamecap.value) // DON'T allow if you've hit the in-game player cap
+				return;
 			if (gamestate != GS_LEVEL) // Allow if you're not in a level
-                continue;
+				continue;
 			if (players[i].exiting) // DON'T allow if anyone's exiting
 				return;
 			if (numingame < 2 || leveltime < starttime || mapreset) // Allow if the match hasn't started yet
-                continue;
+				continue;
 			if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in
-                return;
-            if (G_RaceGametype() && players[i].laps) // DON'T allow if the race is at 2 laps
-                return;
+				return;
+			if (G_RaceGametype() && players[i].laps) // DON'T allow if the race is at 2 laps
+				return;
 			continue;
 		}
 		else if (!(players[i].pflags & PF_WANTSTOJOIN))
@@ -5779,16 +5794,43 @@ void K_CheckSpectateStatus(void)
 	if (!numjoiners)
 		return;
 
-	// Reset the match if you're in an empty server
-	if (!mapreset && gamestate == GS_LEVEL && leveltime >= starttime && (numingame < 2 && numingame+numjoiners >= 2))
+	// Organize by spectate wait timer
 	{
-		S_ChangeMusicInternal("chalng", false); // COME ON
-		mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD in the future
+		UINT8 oldrespawnlist[MAXPLAYERS];
+
+		for (i = 0; i < numjoiners; i++) // copy old table before modifying
+			oldrespawnlist[i] = respawnlist[i];
+
+		for (i = 0; i < numjoiners; i++)
+		{
+			UINT8 pos = 0;
+
+			for (j = 0; j < numjoiners; j++)
+			{
+				if (j == i)
+					continue;
+				if (players[oldrespawnlist[j]].kartstuff[k_spectatewait] > players[oldrespawnlist[i]].kartstuff[k_spectatewait])
+					pos++;
+			}
+
+			respawnlist[pos] = oldrespawnlist[i];
+		}
 	}
 
 	// Finally, we can de-spectate everyone!
 	for (i = 0; i < numjoiners; i++)
+	{
+		if (cv_ingamecap.value && numingame+i >= cv_ingamecap.value) // Hit the in-game player cap while adding people?
+			break;
 		P_SpectatorJoinGame(&players[respawnlist[i]]);
+	}
+
+	// Reset the match if you're in an empty server
+	if (!mapreset && gamestate == GS_LEVEL && leveltime >= starttime && (numingame < 2 && numingame+i >= 2)) // use previous i value
+	{
+		S_ChangeMusicInternal("chalng", false); // COME ON
+		mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD
+	}
 }
 
 //}
diff --git a/src/p_user.c b/src/p_user.c
index d7423d80..0978a0da 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8763,6 +8763,7 @@ boolean P_SpectatorJoinGame(player_t *player)
 		}
 		player->spectator = false;
 		player->pflags &= ~PF_WANTSTOJOIN;
+		player->kartstuff[k_spectatewait] = 0;
 		player->ctfteam = changeto;
 		player->playerstate = PST_REBORN;
 
@@ -8787,6 +8788,7 @@ boolean P_SpectatorJoinGame(player_t *player)
 		}
 		player->spectator = false;
 		player->pflags &= ~PF_WANTSTOJOIN;
+		player->kartstuff[k_spectatewait] = 0;
 		player->playerstate = PST_REBORN;
 
 		//Reset away view
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 8ebd2132..36a658ae 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1952,31 +1952,38 @@ static void ST_overlayDrawer(void)
 #endif
 		)
 		{
+			const char *itemtxt = M_GetText("Item - Join Game");
+
+			if (stplyr->powers[pw_flashing])
+				itemtxt = M_GetText("Item - . . .");
+			else if (stplyr->pflags & PF_WANTSTOJOIN)
+				itemtxt = M_GetText("Item - Cancel Join");
+			else if (G_GametypeHasTeams())
+				itemtxt = M_GetText("Item - Join Team");
+
+			if (cv_ingamecap.value)
+			{
+				UINT8 numingame = 0;
+				UINT8 i;
+
+				for (i = 0; i < MAXPLAYERS; i++)
+					if (playeringame[i] && !players[i].spectator)
+						numingame++;
+
+				itemtxt = va("%s (%s: %d)", itemtxt, M_GetText("Slots left"), max(0, cv_ingamecap.value - numingame));
+			}
+
 			// SRB2kart: changed positions & text
 			if (splitscreen)
 			{
 				INT32 splitflags = K_calcSplitFlags(0);
 				V_DrawThinString(2, (BASEVIDHEIGHT/2)-20, V_YELLOWMAP|V_HUDTRANSHALF|splitflags, M_GetText("- SPECTATING -"));
-				if (stplyr->powers[pw_flashing])
-					V_DrawThinString(2, (BASEVIDHEIGHT/2)-10, V_HUDTRANSHALF|splitflags, M_GetText("Item - . . ."));
-				else if (stplyr->pflags & PF_WANTSTOJOIN)
-					V_DrawThinString(2, (BASEVIDHEIGHT/2)-10, V_HUDTRANSHALF|splitflags, M_GetText("Item - Cancel Join"));
-				/*else if (G_GametypeHasTeams())
-					V_DrawThinString(2, (BASEVIDHEIGHT/2)-10, V_HUDTRANSHALF|splitflags, M_GetText("Item - Join Team"));*/
-				else
-					V_DrawThinString(2, (BASEVIDHEIGHT/2)-10, V_HUDTRANSHALF|splitflags, M_GetText("Item - Join Game"));
+				V_DrawThinString(2, (BASEVIDHEIGHT/2)-10, V_HUDTRANSHALF|splitflags, itemtxt);
 			}
 			else
 			{
 				V_DrawString(2, BASEVIDHEIGHT-40, V_HUDTRANSHALF|V_YELLOWMAP, M_GetText("- SPECTATING -"));
-				if (stplyr->powers[pw_flashing])
-					V_DrawString(2, BASEVIDHEIGHT-30, V_HUDTRANSHALF, M_GetText("Item - . . ."));
-				else if (stplyr->pflags & PF_WANTSTOJOIN)
-					V_DrawString(2, BASEVIDHEIGHT-30, V_HUDTRANSHALF, M_GetText("Item - Cancel Join"));
-				/*else if (G_GametypeHasTeams())
-					V_DrawString(2, BASEVIDHEIGHT-30, V_HUDTRANSHALF, M_GetText("Item - Join Team"));*/
-				else
-					V_DrawString(2, BASEVIDHEIGHT-30, V_HUDTRANSHALF, M_GetText("Item - Join Game"));
+				V_DrawString(2, BASEVIDHEIGHT-30, V_HUDTRANSHALF, itemtxt);
 				V_DrawString(2, BASEVIDHEIGHT-20, V_HUDTRANSHALF, M_GetText("Accelerate - Float"));
 				V_DrawString(2, BASEVIDHEIGHT-10, V_HUDTRANSHALF, M_GetText("Brake - Sink"));
 			}

From a6ac6ecdc57970a997a322e28eef93ef275143cf Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 3 Feb 2019 17:15:33 -0500
Subject: [PATCH 60/73] Missed this

---
 src/k_kart.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/k_kart.c b/src/k_kart.c
index 8cb0941e..d77c76fa 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -5795,6 +5795,7 @@ void K_CheckSpectateStatus(void)
 		return;
 
 	// Organize by spectate wait timer
+	if (cv_ingamecap.value)
 	{
 		UINT8 oldrespawnlist[MAXPLAYERS];
 

From 068ee93422a8db4d037aeead2327ac2284e8f558 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 3 Feb 2019 23:09:09 +0000
Subject: [PATCH 61/73] gotta break a few eggboxes to fix a crash

---
 src/k_kart.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 37c70681..218e8c6f 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -3021,7 +3021,7 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map
 				newz = player->mo->z;
 			}
 
-			mo = P_SpawnMobj(newx, newy, newz, mapthing);
+			mo = P_SpawnMobj(newx, newy, newz, mapthing); // this will never return null because collision isn't processed here
 
 			if (P_MobjFlip(player->mo) < 0)
 				mo->z = player->mo->z + player->mo->height - mo->height;
@@ -3033,7 +3033,9 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map
 			{
 				// floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn
 				// This should set it for FOFs
-				P_TeleportMove(mo, mo->x, mo->y, mo->z);
+				P_TeleportMove(mo, mo->x, mo->y, mo->z); // however, THIS can fuck up your day. just absolutely ruin you.
+				if (P_MobjWasRemoved(mo))
+					return NULL;
 
 				if (P_MobjFlip(mo) > 0)
 				{
@@ -3051,11 +3053,8 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map
 				}
 			}
 
-			if (mo)
-			{
-				if (player->mo->eflags & MFE_VERTICALFLIP)
-					mo->eflags |= MFE_VERTICALFLIP;
-			}
+			if (player->mo->eflags & MFE_VERTICALFLIP)
+				mo->eflags |= MFE_VERTICALFLIP;
 		}
 	}
 

From 503e4f977f231665cbd9891b05027f8a925346f2 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 3 Feb 2019 23:37:19 +0000
Subject: [PATCH 62/73] Show WANTED and minimap in battle splits even when p1
 is nuked.

---
 src/k_kart.c | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 218e8c6f..755f3802 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -8348,14 +8348,6 @@ void K_drawKartHUD(void)
 		|| ((splitscreen > 2 && stplyr == &players[fourthdisplayplayer]) && !camera4.chase))
 		K_drawKartFirstPerson();
 
-/*	if (splitscreen == 2) // Player 4 in 3P is the minimap :p
-	{
-#ifdef HAVE_BLUA
-		if (LUA_HudEnabled(hud_minimap))
-#endif
-		K_drawKartMinimap();
-	}*/
-
 	// Draw full screen stuff that turns off the rest of the HUD
 	if (mapreset && stplyr == &players[displayplayer])
 	{
@@ -8371,7 +8363,9 @@ void K_drawKartHUD(void)
 		&& stplyr->playerstate == PST_LIVE)))
 	{
 		K_drawBattleFullscreen();
-		return;
+		if (!splitscreen)
+			return;
+		isfreeplay = true; // variable reuse, since isfreeplay will not be otherwise set until after everything we want to happen
 	}
 
 	// Draw the CHECK indicator before the other items, so it's overlapped by everything else
@@ -8392,10 +8386,12 @@ void K_drawKartHUD(void)
 #ifdef HAVE_BLUA
 		if (LUA_HudEnabled(hud_minimap))
 #endif
-			K_drawKartMinimap(); // 3P splitscreen is handled above
-
+			K_drawKartMinimap();
 	}
 
+	if (isfreeplay)
+		return;
+
 	// Draw the item window
 #ifdef HAVE_BLUA
 	if (LUA_HudEnabled(hud_item))

From 55bb82eae8ad1367580840c807792e50932baac1 Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sun, 3 Feb 2019 23:50:02 +0000
Subject: [PATCH 63/73] Change order of operations so that fullscreen stuff is
 done in front of minimap, to match other two/three players in 3/4p.

---
 src/k_kart.c | 59 ++++++++++++++++++++++++++--------------------------
 1 file changed, 30 insertions(+), 29 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 755f3802..e59fbfc9 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -8336,6 +8336,7 @@ static void K_drawCheckpointDebugger(void)
 void K_drawKartHUD(void)
 {
 	boolean isfreeplay = false;
+	boolean battlefullscreen = false;
 
 	// Define the X and Y for each drawn object
 	// This is handled by console/menu values
@@ -8355,42 +8356,42 @@ void K_drawKartHUD(void)
 		return;
 	}
 
-	if ((G_BattleGametype())
+	battlefullscreen = ((G_BattleGametype())
 		&& (stplyr->exiting
 		|| (stplyr->kartstuff[k_bumper] <= 0
 		&& stplyr->kartstuff[k_comebacktimer]
 		&& comeback
-		&& stplyr->playerstate == PST_LIVE)))
+		&& stplyr->playerstate == PST_LIVE)));
+
+	if (!battlefullscreen || splitscreen)
+	{
+		// Draw the CHECK indicator before the other items, so it's overlapped by everything else
+		if (cv_kartcheck.value && !splitscreen && !players[displayplayer].exiting)
+			K_drawKartPlayerCheck();
+
+		// Draw WANTED status
+		if (G_BattleGametype())
+		{
+#ifdef HAVE_BLUA
+			if (LUA_HudEnabled(hud_wanted))
+#endif
+				K_drawKartWanted();
+		}
+
+		if (cv_kartminimap.value && !titledemo)
+		{
+#ifdef HAVE_BLUA
+			if (LUA_HudEnabled(hud_minimap))
+#endif
+				K_drawKartMinimap();
+		}
+	}
+
+	if (battlefullscreen)
 	{
 		K_drawBattleFullscreen();
-		if (!splitscreen)
-			return;
-		isfreeplay = true; // variable reuse, since isfreeplay will not be otherwise set until after everything we want to happen
-	}
-
-	// Draw the CHECK indicator before the other items, so it's overlapped by everything else
-	if (cv_kartcheck.value && !splitscreen && !players[displayplayer].exiting)
-		K_drawKartPlayerCheck();
-
-	// Draw WANTED status
-	if (G_BattleGametype())
-	{
-#ifdef HAVE_BLUA
-		if (LUA_HudEnabled(hud_wanted))
-#endif
-			K_drawKartWanted();
-	}
-
-	if (cv_kartminimap.value && !titledemo)
-	{
-#ifdef HAVE_BLUA
-		if (LUA_HudEnabled(hud_minimap))
-#endif
-			K_drawKartMinimap();
-	}
-
-	if (isfreeplay)
 		return;
+	}
 
 	// Draw the item window
 #ifdef HAVE_BLUA

From 26accf087ed4cba12583e3fb8f5917fb5b1cdf96 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 3 Feb 2019 23:13:42 -0500
Subject: [PATCH 64/73] Instant lookback

Still weird snap but this is as good as it'll get short of throwing away all of the camera code
---
 src/p_user.c | 65 ++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 50 insertions(+), 15 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index d7423d80..74644f98 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8165,6 +8165,8 @@ void P_ResetCamera(player_t *player, camera_t *thiscam)
 
 boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcalled)
 {
+	static UINT8 lookbackdelay[4] = {0,0,0,0};
+	UINT8 num;
 	angle_t angle = 0, focusangle = 0, focusaiming = 0;
 	fixed_t x, y, z, dist, height, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight;
 	fixed_t pan, xpan, ypan;
@@ -8293,6 +8295,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 
 	if (thiscam == &camera)
 	{
+		num = 0;
 		camspeed = cv_cam_speed.value;
 		camstill = cv_cam_still.value;
 		camrotate = cv_cam_rotate.value;
@@ -8302,6 +8305,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 	else if (thiscam == &camera2) // Camera 2
 	{
+		num = 1;
 		camspeed = cv_cam2_speed.value;
 		camstill = cv_cam2_still.value;
 		camrotate = cv_cam2_rotate.value;
@@ -8311,6 +8315,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 	else if (thiscam == &camera3) // Camera 3
 	{
+		num = 2;
 		camspeed = cv_cam3_speed.value;
 		camstill = cv_cam3_still.value;
 		camrotate = cv_cam3_rotate.value;
@@ -8320,6 +8325,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 	else // Camera 4
 	{
+		num = 3;
 		camspeed = cv_cam4_speed.value;
 		camstill = cv_cam4_still.value;
 		camrotate = cv_cam4_rotate.value;
@@ -8342,12 +8348,16 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 	else if (player->exiting) // SRB2Kart: Leave the camera behind while exiting, for dramatic effect!
 		camstill = true;
-	else if (lookback) // SRB2kart - Camera flipper
+	else if (lookback || lookbackdelay[num]) // SRB2kart - Camera flipper
 	{
-		camrotate += 180;
-		camspeed *= 2;
-		if (camspeed > FRACUNIT)
-			camspeed = FRACUNIT;
+		camspeed = FRACUNIT;
+		if (lookback)
+		{
+			camrotate += 180;
+			lookbackdelay[num] = 2;
+		}
+		else
+			lookbackdelay[num]--;
 	}
 
 	if (mo->eflags & MFE_VERTICALFLIP)
@@ -8356,6 +8366,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	if (splitscreen == 1)
 		camspeed = (3*camspeed)/4;
 
+	if (camspeed > FRACUNIT)
+		camspeed = FRACUNIT;
+
 	if (timeover)
 		angle = mo->angle + FixedAngle(camrotate*FRACUNIT);
 	else if (leveltime < starttime)
@@ -8364,16 +8377,21 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		angle = thiscam->angle;
 	else
 	{
-		angle_t input = focusangle + FixedAngle(camrotate<<FRACBITS) - thiscam->angle;
-		boolean invert = (input > ANGLE_180);
-		if (invert)
-			input = InvAngle(input);
+		if (camspeed == FRACUNIT)
+			angle = focusangle + FixedAngle(camrotate<<FRACBITS);
+		else
+		{
+			angle_t input = focusangle + FixedAngle(camrotate<<FRACBITS) - thiscam->angle;
+			boolean invert = (input > ANGLE_180);
+			if (invert)
+				input = InvAngle(input);
 
-		input = FixedAngle(FixedMul(AngleFixed(input), camspeed));
-		if (invert)
-			input = InvAngle(input);
+			input = FixedAngle(FixedMul(AngleFixed(input), camspeed));
+			if (invert)
+				input = InvAngle(input);
 
-		angle = thiscam->angle + input;
+			angle = thiscam->angle + input;
+		}
 	}
 
 	if (!resetcalled && (leveltime > starttime && timeover != 2)
@@ -8697,8 +8715,25 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	if (twodlevel || (mo->flags2 & MF2_TWOD) || (!camstill && !timeover)) // Keep the view still...
 	{
 		G_ClipAimingPitch((INT32 *)&angle);
-		dist = thiscam->aiming - angle;
-		thiscam->aiming -= (dist>>3);
+
+		if (camspeed == FRACUNIT)
+			thiscam->aiming = angle;
+		else
+		{
+			angle_t input;
+			boolean invert;
+
+			input = thiscam->aiming - angle;
+			invert = (input > ANGLE_180);
+			if (invert)
+				input = InvAngle(input);
+
+			input = FixedAngle(FixedMul(AngleFixed(input), (5*camspeed)/16));
+			if (invert)
+				input = InvAngle(input);
+
+			thiscam->aiming -= input;
+		}
 	}
 
 	if (!resetcalled && (player->playerstate == PST_DEAD || player->playerstate == PST_REBORN))

From fd529f93634252ed254c69d4c3439cfb34ba516e Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Sun, 3 Feb 2019 23:23:41 -0500
Subject: [PATCH 65/73] fickle suggestions

---
 src/k_kart.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index 51bbccbb..b877e13f 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -5808,19 +5808,20 @@ void K_CheckSpectateStatus(void)
 	if (cv_ingamecap.value)
 	{
 		UINT8 oldrespawnlist[MAXPLAYERS];
-
-		for (i = 0; i < numjoiners; i++) // copy old table before modifying
-			oldrespawnlist[i] = respawnlist[i];
-
+		memcpy(oldrespawnlist, respawnlist, numjoiners);
 		for (i = 0; i < numjoiners; i++)
 		{
 			UINT8 pos = 0;
+			INT32 ispecwait = players[oldrespawnlist[i]].kartstuff[k_spectatewait];
 
 			for (j = 0; j < numjoiners; j++)
 			{
+				INT32 jspecwait = players[oldrespawnlist[j]].kartstuff[k_spectatewait];
 				if (j == i)
 					continue;
-				if (players[oldrespawnlist[j]].kartstuff[k_spectatewait] > players[oldrespawnlist[i]].kartstuff[k_spectatewait])
+				if (jspecwait > ispecwait)
+					pos++;
+				else if (jspecwait == ispecwait && j < i)
 					pos++;
 			}
 

From caba5cb02e187aec46e4a8d5b18e7c0b2dfe801c Mon Sep 17 00:00:00 2001
From: Sryder <sryder13@gmail.com>
Date: Mon, 4 Feb 2019 21:54:10 +0000
Subject: [PATCH 66/73] P_NetKeepAlive should also be here because it
 potentially pauses mid-game!

---
 src/p_setup.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/p_setup.c b/src/p_setup.c
index 912791cf..58e13c2e 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2857,6 +2857,9 @@ boolean P_SetupLevel(boolean skipprecip)
 			lastwipetic = nowtime;
 			if (moviemode) // make sure we save frames for the white hold too
 				M_SaveFrame();
+
+			// Keep the network alive
+			NetKeepAlive();
 		}
 
 		ranspecialwipe = 1;
@@ -3435,7 +3438,7 @@ boolean P_AddWadFile(const char *wadfilename)
 	//
 	R_AddSkins(wadnum); // faB: wadfile index in wadfiles[]
 
-	// 
+	//
 	// edit music defs
 	//
 	S_LoadMusicDefs(wadnum);

From 3b61cba1a762bfeb01bbb5899830d6ec97f5943a Mon Sep 17 00:00:00 2001
From: Sryder <sryder13@gmail.com>
Date: Mon, 4 Feb 2019 22:02:40 +0000
Subject: [PATCH 67/73] The string that got away from my memory

---
 src/d_net.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/d_net.c b/src/d_net.c
index 7e16297b..6702a60a 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -904,7 +904,7 @@ static void DebugPrintpacket(const char *header)
 				(UINT32)ExpandTics (netbuffer->u.clientpak.resendfrom));
 			break;
 		case PT_BASICKEEPALIVE:
-			fprintf(debugfile, "    wipetime\n");
+			fprintf(debugfile, "    keep alive\n");
 			break;
 		case PT_TEXTCMD:
 		case PT_TEXTCMD2:

From f46eab6b8a330353d3e6302d45021ad4f6eb427a Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Mon, 4 Feb 2019 22:00:36 -0500
Subject: [PATCH 68/73] Update version

---
 src/doomdef.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/doomdef.h b/src/doomdef.h
index 70e521b1..ab863c6f 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -150,9 +150,9 @@ extern FILE *logstream;
 // we use comprevision and compbranch instead.
 #else
 #define VERSION    100 // Game version
-#define SUBVERSION 2 // more precise version number
-#define VERSIONSTRING "v1.0.2"
-#define VERSIONSTRINGW L"v1.0.2"
+#define SUBVERSION 3 // more precise version number
+#define VERSIONSTRING "v1.0.3"
+#define VERSIONSTRINGW L"v1.0.3"
 // Hey! If you change this, add 1 to the MODVERSION below!
 // Otherwise we can't force updates!
 #endif
@@ -221,7 +221,7 @@ extern FILE *logstream;
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.1.0 is not version "1".
-#define MODVERSION 2
+#define MODVERSION 3
 
 // Filter consvars by version
 // To version config.cfg, MAJOREXECVERSION is set equal to MODVERSION automatically.

From 75b0dd3c1b0f3456fc97ee4eddf4033aa267660c Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Mon, 4 Feb 2019 22:02:34 -0500
Subject: [PATCH 69/73] Update patch.kart hash

---
 src/config.h.in | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/config.h.in b/src/config.h.in
index cb343d06..a1f5d0a6 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -37,7 +37,7 @@
  * Last updated 2015 / 05 / 03 - SRB2 v2.1.15 - srb2.srb
  * Last updated 2018 / 12 / 23 - SRB2 v2.1.22 - patch.dta
  * Last updated 2019 / 01 / 18 - Kart v1.0.2 - Main assets
- * Last updated 2019 / 01 / 15 - Kart v1.0.2 - patch.kart
+ * Last updated 2019 / 02 / 04 - Kart v1.0.3 - patch.kart
  */
 
 // Base SRB2 hashes
@@ -52,7 +52,7 @@
 #define ASSET_HASH_CHARS_KART    "e2c428347dde52858a3dacd29fc5b964"
 #define ASSET_HASH_MAPS_KART     "1335cd064656aedca359cfbb5233ac4a"
 #ifdef USE_PATCH_KART
-#define ASSET_HASH_PATCH_KART    "899aee1b63e731b7e2098406c85608b4"
+#define ASSET_HASH_PATCH_KART    "e06c1c90e5645c886026311964f8e1f5"
 #endif
 
 #endif

From 8e41f09c59a05a57c2706356f8bf75de8cf8e5fa Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Mon, 4 Feb 2019 22:22:12 -0500
Subject: [PATCH 70/73] why does this trigger ERRORMODE now?!

---
 src/f_wipe.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/f_wipe.c b/src/f_wipe.c
index eaa5a013..3c8713d1 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -97,7 +97,7 @@ static fixed_t paldiv;
   * \return	fademask_t for lump
   */
 static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
-	static char lumpname[10] = "FADEmmss";
+	static char lumpname[9] = "FADEmmss";
 	static fademask_t fm = {NULL,0,0,0,0,0};
 	lumpnum_t lumpnum;
 	UINT8 *lump, *mask;
@@ -107,7 +107,14 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
 	if (masknum > 99 || scrnnum > 99)
 		goto freemask;
 
-	sprintf(&lumpname[4], "%.2hu%.2hu", (UINT16)masknum, (UINT16)scrnnum);
+	// SRB2Kart: This suddenly triggers ERRORMODE now
+	//sprintf(&lumpname[4], "%.2hu%.2hu", (UINT16)masknum, (UINT16)scrnnum);
+
+	lumpname[4] = '0'+(masknum/10);
+	lumpname[5] = '0'+(masknum%10);
+
+	lumpname[6] = '0'+(scrnnum/10);
+	lumpname[7] = '0'+(scrnnum%10);
 
 	lumpnum = W_CheckNumForName(lumpname);
 	if (lumpnum == LUMPERROR)

From cc363086f9b8db94156aae3e13a9fb1107e3573f Mon Sep 17 00:00:00 2001
From: Alam Arias <Alam.GBC@gmail.com>
Date: Tue, 5 Feb 2019 15:36:30 -0500
Subject: [PATCH 71/73] fixup EOL in kart files

---
 src/k_kart.c | 17144 ++++++++++++++++++++++++-------------------------
 src/k_kart.h |   166 +-
 2 files changed, 8655 insertions(+), 8655 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index c72c6579..ae200f74 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1,8572 +1,8572 @@
-// SONIC ROBO BLAST 2 KART ~ ZarroTsu
-//-----------------------------------------------------------------------------
-/// \file  k_kart.c
-/// \brief SRB2kart general.
-///        All of the SRB2kart-unique stuff.
-
-#include "doomdef.h"
-#include "hu_stuff.h"
-#include "g_game.h"
-#include "m_random.h"
-#include "p_local.h"
-#include "p_slopes.h"
-#include "r_draw.h"
-#include "r_local.h"
-#include "s_sound.h"
-#include "st_stuff.h"
-#include "v_video.h"
-#include "z_zone.h"
-#include "m_misc.h"
-#include "m_cond.h"
-#include "k_kart.h"
-#include "f_finale.h"
-#include "lua_hud.h"	// For Lua hud checks
-#include "lua_hook.h"	// For MobjDamage and ShouldDamage
-
-// SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H:
-// gamespeed is cc (0 for easy, 1 for normal, 2 for hard)
-// franticitems is Frantic Mode items, bool
-// encoremode is Encore Mode (duh), bool
-// comeback is Battle Mode's karma comeback, also bool
-// battlewanted is an array of the WANTED player nums, -1 for no player in that slot
-// indirectitemcooldown is timer before anyone's allowed another Shrink/SPB
-// mapreset is set when enough players fill an empty server
-// nospectategrief is the players in-game needed to eliminate the person in last
-
-
-//{ SRB2kart Color Code
-
-#define SKIN_RAMP_LENGTH 16
-#define DEFAULT_STARTTRANSCOLOR 160
-#define NUM_PALETTE_ENTRIES 256
-
-// These should be within 14 characters to fit on the character select screen
-const char *KartColor_Names[MAXSKINCOLORS] =
-{
-	"None",           // 00 // SKINCOLOR_NONE
-	"White",          // 01 // SKINCOLOR_WHITE
-	"Silver",         // 02 // SKINCOLOR_SILVER
-	"Grey",           // 03 // SKINCOLOR_GREY
-	"Nickel",         // 04 // SKINCOLOR_NICKEL
-	"Black",          // 05 // SKINCOLOR_BLACK
-	"Sepia",          // 06 // SKINCOLOR_SEPIA
-	"Beige",          // 07 // SKINCOLOR_BEIGE
-	"Brown",          // 08 // SKINCOLOR_BROWN
-	"Leather",        // 09 // SKINCOLOR_LEATHER
-	"Salmon",         // 10 // SKINCOLOR_SALMON
-	"Pink",           // 11 // SKINCOLOR_PINK
-	"Rose",           // 12 // SKINCOLOR_ROSE
-	"Ruby",           // 13 // SKINCOLOR_RUBY
-	"Raspberry",      // 14 // SKINCOLOR_RASPBERRY
-	"Red",            // 15 // SKINCOLOR_RED
-	"Crimson",        // 16 // SKINCOLOR_CRIMSON
-	"Ketchup",        // 17 // SKINCOLOR_KETCHUP
-	"Dawn",           // 18 // SKINCOLOR_DAWN
-	"Creamsicle",     // 19 // SKINCOLOR_CREAMSICLE
-	"Orange",         // 20 // SKINCOLOR_ORANGE
-	"Pumpkin",        // 21 // SKINCOLOR_PUMPKIN
-	"Rosewood",       // 22 // SKINCOLOR_ROSEWOOD
-	"Burgundy",       // 23 // SKINCOLOR_BURGUNDY
-	"Tangerine",      // 24 // SKINCOLOR_TANGERINE
-	"Peach",          // 25 // SKINCOLOR_PEACH
-	"Caramel",        // 26 // SKINCOLOR_CARAMEL
-	"Gold",           // 27 // SKINCOLOR_GOLD
-	"Bronze",         // 28 // SKINCOLOR_BRONZE
-	"Yellow",         // 29 // SKINCOLOR_YELLOW
-	"Mustard",        // 30 // SKINCOLOR_MUSTARD
-	"Olive",          // 31 // SKINCOLOR_OLIVE
-	"Vomit",          // 32 // SKINCOLOR_VOMIT
-	"Garden",         // 33 // SKINCOLOR_GARDEN
-	"Lime",           // 34 // SKINCOLOR_LIME
-	"Tea",            // 35 // SKINCOLOR_TEA
-	"Pistachio",      // 36 // SKINCOLOR_PISTACHIO
-	"Robo-Hood",      // 37 // SKINCOLOR_ROBOHOOD
-	"Moss",           // 38 // SKINCOLOR_MOSS
-	"Mint",           // 39 // SKINCOLOR_MINT
-	"Green",          // 40 // SKINCOLOR_GREEN
-	"Pinetree",       // 41 // SKINCOLOR_PINETREE
-	"Emerald",        // 42 // SKINCOLOR_EMERALD
-	"Swamp",          // 43 // SKINCOLOR_SWAMP
-	"Dream",          // 44 // SKINCOLOR_DREAM
-	"Aqua",           // 45 // SKINCOLOR_AQUA
-	"Teal",           // 46 // SKINCOLOR_TEAL
-	"Cyan",           // 47 // SKINCOLOR_CYAN
-	"Jawz",           // 48 // SKINCOLOR_JAWZ
-	"Cerulean",       // 49 // SKINCOLOR_CERULEAN
-	"Navy",           // 50 // SKINCOLOR_NAVY
-	"Slate",          // 51 // SKINCOLOR_SLATE
-	"Steel",          // 52 // SKINCOLOR_STEEL
-	"Jet",            // 53 // SKINCOLOR_JET
-	"Sapphire",       // 54 // SKINCOLOR_SAPPHIRE
-	"Periwinkle",     // 55 // SKINCOLOR_PERIWINKLE
-	"Blue",           // 56 // SKINCOLOR_BLUE
-	"Blueberry",      // 57 // SKINCOLOR_BLUEBERRY
-	"Dusk",           // 58 // SKINCOLOR_DUSK
-	"Purple",         // 59 // SKINCOLOR_PURPLE
-	"Lavender",       // 60 // SKINCOLOR_LAVENDER
-	"Byzantium",      // 61 // SKINCOLOR_BYZANTIUM
-	"Pomegranate",    // 62 // SKINCOLOR_POMEGRANATE
-	"Lilac"           // 63 // SKINCOLOR_LILAC
-};
-
-// Color_Opposite replacement; frame setting has not been changed from 8 for most, should be done later
-const UINT8 KartColor_Opposite[MAXSKINCOLORS*2] =
-{
-	SKINCOLOR_NONE,8,         // 00 // SKINCOLOR_NONE
-	SKINCOLOR_BLACK,8,        // 01 // SKINCOLOR_WHITE
-	SKINCOLOR_NICKEL,8,       // 02 // SKINCOLOR_SILVER
-	SKINCOLOR_GREY,8,         // 03 // SKINCOLOR_GREY
-	SKINCOLOR_SILVER,8,       // 04 // SKINCOLOR_NICKEL
-	SKINCOLOR_WHITE,8,        // 05 // SKINCOLOR_BLACK
-	SKINCOLOR_LEATHER,6,      // 06 // SKINCOLOR_SEPIA
-	SKINCOLOR_BROWN,2,        // 07 // SKINCOLOR_BEIGE
-	SKINCOLOR_BEIGE,8,        // 08 // SKINCOLOR_BROWN
-	SKINCOLOR_SEPIA,8,        // 09 // SKINCOLOR_LEATHER
-	SKINCOLOR_TEA,8,          // 10 // SKINCOLOR_SALMON
-	SKINCOLOR_PISTACHIO,8,    // 11 // SKINCOLOR_PINK
-	SKINCOLOR_MOSS,8,         // 12 // SKINCOLOR_ROSE
-	SKINCOLOR_SAPPHIRE,8,     // 13 // SKINCOLOR_RUBY
-	SKINCOLOR_MINT,8,         // 14 // SKINCOLOR_RASPBERRY
-	SKINCOLOR_GREEN,6,        // 15 // SKINCOLOR_RED
-	SKINCOLOR_PINETREE,6,     // 16 // SKINCOLOR_CRIMSON
-	SKINCOLOR_MUSTARD,10,     // 17 // SKINCOLOR_KETCHUP
-	SKINCOLOR_DUSK,8,         // 18 // SKINCOLOR_DAWN
-	SKINCOLOR_PERIWINKLE,8,   // 19 // SKINCOLOR_CREAMSICLE
-	SKINCOLOR_BLUE,8,         // 20 // SKINCOLOR_ORANGE
-	SKINCOLOR_BLUEBERRY,8,    // 21 // SKINCOLOR_PUMPKIN
-	SKINCOLOR_NAVY,6,         // 22 // SKINCOLOR_ROSEWOOD
-	SKINCOLOR_JET,8,          // 23 // SKINCOLOR_BURGUNDY
-	SKINCOLOR_LIME,8,         // 24 // SKINCOLOR_TANGERINE
-	SKINCOLOR_CYAN,8,         // 25 // SKINCOLOR_PEACH
-	SKINCOLOR_CERULEAN,8,     // 26 // SKINCOLOR_CARAMEL
-	SKINCOLOR_SLATE,8,        // 27 // SKINCOLOR_GOLD
-	SKINCOLOR_STEEL,8,        // 28 // SKINCOLOR_BRONZE
-	SKINCOLOR_AQUA,8,         // 29 // SKINCOLOR_YELLOW
-	SKINCOLOR_KETCHUP,8,      // 30 // SKINCOLOR_MUSTARD
-	SKINCOLOR_TEAL,8,         // 31 // SKINCOLOR_OLIVE
-	SKINCOLOR_ROBOHOOD,8,     // 32 // SKINCOLOR_VOMIT
-	SKINCOLOR_LAVENDER,6,     // 33 // SKINCOLOR_GARDEN
-	SKINCOLOR_TANGERINE,8,    // 34 // SKINCOLOR_LIME
-	SKINCOLOR_SALMON,8,       // 35 // SKINCOLOR_TEA
-	SKINCOLOR_PINK,6,         // 36 // SKINCOLOR_PISTACHIO
-	SKINCOLOR_VOMIT,8,        // 37 // SKINCOLOR_ROBOHOOD
-	SKINCOLOR_ROSE,8,         // 38 // SKINCOLOR_MOSS
-	SKINCOLOR_RASPBERRY,8,    // 39 // SKINCOLOR_MINT
-	SKINCOLOR_RED,8,          // 40 // SKINCOLOR_GREEN
-	SKINCOLOR_CRIMSON,8,      // 41 // SKINCOLOR_PINETREE
-	SKINCOLOR_PURPLE,8,       // 42 // SKINCOLOR_EMERALD
-	SKINCOLOR_BYZANTIUM,8,    // 43 // SKINCOLOR_SWAMP
-	SKINCOLOR_POMEGRANATE,8,  // 44 // SKINCOLOR_DREAM
-	SKINCOLOR_YELLOW,8,       // 45 // SKINCOLOR_AQUA
-	SKINCOLOR_OLIVE,8,        // 46 // SKINCOLOR_TEAL
-	SKINCOLOR_PEACH,8,        // 47 // SKINCOLOR_CYAN
-	SKINCOLOR_LILAC,10,       // 48 // SKINCOLOR_JAWZ
-	SKINCOLOR_CARAMEL,8,      // 49 // SKINCOLOR_CERULEAN
-	SKINCOLOR_ROSEWOOD,8,     // 50 // SKINCOLOR_NAVY
-	SKINCOLOR_GOLD,10,        // 51 // SKINCOLOR_SLATE
-	SKINCOLOR_BRONZE,10,      // 52 // SKINCOLOR_STEEL
-	SKINCOLOR_BURGUNDY,8,     // 53 // SKINCOLOR_JET
-	SKINCOLOR_RUBY,6,         // 54 // SKINCOLOR_SAPPHIRE
-	SKINCOLOR_CREAMSICLE,8,   // 55 // SKINCOLOR_PERIWINKLE
-	SKINCOLOR_ORANGE,8,       // 56 // SKINCOLOR_BLUE
-	SKINCOLOR_PUMPKIN,8,      // 57 // SKINCOLOR_BLUEBERRY
-	SKINCOLOR_DAWN,6,         // 58 // SKINCOLOR_DUSK
-	SKINCOLOR_EMERALD,8,      // 59 // SKINCOLOR_PURPLE
-	SKINCOLOR_GARDEN,6,       // 60 // SKINCOLOR_LAVENDER
-	SKINCOLOR_SWAMP,8,        // 61 // SKINCOLOR_BYZANTIUM
-	SKINCOLOR_DREAM,8,        // 62 // SKINCOLOR_POMEGRANATE
-	SKINCOLOR_JAWZ,6          // 63 // SKINCOLOR_LILAC
-};
-
-UINT8 colortranslations[MAXSKINCOLORS][16] = {
-	{  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0}, // SKINCOLOR_NONE
-	{120, 120, 120, 120,   0,   1,   3,   4,   6,   7,  10,  14,  18,  22,  25,  28}, // SKINCOLOR_WHITE
-	{  0,   1,   2,   4,   5,   7,   8,  10,  13,  15,  18,  20,  23,  25,  28,  30}, // SKINCOLOR_SILVER
-	{  1,   3,   5,   7,   9,  11,  13,  15,  17,  19,  21,  23,  25,  27,  29,  31}, // SKINCOLOR_GREY
-	{ 12,  14,  16,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31}, // SKINCOLOR_NICKEL
-	{ 16,  17,  19,  21,  22,  24,  26,  27,  27,  28,  28,  29,  29,  30,  30,  31}, // SKINCOLOR_BLACK
-	{  0,   1,   3,   5,   7,   9,  34,  36,  38,  40,  42,  44,  60,  61,  62,  63}, // SKINCOLOR_SEPIA
-	{ 32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47}, // SKINCOLOR_BEIGE
-	{ 48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63}, // SKINCOLOR_BROWN
-	{ 51,  52,  53,  55,  56,  57,  58,  60,  61,  63,  28,  28,  29,  29,  30,  31}, // SKINCOLOR_LEATHER
-	{120, 120, 120, 121, 121, 122, 122, 123, 124, 125, 126, 128, 129, 131, 133, 135}, // SKINCOLOR_SALMON
-	{120, 121, 121, 122, 144, 145, 146, 147, 148, 149, 150, 151, 134, 136, 138, 140}, // SKINCOLOR_PINK
-	{144, 145, 146, 147, 148, 149, 150, 151, 134, 135, 136, 137, 138, 139, 140, 141}, // SKINCOLOR_ROSE
-	{121, 122, 145, 146, 147, 149, 131, 132, 133, 134, 135, 197, 197, 198, 199, 255}, // SKINCOLOR_RUBY
-	{120, 121, 122, 123, 124, 125, 126, 127, 128, 130, 131, 133, 134, 136, 137, 139}, // SKINCOLOR_RASPBERRY
-	{125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140}, // SKINCOLOR_RED
-	{130, 131, 132, 133, 134, 136, 137, 138, 139, 139, 140, 140, 141, 141, 142, 143}, // SKINCOLOR_CRIMSON
-	{104, 113, 113,  85,  86,  88, 128, 129, 131, 133, 134, 136, 138, 139, 141, 143}, // SKINCOLOR_KETCHUP
-	{120, 121, 122, 123, 124, 147, 147, 148,  90,  91,  92,  93,  94,  95, 152, 154}, // SKINCOLOR_DAWN
-	{120, 120,  80,  80,  81,  82,  83,  83,  84,  85,  86,  88,  89,  91,  93,  95}, // SKINCOLOR_CREAMSICLE
-	{ 80,  81,  82,  83,  84,  85,  86,  88,  89,  91,  94,  95, 154, 156, 158, 159}, // SKINCOLOR_ORANGE
-	{ 84,  85,  86,  87,  88,  90,  92,  93,  94,  95, 152, 153, 154, 156, 157, 159}, // SKINCOLOR_PUMPKIN
-	{ 90,  91,  92,  93,  94, 152, 153, 154, 155, 156, 157, 158, 159, 139, 141, 143}, // SKINCOLOR_ROSEWOOD
-	{ 94,  95, 152, 153, 154, 156, 157, 159, 141, 141, 141, 142, 142, 143, 143,  31}, // SKINCOLOR_BURGUNDY
-	{ 98,  98, 112, 112, 113, 113,  84,  85,  87,  89,  91,  93,  95, 153, 156, 159}, // SKINCOLOR_TANGERINE
-	{ 64,  65,  67,  68,  70,  71,  73,  74,  76,  77,  79,  48,  50,  53,  56,  59}, // SKINCOLOR_PEACH
-	{ 64,  66,  68,  70,  72,  74,  76,  78,  48,  50,  52,  54,  56,  58,  60,  62}, // SKINCOLOR_CARAMEL
-	{112, 112, 112, 113, 113, 114, 114, 115, 115, 116, 116, 117, 117, 118, 118, 119}, // SKINCOLOR_GOLD
-	{112, 113, 114, 115, 116, 117, 118, 119, 156, 157, 158, 159, 141, 141, 142, 143}, // SKINCOLOR_BRONZE
-	{ 96,  97,  98, 100, 101, 102, 104, 113, 114, 115, 116, 117, 118, 119, 156, 159}, // SKINCOLOR_YELLOW
-	{ 96,  98,  99, 112, 113, 114, 114, 106, 106, 107, 107, 108, 108, 109, 110, 111}, // SKINCOLOR_MUSTARD
-	{105, 105, 105, 106, 106, 107, 107, 108, 108, 109, 109, 110, 110, 111, 111,  31}, // SKINCOLOR_OLIVE
-	{121, 144, 145,  72,  73,  84, 114, 115, 107, 108, 109, 183, 223, 207,  30, 246}, // SKINCOLOR_VOMIT
-	{ 98,  99, 112, 101, 113, 114, 106, 179, 180, 180, 181, 182, 183, 173, 174, 175}, // SKINCOLOR_GARDEN
-	{ 96,  97,  99, 100, 102, 104, 160, 162, 164, 166, 168, 171, 223, 223, 207,  31}, // SKINCOLOR_LIME
-	{120, 120, 176, 176, 176, 177, 177, 178, 178, 179, 179, 180, 180, 181, 182, 183}, // SKINCOLOR_TEA
-	{120, 120, 176, 176, 177, 177, 178, 179, 165, 166, 167, 168, 169, 170, 171, 172}, // SKINCOLOR_PISTACHIO
-	{176, 176, 177, 178, 165, 166, 167, 167, 168, 169, 182, 182, 182, 183, 183, 183}, // SKINCOLOR_ROBOHOOD
-	{178, 178, 178, 179, 179, 180, 181, 182, 183, 172, 172, 173, 173, 174, 174, 175}, // SKINCOLOR_MOSS
-	{120, 176, 176, 176, 177, 163, 164, 165, 167, 221, 221, 222, 223, 207, 207,  31}, // SKINCOLOR_MINT
-	{160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175}, // SKINCOLOR_GREEN
-	{160, 161, 162, 164, 165, 167, 169, 170, 171, 171, 172, 173, 174, 175,  30,  31}, // SKINCOLOR_PINETREE
-	{160, 184, 184, 185, 185, 186, 186, 187, 187, 188, 188, 189, 189, 190, 191, 175}, // SKINCOLOR_EMERALD
-	{186, 187, 188, 188, 188, 189, 189, 190, 190, 191, 175, 175,  30,  30,  31,  31}, // SKINCOLOR_SWAMP
-	{120, 120,  80,  80,  81, 177, 162, 164, 228, 228, 204, 204, 205, 205, 206, 207}, // SKINCOLOR_DREAM
-	{120, 208, 208, 210, 212, 214, 220, 220, 220, 221, 221, 222, 222, 223, 223, 191}, // SKINCOLOR_AQUA
-	{210, 213, 220, 220, 220, 216, 216, 221, 221, 221, 222, 222, 223, 223, 191,  31}, // SKINCOLOR_TEAL
-	{120, 120, 208, 208, 209, 210, 211, 212, 213, 215, 216, 217, 218, 219, 222, 223}, // SKINCOLOR_CYAN
-	{120, 120, 208, 209, 210, 226, 215, 216, 217, 229, 229, 205, 205, 206, 207,  31}, // SKINCOLOR_JAWZ
-	{208, 209, 211, 213, 215, 216, 216, 217, 217, 218, 218, 219, 205, 206, 207, 207}, // SKINCOLOR_CERULEAN
-	{211, 212, 213, 215, 216, 218, 219, 205, 206, 206, 207, 207,  28,  29,  30,  31}, // SKINCOLOR_NAVY
-	{120, 120, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 204, 205, 206, 207}, // SKINCOLOR_SLATE
-	{120, 200, 200, 201, 201, 202, 202, 203, 203, 204, 204, 205, 205, 206, 207,  31}, // SKINCOLOR_STEEL
-	{225, 226, 227, 228, 229, 205, 205, 206, 207, 207,  28,  28,  29,  29,  30,  31}, // SKINCOLOR_JET
-	{208, 209, 211, 213, 215, 217, 229, 230, 232, 234, 236, 238, 240, 242, 244, 246}, // SKINCOLOR_SAPPHIRE
-	{120, 224, 225, 226, 226, 227, 228, 228, 229, 230, 231, 234, 235, 237, 239, 241}, // SKINCOLOR_PERIWINKLE
-	{224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239}, // SKINCOLOR_BLUE
-	{228, 229, 230, 231, 232, 233, 234, 235, 237, 238, 239, 240, 242, 243, 244, 245}, // SKINCOLOR_BLUEBERRY
-	{192, 192, 248, 249, 250, 251, 204, 204, 205, 205, 206, 206, 207,  29,  30,  31}, // SKINCOLOR_DUSK
-	{192, 192, 192, 193, 193, 194, 194, 195, 195, 196, 196, 197, 197, 198, 198, 199}, // SKINCOLOR_PURPLE
-	{248, 248, 248, 249, 249, 250, 250, 251, 251, 252, 252, 253, 253, 254, 254, 255}, // SKINCOLOR_LAVENDER
-	{192, 248, 249, 250, 251, 252, 253, 254, 255, 255,  29,  29,  30,  30,  31,  31}, // SKINCOLOR_BYZANTIUM
-	{144, 145, 146, 147, 148, 149, 150, 251, 251, 252, 252, 253, 254, 255,  29,  30}, // SKINCOLOR_POMEGRANATE
-	{120, 120, 120, 121, 121, 122, 122, 123, 192, 248, 249, 250, 251, 252, 253, 254}, // SKINCOLOR_LILAC
-	/* Removed Colours
-		{120, 121, 123, 124, 126, 127, 129, 130, 132, 133, 135, 136, 138, 139, 141, 143}, // old SKINCOLOR_RUBY, removed for other colors
-		{224, 225, 226, 228, 229, 231, 232, 234, 235, 237, 238, 240, 241, 243, 244, 246}, // old SKINCOLOR_SAPPHIRE, removed for other colors
-		{ 72,  73,  74,  75,  76,  77,  78,  79,  48,  49,  50,  51,  52,  53,  54,  55}, // old SKINCOLOR_CARAMEL, new Caramel was previously Shiny Caramel
-		{215, 216, 217, 218, 204, 205, 206, 237, 238, 239, 240, 241, 242, 243, 244, 245}, // old SKINCOLOR_NAVY, too similar to Jet
-		{ 80,  81,  83,  85,  86,  88,  90,  91,  93,  95, 152, 153, 154, 156, 157, 159}, // SKINCOLOR_AMBER, removed for other colors
-		{160, 160, 160, 184, 184, 184, 185, 185, 185, 186, 187, 187, 188, 188, 189, 190}, // SKINCOLOR_JADE, removed for other colors
-		{224, 225, 226, 212, 213, 213, 214, 215, 220, 221, 172, 222, 173, 223, 174, 175}, // SKINCOLOR_FROST, merged into Aqua
-		{ 96,  97,  99, 100, 102, 104, 105, 105, 106, 107, 107, 108, 109, 109, 110, 111}, // SKINCOLOR_CANARY, replaced with Mustard
-		{192, 193, 194, 195, 196, 197, 198, 199, 255, 255,  29,  29,  30,  30,  31,  31}, // SKINCOLOR_INDIGO, too similar to Byzantium
-		{  1, 145, 125,  73,  83, 114, 106, 180, 187, 168, 219, 205, 236, 206, 199, 255}, // SKINCOLOR_RAINBOW, is Vomit 2.0
-	*/
-};
-
-// Define for getting accurate color brightness readings according to how the human eye sees them.
-// https://en.wikipedia.org/wiki/Relative_luminance
-// 0.2126 to red
-// 0.7152 to green
-// 0.0722 to blue
-// (See this same define in hw_md2.c!)
-#define SETBRIGHTNESS(brightness,r,g,b) \
-	brightness = (UINT8)(((1063*((UINT16)r)/5000) + (3576*((UINT16)g)/5000) + (361*((UINT16)b)/5000)) / 3)
-
-/** \brief	Generates the rainbow colourmaps that are used when a player has the invincibility power
-
-	\param	dest_colormap	colormap to populate
-	\param	skincolor		translation color
-*/
-void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor)
-{
-	INT32 i;
-	RGBA_t color;
-	UINT8 brightness;
-	INT32 j;
-	UINT8 colorbrightnesses[16];
-	UINT16 brightdif;
-	INT32 temp;
-
-	// first generate the brightness of all the colours of that skincolour
-	for (i = 0; i < 16; i++)
-	{
-		color = V_GetColor(colortranslations[skincolor][i]);
-		SETBRIGHTNESS(colorbrightnesses[i], color.s.red, color.s.green, color.s.blue);
-	}
-
-	// next, for every colour in the palette, choose the transcolor that has the closest brightness
-	for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
-	{
-		if (i == 0 || i == 31 || i == 120) // pure black and pure white don't change
-		{
-			dest_colormap[i] = (UINT8)i;
-			continue;
-		}
-		color = V_GetColor(i);
-		SETBRIGHTNESS(brightness, color.s.red, color.s.green, color.s.blue);
-		brightdif = 256;
-		for (j = 0; j < 16; j++)
-		{
-			temp = abs((INT16)brightness - (INT16)colorbrightnesses[j]);
-			if (temp < brightdif)
-			{
-				brightdif = (UINT16)temp;
-				dest_colormap[i] = colortranslations[skincolor][j];
-			}
-		}
-	}
-}
-
-#undef SETBRIGHTNESS
-
-/**	\brief	Generates a translation colormap for Kart, to replace R_GenerateTranslationColormap in r_draw.c
-
-	\param	dest_colormap	colormap to populate
-	\param	skinnum			number of skin, TC_DEFAULT or TC_BOSS
-	\param	color			translation color
-
-	\return	void
-*/
-void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color)
-{
-	INT32 i;
-	INT32 starttranscolor;
-
-	// Handle a couple of simple special cases
-	if (skinnum == TC_BOSS
-		|| skinnum == TC_ALLWHITE
-		|| skinnum == TC_METALSONIC
-		|| skinnum == TC_BLINK
-		|| color == SKINCOLOR_NONE)
-	{
-		for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
-		{
-			if (skinnum == TC_ALLWHITE)
-				dest_colormap[i] = 0;
-			else if (skinnum == TC_BLINK)
-				dest_colormap[i] = colortranslations[color][3];
-			else
-				dest_colormap[i] = (UINT8)i;
-		}
-
-		// White!
-		if (skinnum == TC_BOSS)
-			dest_colormap[31] = 0;
-		else if (skinnum == TC_METALSONIC)
-			dest_colormap[239] = 0;
-
-		return;
-	}
-	else if (skinnum == TC_RAINBOW)
-	{
-		K_RainbowColormap(dest_colormap, color);
-		return;
-	}
-
-	starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum].starttranscolor : DEFAULT_STARTTRANSCOLOR;
-
-	// Fill in the entries of the palette that are fixed
-	for (i = 0; i < starttranscolor; i++)
-		dest_colormap[i] = (UINT8)i;
-
-	for (i = (UINT8)(starttranscolor + 16); i < NUM_PALETTE_ENTRIES; i++)
-		dest_colormap[i] = (UINT8)i;
-
-	// Build the translated ramp
-	for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-	{
-		// Sryder 2017-10-26: What was here before was most definitely not particularly readable, check above for new color translation table
-		dest_colormap[starttranscolor + i] = colortranslations[color][i];
-	}
-}
-
-/**	\brief	Pulls kart color by name, to replace R_GetColorByName in r_draw.c
-
-	\param	name	color name
-
-	\return	0
-*/
-UINT8 K_GetKartColorByName(const char *name)
-{
-	UINT8 color = (UINT8)atoi(name);
-	if (color > 0 && color < MAXSKINCOLORS)
-		return color;
-	for (color = 1; color < MAXSKINCOLORS; color++)
-		if (!stricmp(KartColor_Names[color], name))
-			return color;
-	return 0;
-}
-
-//}
-
-//{ SRB2kart Net Variables
-
-void K_RegisterKartStuff(void)
-{
-	CV_RegisterVar(&cv_sneaker);
-	CV_RegisterVar(&cv_rocketsneaker);
-	CV_RegisterVar(&cv_invincibility);
-	CV_RegisterVar(&cv_banana);
-	CV_RegisterVar(&cv_eggmanmonitor);
-	CV_RegisterVar(&cv_orbinaut);
-	CV_RegisterVar(&cv_jawz);
-	CV_RegisterVar(&cv_mine);
-	CV_RegisterVar(&cv_ballhog);
-	CV_RegisterVar(&cv_selfpropelledbomb);
-	CV_RegisterVar(&cv_grow);
-	CV_RegisterVar(&cv_shrink);
-	CV_RegisterVar(&cv_thundershield);
-	CV_RegisterVar(&cv_hyudoro);
-	CV_RegisterVar(&cv_pogospring);
-	CV_RegisterVar(&cv_kitchensink);
-
-	CV_RegisterVar(&cv_triplesneaker);
-	CV_RegisterVar(&cv_triplebanana);
-	CV_RegisterVar(&cv_decabanana);
-	CV_RegisterVar(&cv_tripleorbinaut);
-	CV_RegisterVar(&cv_quadorbinaut);
-	CV_RegisterVar(&cv_dualjawz);
-
-	CV_RegisterVar(&cv_kartminimap);
-	CV_RegisterVar(&cv_kartcheck);
-	CV_RegisterVar(&cv_kartinvinsfx);
-	CV_RegisterVar(&cv_kartspeed);
-	CV_RegisterVar(&cv_kartbumpers);
-	CV_RegisterVar(&cv_kartfrantic);
-	CV_RegisterVar(&cv_kartcomeback);
-	CV_RegisterVar(&cv_kartencore);
-	CV_RegisterVar(&cv_kartvoterulechanges);
-	CV_RegisterVar(&cv_kartspeedometer);
-	CV_RegisterVar(&cv_kartvoices);
-	CV_RegisterVar(&cv_karteliminatelast);
-	CV_RegisterVar(&cv_votetime);
-
-	CV_RegisterVar(&cv_kartdebugitem);
-	CV_RegisterVar(&cv_kartdebugamount);
-	CV_RegisterVar(&cv_kartdebugshrink);
-	CV_RegisterVar(&cv_kartdebugdistribution);
-	CV_RegisterVar(&cv_kartdebughuddrop);
-
-	CV_RegisterVar(&cv_kartdebugcheckpoint);
-	CV_RegisterVar(&cv_kartdebugnodes);
-}
-
-//}
-
-boolean K_IsPlayerLosing(player_t *player)
-{
-	INT32 winningpos = 1;
-	UINT8 i, pcount = 0;
-
-	if (player->kartstuff[k_position] == 1)
-		return false;
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i] || players[i].spectator)
-			continue;
-		if (players[i].kartstuff[k_position] > pcount)
-			pcount = players[i].kartstuff[k_position];
-	}
-
-	if (pcount <= 1)
-		return false;
-
-	winningpos = pcount/2;
-	if (pcount % 2) // any remainder?
-		winningpos++;
-
-	return (player->kartstuff[k_position] > winningpos);
-}
-
-boolean K_IsPlayerWanted(player_t *player)
-{
-	UINT8 i;
-	if (!(G_BattleGametype()))
-		return false;
-	for (i = 0; i < 4; i++)
-	{
-		if (battlewanted[i] == -1)
-			break;
-		if (player == &players[battlewanted[i]])
-			return true;
-	}
-	return false;
-}
-
-//{ SRB2kart Roulette Code - Position Based
-
-#define NUMKARTODDS 	80
-
-// Less ugly 2D arrays
-static INT32 K_KartItemOddsRace[NUMKARTRESULTS][10] =
-{
-				//P-Odds	 0  1  2  3  4  5  6  7  8  9
-			   /*Sneaker*/ {20, 0, 0, 4, 6, 6, 0, 0, 0, 0 }, // Sneaker
-		/*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 1, 3, 5, 3, 0 }, // Rocket Sneaker
-		 /*Invincibility*/ { 0, 0, 0, 0, 0, 1, 4, 6,14, 0 }, // Invincibility
-				/*Banana*/ { 0,10, 4, 2, 1, 0, 0, 0, 0, 0 }, // Banana
-		/*Eggman Monitor*/ { 0, 3, 2, 1, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor
-			  /*Orbinaut*/ { 0, 8, 6, 4, 2, 0, 0, 0, 0, 0 }, // Orbinaut
-				  /*Jawz*/ { 0, 0, 3, 2, 1, 1, 0, 0, 0, 0 }, // Jawz
-				  /*Mine*/ { 0, 0, 2, 2, 1, 0, 0, 0, 0, 0 }, // Mine
-			   /*Ballhog*/ { 0, 0, 0, 2, 1, 0, 0, 0, 0, 0 }, // Ballhog
-   /*Self-Propelled Bomb*/ { 0, 0, 1, 2, 3, 4, 2, 2, 0,20 }, // Self-Propelled Bomb
-				  /*Grow*/ { 0, 0, 0, 0, 0, 1, 3, 5, 3, 0 }, // Grow
-				/*Shrink*/ { 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 }, // Shrink
-		/*Thunder Shield*/ { 0, 1, 2, 0, 0, 0, 0, 0, 0, 0 }, // Thunder Shield
-			   /*Hyudoro*/ { 0, 0, 0, 0, 1, 2, 1, 0, 0, 0 }, // Hyudoro
-		   /*Pogo Spring*/ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring
-		  /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink
-			/*Sneaker x3*/ { 0, 0, 0, 0, 3, 7, 9, 2, 0, 0 }, // Sneaker x3
-			 /*Banana x3*/ { 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, // Banana x3
-			/*Banana x10*/ { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, // Banana x10
-		   /*Orbinaut x3*/ { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // Orbinaut x3
-		   /*Orbinaut x4*/ { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // Orbinaut x4
-			   /*Jawz x2*/ { 0, 0, 0, 1, 2, 0, 0, 0, 0, 0 }  // Jawz x2
-};
-
-static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][6] =
-{
-				//P-Odds	 0  1  2  3  4  5
-			   /*Sneaker*/ { 3, 2, 2, 2, 0, 2 }, // Sneaker
-		/*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 0 }, // Rocket Sneaker
-		 /*Invincibility*/ { 0, 1, 2, 3, 4, 2 }, // Invincibility
-				/*Banana*/ { 2, 1, 0, 0, 0, 0 }, // Banana
-		/*Eggman Monitor*/ { 1, 1, 0, 0, 0, 0 }, // Eggman Monitor
-			  /*Orbinaut*/ { 6, 2, 1, 0, 0, 0 }, // Orbinaut
-				  /*Jawz*/ { 3, 3, 3, 2, 0, 2 }, // Jawz
-				  /*Mine*/ { 2, 3, 3, 1, 0, 2 }, // Mine
-			   /*Ballhog*/ { 0, 1, 2, 1, 0, 2 }, // Ballhog
-   /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 0 }, // Self-Propelled Bomb
-				  /*Grow*/ { 0, 0, 1, 2, 4, 2 }, // Grow
-				/*Shrink*/ { 0, 0, 0, 0, 0, 0 }, // Shrink
-		/*Thunder Shield*/ { 0, 0, 0, 0, 0, 0 }, // Thunder Shield
-			   /*Hyudoro*/ { 1, 1, 0, 0, 0, 0 }, // Hyudoro
-		   /*Pogo Spring*/ { 1, 1, 0, 0, 0, 0 }, // Pogo Spring
-		  /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0 }, // Kitchen Sink
-			/*Sneaker x3*/ { 0, 0, 0, 2, 4, 2 }, // Sneaker x3
-			 /*Banana x3*/ { 1, 2, 1, 0, 0, 0 }, // Banana x3
-			/*Banana x10*/ { 0, 0, 1, 1, 0, 2 }, // Banana x10
-		   /*Orbinaut x3*/ { 0, 1, 2, 1, 0, 0 }, // Orbinaut x3
-		   /*Orbinaut x4*/ { 0, 0, 1, 3, 4, 2 }, // Orbinaut x4
-			   /*Jawz x2*/ { 0, 0, 1, 2, 4, 2 }  // Jawz x2
-};
-
-/**	\brief	Item Roulette for Kart
-
-	\param	player		player
-	\param	getitem		what item we're looking for
-
-	\return	void
-*/
-static void K_KartGetItemResult(player_t *player, SINT8 getitem)
-{
-	switch (getitem)
-	{
-		// Special roulettes first, then the generic ones are handled by default
-		case KRITEM_TRIPLESNEAKER: // Sneaker x3
-			player->kartstuff[k_itemtype] = KITEM_SNEAKER;
-			player->kartstuff[k_itemamount] = 3;
-			break;
-		case KRITEM_TRIPLEBANANA: // Banana x3
-			player->kartstuff[k_itemtype] = KITEM_BANANA;
-			player->kartstuff[k_itemamount] = 3;
-			break;
-		case KRITEM_TENFOLDBANANA: // Banana x10
-			player->kartstuff[k_itemtype] = KITEM_BANANA;
-			player->kartstuff[k_itemamount] = 10;
-			break;
-		case KRITEM_TRIPLEORBINAUT: // Orbinaut x3
-			player->kartstuff[k_itemtype] = KITEM_ORBINAUT;
-			player->kartstuff[k_itemamount] = 3;
-			break;
-		case KRITEM_QUADORBINAUT: // Orbinaut x4
-			player->kartstuff[k_itemtype] = KITEM_ORBINAUT;
-			player->kartstuff[k_itemamount] = 4;
-			break;
-		case KRITEM_DUALJAWZ: // Jawz x2
-			player->kartstuff[k_itemtype] = KITEM_JAWZ;
-			player->kartstuff[k_itemamount] = 2;
-			break;
-		case KITEM_SPB:
-		case KITEM_SHRINK: // Indirect items
-			indirectitemcooldown = 20*TICRATE;
-			/* FALLTHRU */
-		default:
-			if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback)
-			{
-				if (getitem != 0)
-					CONS_Printf("ERROR: P_KartGetItemResult - Item roulette gave bad item (%d) :(\n", getitem);
-				player->kartstuff[k_itemtype] = KITEM_SAD;
-			}
-			else
-				player->kartstuff[k_itemtype] = getitem;
-			player->kartstuff[k_itemamount] = 1;
-			break;
-	}
-}
-
-/**	\brief	Item Roulette for Kart
-
-	\param	player	player object passed from P_KartPlayerThink
-
-	\return	void
-*/
-
-static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
-{
-	const INT32 distvar = (64*14);
-	INT32 newodds;
-	INT32 i;
-	UINT8 pingame = 0, pexiting = 0, pinvin = 0;
-	SINT8 first = -1, second = -1;
-	INT32 secondist = 0;
-	boolean itemenabled[NUMKARTRESULTS] = {
-		cv_sneaker.value,
-		cv_rocketsneaker.value,
-		cv_invincibility.value,
-		cv_banana.value,
-		cv_eggmanmonitor.value,
-		cv_orbinaut.value,
-		cv_jawz.value,
-		cv_mine.value,
-		cv_ballhog.value,
-		cv_selfpropelledbomb.value,
-		cv_grow.value,
-		cv_shrink.value,
-		cv_thundershield.value,
-		cv_hyudoro.value,
-		cv_kitchensink.value,
-		cv_triplesneaker.value,
-		cv_triplebanana.value,
-		cv_decabanana.value,
-		cv_tripleorbinaut.value,
-		cv_quadorbinaut.value,
-		cv_dualjawz.value
-	};
-
-	if (!itemenabled[item] && !modeattacking)
-		return 0;
-
-	if (G_BattleGametype())
-		newodds = K_KartItemOddsBattle[item-1][pos];
-	else
-		newodds = K_KartItemOddsRace[item-1][pos];
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i] || players[i].spectator)
-			continue;
-		if (!G_BattleGametype() || players[i].kartstuff[k_bumper])
-			pingame++;
-		if (players[i].exiting)
-			pexiting++;
-		if (players[i].mo)
-		{
-			if (players[i].kartstuff[k_itemtype] == KITEM_INVINCIBILITY
-				|| players[i].kartstuff[k_itemtype] == KITEM_GROW
-				|| players[i].kartstuff[k_invincibilitytimer]
-				|| players[i].kartstuff[k_growshrinktimer] > 0)
-				pinvin++;
-			if (!G_BattleGametype())
-			{
-				if (players[i].kartstuff[k_position] == 1 && first == -1)
-					first = i;
-				if (players[i].kartstuff[k_position] == 2 && second == -1)
-					second = i;
-			}
-		}
-	}
-
-	if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB
-	{
-		secondist = P_AproxDistance(P_AproxDistance(players[first].mo->x - players[second].mo->x,
-													players[first].mo->y - players[second].mo->y),
-													players[first].mo->z - players[second].mo->z) / mapobjectscale;
-		if (franticitems)
-			secondist = (15 * secondist) / 14;
-		secondist = ((28 + (8-pingame)) * secondist) / 28;
-	}
-
-	// POWERITEMODDS handles all of the "frantic item" related functionality, for all of our powerful items.
-	// First, it multiplies it by 2 if franticitems is true; easy-peasy.
-	// Next, it multiplies it again if it's in SPB mode and 2nd needs to apply pressure to 1st.
-	// Then, it multiplies it further if there's less than 5 players in game.
-	// This is done to make low player count races more fair & interesting. (2P normal would be about halfway between 8P normal and 8P frantic)
-	// Lastly, it *divides* it by your mashed value, which was determined in K_KartItemRoulette, to punish those who are impatient.
-#define POWERITEMODDS(odds) \
-	if (franticitems) \
-		odds <<= 1; \
-	odds = FixedMul(odds<<FRACBITS, FRACUNIT + ((8-pingame) * (FRACUNIT/25))) >> FRACBITS; \
-	if (mashed > 0) \
-		odds = FixedDiv(odds<<FRACBITS, FRACUNIT + mashed) >> FRACBITS \
-
-	switch (item)
-	{
-		case KITEM_INVINCIBILITY:
-		case KITEM_GROW:
-			if (pinvin >= max(1, (pingame+2) / 4))
-				newodds = 0;
-			else
-			/* FALLTHRU */
-		case KITEM_ROCKETSNEAKER:
-		case KITEM_JAWZ:
-		case KITEM_MINE:
-		case KITEM_BALLHOG:
-		case KITEM_THUNDERSHIELD:
-		case KRITEM_TRIPLESNEAKER:
-		case KRITEM_TRIPLEBANANA:
-		case KRITEM_TENFOLDBANANA:
-		case KRITEM_TRIPLEORBINAUT:
-		case KRITEM_QUADORBINAUT:
-		case KRITEM_DUALJAWZ:
-			POWERITEMODDS(newodds);
-			break;
-		case KITEM_SPB:
-			//POWERITEMODDS(newodds);
-			if (((indirectitemcooldown > 0) || (pexiting > 0) || (secondist/distvar < 3))
-				&& (pos != 9)) // Force SPB
-				newodds = 0;
-			else
-				newodds *= min((secondist/distvar)-4, 3);
-			break;
-		case KITEM_SHRINK:
-			POWERITEMODDS(newodds);
-			if ((indirectitemcooldown > 0) || (pingame-1 <= pexiting))
-				newodds = 0;
-			break;
-		default:
-			break;
-	}
-
-#undef POWERITEMODDS
-
-	return newodds;
-}
-
-//{ SRB2kart Roulette Code - Distance Based, no waypoints
-
-static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT32 bestbumper, boolean spbrush, boolean dontforcespb)
-{
-	const INT32 distvar = (64*14);
-	INT32 i;
-	INT32 pdis = 0, useodds = 0;
-	UINT8 disttable[14];
-	UINT8 distlen = 0;
-	boolean oddsvalid[10];
-
-	for (i = 0; i < 10; i++)
-	{
-		INT32 j;
-		boolean available = false;
-
-		if (G_BattleGametype() && i > 5)
-		{
-			oddsvalid[i] = false;
-			break;
-		}
-
-		for (j = 0; j < NUMKARTRESULTS; j++)
-		{
-			if (K_KartGetItemOdds(i, j, mashed) > 0)
-			{
-				available = true;
-				break;
-			}
-		}
-
-		oddsvalid[i] = available;
-	}
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (playeringame[i] && !players[i].spectator && players[i].mo
-			&& players[i].kartstuff[k_position] < player->kartstuff[k_position])
-			pdis += P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x,
-													players[i].mo->y - player->mo->y),
-													players[i].mo->z - player->mo->z) / mapobjectscale
-													* (pingame - players[i].kartstuff[k_position])
-													/ max(1, ((pingame - 1) * (pingame + 1) / 3));
-	}
-
-#define SETUPDISTTABLE(odds, num) \
-	for (i = num; i; --i) disttable[distlen++] = odds
-
-	if (G_BattleGametype()) // Battle Mode
-	{
-		if (oddsvalid[0]) SETUPDISTTABLE(0,1);
-		if (oddsvalid[1]) SETUPDISTTABLE(1,1);
-		if (oddsvalid[2]) SETUPDISTTABLE(2,1);
-		if (oddsvalid[3]) SETUPDISTTABLE(3,1);
-		if (oddsvalid[4]) SETUPDISTTABLE(4,1);
-
-		if (player->kartstuff[k_roulettetype] == 1 && oddsvalid[5]) // 5 is the extreme odds of player-controlled "Karma" items
-			useodds = 5;
-		else
-		{
-			SINT8 wantedpos = (bestbumper-player->kartstuff[k_bumper]); // 0 is the best player's bumper count, 1 is a bumper below best, 2 is two bumpers below, etc
-			if (K_IsPlayerWanted(player))
-				wantedpos++;
-			if (wantedpos > 4) // Don't run off into karma items
-				wantedpos = 4;
-			if (wantedpos < 0) // Don't go below somehow
-				wantedpos = 0;
-			useodds = disttable[(wantedpos * distlen) / 5];
-		}
-	}
-	else
-	{
-		if (oddsvalid[1]) SETUPDISTTABLE(1,1);
-		if (oddsvalid[2]) SETUPDISTTABLE(2,1);
-		if (oddsvalid[3]) SETUPDISTTABLE(3,1);
-		if (oddsvalid[4]) SETUPDISTTABLE(4,2);
-		if (oddsvalid[5]) SETUPDISTTABLE(5,2);
-		if (oddsvalid[6]) SETUPDISTTABLE(6,3);
-		if (oddsvalid[7]) SETUPDISTTABLE(7,3);
-		if (oddsvalid[8]) SETUPDISTTABLE(8,1);
-
-		if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items
-			pdis = (15 * pdis) / 14;
-
-		if (spbrush) // SPB Rush Mode: It's 2nd place's job to catch-up items and make 1st place's job hell
-			pdis = (3 * pdis) / 2;
-
-		pdis = ((28 + (8-pingame)) * pdis) / 28;
-
-		if (pingame == 1 && oddsvalid[0])					// Record Attack, or just alone
-			useodds = 0;
-		else if (pdis <= 0)									// (64*14) *  0 =     0
-			useodds = disttable[0];
-		else if (player->kartstuff[k_position] == 2 && pdis > (distvar*6)
-			&& spbplace == -1 && !indirectitemcooldown && !dontforcespb
-			&& oddsvalid[9])								// Force SPB in 2nd
-			useodds = 9;
-		else if (pdis > distvar * ((12 * distlen) / 14))	// (64*14) * 12 = 10752
-			useodds = disttable[distlen-1];
-		else
-		{
-			for (i = 1; i < 13; i++)
-			{
-				if (pdis <= distvar * ((i * distlen) / 14))
-				{
-					useodds = disttable[((i * distlen) / 14)];
-					break;
-				}
-			}
-		}
-	}
-
-#undef SETUPDISTTABLE
-
-	//CONS_Printf("Got useodds %d. (position: %d, distance: %d)\n", useodds, player->kartstuff[k_position], pdis);
-
-	return useodds;
-}
-
-static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
-{
-	INT32 i;
-	UINT8 pingame = 0;
-	UINT8 roulettestop;
-	INT32 useodds = 0;
-	INT32 spawnchance[NUMKARTRESULTS * NUMKARTODDS];
-	INT32 chance = 0, numchoices = 0;
-	INT32 bestbumper = 0;
-	fixed_t mashed = 0;
-	boolean dontforcespb = false;
-
-	// This makes the roulette cycle through items - if this is 0, you shouldn't be here.
-	if (player->kartstuff[k_itemroulette])
-		player->kartstuff[k_itemroulette]++;
-	else
-		return;
-
-	// Gotta check how many players are active at this moment.
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i] || players[i].spectator)
-			continue;
-		pingame++;
-		if (players[i].exiting)
-			dontforcespb = true;
-		if (players[i].kartstuff[k_bumper] > bestbumper)
-			bestbumper = players[i].kartstuff[k_bumper];
-	}
-
-	// This makes the roulette produce the random noises.
-	if ((player->kartstuff[k_itemroulette] % 3) == 1 && P_IsLocalPlayer(player))
-	{
-#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->kartstuff[k_itemroulette] / 3) % 8));
-		if (splitscreen)
-		{
-			if (players[displayplayer].kartstuff[k_itemroulette])
-			{
-				if (player == &players[displayplayer])
-					PLAYROULETTESND;
-			}
-			else if (players[secondarydisplayplayer].kartstuff[k_itemroulette])
-			{
-				if (player == &players[secondarydisplayplayer])
-					PLAYROULETTESND;
-			}
-			else if (players[thirddisplayplayer].kartstuff[k_itemroulette] && splitscreen > 1)
-			{
-				if (player == &players[thirddisplayplayer])
-					PLAYROULETTESND;
-			}
-			else if (players[fourthdisplayplayer].kartstuff[k_itemroulette] && splitscreen > 2)
-			{
-				if (player == &players[fourthdisplayplayer])
-					PLAYROULETTESND;
-			}
-		}
-		else
-			PLAYROULETTESND;
-#undef PLAYROULETTESND
-	}
-
-	roulettestop = TICRATE + (3*(pingame - player->kartstuff[k_position]));
-
-	// If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item.
-	// I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think.
-	// Finally, if you get past this check, now you can actually start calculating what item you get.
-	if ((cmd->buttons & BT_ATTACK) && !(player->kartstuff[k_eggmanheld] || player->kartstuff[k_itemheld]) && player->kartstuff[k_itemroulette] >= roulettestop && !modeattacking)
-	{
-		// Mashing reduces your chances for the good items
-		mashed = FixedDiv((player->kartstuff[k_itemroulette])*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT;
-	}
-	else if (!(player->kartstuff[k_itemroulette] >= (TICRATE*3)))
-		return;
-
-	if (cmd->buttons & BT_ATTACK)
-		player->pflags |= PF_ATTACKDOWN;
-
-	if (player->kartstuff[k_roulettetype] == 2) // Fake items
-	{
-		player->kartstuff[k_eggmanexplode] = 4*TICRATE;
-		//player->kartstuff[k_itemblink] = TICRATE;
-		//player->kartstuff[k_itemblinkmode] = 1;
-		player->kartstuff[k_itemroulette] = 0;
-		player->kartstuff[k_roulettetype] = 0;
-		if (P_IsLocalPlayer(player))
-			S_StartSound(NULL, sfx_itrole);
-		return;
-	}
-
-	if (cv_kartdebugitem.value != 0 && !modeattacking)
-	{
-		K_KartGetItemResult(player, cv_kartdebugitem.value);
-		player->kartstuff[k_itemamount] = cv_kartdebugamount.value;
-		player->kartstuff[k_itemblink] = TICRATE;
-		player->kartstuff[k_itemblinkmode] = 2;
-		player->kartstuff[k_itemroulette] = 0;
-		player->kartstuff[k_roulettetype] = 0;
-		if (P_IsLocalPlayer(player))
-			S_StartSound(NULL, sfx_dbgsal);
-		return;
-	}
-
-	// Initializes existing spawnchance values
-	for (i = 0; i < (NUMKARTRESULTS * NUMKARTODDS); i++)
-		spawnchance[i] = 0;
-
-	// Split into another function for a debug function below
-	useodds = K_FindUseodds(player, mashed, pingame, bestbumper, (spbplace != -1 && player->kartstuff[k_position] == spbplace+1), dontforcespb);
-
-#define SETITEMRESULT(itemnum) \
-	for (chance = 0; chance < K_KartGetItemOdds(useodds, itemnum, mashed); chance++) \
-		spawnchance[numchoices++] = itemnum
-
-	for (i = 1; i < NUMKARTRESULTS; i++)
-		SETITEMRESULT(i);
-
-#undef SETITEMRESULT
-
-	// Award the player whatever power is rolled
-	if (numchoices > 0)
-		K_KartGetItemResult(player, spawnchance[P_RandomKey(numchoices)]);
-	else
-	{
-		player->kartstuff[k_itemtype] = KITEM_SAD;
-		player->kartstuff[k_itemamount] = 1;
-	}
-
-	if (P_IsLocalPlayer(player))
-		S_StartSound(NULL, ((player->kartstuff[k_roulettetype] == 1) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf)));
-
-	player->kartstuff[k_itemblink] = TICRATE;
-	player->kartstuff[k_itemblinkmode] = ((player->kartstuff[k_roulettetype] == 1) ? 2 : (mashed ? 1 : 0));
-
-	player->kartstuff[k_itemroulette] = 0; // Since we're done, clear the roulette number
-	player->kartstuff[k_roulettetype] = 0; // This too
-}
-
-//}
-
-//{ SRB2kart p_user.c Stuff
-
-static fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against)
-{
-	fixed_t weight = 5<<FRACBITS;
-
-	switch (mobj->type)
-	{
-		case MT_PLAYER:
-			if (!mobj->player)
-				break;
-			if (against->player && !against->player->kartstuff[k_spinouttimer] && mobj->player->kartstuff[k_spinouttimer])
-				weight = 0; // Do not bump
-			else
-			{
-				weight = (mobj->player->kartweight)<<FRACBITS;
-				if (mobj->player->speed > K_GetKartSpeed(mobj->player, false))
-					weight += (mobj->player->speed - K_GetKartSpeed(mobj->player, false))/8;
-			}
-			break;
-		case MT_FALLINGROCK:
-			if (against->player)
-			{
-				if (against->player->kartstuff[k_invincibilitytimer]
-					|| against->player->kartstuff[k_growshrinktimer] > 0)
-					weight = 0;
-				else
-					weight = (against->player->kartweight)<<FRACBITS;
-			}
-			break;
-		case MT_ORBINAUT:
-		case MT_ORBINAUT_SHIELD:
-			if (against->player)
-				weight = (against->player->kartweight)<<FRACBITS;
-			break;
-		case MT_JAWZ:
-		case MT_JAWZ_DUD:
-		case MT_JAWZ_SHIELD:
-			if (against->player)
-				weight = (against->player->kartweight+3)<<FRACBITS;
-			else
-				weight = 8<<FRACBITS;
-			break;
-		default:
-			break;
-	}
-
-	return weight;
-}
-
-void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid)
-{
-	mobj_t *fx;
-	fixed_t momdifx, momdify;
-	fixed_t distx, disty;
-	fixed_t dot, p;
-	fixed_t mass1, mass2;
-
-	if (!mobj1 || !mobj2)
-		return;
-
-	// Don't bump when you're being reborn
-	if ((mobj1->player && mobj1->player->playerstate != PST_LIVE)
-		|| (mobj2->player && mobj2->player->playerstate != PST_LIVE))
-		return;
-
-	if ((mobj1->player && mobj1->player->kartstuff[k_respawn])
-		|| (mobj2->player && mobj2->player->kartstuff[k_respawn]))
-		return;
-
-	{ // Don't bump if you're flashing
-		INT32 flash;
-
-		flash = K_GetKartFlashing(mobj1->player);
-		if (mobj1->player && mobj1->player->powers[pw_flashing] > 0 && mobj1->player->powers[pw_flashing] < flash)
-		{
-			if (mobj1->player->powers[pw_flashing] < flash-1)
-				mobj1->player->powers[pw_flashing]++;
-			return;
-		}
-
-		flash = K_GetKartFlashing(mobj2->player);
-		if (mobj2->player && mobj2->player->powers[pw_flashing] > 0 && mobj2->player->powers[pw_flashing] < flash)
-		{
-			if (mobj2->player->powers[pw_flashing] < flash-1)
-				mobj2->player->powers[pw_flashing]++;
-			return;
-		}
-	}
-
-	// Don't bump if you've recently bumped
-	if (mobj1->player && mobj1->player->kartstuff[k_justbumped])
-	{
-		mobj1->player->kartstuff[k_justbumped] = bumptime;
-		return;
-	}
-
-	if (mobj2->player && mobj2->player->kartstuff[k_justbumped])
-	{
-		mobj2->player->kartstuff[k_justbumped] = bumptime;
-		return;
-	}
-
-	mass1 = K_GetMobjWeight(mobj1, mobj2);
-
-	if (solid == true && mass1 > 0)
-		mass2 = mass1;
-	else
-		mass2 = K_GetMobjWeight(mobj2, mobj1);
-
-	momdifx = mobj1->momx - mobj2->momx;
-	momdify = mobj1->momy - mobj2->momy;
-
-	// 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))
-	{
-		fixed_t momdiflength = P_AproxDistance(momdifx, momdify);
-		fixed_t normalisedx = FixedDiv(momdifx, momdiflength);
-		fixed_t normalisedy = FixedDiv(momdify, momdiflength);
-		momdifx = FixedMul((25*mapobjectscale), normalisedx);
-		momdify = FixedMul((25*mapobjectscale), normalisedy);
-	}
-
-	// Adds the OTHER player's momentum, so that it reduces the chance of you being "inside" the other object
-	distx = (mobj1->x + mobj2->momx) - (mobj2->x + mobj1->momx);
-	disty = (mobj1->y + mobj2->momy) - (mobj2->y + mobj1->momy);
-
-	{ // Don't allow dist to get WAY too low, that it pushes you stupidly huge amounts, or backwards...
-		fixed_t dist = P_AproxDistance(distx, disty);
-		fixed_t nx = FixedDiv(distx, dist);
-		fixed_t ny = FixedDiv(disty, dist);
-
-		if (P_AproxDistance(distx, disty) < (3*mobj1->radius)/4)
-		{
-			distx = FixedMul((3*mobj1->radius)/4, nx);
-			disty = FixedMul((3*mobj1->radius)/4, ny);
-		}
-
-		if (P_AproxDistance(distx, disty) < (3*mobj2->radius)/4)
-		{
-			distx = FixedMul((3*mobj2->radius)/4, nx);
-			disty = FixedMul((3*mobj2->radius)/4, ny);
-		}
-	}
-
-	if (distx == 0 && disty == 0)
-	{
-		// if there's no distance between the 2, they're directly on top of each other, don't run this
-		return;
-	}
-
-	dot = FixedMul(momdifx, distx) + FixedMul(momdify, disty);
-
-	if (dot >= 0)
-	{
-		// They're moving away from each other
-		return;
-	}
-
-	p = FixedDiv(dot, FixedMul(distx, distx)+FixedMul(disty, disty));
-
-	if (bounce == true && mass2 > 0) // Perform a Goomba Bounce.
-		mobj1->momz = -mobj1->momz;
-	else
-	{
-		fixed_t newz = mobj1->momz;
-		if (mass2 > 0)
-			mobj1->momz = mobj2->momz;
-		if (mass1 > 0 && solid == false)
-			mobj2->momz = newz;
-	}
-
-	if (mass2 > 0)
-	{
-		mobj1->momx = mobj1->momx - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), p), distx);
-		mobj1->momy = mobj1->momy - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), p), disty);
-	}
-
-	if (mass1 > 0 && solid == false)
-	{
-		mobj2->momx = mobj2->momx - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), p), -distx);
-		mobj2->momy = mobj2->momy - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), p), -disty);
-	}
-
-	// Do the bump fx when we've CONFIRMED we can bump.
-	S_StartSound(mobj1, sfx_s3k49);
-
-	fx = P_SpawnMobj(mobj1->x/2 + mobj2->x/2, mobj1->y/2 + mobj2->y/2, mobj1->z/2 + mobj2->z/2, MT_BUMP);
-	if (mobj1->eflags & MFE_VERTICALFLIP)
-		fx->eflags |= MFE_VERTICALFLIP;
-	else
-		fx->eflags &= ~MFE_VERTICALFLIP;
-	P_SetScale(fx, mobj1->scale);
-
-	// 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)
-	{
-		mobj1->player->rmomx = mobj1->momx - mobj1->player->cmomx;
-		mobj1->player->rmomy = mobj1->momy - mobj1->player->cmomy;
-		mobj1->player->kartstuff[k_justbumped] = bumptime;
-		if (mobj1->player->kartstuff[k_spinouttimer])
-		{
-			mobj1->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1;
-			mobj1->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj1->player->kartstuff[k_spinouttimer]);
-		}
-	}
-
-	if (mobj2->player)
-	{
-		mobj2->player->rmomx = mobj2->momx - mobj2->player->cmomx;
-		mobj2->player->rmomy = mobj2->momy - mobj2->player->cmomy;
-		mobj2->player->kartstuff[k_justbumped] = bumptime;
-		if (mobj2->player->kartstuff[k_spinouttimer])
-		{
-			mobj2->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1;
-			mobj2->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj2->player->kartstuff[k_spinouttimer]);
-		}
-	}
-}
-
-/**	\brief	Checks that the player is on an offroad subsector for realsies
-
-	\param	mo	player mobj object
-
-	\return	boolean
-*/
-static UINT8 K_CheckOffroadCollide(mobj_t *mo, sector_t *sec)
-{
-	UINT8 i;
-	sector_t *sec2;
-
-	I_Assert(mo != NULL);
-	I_Assert(!P_MobjWasRemoved(mo));
-
-	sec2 = P_ThingOnSpecial3DFloor(mo);
-
-	for (i = 2; i < 5; i++)
-	{
-		if ((sec2 && GETSECSPECIAL(sec2->special, 1) == i)
-			|| (P_IsObjectOnRealGround(mo, sec) && GETSECSPECIAL(sec->special, 1) == i))
-			return i-1;
-	}
-
-	return 0;
-}
-
-/**	\brief	Updates the Player's offroad value once per frame
-
-	\param	player	player object passed from K_KartPlayerThink
-
-	\return	void
-*/
-static void K_UpdateOffroad(player_t *player)
-{
-	fixed_t offroad;
-	sector_t *nextsector = R_PointInSubsector(
-		player->mo->x + player->mo->momx*2, player->mo->y + player->mo->momy*2)->sector;
-	UINT8 offroadstrength = K_CheckOffroadCollide(player->mo, nextsector);
-
-	// If you are in offroad, a timer starts.
-	if (offroadstrength)
-	{
-		if (K_CheckOffroadCollide(player->mo, player->mo->subsector->sector) && player->kartstuff[k_offroad] == 0)
-			player->kartstuff[k_offroad] = (TICRATE/2);
-
-		if (player->kartstuff[k_offroad] > 0)
-		{
-			offroad = (offroadstrength << FRACBITS) / (TICRATE/2);
-
-			//if (player->kartstuff[k_growshrinktimer] > 1) // grow slows down half as fast
-			//	offroad /= 2;
-
-			player->kartstuff[k_offroad] += offroad;
-		}
-
-		if (player->kartstuff[k_offroad] > (offroadstrength << FRACBITS))
-			player->kartstuff[k_offroad] = (offroadstrength << FRACBITS);
-	}
-	else
-		player->kartstuff[k_offroad] = 0;
-}
-
-// These have to go earlier than its sisters because of K_RespawnChecker...
-void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master)
-{
-	// flipping
-	mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP)|(master->eflags & MFE_VERTICALFLIP);
-	// visibility (usually for hyudoro)
-	mo->flags2 = (mo->flags2 & ~MF2_DONTDRAW)|(master->flags2 & MF2_DONTDRAW);
-	mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP1)|(master->eflags & MFE_DRAWONLYFORP1);
-	mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP2)|(master->eflags & MFE_DRAWONLYFORP2);
-	mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP3)|(master->eflags & MFE_DRAWONLYFORP3);
-	mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP4)|(master->eflags & MFE_DRAWONLYFORP4);
-}
-
-static void K_SpawnDashDustRelease(player_t *player)
-{
-	fixed_t newx;
-	fixed_t newy;
-	mobj_t *dust;
-	angle_t travelangle;
-	INT32 i;
-
-	I_Assert(player != NULL);
-	I_Assert(player->mo != NULL);
-	I_Assert(!P_MobjWasRemoved(player->mo));
-
-	if (!P_IsObjectOnGround(player->mo))
-		return;
-
-	if (!player->speed && !player->kartstuff[k_startboost])
-		return;
-
-	travelangle = player->mo->angle;
-
-	if (player->kartstuff[k_drift] || player->kartstuff[k_driftend])
-		travelangle -= (ANGLE_45/5)*player->kartstuff[k_drift];
-
-	for (i = 0; i < 2; i++)
-	{
-		newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale));
-		newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale));
-		dust = P_SpawnMobj(newx, newy, player->mo->z, MT_FASTDUST);
-
-		P_SetTarget(&dust->target, player->mo);
-		dust->angle = travelangle - ((i&1) ? -1 : 1)*ANGLE_45;
-		dust->destscale = player->mo->scale;
-		P_SetScale(dust, player->mo->scale);
-
-		dust->momx = 3*player->mo->momx/5;
-		dust->momy = 3*player->mo->momy/5;
-		//dust->momz = 3*player->mo->momz/5;
-
-		K_MatchGenericExtraFlags(dust, player->mo);
-	}
-}
-
-static void K_SpawnBrakeDriftSparks(player_t *player) // Be sure to update the mobj thinker case too!
-{
-	mobj_t *sparks;
-
-	I_Assert(player != NULL);
-	I_Assert(player->mo != NULL);
-	I_Assert(!P_MobjWasRemoved(player->mo));
-
-	// Position & etc are handled in its thinker, and its spawned invisible.
-	// This avoids needing to dupe code if we don't need it.
-	sparks = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BRAKEDRIFT);
-	P_SetTarget(&sparks->target, player->mo);
-	P_SetScale(sparks, (sparks->destscale = player->mo->scale));
-	K_MatchGenericExtraFlags(sparks, player->mo);
-	sparks->flags2 |= MF2_DONTDRAW;
-}
-
-/**	\brief	Calculates the respawn timer and drop-boosting
-
-	\param	player	player object passed from K_KartPlayerThink
-
-	\return	void
-*/
-void K_RespawnChecker(player_t *player)
-{
-	ticcmd_t *cmd = &player->cmd;
-
-	if (player->spectator)
-		return;
-
-	if (player->kartstuff[k_respawn] > 1)
-	{
-		player->kartstuff[k_respawn]--;
-		player->mo->momz = 0;
-		player->powers[pw_flashing] = 2;
-		player->powers[pw_nocontrol] = 2;
-		if (leveltime % 8 == 0)
-		{
-			INT32 i;
-			if (!mapreset)
-				S_StartSound(player->mo, sfx_s3kcas);
-
-			for (i = 0; i < 8; i++)
-			{
-				mobj_t *mo;
-				angle_t newangle;
-				fixed_t newx, newy, newz;
-
-				newangle = FixedAngle(((360/8)*i)*FRACUNIT);
-				newx = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31<<FRACBITS); // does NOT use scale, since this effect doesn't scale properly
-				newy = player->mo->y + P_ReturnThrustY(player->mo, newangle, 31<<FRACBITS);
-				if (player->mo->eflags & MFE_VERTICALFLIP)
-					newz = player->mo->z + player->mo->height;
-				else
-					newz = player->mo->z;
-
-				mo = P_SpawnMobj(newx, newy, newz, MT_DEZLASER);
-				if (mo)
-				{
-					if (player->mo->eflags & MFE_VERTICALFLIP)
-						mo->eflags |= MFE_VERTICALFLIP;
-					P_SetTarget(&mo->target, player->mo);
-					mo->angle = newangle+ANGLE_90;
-					mo->momz = (8*FRACUNIT)*P_MobjFlip(player->mo);
-					P_SetScale(mo, (mo->destscale = FRACUNIT));
-				}
-			}
-		}
-	}
-	else if (player->kartstuff[k_respawn] == 1)
-	{
-		if (player->kartstuff[k_growshrinktimer] < 0)
-		{
-			player->mo->scalespeed = mapobjectscale/TICRATE;
-			player->mo->destscale = (6*mapobjectscale)/8;
-			if (cv_kartdebugshrink.value && !modeattacking && !player->bot)
-				player->mo->destscale = (6*player->mo->destscale)/8;
-		}
-
-		if (!P_IsObjectOnGround(player->mo) && !mapreset)
-		{
-			player->powers[pw_flashing] = 2;
-
-			// Sal: The old behavior was stupid and prone to accidental usage.
-			// Let's rip off Mania instead, and turn this into a Drop Dash!
-
-			if (cmd->buttons & BT_ACCELERATE)
-				player->kartstuff[k_dropdash]++;
-			else
-				player->kartstuff[k_dropdash] = 0;
-
-			if (player->kartstuff[k_dropdash] == TICRATE/4)
-				S_StartSound(player->mo, sfx_ddash);
-
-			if ((player->kartstuff[k_dropdash] >= TICRATE/4)
-				&& (player->kartstuff[k_dropdash] & 1))
-				player->mo->colorized = true;
-			else
-				player->mo->colorized = false;
-		}
-		else
-		{
-			if ((cmd->buttons & BT_ACCELERATE) && (player->kartstuff[k_dropdash] >= TICRATE/4))
-			{
-				S_StartSound(player->mo, sfx_s23c);
-				player->kartstuff[k_startboost] = 50;
-				K_SpawnDashDustRelease(player);
-			}
-			player->mo->colorized = false;
-			player->kartstuff[k_dropdash] = 0;
-			player->kartstuff[k_respawn] = 0;
-		}
-	}
-}
-
-/**	\brief Handles the state changing for moving players, moved here to eliminate duplicate code
-
-	\param	player	player data
-
-	\return	void
-*/
-void K_KartMoveAnimation(player_t *player)
-{
-	ticcmd_t *cmd = &player->cmd;
-	// Standing frames - S_KART_STND1   S_KART_STND1_L   S_KART_STND1_R
-	if (player->speed == 0)
-	{
-		if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_STND1_R] && player->mo->state <= &states[S_KART_STND2_R]))
-			P_SetPlayerMobjState(player->mo, S_KART_STND1_R);
-		else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_STND1_L] && player->mo->state <= &states[S_KART_STND2_L]))
-			P_SetPlayerMobjState(player->mo, S_KART_STND1_L);
-		else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_STND1] && player->mo->state <= &states[S_KART_STND2]))
-			P_SetPlayerMobjState(player->mo, S_KART_STND1);
-	}
-	// Drifting Left - S_KART_DRIFT1_L
-	else if (player->kartstuff[k_drift] > 0 && P_IsObjectOnGround(player->mo))
-	{
-		if (!(player->mo->state >= &states[S_KART_DRIFT1_L] && player->mo->state <= &states[S_KART_DRIFT2_L]))
-			P_SetPlayerMobjState(player->mo, S_KART_DRIFT1_L);
-	}
-	// Drifting Right - S_KART_DRIFT1_R
-	else if (player->kartstuff[k_drift] < 0 && P_IsObjectOnGround(player->mo))
-	{
-		if (!(player->mo->state >= &states[S_KART_DRIFT1_R] && player->mo->state <= &states[S_KART_DRIFT2_R]))
-			P_SetPlayerMobjState(player->mo, S_KART_DRIFT1_R);
-	}
-	// Run frames - S_KART_RUN1   S_KART_RUN1_L   S_KART_RUN1_R
-	else if (player->speed > FixedMul(player->runspeed, player->mo->scale))
-	{
-		if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_RUN1_R] && player->mo->state <= &states[S_KART_RUN2_R]))
-			P_SetPlayerMobjState(player->mo, S_KART_RUN1_R);
-		else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_RUN1_L] && player->mo->state <= &states[S_KART_RUN2_L]))
-			P_SetPlayerMobjState(player->mo, S_KART_RUN1_L);
-		else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_RUN1] && player->mo->state <= &states[S_KART_RUN2]))
-			P_SetPlayerMobjState(player->mo, S_KART_RUN1);
-	}
-	// Walk frames - S_KART_WALK1   S_KART_WALK1_L   S_KART_WALK1_R
-	else if (player->speed <= FixedMul(player->runspeed, player->mo->scale))
-	{
-		if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_WALK1_R] && player->mo->state <= &states[S_KART_WALK2_R]))
-			P_SetPlayerMobjState(player->mo, S_KART_WALK1_R);
-		else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_WALK1_L] && player->mo->state <= &states[S_KART_WALK2_L]))
-			P_SetPlayerMobjState(player->mo, S_KART_WALK1_L);
-		else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_WALK1] && player->mo->state <= &states[S_KART_WALK2]))
-			P_SetPlayerMobjState(player->mo, S_KART_WALK1);
-	}
-}
-
-static void K_TauntVoiceTimers(player_t *player)
-{
-	if (!player)
-		return;
-
-	player->kartstuff[k_tauntvoices] = 6*TICRATE;
-	player->kartstuff[k_voices] = 4*TICRATE;
-}
-
-static void K_RegularVoiceTimers(player_t *player)
-{
-	if (!player)
-		return;
-
-	player->kartstuff[k_voices] = 4*TICRATE;
-
-	if (player->kartstuff[k_tauntvoices] < 4*TICRATE)
-		player->kartstuff[k_tauntvoices] = 4*TICRATE;
-}
-
-void K_PlayAttackTaunt(mobj_t *source)
-{
-	sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons
-	boolean tasteful = (!source->player || !source->player->kartstuff[k_tauntvoices]);
-
-	if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2))
-		S_StartSound(source, sfx_kattk1+pick);
-
-	if (!tasteful)
-		return;
-
-	K_TauntVoiceTimers(source->player);
-}
-
-void K_PlayBoostTaunt(mobj_t *source)
-{
-	sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons
-	boolean tasteful = (!source->player || !source->player->kartstuff[k_tauntvoices]);
-
-	if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2))
-		S_StartSound(source, sfx_kbost1+pick);
-
-	if (!tasteful)
-		return;
-
-	K_TauntVoiceTimers(source->player);
-}
-
-void K_PlayOvertakeSound(mobj_t *source)
-{
-	boolean tasteful = (!source->player || !source->player->kartstuff[k_voices]);
-
-	if (!G_RaceGametype()) // Only in race
-		return;
-
-	// 4 seconds from before race begins, 10 seconds afterwards
-	if (leveltime < starttime+(10*TICRATE))
-		return;
-
-	if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2))
-		S_StartSound(source, sfx_kslow);
-
-	if (!tasteful)
-		return;
-
-	K_RegularVoiceTimers(source->player);
-}
-
-void K_PlayHitEmSound(mobj_t *source)
-{
-	if (cv_kartvoices.value)
-		S_StartSound(source, sfx_khitem);
-	else
-		S_StartSound(source, sfx_s1c9); // The only lost gameplay functionality with voices disabled
-
-	K_RegularVoiceTimers(source->player);
-}
-
-void K_PlayPowerGloatSound(mobj_t *source)
-{
-	if (cv_kartvoices.value)
-		S_StartSound(source, sfx_kgloat);
-
-	K_RegularVoiceTimers(source->player);
-}
-
-void K_MomentumToFacing(player_t *player)
-{
-	angle_t dangle = player->mo->angle - R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
-
-	if (dangle > ANGLE_180)
-		dangle = InvAngle(dangle);
-
-	// If you aren't on the ground or are moving in too different of a direction don't do this
-	if (player->mo->eflags & MFE_JUSTHITFLOOR)
-		; // Just hit floor ALWAYS redirects
-	else if (!P_IsObjectOnGround(player->mo) || dangle > ANGLE_90)
-		return;
-
-	P_Thrust(player->mo, player->mo->angle, player->speed - FixedMul(player->speed, player->mo->friction));
-	player->mo->momx = FixedMul(player->mo->momx - player->cmomx, player->mo->friction) + player->cmomx;
-	player->mo->momy = FixedMul(player->mo->momy - player->cmomy, player->mo->friction) + player->cmomy;
-}
-
-// sets k_boostpower, k_speedboost, and k_accelboost to whatever we need it to be
-static void K_GetKartBoostPower(player_t *player)
-{
-	fixed_t boostpower = FRACUNIT;
-	fixed_t speedboost = 0, accelboost = 0;
-
-	if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow] == 1) // Slow down after you've been bumped
-	{
-		player->kartstuff[k_boostpower] = player->kartstuff[k_speedboost] = player->kartstuff[k_accelboost] = 0;
-		return;
-	}
-
-	// Offroad is separate, it's difficult to factor it in with a variable value anyway.
-	if (!(player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || player->kartstuff[k_sneakertimer])
-		&& player->kartstuff[k_offroad] >= 0)
-		boostpower = FixedDiv(boostpower, player->kartstuff[k_offroad] + FRACUNIT);
-
-	if (player->kartstuff[k_bananadrag] > TICRATE)
-		boostpower = (4*boostpower)/5;
-
-	// Banana drag/offroad dust
-	if (boostpower < FRACUNIT
-		&& player->mo && P_IsObjectOnGround(player->mo)
-		&& player->speed > 0
-		&& !player->spectator)
-	{
-		K_SpawnWipeoutTrail(player->mo, true);
-		if (leveltime % 6 == 0)
-			S_StartSound(player->mo, sfx_cdfm70);
-	}
-
-	if (player->kartstuff[k_sneakertimer]) // Sneaker
-	{
-		switch (gamespeed)
-		{
-			case 0:
-				speedboost = max(speedboost, 53740+768);
-				break;
-			case 2:
-				speedboost = max(speedboost, 17294+768);
-				break;
-			default:
-				speedboost = max(speedboost, 32768);
-				break;
-		}
-		accelboost = max(accelboost, 8*FRACUNIT); // + 800%
-	}
-
-	if (player->kartstuff[k_invincibilitytimer]) // Invincibility
-	{
-		speedboost = max(speedboost, 3*FRACUNIT/8); // + 37.5%
-		accelboost = max(accelboost, 3*FRACUNIT); // + 300%
-	}
-
-	if (player->kartstuff[k_growshrinktimer] > 0) // Grow
-	{
-		speedboost = max(speedboost, FRACUNIT/5); // + 20%
-	}
-
-	if (player->kartstuff[k_driftboost]) // Drift Boost
-	{
-		speedboost = max(speedboost, FRACUNIT/4); // + 25%
-		accelboost = max(accelboost, 4*FRACUNIT); // + 400%
-	}
-
-	if (player->kartstuff[k_startboost]) // Startup Boost
-	{
-		speedboost = max(speedboost, FRACUNIT/4); // + 25%
-		accelboost = max(accelboost, 6*FRACUNIT); // + 300%
-	}
-
-	// don't average them anymore, this would make a small boost and a high boost less useful
-	// just take the highest we want instead
-
-	player->kartstuff[k_boostpower] = boostpower;
-
-	// value smoothing
-	if (speedboost > player->kartstuff[k_speedboost])
-		player->kartstuff[k_speedboost] = speedboost;
-	else
-		player->kartstuff[k_speedboost] += (speedboost - player->kartstuff[k_speedboost])/(TICRATE/2);
-
-	player->kartstuff[k_accelboost] = accelboost;
-}
-
-fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower)
-{
-	fixed_t k_speed = 150;
-	fixed_t g_cc = FRACUNIT;
-	fixed_t xspd = 3072;		// 4.6875 aka 3/64
-	UINT8 kartspeed = player->kartspeed;
-	fixed_t finalspeed;
-
-	if (doboostpower && !player->kartstuff[k_pogospring] && !P_IsObjectOnGround(player->mo))
-		return (75*mapobjectscale); // air speed cap
-
-	switch (gamespeed)
-	{
-		case 0:
-			g_cc = 53248 + xspd; //  50cc =  81.25 + 4.69 =  85.94%
-			break;
-		case 2:
-			g_cc = 77824 + xspd; // 150cc = 118.75 + 4.69 = 123.44%
-			break;
-		default:
-			g_cc = 65536 + xspd; // 100cc = 100.00 + 4.69 = 104.69%
-			break;
-	}
-
-	if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)
-		kartspeed = 1;
-
-	k_speed += kartspeed*3; // 153 - 177
-
-	finalspeed = FixedMul(FixedMul(k_speed<<14, g_cc), player->mo->scale);
-
-	if (doboostpower)
-		return FixedMul(finalspeed, player->kartstuff[k_boostpower]+player->kartstuff[k_speedboost]);
-	return finalspeed;
-}
-
-fixed_t K_GetKartAccel(player_t *player)
-{
-	fixed_t k_accel = 32; // 36;
-	UINT8 kartspeed = player->kartspeed;
-
-	if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)
-		kartspeed = 1;
-
-	//k_accel += 3 * (9 - kartspeed); // 36 - 60
-	k_accel += 4 * (9 - kartspeed); // 32 - 64
-
-	return FixedMul(k_accel, FRACUNIT+player->kartstuff[k_accelboost]);
-}
-
-UINT16 K_GetKartFlashing(player_t *player)
-{
-    UINT16 tics = flashingtics;
-    if (G_BattleGametype())
-        tics *= 2;
-    tics += (flashingtics/8) * (player->kartspeed);
-    return tics;
-}
-
-fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove)
-{
-	fixed_t accelmax = 4000;
-	fixed_t newspeed, oldspeed, finalspeed;
-	fixed_t p_speed = K_GetKartSpeed(player, true);
-	fixed_t p_accel = K_GetKartAccel(player);
-
-	if (!onground) return 0; // If the player isn't on the ground, there is no change in speed
-
-	// ACCELCODE!!!1!11!
-	oldspeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy); // FixedMul(P_AproxDistance(player->rmomx, player->rmomy), player->mo->scale);
-	newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), ORIG_FRICTION);
-
-	if (player->kartstuff[k_pogospring]) // Pogo Spring minimum/maximum thrust
-	{
-		const fixed_t hscale = mapobjectscale /*+ (mapobjectscale - player->mo->scale)*/;
-		const fixed_t minspeed = 24*hscale;
-		const fixed_t maxspeed = 28*hscale;
-
-		if (newspeed > maxspeed && player->kartstuff[k_pogospring] == 2)
-			newspeed = maxspeed;
-		if (newspeed < minspeed)
-			newspeed = minspeed;
-	}
-
-	finalspeed = newspeed - oldspeed;
-
-	// forwardmove is:
-	//  50 while accelerating,
-	//  25 while clutching,
-	//   0 with no gas, and
-	// -25 when only braking.
-
-	finalspeed *= forwardmove/25;
-	finalspeed /= 2;
-
-	if (forwardmove < 0 && finalspeed > FRACUNIT*2)
-		return finalspeed/2;
-	else if (forwardmove < 0)
-		return -FRACUNIT/2;
-
-	if (finalspeed < 0)
-		finalspeed = 0;
-
-	return finalspeed;
-}
-
-void K_DoInstashield(player_t *player)
-{
-	mobj_t *layera;
-	mobj_t *layerb;
-
-	if (player->kartstuff[k_instashield] > 0)
-		return;
-
-	player->kartstuff[k_instashield] = 15; // length of instashield animation
-	S_StartSound(player->mo, sfx_cdpcm9);
-
-	layera = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDA);
-	P_SetTarget(&layera->target, player->mo);
-
-	layerb = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDB);
-	P_SetTarget(&layerb->target, player->mo);
-}
-
-void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount)
-{
-	statenum_t st;
-	mobj_t *pt;
-
-	if (!source || !source->mo)
-		return;
-
-	if (amount == 1)
-		st = S_BATTLEPOINT1A;
-	else if (amount == 2)
-		st = S_BATTLEPOINT2A;
-	else if (amount == 3)
-		st = S_BATTLEPOINT3A;
-	else
-		return; // NO STATE!
-
-	pt = P_SpawnMobj(source->mo->x, source->mo->y, source->mo->z, MT_BATTLEPOINT);
-	P_SetTarget(&pt->target, source->mo);
-	P_SetMobjState(pt, st);
-	if (victim && victim->skincolor)
-		pt->color = victim->skincolor;
-	else
-		pt->color = source->skincolor;
-}
-
-void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, mobj_t *inflictor, boolean trapitem)
-{
-	UINT8 scoremultiply = 1;
-	// PS: Inflictor is unused for all purposes here and is actually only ever relevant to Lua. It may be nil too.
-#ifdef HAVE_BLUA
-	boolean force = false;	// Used to check if Lua ShouldSpin should get us damaged reguardless of flashtics or heck knows what.
-	UINT8 shouldForce = LUAh_ShouldSpin(player, inflictor, source);
-	if (P_MobjWasRemoved(player->mo))
-		return; // mobj was removed (in theory that shouldn't happen)
-	if (shouldForce == 1)
-		force = true;
-	else if (shouldForce == 2)
-		return;
-#else
-	static const boolean force = false;
-	(void)inflictor;	// in case some weirdo doesn't want Lua.
-#endif
-
-
-	if (!trapitem && G_BattleGametype())
-	{
-		if (K_IsPlayerWanted(player))
-			scoremultiply = 3;
-		else if (player->kartstuff[k_bumper] == 1)
-			scoremultiply = 2;
-	}
-
-	if (player->health <= 0)
-		return;
-
-	if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0
-		|| player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
-		|| (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1)))
-	{
-		if (!force)	// if shoulddamage force, we go THROUGH that.
-		{
-			K_DoInstashield(player);
-			return;
-		}
-	}
-
-	if (LUAh_PlayerSpin(player, inflictor, source))	// Let Lua do its thing or overwrite if it wants to. Make sure to let any possible instashield happen because we didn't get "damaged" in this case.
-		return;
-
-	if (source && source != player->mo && source->player)
-		K_PlayHitEmSound(source);
-
-	//player->kartstuff[k_sneakertimer] = 0;
-	player->kartstuff[k_driftboost] = 0;
-
-	player->kartstuff[k_drift] = 0;
-	player->kartstuff[k_driftcharge] = 0;
-	player->kartstuff[k_pogospring] = 0;
-
-	if (G_BattleGametype())
-	{
-		if (source && source->player && player != source->player)
-		{
-			P_AddPlayerScore(source->player, scoremultiply);
-			K_SpawnBattlePoints(source->player, player, scoremultiply);
-			if (!trapitem)
-			{
-				source->player->kartstuff[k_wanted] -= wantedreduce;
-				player->kartstuff[k_wanted] -= (wantedreduce/2);
-			}
-		}
-
-		if (player->kartstuff[k_bumper] > 0)
-		{
-			if (player->kartstuff[k_bumper] == 1)
-			{
-				mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!!
-				P_SetTarget(&karmahitbox->target, player->mo);
-				karmahitbox->destscale = player->mo->scale;
-				P_SetScale(karmahitbox, player->mo->scale);
-				CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]);
-			}
-			player->kartstuff[k_bumper]--;
-			if (K_IsPlayerWanted(player))
-				K_CalculateBattleWanted();
-		}
-
-		if (!player->kartstuff[k_bumper])
-		{
-			player->kartstuff[k_comebacktimer] = comebacktime;
-			if (player->kartstuff[k_comebackmode] == 2)
-			{
-				mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE);
-				S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound);
-				player->kartstuff[k_comebackmode] = 0;
-			}
-		}
-
-		K_CheckBumpers();
-	}
-
-	player->kartstuff[k_spinouttype] = type;
-
-	if (player->kartstuff[k_spinouttype] <= 0) // type 0 is spinout, type 1 is wipeout
-	{
-		// At spinout, player speed is increased to 1/4 their regular speed, moving them forward
-		if (player->speed < K_GetKartSpeed(player, true)/4)
-			P_InstaThrust(player->mo, player->mo->angle, FixedMul(K_GetKartSpeed(player, true)/4, player->mo->scale));
-		S_StartSound(player->mo, sfx_slip);
-	}
-
-	player->kartstuff[k_spinouttimer] = (3*TICRATE/2)+2;
-	player->powers[pw_flashing] = K_GetKartFlashing(player);
-
-	if (player->mo->state != &states[S_KART_SPIN])
-		P_SetPlayerMobjState(player->mo, S_KART_SPIN);
-
-	player->kartstuff[k_instashield] = 15;
-	if (cv_kartdebughuddrop.value && !modeattacking)
-		K_DropItems(player);
-	else
-		K_DropHnextList(player);
-	return;
-}
-
-static void K_RemoveGrowShrink(player_t *player)
-{
-	player->kartstuff[k_growshrinktimer] = 0;
-	if (player->kartstuff[k_invincibilitytimer] == 0)
-		player->mo->color = player->skincolor;
-	player->mo->scalespeed = mapobjectscale/TICRATE;
-	player->mo->destscale = mapobjectscale;
-	if (cv_kartdebugshrink.value && !modeattacking && !player->bot)
-		player->mo->destscale = (6*player->mo->destscale)/8;
-	P_RestoreMusic(player);
-}
-
-void K_SquishPlayer(player_t *player, mobj_t *source, mobj_t *inflictor)
-{
-	UINT8 scoremultiply = 1;
-	// PS: Inflictor is unused for all purposes here and is actually only ever relevant to Lua. It may be nil too.
-#ifdef HAVE_BLUA
-	boolean force = false;	// Used to check if Lua ShouldSquish should get us damaged reguardless of flashtics or heck knows what.
-	UINT8 shouldForce = LUAh_ShouldSquish(player, inflictor, source);
-	if (P_MobjWasRemoved(player->mo))
-		return; // mobj was removed (in theory that shouldn't happen)
-	if (shouldForce == 1)
-		force = true;
-	else if (shouldForce == 2)
-		return;
-#else
-	static const boolean force = false;
-	(void)inflictor;	// Please stop forgetting to put inflictor in yer functions thank -Lat'
-#endif
-
-	if (G_BattleGametype())
-	{
-		if (K_IsPlayerWanted(player))
-			scoremultiply = 3;
-		else if (player->kartstuff[k_bumper] == 1)
-			scoremultiply = 2;
-	}
-
-	if (player->health <= 0)
-		return;
-
-	if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_invincibilitytimer] > 0
-		|| player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
-		|| (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1)))
-	{
-		if (!force)	// You know the drill by now.
-		{
-			K_DoInstashield(player);
-			return;
-		}
-	}
-
-	if (LUAh_PlayerSquish(player, inflictor, source))	// Let Lua do its thing or overwrite if it wants to. Make sure to let any possible instashield happen because we didn't get "damaged" in this case.
-		return;
-
-	player->kartstuff[k_sneakertimer] = 0;
-	player->kartstuff[k_driftboost] = 0;
-
-	player->kartstuff[k_drift] = 0;
-	player->kartstuff[k_driftcharge] = 0;
-	player->kartstuff[k_pogospring] = 0;
-
-	if (G_BattleGametype())
-	{
-		if (source && source->player && player != source->player)
-		{
-			P_AddPlayerScore(source->player, scoremultiply);
-			K_SpawnBattlePoints(source->player, player, scoremultiply);
-			source->player->kartstuff[k_wanted] -= wantedreduce;
-			player->kartstuff[k_wanted] -= (wantedreduce/2);
-		}
-
-		if (player->kartstuff[k_bumper] > 0)
-		{
-			if (player->kartstuff[k_bumper] == 1)
-			{
-				mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!!
-				P_SetTarget(&karmahitbox->target, player->mo);
-				karmahitbox->destscale = player->mo->scale;
-				P_SetScale(karmahitbox, player->mo->scale);
-				CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]);
-			}
-			player->kartstuff[k_bumper]--;
-			if (K_IsPlayerWanted(player))
-				K_CalculateBattleWanted();
-		}
-
-		if (!player->kartstuff[k_bumper])
-		{
-			player->kartstuff[k_comebacktimer] = comebacktime;
-			if (player->kartstuff[k_comebackmode] == 2)
-			{
-				mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE);
-				S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound);
-				player->kartstuff[k_comebackmode] = 0;
-			}
-		}
-
-		K_CheckBumpers();
-	}
-
-	player->kartstuff[k_squishedtimer] = TICRATE;
-
-	// Reduce Shrink timer
-	if (player->kartstuff[k_growshrinktimer] < 0)
-	{
-		player->kartstuff[k_growshrinktimer] += TICRATE;
-		if (player->kartstuff[k_growshrinktimer] >= 0)
-			K_RemoveGrowShrink(player);
-	}
-
-	player->powers[pw_flashing] = K_GetKartFlashing(player);
-
-	player->mo->flags |= MF_NOCLIP;
-
-	if (player->mo->state != &states[S_KART_SQUISH]) // Squash
-		P_SetPlayerMobjState(player->mo, S_KART_SQUISH);
-
-	P_PlayRinglossSound(player->mo);
-
-	player->kartstuff[k_instashield] = 15;
-	if (cv_kartdebughuddrop.value && !modeattacking)
-		K_DropItems(player);
-	else
-		K_DropHnextList(player);
-	return;
-}
-
-void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor) // A bit of a hack, we just throw the player up higher here and extend their spinout timer
-{
-	UINT8 scoremultiply = 1;
-#ifdef HAVE_BLUA
-	boolean force = false;	// Used to check if Lua ShouldExplode should get us damaged reguardless of flashtics or heck knows what.
-	UINT8 shouldForce = LUAh_ShouldExplode(player, inflictor, source);
-	if (P_MobjWasRemoved(player->mo))
-		return; // mobj was removed (in theory that shouldn't happen)
-	if (shouldForce == 1)
-		force = true;
-	else if (shouldForce == 2)
-		return;
-
-#else
-	static const boolean force = false;
-#endif
-	if (G_BattleGametype())
-	{
-		if (K_IsPlayerWanted(player))
-			scoremultiply = 3;
-		else if (player->kartstuff[k_bumper] == 1)
-			scoremultiply = 2;
-	}
-
-	if (player->health <= 0)
-		return;
-
-	if (/*player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0 // Explosions should combo, because of SPB and Eggman
-		||*/player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
-		|| (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1)))
-	{
-		if (!force)	// ShouldDamage can bypass that, again.
-		{
-			K_DoInstashield(player);
-			return;
-		}
-	}
-
-	if (LUAh_PlayerExplode(player, inflictor, source))	// Same thing. Also make sure to let Instashield happen blah blah
-		return;
-
-	if (source && source != player->mo && source->player)
-		K_PlayHitEmSound(source);
-
-	player->mo->momz = 18*mapobjectscale;
-	player->mo->momx = player->mo->momy = 0;
-
-	player->kartstuff[k_sneakertimer] = 0;
-	player->kartstuff[k_driftboost] = 0;
-
-	player->kartstuff[k_drift] = 0;
-	player->kartstuff[k_driftcharge] = 0;
-	player->kartstuff[k_pogospring] = 0;
-
-	// This is the only part that SHOULDN'T combo :VVVVV
-	if (G_BattleGametype() && !(player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0))
-	{
-		if (source && source->player && player != source->player)
-		{
-			P_AddPlayerScore(source->player, scoremultiply);
-			K_SpawnBattlePoints(source->player, player, scoremultiply);
-			source->player->kartstuff[k_wanted] -= wantedreduce;
-			player->kartstuff[k_wanted] -= (wantedreduce/2);
-		}
-
-		if (player->kartstuff[k_bumper] > 0)
-		{
-			if (player->kartstuff[k_bumper] == 1)
-			{
-				mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!!
-				P_SetTarget(&karmahitbox->target, player->mo);
-				karmahitbox->destscale = player->mo->scale;
-				P_SetScale(karmahitbox, player->mo->scale);
-				CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]);
-			}
-			player->kartstuff[k_bumper]--;
-			if (K_IsPlayerWanted(player))
-				K_CalculateBattleWanted();
-		}
-
-		if (!player->kartstuff[k_bumper])
-		{
-			player->kartstuff[k_comebacktimer] = comebacktime;
-			if (player->kartstuff[k_comebackmode] == 2)
-			{
-				mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE);
-				S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound);
-				player->kartstuff[k_comebackmode] = 0;
-			}
-		}
-
-		K_CheckBumpers();
-	}
-
-	player->kartstuff[k_spinouttype] = 1;
-	player->kartstuff[k_spinouttimer] = 2*TICRATE+(TICRATE/2);
-
-	player->powers[pw_flashing] = K_GetKartFlashing(player);
-
-	if (inflictor && inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1)
-	{
-		player->kartstuff[k_spinouttimer] = ((3*player->kartstuff[k_spinouttimer])/2)+1;
-		player->mo->momz *= 2;
-	}
-
-	if (player->mo->state != &states[S_KART_SPIN])
-		P_SetPlayerMobjState(player->mo, S_KART_SPIN);
-
-	P_PlayRinglossSound(player->mo);
-
-	if (P_IsLocalPlayer(player))
-	{
-		quake.intensity = 64*FRACUNIT;
-		quake.time = 5;
-	}
-
-	player->kartstuff[k_instashield] = 15;
-	K_DropItems(player);
-
-	return;
-}
-
-void K_StealBumper(player_t *player, player_t *victim, boolean force)
-{
-	INT32 newbumper;
-	angle_t newangle, diff;
-	fixed_t newx, newy;
-	mobj_t *newmo;
-
-	if (!G_BattleGametype())
-		return;
-
-	if (player->health <= 0 || victim->health <= 0)
-		return;
-
-	if (!force)
-	{
-		if (victim->kartstuff[k_bumper] <= 0) // || player->kartstuff[k_bumper] >= cv_kartbumpers.value+2
-			return;
-
-		if (player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0)
-			return;
-
-		if (victim->powers[pw_flashing] > 0 || victim->kartstuff[k_squishedtimer] > 0 || victim->kartstuff[k_spinouttimer] > 0
-			|| victim->kartstuff[k_invincibilitytimer] > 0 || victim->kartstuff[k_growshrinktimer] > 0 || victim->kartstuff[k_hyudorotimer] > 0)
-		{
-			K_DoInstashield(victim);
-			return;
-		}
-	}
-
-	if (netgame && player->kartstuff[k_bumper] <= 0)
-		CONS_Printf(M_GetText("%s is back in the game!\n"), player_names[player-players]);
-
-	newbumper = player->kartstuff[k_bumper];
-	if (newbumper <= 1)
-		diff = 0;
-	else
-		diff = FixedAngle(360*FRACUNIT/newbumper);
-
-	newangle = player->mo->angle;
-	newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, 64*FRACUNIT);
-	newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, 64*FRACUNIT);
-
-	newmo = P_SpawnMobj(newx, newy, player->mo->z, MT_BATTLEBUMPER);
-	newmo->threshold = newbumper;
-	P_SetTarget(&newmo->tracer, victim->mo);
-	P_SetTarget(&newmo->target, player->mo);
-	newmo->angle = (diff * (newbumper-1));
-	newmo->color = victim->skincolor;
-
-	if (newbumper+1 < 2)
-		P_SetMobjState(newmo, S_BATTLEBUMPER3);
-	else if (newbumper+1 < 3)
-		P_SetMobjState(newmo, S_BATTLEBUMPER2);
-	else
-		P_SetMobjState(newmo, S_BATTLEBUMPER1);
-
-	S_StartSound(player->mo, sfx_3db06);
-
-	player->kartstuff[k_bumper]++;
-	player->kartstuff[k_comebackpoints] = 0;
-	player->powers[pw_flashing] = K_GetKartFlashing(player);
-	player->kartstuff[k_comebacktimer] = comebacktime;
-
-	/*victim->powers[pw_flashing] = K_GetKartFlashing(victim);
-	victim->kartstuff[k_comebacktimer] = comebacktime;*/
-
-	victim->kartstuff[k_instashield] = 15;
-	if (cv_kartdebughuddrop.value && !modeattacking)
-		K_DropItems(victim);
-	else
-		K_DropHnextList(victim);
-	return;
-}
-
-// source is the mobj that originally threw the bomb that exploded etc.
-// Spawns the sphere around the explosion that handles spinout
-void K_SpawnKartExplosion(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle, boolean spawncenter, boolean ghostit, mobj_t *source)
-{
-	mobj_t *mobj;
-	mobj_t *ghost = NULL;
-	INT32 i;
-	TVector v;
-	TVector *res;
-	fixed_t finalx, finaly, finalz, dist;
-	//mobj_t hoopcenter;
-	angle_t degrees, fa, closestangle;
-	fixed_t mobjx, mobjy, mobjz;
-
-	//hoopcenter.x = x;
-	//hoopcenter.y = y;
-	//hoopcenter.z = z;
-
-	//hoopcenter.z = z - mobjinfo[type].height/2;
-
-	degrees = FINEANGLES/number;
-
-	closestangle = 0;
-
-	// Create the hoop!
-	for (i = 0; i < number; i++)
-	{
-		fa = (i*degrees);
-		v[0] = FixedMul(FINECOSINE(fa),radius);
-		v[1] = 0;
-		v[2] = FixedMul(FINESINE(fa),radius);
-		v[3] = FRACUNIT;
-
-		res = VectorMatrixMultiply(v, *RotateXMatrix(rotangle));
-		M_Memcpy(&v, res, sizeof (v));
-		res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle));
-		M_Memcpy(&v, res, sizeof (v));
-
-		finalx = x + v[0];
-		finaly = y + v[1];
-		finalz = z + v[2];
-
-		mobj = P_SpawnMobj(finalx, finaly, finalz, type);
-
-		mobj->z -= mobj->height>>1;
-
-		// change angle
-		mobj->angle = R_PointToAngle2(mobj->x, mobj->y, x, y);
-
-		// change slope
-		dist = P_AproxDistance(P_AproxDistance(x - mobj->x, y - mobj->y), z - mobj->z);
-
-		if (dist < 1)
-			dist = 1;
-
-		mobjx = mobj->x;
-		mobjy = mobj->y;
-		mobjz = mobj->z;
-
-		if (ghostit)
-		{
-			ghost = P_SpawnGhostMobj(mobj);
-			P_SetMobjState(mobj, S_NULL);
-			mobj = ghost;
-		}
-
-		if (spawncenter)
-		{
-			mobj->x = x;
-			mobj->y = y;
-			mobj->z = z;
-		}
-
-		mobj->momx = FixedMul(FixedDiv(mobjx - x, dist), FixedDiv(dist, 6*FRACUNIT));
-		mobj->momy = FixedMul(FixedDiv(mobjy - y, dist), FixedDiv(dist, 6*FRACUNIT));
-		mobj->momz = FixedMul(FixedDiv(mobjz - z, dist), FixedDiv(dist, 6*FRACUNIT));
-
-		if (source && !P_MobjWasRemoved(source))
-			P_SetTarget(&mobj->target, source);
-	}
-}
-
-// Spawns the purely visual explosion
-void K_SpawnMineExplosion(mobj_t *source, UINT8 color)
-{
-	INT32 i, radius, height;
-	mobj_t *smoldering = P_SpawnMobj(source->x, source->y, source->z, MT_SMOLDERING);
-	mobj_t *dust;
-	mobj_t *truc;
-	INT32 speed, speed2;
-
-	smoldering->tics = TICRATE*3;
-	radius = source->radius>>FRACBITS;
-	height = source->height>>FRACBITS;
-
-	if (!color)
-		color = SKINCOLOR_KETCHUP;
-
-	for (i = 0; i < 32; i++)
-	{
-		dust = P_SpawnMobj(source->x, source->y, source->z, MT_SMOKE);
-		dust->angle = (ANGLE_180/16) * i;
-		P_SetScale(dust, source->scale);
-		dust->destscale = source->scale*10;
-		dust->scalespeed = source->scale/12;
-		P_InstaThrust(dust, dust->angle, FixedMul(20*FRACUNIT, source->scale));
-
-		truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT,
-			source->y + P_RandomRange(-radius, radius)*FRACUNIT,
-			source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMEXPLODE);
-		P_SetScale(truc, source->scale);
-		truc->destscale = source->scale*6;
-		truc->scalespeed = source->scale/12;
-		speed = FixedMul(10*FRACUNIT, source->scale)>>FRACBITS;
-		truc->momx = P_RandomRange(-speed, speed)*FRACUNIT;
-		truc->momy = P_RandomRange(-speed, speed)*FRACUNIT;
-		speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS;
-		truc->momz = P_RandomRange(-speed, speed)*FRACUNIT;
-		truc->color = color;
-	}
-
-	for (i = 0; i < 16; i++)
-	{
-		dust = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT,
-			source->y + P_RandomRange(-radius, radius)*FRACUNIT,
-			source->z + P_RandomRange(0, height)*FRACUNIT, MT_SMOKE);
-		P_SetScale(dust, source->scale);
-		dust->destscale = source->scale*10;
-		dust->scalespeed = source->scale/12;
-		dust->tics = 30;
-		dust->momz = P_RandomRange(FixedMul(3*FRACUNIT, source->scale)>>FRACBITS, FixedMul(7*FRACUNIT, source->scale)>>FRACBITS)*FRACUNIT;
-
-		truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT,
-			source->y + P_RandomRange(-radius, radius)*FRACUNIT,
-			source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMPARTICLE);
-		P_SetScale(truc, source->scale);
-		truc->destscale = source->scale*5;
-		truc->scalespeed = source->scale/12;
-		speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS;
-		truc->momx = P_RandomRange(-speed, speed)*FRACUNIT;
-		truc->momy = P_RandomRange(-speed, speed)*FRACUNIT;
-		speed = FixedMul(15*FRACUNIT, source->scale)>>FRACBITS;
-		speed2 = FixedMul(45*FRACUNIT, source->scale)>>FRACBITS;
-		truc->momz = P_RandomRange(speed, speed2)*FRACUNIT;
-		if (P_RandomChance(FRACUNIT/2))
-			truc->momz = -truc->momz;
-		truc->tics = TICRATE*2;
-		truc->color = color;
-	}
-}
-
-static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed)
-{
-	mobj_t *th;
-	fixed_t x, y, z;
-	fixed_t finalspeed = speed;
-	mobj_t *throwmo;
-
-	if (source->player && source->player->speed > K_GetKartSpeed(source->player, false))
-	{
-		angle_t input = source->angle - an;
-		boolean invert = (input > ANGLE_180);
-		if (invert)
-			input = InvAngle(input);
-
-		finalspeed = max(speed, FixedMul(speed, FixedMul(
-			FixedDiv(source->player->speed, K_GetKartSpeed(source->player, false)), // Multiply speed to be proportional to your own, boosted maxspeed.
-			(((180<<FRACBITS) - AngleFixed(input)) / 180) // multiply speed based on angle diff... i.e: don't do this for firing backward :V
-			)));
-	}
-
-	x = source->x + source->momx + FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT));
-	y = source->y + source->momy + FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT));
-	z = source->z; // spawn on the ground please
-
-	if (P_MobjFlip(source) < 0)
-	{
-		z = source->z+source->height - mobjinfo[type].height;
-	}
-
-	th = P_SpawnMobj(x, y, z, type);
-
-	th->flags2 |= flags2;
-
-	th->threshold = 10;
-
-	if (th->info->seesound)
-		S_StartSound(source, th->info->seesound);
-
-	P_SetTarget(&th->target, source);
-
-	if (P_IsObjectOnGround(source))
-	{
-		// floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn
-		// This should set it for FOFs
-		P_TeleportMove(th, th->x, th->y, th->z);
-		// spawn on the ground if the player is on the ground
-		if (P_MobjFlip(source) < 0)
-		{
-			th->z = th->ceilingz - th->height;
-			th->eflags |= MFE_VERTICALFLIP;
-		}
-		else
-			th->z = th->floorz;
-	}
-
-	th->angle = an;
-	th->momx = FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT));
-	th->momy = FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT));
-
-	switch (type)
-	{
-		case MT_ORBINAUT:
-			if (source && source->player)
-				th->color = source->player->skincolor;
-			else
-				th->color = SKINCOLOR_GREY;
-			th->movefactor = finalspeed;
-			break;
-		case MT_JAWZ:
-			if (source && source->player)
-			{
-				INT32 lasttarg = source->player->kartstuff[k_lastjawztarget];
-				th->cvmem = source->player->skincolor;
-				if ((lasttarg >= 0 && lasttarg < MAXPLAYERS)
-					&& playeringame[lasttarg]
-					&& !players[lasttarg].spectator
-					&& players[lasttarg].mo)
-				{
-					P_SetTarget(&th->tracer, players[lasttarg].mo);
-				}
-			}
-			else
-				th->cvmem = SKINCOLOR_KETCHUP;
-			/* FALLTHRU */
-		case MT_JAWZ_DUD:
-			S_StartSound(th, th->info->activesound);
-			/* FALLTHRU */
-		case MT_SPB:
-			th->movefactor = finalspeed;
-			break;
-		default:
-			break;
-	}
-
-	x = x + P_ReturnThrustX(source, an, source->radius + th->radius);
-	y = y + P_ReturnThrustY(source, an, source->radius + th->radius);
-	throwmo = P_SpawnMobj(x, y, z, MT_FIREDITEM);
-	throwmo->movecount = 1;
-	throwmo->movedir = source->angle - an;
-	P_SetTarget(&throwmo->target, source);
-
-	return NULL;
-}
-
-static void K_SpawnDriftSparks(player_t *player)
-{
-	fixed_t newx;
-	fixed_t newy;
-	mobj_t *spark;
-	angle_t travelangle;
-	INT32 i;
-
-	I_Assert(player != NULL);
-	I_Assert(player->mo != NULL);
-	I_Assert(!P_MobjWasRemoved(player->mo));
-
-	if (leveltime % 2 == 1)
-		return;
-
-	if (!P_IsObjectOnGround(player->mo))
-		return;
-
-	if (!player->kartstuff[k_drift] || player->kartstuff[k_driftcharge] < K_GetKartDriftSparkValue(player))
-		return;
-
-	travelangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift];
-
-	for (i = 0; i < 2; i++)
-	{
-		newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale));
-		newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale));
-		spark = P_SpawnMobj(newx, newy, player->mo->z, MT_DRIFTSPARK);
-
-		P_SetTarget(&spark->target, player->mo);
-		spark->angle = travelangle-(ANGLE_45/5)*player->kartstuff[k_drift];
-		spark->destscale = player->mo->scale;
-		P_SetScale(spark, player->mo->scale);
-
-		spark->momx = player->mo->momx/2;
-		spark->momy = player->mo->momy/2;
-		//spark->momz = player->mo->momz/2;
-
-		if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*4)
-		{
-			spark->color = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1)));
-		}
-		else if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*2)
-		{
-			if (player->kartstuff[k_driftcharge] <= (K_GetKartDriftSparkValue(player)*2)+(24*3))
-				spark->color = SKINCOLOR_RASPBERRY; // transition
-			else
-				spark->color = SKINCOLOR_KETCHUP;
-		}
-		else
-		{
-			spark->color = SKINCOLOR_SAPPHIRE;
-		}
-
-		if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn > 0) // Inward drifts
-			|| (player->kartstuff[k_drift] < 0 && player->cmd.driftturn < 0))
-		{
-			if ((player->kartstuff[k_drift] < 0 && (i & 1))
-				|| (player->kartstuff[k_drift] > 0 && !(i & 1)))
-				P_SetMobjState(spark, S_DRIFTSPARK_A1);
-			else if ((player->kartstuff[k_drift] < 0 && !(i & 1))
-				|| (player->kartstuff[k_drift] > 0 && (i & 1)))
-				P_SetMobjState(spark, S_DRIFTSPARK_C1);
-		}
-		else if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn < 0) // Outward drifts
-			|| (player->kartstuff[k_drift] < 0 && player->cmd.driftturn > 0))
-		{
-			if ((player->kartstuff[k_drift] < 0 && (i & 1))
-				|| (player->kartstuff[k_drift] > 0 && !(i & 1)))
-				P_SetMobjState(spark, S_DRIFTSPARK_C1);
-			else if ((player->kartstuff[k_drift] < 0 && !(i & 1))
-				|| (player->kartstuff[k_drift] > 0 && (i & 1)))
-				P_SetMobjState(spark, S_DRIFTSPARK_A1);
-		}
-
-		K_MatchGenericExtraFlags(spark, player->mo);
-	}
-}
-
-static void K_SpawnAIZDust(player_t *player)
-{
-	fixed_t newx;
-	fixed_t newy;
-	mobj_t *spark;
-	angle_t travelangle;
-
-	I_Assert(player != NULL);
-	I_Assert(player->mo != NULL);
-	I_Assert(!P_MobjWasRemoved(player->mo));
-
-	if (leveltime % 2 == 1)
-		return;
-
-	if (!P_IsObjectOnGround(player->mo))
-		return;
-
-	travelangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
-	//S_StartSound(player->mo, sfx_s3k47);
-
-	{
-		newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle - (player->kartstuff[k_aizdriftstrat]*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale));
-		newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle - (player->kartstuff[k_aizdriftstrat]*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale));
-		spark = P_SpawnMobj(newx, newy, player->mo->z, MT_AIZDRIFTSTRAT);
-
-		spark->angle = travelangle+(player->kartstuff[k_aizdriftstrat]*ANGLE_90);
-		P_SetScale(spark, (spark->destscale = (3*player->mo->scale)>>2));
-
-		spark->momx = (6*player->mo->momx)/5;
-		spark->momy = (6*player->mo->momy)/5;
-		//spark->momz = player->mo->momz/2;
-
-		K_MatchGenericExtraFlags(spark, player->mo);
-	}
-}
-
-void K_SpawnBoostTrail(player_t *player)
-{
-	fixed_t newx;
-	fixed_t newy;
-	fixed_t ground;
-	mobj_t *flame;
-	angle_t travelangle;
-	INT32 i;
-
-	I_Assert(player != NULL);
-	I_Assert(player->mo != NULL);
-	I_Assert(!P_MobjWasRemoved(player->mo));
-
-	if (!P_IsObjectOnGround(player->mo)
-		|| player->kartstuff[k_hyudorotimer] != 0
-		|| (G_BattleGametype() && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]))
-		return;
-
-	if (player->mo->eflags & MFE_VERTICALFLIP)
-		ground = player->mo->ceilingz - FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale);
-	else
-		ground = player->mo->floorz;
-
-	if (player->kartstuff[k_drift] != 0)
-		travelangle = player->mo->angle;
-	else
-		travelangle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy);
-
-	for (i = 0; i < 2; i++)
-	{
-		newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale));
-		newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale));
-#ifdef ESLOPE
-		if (player->mo->standingslope)
-		{
-			ground = P_GetZAt(player->mo->standingslope, newx, newy);
-			if (player->mo->eflags & MFE_VERTICALFLIP)
-				ground -= FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale);
-		}
-#endif
-		flame = P_SpawnMobj(newx, newy, ground, MT_SNEAKERTRAIL);
-
-		P_SetTarget(&flame->target, player->mo);
-		flame->angle = travelangle;
-		flame->fuse = TICRATE*2;
-		flame->destscale = player->mo->scale;
-		P_SetScale(flame, player->mo->scale);
-		flame->eflags = (flame->eflags & ~MFE_VERTICALFLIP)|(player->mo->eflags & MFE_VERTICALFLIP); // not K_MatchGenericExtraFlags so that a stolen sneaker can be seen
-
-		flame->momx = 8;
-		P_XYMovement(flame);
-		if (P_MobjWasRemoved(flame))
-			continue;
-
-		if (player->mo->eflags & MFE_VERTICALFLIP)
-		{
-			if (flame->z + flame->height < flame->ceilingz)
-				P_RemoveMobj(flame);
-		}
-		else if (flame->z > flame->floorz)
-			P_RemoveMobj(flame);
-	}
-}
-
-void K_SpawnSparkleTrail(mobj_t *mo)
-{
-	const INT32 rad = (mo->radius*2)>>FRACBITS;
-	mobj_t *sparkle;
-	INT32 i;
-
-	I_Assert(mo != NULL);
-	I_Assert(!P_MobjWasRemoved(mo));
-
-	for (i = 0; i < 3; i++)
-	{
-		fixed_t newx = mo->x + mo->momx + (P_RandomRange(-rad, rad)<<FRACBITS);
-		fixed_t newy = mo->y + mo->momy + (P_RandomRange(-rad, rad)<<FRACBITS);
-		fixed_t newz = mo->z + mo->momz + (P_RandomRange(0, mo->height>>FRACBITS)<<FRACBITS);
-
-		sparkle = P_SpawnMobj(newx, newy, newz, MT_SPARKLETRAIL);
-
-		//if (i == 0)
-			//P_SetMobjState(sparkle, S_KARTINVULN_LARGE1);
-
-		P_SetTarget(&sparkle->target, mo);
-		sparkle->destscale = mo->destscale;
-		P_SetScale(sparkle, mo->scale);
-		sparkle->eflags = (sparkle->eflags & ~MFE_VERTICALFLIP)|(mo->eflags & MFE_VERTICALFLIP); // not K_MatchGenericExtraFlags so that a stolen invincibility can be seen
-		sparkle->color = mo->color;
-		//sparkle->colorized = mo->colorized;
-	}
-
-	P_SetMobjState(sparkle, S_KARTINVULN_LARGE1);
-}
-
-void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent)
-{
-	mobj_t *dust;
-
-	I_Assert(mo != NULL);
-	I_Assert(!P_MobjWasRemoved(mo));
-
-	dust = P_SpawnMobj(mo->x + (P_RandomRange(-25,25) * mo->scale), mo->y + (P_RandomRange(-25,25) * mo->scale), mo->z, MT_WIPEOUTTRAIL);
-
-	P_SetTarget(&dust->target, mo);
-	dust->angle = R_PointToAngle2(0,0,mo->momx,mo->momy);
-	dust->destscale = mo->scale;
-	P_SetScale(dust, mo->scale);
-	dust->eflags = (dust->eflags & ~MFE_VERTICALFLIP)|(mo->eflags & MFE_VERTICALFLIP); // not K_MatchGenericExtraFlags because hyudoro shouldn't be able to wipeout
-
-	if (translucent) // offroad effect
-	{
-		dust->momx = mo->momx/2;
-		dust->momy = mo->momy/2;
-		dust->momz = mo->momz/2;
-	}
-
-	if (translucent)
-		dust->flags2 |= MF2_SHADOW;
-}
-
-//	K_DriftDustHandling
-//	Parameters:
-//		spawner: The map object that is spawning the drift dust
-//	Description: Spawns the drift dust for objects, players use rmomx/y, other objects use regular momx/y.
-//		Also plays the drift sound.
-//		Other objects should be angled towards where they're trying to go so they don't randomly spawn dust
-//		Do note that most of the function won't run in odd intervals of frames
-void K_DriftDustHandling(mobj_t *spawner)
-{
-	angle_t anglediff;
-	const INT16 spawnrange = spawner->radius>>FRACBITS;
-
-	if (!P_IsObjectOnGround(spawner) || leveltime % 2 != 0)
-		return;
-
-	if (spawner->player)
-	{
-		if (spawner->player->pflags & PF_SKIDDOWN)
-		{
-			anglediff = abs((signed)(spawner->angle - spawner->player->frameangle));
-			if (leveltime % 6 == 0)
-				S_StartSound(spawner, sfx_screec); // repeated here because it doesn't always happen to be within the range when this is the case
-		}
-		else
-		{
-			angle_t playerangle = spawner->angle;
-
-			if (spawner->player->speed < 5<<FRACBITS)
-				return;
-
-			if (spawner->player->cmd.forwardmove < 0)
-				playerangle += ANGLE_180;
-
-			anglediff = abs((signed)(playerangle - R_PointToAngle2(0, 0, spawner->player->rmomx, spawner->player->rmomy)));
-		}
-	}
-	else
-	{
-		if (P_AproxDistance(spawner->momx, spawner->momy) < 5<<FRACBITS)
-			return;
-
-		anglediff = abs((signed)(spawner->angle - R_PointToAngle2(0, 0, spawner->momx, spawner->momy)));
-	}
-
-	if (anglediff > ANGLE_180)
-		anglediff = InvAngle(anglediff);
-
-	if (anglediff > ANG10*4) // Trying to turn further than 40 degrees
-	{
-		fixed_t spawnx = P_RandomRange(-spawnrange, spawnrange)<<FRACBITS;
-		fixed_t spawny = P_RandomRange(-spawnrange, spawnrange)<<FRACBITS;
-		INT32 speedrange = 2;
-		mobj_t *dust = P_SpawnMobj(spawner->x + spawnx, spawner->y + spawny, spawner->z, MT_DRIFTDUST);
-		if (spawner->eflags & MFE_VERTICALFLIP)
-		{
-			dust->z += spawner->height - dust->height;
-		}
-		dust->momx = FixedMul(spawner->momx + (P_RandomRange(-speedrange, speedrange)<<FRACBITS), 3*(spawner->scale)/4);
-		dust->momy = FixedMul(spawner->momy + (P_RandomRange(-speedrange, speedrange)<<FRACBITS), 3*(spawner->scale)/4);
-		dust->momz = P_MobjFlip(spawner) * (P_RandomRange(1, 4) * (spawner->scale));
-		P_SetScale(dust, spawner->scale/2);
-		dust->destscale = spawner->scale * 3;
-		dust->scalespeed = spawner->scale/12;
-
-		if (leveltime % 6 == 0)
-			S_StartSound(spawner, sfx_screec);
-
-		K_MatchGenericExtraFlags(dust, spawner);
-	}
-}
-
-static mobj_t *K_FindLastTrailMobj(player_t *player)
-{
-	mobj_t *trail;
-
-	if (!player || !(trail = player->mo) || !player->mo->hnext || !player->mo->hnext->health)
-		return NULL;
-
-	while (trail->hnext && !P_MobjWasRemoved(trail->hnext) && trail->hnext->health)
-	{
-		trail = trail->hnext;
-	}
-
-	return trail;
-}
-
-static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow)
-{
-	mobj_t *mo;
-	INT32 dir, PROJSPEED;
-	angle_t newangle;
-	fixed_t newx, newy, newz;
-	mobj_t *throwmo;
-
-	if (!player)
-		return NULL;
-
-	// Figure out projectile speed by game speed
-	if (missile && mapthing != MT_BALLHOG) // Trying to keep compatability...
-	{
-		PROJSPEED = mobjinfo[mapthing].speed;
-		if (gamespeed == 0)
-			PROJSPEED = FixedMul(PROJSPEED, FRACUNIT-FRACUNIT/4);
-		else if (gamespeed == 2)
-			PROJSPEED = FixedMul(PROJSPEED, FRACUNIT+FRACUNIT/4);
-		PROJSPEED = FixedMul(PROJSPEED, mapobjectscale);
-	}
-	else
-	{
-		switch (gamespeed)
-		{
-			case 0:
-				PROJSPEED = 68*mapobjectscale; // Avg Speed is 34
-				break;
-			case 2:
-				PROJSPEED = 96*mapobjectscale; // Avg Speed is 48
-				break;
-			default:
-				PROJSPEED = 82*mapobjectscale; // Avg Speed is 41
-				break;
-		}
-	}
-
-	if (altthrow)
-	{
-		if (altthrow == 2) // Kitchen sink throwing
-		{
-			if (player->kartstuff[k_throwdir] == 1)
-				dir = 3;
-			else if (player->kartstuff[k_throwdir] == -1)
-				dir = 1;
-			else
-				dir = 2;
-		}
-		else
-		{
-			if (player->kartstuff[k_throwdir] == 1)
-				dir = 2;
-			else if (player->kartstuff[k_throwdir] == -1)
-				dir = -1;
-			else
-				dir = 1;
-		}
-	}
-	else
-	{
-		if (player->kartstuff[k_throwdir] != 0)
-			dir = player->kartstuff[k_throwdir];
-		else
-			dir = defaultDir;
-	}
-
-	if (missile) // Shootables
-	{
-		if (mapthing == MT_BALLHOG) // Messy
-		{
-			if (dir == -1)
-			{
-				// Shoot backward
-				mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x06000000, 0, PROJSPEED/4);
-				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x03000000, 0, PROJSPEED/4);
-				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/4);
-				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x03000000, 0, PROJSPEED/4);
-				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x06000000, 0, PROJSPEED/4);
-			}
-			else
-			{
-				// Shoot forward
-				mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x06000000, 0, PROJSPEED);
-				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x03000000, 0, PROJSPEED);
-				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED);
-				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x03000000, 0, PROJSPEED);
-				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x06000000, 0, PROJSPEED);
-			}
-		}
-		else
-		{
-			if (dir == -1 && mapthing != MT_SPB)
-			{
-				// Shoot backward
-				mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/2);
-			}
-			else
-			{
-				// Shoot forward
-				mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED);
-			}
-		}
-	}
-	else
-	{
-		player->kartstuff[k_bananadrag] = 0; // RESET timer, for multiple bananas
-
-		if (dir > 0)
-		{
-			// Shoot forward
-			mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, mapthing);
-
-			mo->threshold = 10;
-			P_SetTarget(&mo->target, player->mo);
-
-			S_StartSound(player->mo, mo->info->seesound);
-
-			if (mo)
-			{
-				angle_t fa = player->mo->angle>>ANGLETOFINESHIFT;
-				INT32 HEIGHT = (20 + (dir*10))*mapobjectscale + player->mo->momz;
-
-				mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), (altthrow == 2 ? 2*PROJSPEED/3 : PROJSPEED));
-				mo->momy = player->mo->momy + FixedMul(FINESINE(fa), (altthrow == 2 ? 2*PROJSPEED/3 : PROJSPEED));
-				mo->momz = P_MobjFlip(player->mo) * HEIGHT;
-
-				if (player->mo->eflags & MFE_VERTICALFLIP)
-					mo->eflags |= MFE_VERTICALFLIP;
-			}
-
-			throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM);
-			P_SetTarget(&throwmo->target, player->mo);
-			throwmo->movecount = 0; // above player
-		}
-		else
-		{
-			mobj_t *lasttrail = K_FindLastTrailMobj(player);
-
-			if (lasttrail)
-			{
-				newx = lasttrail->x;
-				newy = lasttrail->y;
-				newz = lasttrail->z;
-			}
-			else
-			{
-				// Drop it directly behind you.
-				fixed_t dropradius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(mobjinfo[mapthing].radius, mobjinfo[mapthing].radius);
-
-				newangle = player->mo->angle;
-
-				newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, dropradius);
-				newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, dropradius);
-				newz = player->mo->z;
-			}
-
-			mo = P_SpawnMobj(newx, newy, newz, mapthing); // this will never return null because collision isn't processed here
-
-			if (P_MobjFlip(player->mo) < 0)
-				mo->z = player->mo->z + player->mo->height - mo->height;
-
-			mo->threshold = 10;
-			P_SetTarget(&mo->target, player->mo);
-
-			if (P_IsObjectOnGround(player->mo))
-			{
-				// floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn
-				// This should set it for FOFs
-				P_TeleportMove(mo, mo->x, mo->y, mo->z); // however, THIS can fuck up your day. just absolutely ruin you.
-				if (P_MobjWasRemoved(mo))
-					return NULL;
-
-				if (P_MobjFlip(mo) > 0)
-				{
-					if (mo->floorz > mo->target->z - mo->height)
-					{
-						mo->z = mo->floorz;
-					}
-				}
-				else
-				{
-					if (mo->ceilingz < mo->target->z + mo->target->height + mo->height)
-					{
-						mo->z = mo->ceilingz - mo->height;
-					}
-				}
-			}
-
-			if (player->mo->eflags & MFE_VERTICALFLIP)
-				mo->eflags |= MFE_VERTICALFLIP;
-		}
-	}
-
-	return mo;
-}
-
-#define THUNDERRADIUS 320
-
-static void K_DoThunderShield(player_t *player)
-{
-	mobj_t *mo;
-	int i = 0;
-	fixed_t sx;
-	fixed_t sy;
-	angle_t an;
-
-	S_StartSound(player->mo, sfx_zio3);
-	//player->kartstuff[k_thunderanim] = 35;
-	P_NukeEnemies(player->mo, player->mo, RING_DIST/4);
-
-	// spawn vertical bolt
-	mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK);
-	P_SetTarget(&mo->target, player->mo);
-	P_SetMobjState(mo, S_LZIO11);
-	mo->color = SKINCOLOR_TEAL;
-	mo->scale = player->mo->scale*3 + (player->mo->scale/2);
-
-	mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK);
-	P_SetTarget(&mo->target, player->mo);
-	P_SetMobjState(mo, S_LZIO21);
-	mo->color = SKINCOLOR_CYAN;
-	mo->scale = player->mo->scale*3 + (player->mo->scale/2);
-
-	// spawn horizontal bolts;
-	for (i=0; i<7; i++)
-	{
-		mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK);
-		mo->angle = P_RandomRange(0, 359)*ANG1;
-		mo->fuse = P_RandomRange(20, 50);
-		P_SetTarget(&mo->target, player->mo);
-		P_SetMobjState(mo, S_KLIT1);
-	}
-
-	// spawn the radius thing:
-	an = ANGLE_22h;
-	for (i=0; i<15; i++)
-	{
-		sx = player->mo->x + FixedMul((player->mo->scale*THUNDERRADIUS), FINECOSINE((an*i)>>ANGLETOFINESHIFT));
-		sy = player->mo->y + FixedMul((player->mo->scale*THUNDERRADIUS), FINESINE((an*i)>>ANGLETOFINESHIFT));
-		mo = P_SpawnMobj(sx, sy, player->mo->z, MT_THOK);
-		mo-> angle = an*i;
-		mo->extravalue1 = THUNDERRADIUS;	// Used to know whether we should teleport by radius or something.
-		mo->scale = player->mo->scale*3;
-		P_SetTarget(&mo->target, player->mo);
-		P_SetMobjState(mo, S_KSPARK1);
-	}
-}
-
-#undef THUNDERRADIUS
-
-static void K_DoHyudoroSteal(player_t *player)
-{
-	INT32 i, numplayers = 0;
-	INT32 playerswappable[MAXPLAYERS];
-	INT32 stealplayer = -1; // The player that's getting stolen from
-	INT32 prandom = 0;
-	boolean sink = P_RandomChance(FRACUNIT/64);
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (playeringame[i] && players[i].mo && players[i].mo->health > 0 && players[i].playerstate == PST_LIVE
-			&& player != &players[i] && !players[i].exiting && !players[i].spectator // Player in-game
-
-			// Can steal from this player
-			&& (G_RaceGametype() //&& players[i].kartstuff[k_position] < player->kartstuff[k_position])
-			|| (G_BattleGametype() && players[i].kartstuff[k_bumper] > 0))
-
-			// Has an item
-			&& (players[i].kartstuff[k_itemtype]
-			&& players[i].kartstuff[k_itemamount]
-			&& !players[i].kartstuff[k_itemheld]
-			&& !players[i].kartstuff[k_itemblink]))
-		{
-			playerswappable[numplayers] = i;
-			numplayers++;
-		}
-	}
-
-	prandom = P_RandomFixed();
-	S_StartSound(player->mo, sfx_s3k92);
-
-	if (sink && numplayers > 0 && cv_kitchensink.value) // BEHOLD THE KITCHEN SINK
-	{
-		player->kartstuff[k_hyudorotimer] = hyudorotime;
-		player->kartstuff[k_stealingtimer] = stealtime;
-
-		player->kartstuff[k_itemtype] = KITEM_KITCHENSINK;
-		player->kartstuff[k_itemamount] = 1;
-		player->kartstuff[k_itemheld] = 0;
-		return;
-	}
-	else if ((G_RaceGametype() && player->kartstuff[k_position] == 1) || numplayers == 0) // No-one can be stolen from? Oh well...
-	{
-		player->kartstuff[k_hyudorotimer] = hyudorotime;
-		player->kartstuff[k_stealingtimer] = stealtime;
-		return;
-	}
-	else if (numplayers == 1) // With just 2 players, we just need to set the other player to be the one to steal from
-	{
-		stealplayer = playerswappable[numplayers-1];
-	}
-	else if (numplayers > 1) // We need to choose between the available candidates for the 2nd player
-	{
-		stealplayer = playerswappable[prandom%(numplayers-1)];
-	}
-
-	if (stealplayer > -1) // Now here's where we do the stealing, has to be done here because we still know the player we're stealing from
-	{
-		player->kartstuff[k_hyudorotimer] = hyudorotime;
-		player->kartstuff[k_stealingtimer] = stealtime;
-		players[stealplayer].kartstuff[k_stolentimer] = stealtime;
-
-		player->kartstuff[k_itemtype] = players[stealplayer].kartstuff[k_itemtype];
-		player->kartstuff[k_itemamount] = players[stealplayer].kartstuff[k_itemamount];
-		player->kartstuff[k_itemheld] = 0;
-
-		players[stealplayer].kartstuff[k_itemtype] = KITEM_NONE;
-		players[stealplayer].kartstuff[k_itemamount] = 0;
-		players[stealplayer].kartstuff[k_itemheld] = 0;
-
-		if (P_IsLocalPlayer(&players[stealplayer]) && !splitscreen)
-			S_StartSound(NULL, sfx_s3k92);
-	}
-}
-
-void K_DoSneaker(player_t *player, INT32 type)
-{
-	fixed_t intendedboost;
-
-	switch (gamespeed)
-	{
-		case 0:
-			intendedboost = 53740+768;
-			break;
-		case 2:
-			intendedboost = 17294+768;
-			break;
-		default:
-			intendedboost = 32768;
-			break;
-	}
-
-	if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3)
-	{
-		S_StartSound(player->mo, sfx_cdfm01);
-		K_SpawnDashDustRelease(player);
-		if (intendedboost > player->kartstuff[k_speedboost])
-			player->kartstuff[k_destboostcam] = FixedMul(FRACUNIT, FixedDiv((intendedboost - player->kartstuff[k_speedboost]), intendedboost));
-	}
-
-	if (!player->kartstuff[k_sneakertimer])
-	{
-		if (type == 2)
-		{
-			if (player->mo->hnext)
-			{
-				mobj_t *cur = player->mo->hnext;
-				while (cur && !P_MobjWasRemoved(cur))
-				{
-					if (!cur->tracer)
-					{
-						mobj_t *overlay = P_SpawnMobj(cur->x, cur->y, cur->z, MT_BOOSTFLAME);
-						P_SetTarget(&overlay->target, cur);
-						P_SetTarget(&cur->tracer, overlay);
-						P_SetScale(overlay, (overlay->destscale = 3*cur->scale/4));
-					}
-					cur = cur->hnext;
-				}
-			}
-		}
-		else
-		{
-			mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BOOSTFLAME);
-			P_SetTarget(&overlay->target, player->mo);
-			P_SetScale(overlay, (overlay->destscale = player->mo->scale));
-		}
-	}
-
-	player->kartstuff[k_sneakertimer] = sneakertime;
-
-	if (type != 0)
-	{
-		player->pflags |= PF_ATTACKDOWN;
-		K_PlayBoostTaunt(player->mo);
-	}
-}
-
-static void K_DoShrink(player_t *user)
-{
-	INT32 i;
-
-	S_StartSound(user->mo, sfx_kc46); // Sound the BANG!
-	user->pflags |= PF_ATTACKDOWN;
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i] || players[i].spectator || !players[i].mo)
-			continue;
-		if (&players[i] == user)
-			continue;
-		if (players[i].kartstuff[k_position] < user->kartstuff[k_position])
-		{
-			// Don't hit while invulnerable!
-			if (!players[i].kartstuff[k_invincibilitytimer]
-				&& players[i].kartstuff[k_growshrinktimer] <= 0
-				&& !players[i].kartstuff[k_hyudorotimer])
-			{
-				// Start shrinking!
-				K_DropItems(&players[i]);
-				players[i].mo->scalespeed = mapobjectscale/TICRATE;
-				players[i].mo->destscale = (6*mapobjectscale)/8;
-				if (cv_kartdebugshrink.value && !modeattacking && !players[i].bot)
-					players[i].mo->destscale = (6*players[i].mo->destscale)/8;
-				players[i].kartstuff[k_growshrinktimer] = -(200+(40*(MAXPLAYERS-players[i].kartstuff[k_position])));
-			}
-
-			// Grow should get taken away.
-			if (players[i].kartstuff[k_growshrinktimer] > 0)
-				K_RemoveGrowShrink(&players[i]);
-
-			//P_FlashPal(&players[i], PAL_NUKE, 10);
-			S_StartSound(players[i].mo, sfx_kc59);
-		}
-	}
-}
-
-
-void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound)
-{
-	const fixed_t vscale = mapobjectscale + (mo->scale - mapobjectscale);
-
-	if (mo->player && mo->player->spectator)
-		return;
-
-	if (mo->eflags & MFE_SPRUNG)
-		return;
-
-#ifdef ESLOPE
-	mo->standingslope = NULL;
-#endif
-
-	mo->eflags |= MFE_SPRUNG;
-
-	if (mo->eflags & MFE_VERTICALFLIP)
-		vertispeed *= -1;
-
-	if (vertispeed == 0)
-	{
-		fixed_t thrust;
-
-		if (mo->player)
-		{
-			thrust = 3*mo->player->speed/2;
-			if (thrust < 48<<FRACBITS)
-				thrust = 48<<FRACBITS;
-			if (thrust > 72<<FRACBITS)
-				thrust = 72<<FRACBITS;
-			if (mo->player->kartstuff[k_pogospring] != 2)
-			{
-				if (mo->player->kartstuff[k_sneakertimer])
-					thrust = FixedMul(thrust, 5*FRACUNIT/4);
-				else if (mo->player->kartstuff[k_invincibilitytimer])
-					thrust = FixedMul(thrust, 9*FRACUNIT/8);
-			}
-		}
-		else
-		{
-			thrust = FixedDiv(3*P_AproxDistance(mo->momx, mo->momy)/2, 5*FRACUNIT/2);
-			if (thrust < 16<<FRACBITS)
-				thrust = 16<<FRACBITS;
-			if (thrust > 32<<FRACBITS)
-				thrust = 32<<FRACBITS;
-		}
-
-		mo->momz = FixedMul(FINESINE(ANGLE_22h>>ANGLETOFINESHIFT), FixedMul(thrust, vscale));
-	}
-	else
-		mo->momz = FixedMul(vertispeed, vscale);
-
-	if (sound)
-		S_StartSound(mo, (sound == 1 ? sfx_kc2f : sfx_kpogos));
-}
-
-void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source)
-{
-	mobj_t *cachenext;
-
-killnext:
-	cachenext = banana->hnext;
-
-	if (banana->health)
-	{
-		if (banana->eflags & MFE_VERTICALFLIP)
-			banana->z -= banana->height;
-		else
-			banana->z += banana->height;
-
-		S_StartSound(banana, banana->info->deathsound);
-		P_KillMobj(banana, inflictor, source);
-
-		P_SetObjectMomZ(banana, 8*FRACUNIT, false);
-		if (inflictor)
-			P_InstaThrust(banana, R_PointToAngle2(inflictor->x, inflictor->y, banana->x, banana->y)+ANGLE_90, 16*FRACUNIT);
-	}
-
-	if ((banana = cachenext))
-		goto killnext;
-}
-
-// Just for firing/dropping items.
-void K_UpdateHnextList(player_t *player, boolean clean)
-{
-	mobj_t *work = player->mo, *nextwork;
-
-	if (!work)
-		return;
-
-	nextwork = work->hnext;
-
-	while ((work = nextwork) && !P_MobjWasRemoved(work))
-	{
-		nextwork = work->hnext;
-
-		if (!clean && (!work->movedir || work->movedir <= (UINT16)player->kartstuff[k_itemamount]))
-			continue;
-
-		P_RemoveMobj(work);
-	}
-}
-
-// For getting hit!
-void K_DropHnextList(player_t *player)
-{
-	mobj_t *work = player->mo, *nextwork, *dropwork;
-	INT32 flip;
-	mobjtype_t type;
-	boolean orbit, ponground, dropall = true;
-
-	if (!work)
-		return;
-
-	flip = P_MobjFlip(player->mo);
-	ponground = P_IsObjectOnGround(player->mo);
-
-	if (player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD && player->kartstuff[k_itemamount])
-	{
-		K_DoThunderShield(player);
-		player->kartstuff[k_itemamount] = 0;
-		player->kartstuff[k_itemtype] = KITEM_NONE;
-		player->kartstuff[k_curshield] = 0;
-	}
-
-	nextwork = work->hnext;
-
-	while ((work = nextwork) && !P_MobjWasRemoved(work))
-	{
-		nextwork = work->hnext;
-
-		switch (work->type)
-		{
-			// Kart orbit items
-			case MT_ORBINAUT_SHIELD:
-				orbit = true;
-				type = MT_ORBINAUT;
-				break;
-			case MT_JAWZ_SHIELD:
-				orbit = true;
-				type = MT_JAWZ_DUD;
-				break;
-			// Kart trailing items
-			case MT_BANANA_SHIELD:
-				orbit = false;
-				type = MT_BANANA;
-				break;
-			case MT_SSMINE_SHIELD:
-				orbit = false;
-				dropall = false;
-				type = MT_SSMINE;
-				break;
-			case MT_EGGMANITEM_SHIELD:
-				orbit = false;
-				type = MT_EGGMANITEM;
-				break;
-			// intentionally do nothing
-			case MT_SINK_SHIELD:
-			case MT_ROCKETSNEAKER:
-				return;
-			default:
-				continue;
-		}
-
-		dropwork = P_SpawnMobj(work->x, work->y, work->z, type);
-		P_SetTarget(&dropwork->target, player->mo);
-		dropwork->angle = work->angle;
-		dropwork->flags2 = work->flags2;
-		dropwork->flags |= MF_NOCLIPTHING;
-		dropwork->floorz = work->floorz;
-		dropwork->ceilingz = work->ceilingz;
-
-		if (ponground)
-		{
-			// floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn
-			// This should set it for FOFs
-			//P_TeleportMove(dropwork, dropwork->x, dropwork->y, dropwork->z); -- handled better by above floorz/ceilingz passing
-
-			if (flip == 1)
-			{
-				if (dropwork->floorz > dropwork->target->z - dropwork->height)
-				{
-					dropwork->z = dropwork->floorz;
-				}
-			}
-			else
-			{
-				if (dropwork->ceilingz < dropwork->target->z + dropwork->target->height + dropwork->height)
-				{
-					dropwork->z = dropwork->ceilingz - dropwork->height;
-				}
-			}
-		}
-
-		if (orbit) // splay out
-		{
-			dropwork->flags2 |= MF2_AMBUSH;
-			dropwork->z += flip;
-			dropwork->momx = player->mo->momx>>1;
-			dropwork->momy = player->mo->momy>>1;
-			dropwork->momz = 3*flip*mapobjectscale;
-			P_Thrust(dropwork, work->angle - ANGLE_90, 6*mapobjectscale);
-			dropwork->movecount = 2;
-			dropwork->movedir = work->angle - ANGLE_90;
-			P_SetMobjState(dropwork, dropwork->info->deathstate);
-			dropwork->tics = -1;
-			if (type == MT_JAWZ_DUD)
-				dropwork->z += 20*flip*dropwork->scale;
-			else
-			{
-				dropwork->color = work->color;
-				dropwork->angle -= ANGLE_90;
-			}
-		}
-		else // plop on the ground
-		{
-			dropwork->flags &= ~MF_NOCLIPTHING;
-			dropwork->threshold = 10;
-		}
-
-		P_RemoveMobj(work);
-	}
-
-	{
-		// we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic...
-		P_SetTarget(&player->mo->hnext, NULL);
-		player->kartstuff[k_bananadrag] = 0;
-		if (player->kartstuff[k_eggmanheld])
-			player->kartstuff[k_eggmanheld] = 0;
-		else if (player->kartstuff[k_itemheld]
-			&& (dropall || (--player->kartstuff[k_itemamount] <= 0)))
-		{
-			player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0;
-			player->kartstuff[k_itemtype] = KITEM_NONE;
-		}
-	}
-}
-
-// For getting EXTRA hit!
-void K_DropItems(player_t *player)
-{
-	boolean thunderhack = (player->kartstuff[k_curshield] && player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD);
-
-	if (thunderhack)
-		player->kartstuff[k_itemtype] = KITEM_NONE;
-
-	K_DropHnextList(player);
-
-	if (player->mo && player->kartstuff[k_itemamount])
-	{
-		mobj_t *drop = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FLOATINGITEM);
-		P_SetScale(drop, drop->scale>>4);
-		drop->destscale = (3*drop->destscale)/2;;
-
-		drop->angle = player->mo->angle + ANGLE_90;
-		P_Thrust(drop,
-			FixedAngle(P_RandomFixed()*180) + player->mo->angle + ANGLE_90,
-			16*mapobjectscale);
-		drop->momz = P_MobjFlip(player->mo)*3*mapobjectscale;
-
-		drop->threshold = (thunderhack ? KITEM_THUNDERSHIELD : player->kartstuff[k_itemtype]);
-		drop->movecount = player->kartstuff[k_itemamount];
-
-		drop->flags |= MF_NOCLIPTHING;
-	}
-
-	K_StripItems(player);
-}
-
-// When an item in the hnext chain dies.
-void K_RepairOrbitChain(mobj_t *orbit)
-{
-	mobj_t *cachenext = orbit->hnext;
-
-	// First, repair the chain
-	if (orbit->hnext && orbit->hnext->health && !P_MobjWasRemoved(orbit->hnext))
-	{
-		P_SetTarget(&orbit->hnext->hprev, orbit->hprev);
-		P_SetTarget(&orbit->hnext, NULL);
-	}
-
-	if (orbit->hprev && orbit->hprev->health && !P_MobjWasRemoved(orbit->hprev))
-	{
-		P_SetTarget(&orbit->hprev->hnext, cachenext);
-		P_SetTarget(&orbit->hprev, NULL);
-	}
-
-	// Then recount to make sure item amount is correct
-	if (orbit->target && orbit->target->player)
-	{
-		INT32 num = 0;
-
-		mobj_t *cur = orbit->target->hnext;
-		mobj_t *prev = NULL;
-
-		while (cur && !P_MobjWasRemoved(cur))
-		{
-			prev = cur;
-			cur = cur->hnext;
-			if (++num > orbit->target->player->kartstuff[k_itemamount])
-				P_RemoveMobj(prev);
-			else
-				prev->movedir = num;
-		}
-
-		if (orbit->target->player->kartstuff[k_itemamount] != num)
-			orbit->target->player->kartstuff[k_itemamount] = num;
-	}
-}
-
-// Move the hnext chain!
-static void K_MoveHeldObjects(player_t *player)
-{
-	if (!player->mo)
-		return;
-
-	if (!player->mo->hnext)
-	{
-		player->kartstuff[k_bananadrag] = 0;
-		if (player->kartstuff[k_eggmanheld])
-			player->kartstuff[k_eggmanheld] = 0;
-		else if (player->kartstuff[k_itemheld])
-		{
-			player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0;
-			player->kartstuff[k_itemtype] = KITEM_NONE;
-		}
-		return;
-	}
-
-	if (P_MobjWasRemoved(player->mo->hnext))
-	{
-		// we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic...
-		P_SetTarget(&player->mo->hnext, NULL);
-		player->kartstuff[k_bananadrag] = 0;
-		if (player->kartstuff[k_eggmanheld])
-			player->kartstuff[k_eggmanheld] = 0;
-		else if (player->kartstuff[k_itemheld])
-		{
-			player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0;
-			player->kartstuff[k_itemtype] = KITEM_NONE;
-		}
-		return;
-	}
-
-	switch (player->mo->hnext->type)
-	{
-		case MT_ORBINAUT_SHIELD: // Kart orbit items
-		case MT_JAWZ_SHIELD:
-			{
-				mobj_t *cur = player->mo->hnext;
-				fixed_t speed = ((8 - min(4, player->kartstuff[k_itemamount])) * cur->info->speed) / 7;
-
-				player->kartstuff[k_bananadrag] = 0; // Just to make sure
-
-				while (cur && !P_MobjWasRemoved(cur))
-				{
-					const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); // mobj's distance from its Target, or Radius.
-					fixed_t z;
-
-					if (!cur->health)
-					{
-						cur = cur->hnext;
-						continue;
-					}
-
-					cur->color = player->skincolor;
-
-					cur->angle -= ANGLE_90;
-					cur->angle += FixedAngle(speed);
-
-					if (cur->extravalue1 < radius)
-						cur->extravalue1 += P_AproxDistance(cur->extravalue1, radius) / 12;
-					if (cur->extravalue1 > radius)
-						cur->extravalue1 = radius;
-
-					// If the player is on the ceiling, then flip your items as well.
-					if (player && player->mo->eflags & MFE_VERTICALFLIP)
-						cur->eflags |= MFE_VERTICALFLIP;
-					else
-						cur->eflags &= ~MFE_VERTICALFLIP;
-
-					// Shrink your items if the player shrunk too.
-					P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale)));
-
-					if (P_MobjFlip(cur) > 0)
-						z = player->mo->z;
-					else
-						z = player->mo->z + player->mo->height - cur->height;
-
-					cur->flags |= MF_NOCLIPTHING; // temporarily make them noclip other objects so they can't hit anyone while in the player
-					P_TeleportMove(cur, player->mo->x, player->mo->y, z);
-					cur->momx = FixedMul(FINECOSINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1);
-					cur->momy = FixedMul(FINESINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1);
-					cur->flags &= ~MF_NOCLIPTHING;
-					if (!P_TryMove(cur, player->mo->x + cur->momx, player->mo->y + cur->momy, true))
-						P_SlideMove(cur, true);
-					if (P_IsObjectOnGround(player->mo))
-					{
-						if (P_MobjFlip(cur) > 0)
-						{
-							if (cur->floorz > player->mo->z - cur->height)
-								z = cur->floorz;
-						}
-						else
-						{
-							if (cur->ceilingz < player->mo->z + player->mo->height + cur->height)
-								z = cur->ceilingz - cur->height;
-						}
-					}
-
-					// Center it during the scale up animation
-					z += (FixedMul(mobjinfo[cur->type].height, player->mo->scale - cur->scale)>>1) * P_MobjFlip(cur);
-
-					cur->z = z;
-					cur->momx = cur->momy = 0;
-					cur->angle += ANGLE_90;
-
-					cur = cur->hnext;
-				}
-			}
-			break;
-		case MT_BANANA_SHIELD: // Kart trailing items
-		case MT_SSMINE_SHIELD:
-		case MT_EGGMANITEM_SHIELD:
-		case MT_SINK_SHIELD:
-			{
-				mobj_t *cur = player->mo->hnext;
-				mobj_t *targ = player->mo;
-
-				if (P_IsObjectOnGround(player->mo) && player->speed > 0)
-					player->kartstuff[k_bananadrag]++;
-
-				while (cur && !P_MobjWasRemoved(cur))
-				{
-					const fixed_t radius = FixedHypot(targ->radius, targ->radius) + FixedHypot(cur->radius, cur->radius);
-					angle_t ang;
-					fixed_t targx, targy, targz;
-					fixed_t speed, dist;
-
-					cur->flags &= ~MF_NOCLIPTHING;
-
-					if (!cur->health)
-					{
-						cur = cur->hnext;
-						continue;
-					}
-
-					if (cur->extravalue1 < radius)
-						cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12);
-					if (cur->extravalue1 > radius)
-						cur->extravalue1 = radius;
-
-					if (cur != player->mo->hnext)
-					{
-						targ = cur->hprev;
-						dist = cur->extravalue1/4;
-					}
-					else
-						dist = cur->extravalue1/2;
-
-					if (!targ || P_MobjWasRemoved(targ))
-						continue;
-
-					// Shrink your items if the player shrunk too.
-					P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale)));
-
-					ang = targ->angle;
-					targx = targ->x + P_ReturnThrustX(cur, ang + ANGLE_180, dist);
-					targy = targ->y + P_ReturnThrustY(cur, ang + ANGLE_180, dist);
-					targz = targ->z;
-					speed = FixedMul(R_PointToDist2(cur->x, cur->y, targx, targy), 3*FRACUNIT/4);
-					if (P_IsObjectOnGround(targ))
-						targz = cur->floorz;
-
-					cur->angle = R_PointToAngle2(cur->x, cur->y, targx, targy);
-
-					/*if (P_IsObjectOnGround(player->mo) && player->speed > 0 && player->kartstuff[k_bananadrag] > TICRATE
-						&& P_RandomChance(min(FRACUNIT/2, FixedDiv(player->speed, K_GetKartSpeed(player, false))/2)))
-					{
-						if (leveltime & 1)
-							targz += 8*(2*FRACUNIT)/7;
-						else
-							targz -= 8*(2*FRACUNIT)/7;
-					}*/
-
-					if (speed > dist)
-						P_InstaThrust(cur, cur->angle, speed-dist);
-
-					P_SetObjectMomZ(cur, FixedMul(targz - cur->z, 7*FRACUNIT/8) - gravity, false);
-
-					if (R_PointToDist2(cur->x, cur->y, targx, targy) > 768*FRACUNIT)
-						P_TeleportMove(cur, targx, targy, cur->z);
-
-					cur = cur->hnext;
-				}
-			}
-			break;
-		case MT_ROCKETSNEAKER: // Special rocket sneaker stuff
-			{
-				mobj_t *cur = player->mo->hnext;
-				INT32 num = 0;
-
-				while (cur && !P_MobjWasRemoved(cur))
-				{
-					const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius);
-					boolean vibrate = ((leveltime & 1) && !cur->tracer);
-					angle_t angoffset;
-					fixed_t targx, targy, targz;
-
-					cur->flags &= ~MF_NOCLIPTHING;
-
-					if (player->kartstuff[k_rocketsneakertimer] <= TICRATE && (leveltime & 1))
-						cur->flags2 |= MF2_DONTDRAW;
-					else
-						cur->flags2 &= ~MF2_DONTDRAW;
-
-					if (num & 1)
-						P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_LVIBRATE : S_ROCKETSNEAKER_L));
-					else
-						P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_RVIBRATE : S_ROCKETSNEAKER_R));
-
-					if (!player->kartstuff[k_rocketsneakertimer] || cur->extravalue2 || !cur->health)
-					{
-						num = (num+1) % 2;
-						cur = cur->hnext;
-						continue;
-					}
-
-					if (cur->extravalue1 < radius)
-						cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12);
-					if (cur->extravalue1 > radius)
-						cur->extravalue1 = radius;
-
-					// Shrink your items if the player shrunk too.
-					P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale)));
-
-#if 1
-					{
-						angle_t input = player->frameangle - cur->angle;
-						boolean invert = (input > ANGLE_180);
-						if (invert)
-							input = InvAngle(input);
-
-						input = FixedAngle(AngleFixed(input)/4);
-						if (invert)
-							input = InvAngle(input);
-
-						cur->angle = cur->angle + input;
-					}
-#else
-					cur->angle = player->frameangle;
-#endif
-
-					angoffset = ANGLE_90 + (ANGLE_180 * num);
-
-					targx = player->mo->x + P_ReturnThrustX(cur, cur->angle + angoffset, cur->extravalue1);
-					targy = player->mo->y + P_ReturnThrustY(cur, cur->angle + angoffset, cur->extravalue1);
-
-					{ // bobbing, copy pasted from my kimokawaiii entry
-						const fixed_t pi = (22<<FRACBITS) / 7; // loose approximation, this doesn't need to be incredibly precise
-						fixed_t sine = 8 * FINESINE((((2*pi*(4*TICRATE)) * leveltime)>>ANGLETOFINESHIFT) & FINEMASK);
-						targz = (player->mo->z + (player->mo->height/2)) + sine;
-					}
-
-					if (cur->tracer)
-					{
-						fixed_t diffx, diffy, diffz;
-
-						diffx = targx - cur->x;
-						diffy = targy - cur->y;
-						diffz = targz - cur->z;
-
-						P_TeleportMove(cur->tracer, cur->tracer->x + diffx + P_ReturnThrustX(cur, cur->angle + angoffset, 6*cur->scale),
-							cur->tracer->y + diffy + P_ReturnThrustY(cur, cur->angle + angoffset, 6*cur->scale), cur->tracer->z + diffz);
-						P_SetScale(cur->tracer, (cur->tracer->destscale = 3*cur->scale/4));
-					}
-
-					P_TeleportMove(cur, targx, targy, targz);
-
-					num = (num+1) % 2;
-					cur = cur->hnext;
-				}
-			}
-			break;
-		default:
-			break;
-	}
-}
-
-player_t *K_FindJawzTarget(mobj_t *actor, player_t *source)
-{
-	fixed_t best = -1;
-	player_t *wtarg = NULL;
-	INT32 i;
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		angle_t thisang;
-		player_t *player;
-
-		if (!playeringame[i])
-			continue;
-
-		player = &players[i];
-
-		if (player->spectator)
-			continue; // spectator
-
-		if (!player->mo)
-			continue;
-
-		if (player->mo->health <= 0)
-			continue; // dead
-
-		// Don't target yourself, stupid.
-		if (player == source)
-			continue;
-
-		// Don't home in on teammates.
-		if (G_GametypeHasTeams() && source->ctfteam == player->ctfteam)
-			continue;
-
-		// Invisible, don't bother
-		if (player->kartstuff[k_hyudorotimer])
-			continue;
-
-		// Find the angle, see who's got the best.
-		thisang = actor->angle - R_PointToAngle2(actor->x, actor->y, player->mo->x, player->mo->y);
-		if (thisang > ANGLE_180)
-			thisang = InvAngle(thisang);
-
-		// Jawz only go after the person directly ahead of you in race... sort of literally now!
-		if (G_RaceGametype())
-		{
-			// Don't go for people who are behind you
-			if (thisang > ANGLE_67h)
-				continue;
-			// Don't pay attention to people who aren't above your position
-			if (player->kartstuff[k_position] >= source->kartstuff[k_position])
-				continue;
-			if ((best == -1) || (player->kartstuff[k_position] > best))
-			{
-				wtarg = player;
-				best = player->kartstuff[k_position];
-			}
-		}
-		else
-		{
-			fixed_t thisdist;
-			fixed_t thisavg;
-
-			// Don't go for people who are behind you
-			if (thisang > ANGLE_45)
-				continue;
-
-			// Don't pay attention to dead players
-			if (player->kartstuff[k_bumper] <= 0)
-				continue;
-
-			// Z pos too high/low
-			if (abs(player->mo->z - (actor->z + actor->momz)) > RING_DIST/8)
-				continue;
-
-			thisdist = P_AproxDistance(player->mo->x - (actor->x + actor->momx), player->mo->y - (actor->y + actor->momy));
-
-			if (thisdist > 2*RING_DIST) // Don't go for people who are too far away
-				continue;
-
-			thisavg = (AngleFixed(thisang) + thisdist) / 2;
-
-			//CONS_Printf("got avg %d from player # %d\n", thisavg>>FRACBITS, i);
-
-			if ((best == -1) || (thisavg < best))
-			{
-				wtarg = player;
-				best = thisavg;
-			}
-		}
-	}
-
-	return wtarg;
-}
-
-// Engine Sounds.
-static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd)
-{
-	const INT32 numsnds = 13;
-	INT32 class, s, w; // engine class number
-	UINT8 volume = 255;
-	fixed_t volumedampen = 0;
-	INT32 targetsnd = 0;
-	INT32 i;
-
-	s = (player->kartspeed-1)/3;
-	w = (player->kartweight-1)/3;
-
-#define LOCKSTAT(stat) \
-	if (stat < 0) { stat = 0; } \
-	if (stat > 2) { stat = 2; }
-	LOCKSTAT(s);
-	LOCKSTAT(w);
-#undef LOCKSTAT
-
-	class = s+(3*w);
-
-	// Silence the engines
-	if (leveltime < 8 || player->spectator || player->exiting)
-	{
-		player->kartstuff[k_enginesnd] = 0; // Reset sound number
-		return;
-	}
-
-#if 0
-	if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct!
-#else
-	if (leveltime % 8) // .25 seconds of wait time between engine sounds
-#endif
-		return;
-
-	if ((leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) || (player->kartstuff[k_respawn] == 1)) // Startup boosts
-		targetsnd = ((cmd->buttons & BT_ACCELERATE) ? 12 : 0);
-	else
-		targetsnd = (((6*cmd->forwardmove)/25) + ((player->speed / mapobjectscale)/5))/2;
-
-	if (targetsnd < 0)
-		targetsnd = 0;
-	if (targetsnd > 12)
-		targetsnd = 12;
-
-	if (player->kartstuff[k_enginesnd] < targetsnd)
-		player->kartstuff[k_enginesnd]++;
-	if (player->kartstuff[k_enginesnd] > targetsnd)
-		player->kartstuff[k_enginesnd]--;
-
-	if (player->kartstuff[k_enginesnd] < 0)
-		player->kartstuff[k_enginesnd] = 0;
-	if (player->kartstuff[k_enginesnd] > 12)
-		player->kartstuff[k_enginesnd] = 12;
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		UINT8 thisvol = 0;
-		fixed_t dist;
-
-		if (!playeringame[i] || !players[i].mo || players[i].spectator || players[i].exiting)
-			continue;
-
-		if ((i == displayplayer)
-			|| (i == secondarydisplayplayer && splitscreen)
-			|| (i == thirddisplayplayer && splitscreen > 1)
-			|| (i == fourthdisplayplayer && splitscreen > 2))
-		{
-			volumedampen += FRACUNIT; // We already know what this is gonna be, let's not waste our time.
-			continue;
-		}
-
-		dist = P_AproxDistance(P_AproxDistance(player->mo->x-players[i].mo->x,
-			player->mo->y-players[i].mo->y), player->mo->z-players[i].mo->z) / 2;
-
-		dist = FixedDiv(dist, mapobjectscale);
-
-		if (dist > 1536<<FRACBITS)
-			continue;
-		else if (dist < 160<<FRACBITS) // engine sounds' approx. range
-			thisvol = 255;
-		else
-			thisvol = (15 * (((160<<FRACBITS) - dist)>>FRACBITS)) / (((1536<<FRACBITS)-(160<<FRACBITS))>>(FRACBITS+4));
-
-		if (thisvol == 0)
-			continue;
-
-		volumedampen += (thisvol * 257); // 255 * 257 = FRACUNIT
-	}
-
-	if (volumedampen > FRACUNIT)
-		volume = FixedDiv(volume<<FRACBITS, volumedampen)>>FRACBITS;
-
-	if (volume <= 0) // Might as well
-		return;
-
-	S_StartSoundAtVolume(player->mo, (sfx_krta00 + player->kartstuff[k_enginesnd]) + (class*numsnds), volume);
-}
-
-static void K_UpdateInvincibilitySounds(player_t *player)
-{
-	INT32 sfxnum = sfx_None;
-
-	if (player->mo->health > 0 && !P_IsLocalPlayer(player))
-	{
-		if (cv_kartinvinsfx.value)
-		{
-			if (player->kartstuff[k_growshrinktimer] > 0) // Prioritize Grow
-				sfxnum = sfx_alarmg;
-			else if (player->kartstuff[k_invincibilitytimer] > 0)
-				sfxnum = sfx_alarmi;
-		}
-		else
-		{
-			if (player->kartstuff[k_growshrinktimer] > 0)
-				sfxnum = sfx_kgrow;
-			else if (player->kartstuff[k_invincibilitytimer] > 0)
-				sfxnum = sfx_kinvnc;
-		}
-	}
-
-	if (sfxnum != sfx_None && !S_SoundPlaying(player->mo, sfxnum))
-		S_StartSound(player->mo, sfxnum);
-
-#define STOPTHIS(this) \
-	if (sfxnum != this && S_SoundPlaying(player->mo, this)) \
-		S_StopSoundByID(player->mo, this);
-	STOPTHIS(sfx_alarmi);
-	STOPTHIS(sfx_alarmg);
-	STOPTHIS(sfx_kinvnc);
-	STOPTHIS(sfx_kgrow);
-#undef STOPTHIS
-}
-
-void K_KartPlayerHUDUpdate(player_t *player)
-{
-	if (player->kartstuff[k_lapanimation])
-		player->kartstuff[k_lapanimation]--;
-
-	if (player->kartstuff[k_yougotem])
-		player->kartstuff[k_yougotem]--;
-
-	if (G_BattleGametype() && (player->exiting || player->kartstuff[k_comebacktimer]))
-	{
-		if (player->exiting)
-		{
-			if (player->exiting < 6*TICRATE)
-				player->kartstuff[k_cardanimation] += ((164-player->kartstuff[k_cardanimation])/8)+1;
-			else if (player->exiting == 6*TICRATE)
-				player->kartstuff[k_cardanimation] = 0;
-			else if (player->kartstuff[k_cardanimation] < 2*TICRATE)
-				player->kartstuff[k_cardanimation]++;
-		}
-		else
-		{
-			if (player->kartstuff[k_comebacktimer] < 6*TICRATE)
-				player->kartstuff[k_cardanimation] -= ((164-player->kartstuff[k_cardanimation])/8)+1;
-			else if (player->kartstuff[k_comebacktimer] < 9*TICRATE)
-				player->kartstuff[k_cardanimation] += ((164-player->kartstuff[k_cardanimation])/8)+1;
-		}
-
-		if (player->kartstuff[k_cardanimation] > 164)
-			player->kartstuff[k_cardanimation] = 164;
-		if (player->kartstuff[k_cardanimation] < 0)
-			player->kartstuff[k_cardanimation] = 0;
-	}
-	else if (G_RaceGametype() && player->exiting)
-	{
-		if (player->kartstuff[k_cardanimation] < 2*TICRATE)
-			player->kartstuff[k_cardanimation]++;
-	}
-	else
-		player->kartstuff[k_cardanimation] = 0;
-}
-
-/**	\brief	Decreases various kart timers and powers per frame. Called in P_PlayerThink in p_user.c
-
-	\param	player	player object passed from P_PlayerThink
-	\param	cmd		control input from player
-
-	\return	void
-*/
-void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
-{
-	K_UpdateOffroad(player);
-	K_UpdateEngineSounds(player, cmd); // Thanks, VAda!
-	K_GetKartBoostPower(player);
-
-	// Speed lines
-	if ((player->kartstuff[k_sneakertimer] || player->kartstuff[k_driftboost] || player->kartstuff[k_startboost]) && player->speed > 0)
-	{
-		mobj_t *fast = P_SpawnMobj(player->mo->x + (P_RandomRange(-36,36) * player->mo->scale),
-			player->mo->y + (P_RandomRange(-36,36) * player->mo->scale),
-			player->mo->z + (player->mo->height/2) + (P_RandomRange(-20,20) * player->mo->scale),
-			MT_FASTLINE);
-		fast->angle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
-		fast->momx = 3*player->mo->momx/4;
-		fast->momy = 3*player->mo->momy/4;
-		fast->momz = 3*player->mo->momz/4;
-		K_MatchGenericExtraFlags(fast, player->mo);
-	}
-
-	if (player->playerstate == PST_DEAD || player->kartstuff[k_respawn] > 1) // Ensure these are set correctly here
-	{
-		player->mo->colorized = false;
-		player->mo->color = player->skincolor;
-	}
-	else if (player->kartstuff[k_eggmanexplode]) // You're gonna diiiiie
-	{
-		const INT32 flashtime = 4<<(player->kartstuff[k_eggmanexplode]/TICRATE);
-		if (player->kartstuff[k_eggmanexplode] == 1 || (player->kartstuff[k_eggmanexplode] % (flashtime/2) != 0))
-		{
-			player->mo->colorized = false;
-			player->mo->color = player->skincolor;
-		}
-		else if (player->kartstuff[k_eggmanexplode] % flashtime == 0)
-		{
-			player->mo->colorized = true;
-			player->mo->color = SKINCOLOR_BLACK;
-		}
-		else
-		{
-			player->mo->colorized = true;
-			player->mo->color = SKINCOLOR_CRIMSON;
-		}
-	}
-	else if (player->kartstuff[k_invincibilitytimer]) // setting players to use the star colormap and spawning afterimages
-	{
-		mobj_t *ghost;
-		player->mo->colorized = true;
-		ghost = P_SpawnGhostMobj(player->mo);
-		ghost->fuse = 4;
-		ghost->frame |= FF_FULLBRIGHT;
-	}
-	else if (player->kartstuff[k_growshrinktimer]) // Ditto, for grow/shrink
-	{
-		if (player->kartstuff[k_growshrinktimer] % 5 == 0)
-		{
-			player->mo->colorized = true;
-			player->mo->color = (player->kartstuff[k_growshrinktimer] < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE);
-		}
-		else
-		{
-			player->mo->colorized = false;
-			player->mo->color = player->skincolor;
-		}
-	}
-	else
-	{
-		player->mo->colorized = false;
-	}
-
-	if (player->kartstuff[k_dashpadcooldown]) // Twinkle Circuit inspired afterimages
-	{
-		mobj_t *ghost;
-		ghost = P_SpawnGhostMobj(player->mo);
-		ghost->fuse = player->kartstuff[k_dashpadcooldown]+1;
-		ghost->momx = player->mo->momx / (player->kartstuff[k_dashpadcooldown]+1);
-		ghost->momy = player->mo->momy / (player->kartstuff[k_dashpadcooldown]+1);
-		ghost->momz = player->mo->momz / (player->kartstuff[k_dashpadcooldown]+1);
-		player->kartstuff[k_dashpadcooldown]--;
-	}
-
-	// DKR style camera for boosting
-	if (player->kartstuff[k_boostcam] != 0 || player->kartstuff[k_destboostcam] != 0)
-	{
-		if (player->kartstuff[k_boostcam] < player->kartstuff[k_destboostcam]
-			&& player->kartstuff[k_destboostcam] != 0)
-		{
-			player->kartstuff[k_boostcam] += FRACUNIT/(TICRATE/4);
-			if (player->kartstuff[k_boostcam] >= player->kartstuff[k_destboostcam])
-				player->kartstuff[k_destboostcam] = 0;
-		}
-		else
-		{
-			player->kartstuff[k_boostcam] -= FRACUNIT/TICRATE;
-			if (player->kartstuff[k_boostcam] < player->kartstuff[k_destboostcam])
-				player->kartstuff[k_boostcam] = player->kartstuff[k_destboostcam] = 0;
-		}
-		//CONS_Printf("cam: %d, dest: %d\n", player->kartstuff[k_boostcam], player->kartstuff[k_destboostcam]);
-	}
-
-	player->kartstuff[k_timeovercam] = 0;
-
-	// Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations.
-	if (player->kartstuff[k_spinouttimer] != 0
-		|| player->kartstuff[k_wipeoutslow] != 0
-		|| player->kartstuff[k_squishedtimer] != 0)
-	{
-		player->powers[pw_flashing] = K_GetKartFlashing(player);
-	}
-	else if (player->powers[pw_flashing] == K_GetKartFlashing(player))
-	{
-		player->powers[pw_flashing]--;
-	}
-
-	if (player->kartstuff[k_spinouttimer])
-	{
-		if ((P_IsObjectOnGround(player->mo) || player->kartstuff[k_spinouttype] == 1)
-			&& (player->kartstuff[k_sneakertimer] == 0))
-		{
-			player->kartstuff[k_spinouttimer]--;
-			if (player->kartstuff[k_wipeoutslow] > 1)
-				player->kartstuff[k_wipeoutslow]--;
-			if (player->kartstuff[k_spinouttimer] == 0)
-				player->kartstuff[k_spinouttype] = 0; // Reset type
-		}
-	}
-	else
-	{
-		if (player->kartstuff[k_wipeoutslow] == 1)
-			player->mo->friction = ORIG_FRICTION;
-		player->kartstuff[k_wipeoutslow] = 0;
-		if (!comeback)
-			player->kartstuff[k_comebacktimer] = comebacktime;
-		else if (player->kartstuff[k_comebacktimer])
-		{
-			player->kartstuff[k_comebacktimer]--;
-			if (P_IsLocalPlayer(player) && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer] <= 0)
-				comebackshowninfo = true; // client has already seen the message
-		}
-	}
-
-	/*if (player->kartstuff[k_thunderanim])
-		player->kartstuff[k_thunderanim]--;*/
-
-	if (player->kartstuff[k_sneakertimer])
-	{
-		player->kartstuff[k_sneakertimer]--;
-		if (player->kartstuff[k_wipeoutslow] > 0 && player->kartstuff[k_wipeoutslow] < wipeoutslowtime+1)
-			player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1;
-	}
-
-	if (player->kartstuff[k_floorboost])
-		player->kartstuff[k_floorboost]--;
-
-	if (player->kartstuff[k_driftboost])
-		player->kartstuff[k_driftboost]--;
-
-	if (player->kartstuff[k_startboost])
-		player->kartstuff[k_startboost]--;
-
-	if (player->kartstuff[k_invincibilitytimer])
-		player->kartstuff[k_invincibilitytimer]--;
-
-	if (!player->kartstuff[k_respawn] && player->kartstuff[k_growshrinktimer] != 0)
-	{
-		if (player->kartstuff[k_growshrinktimer] > 0)
-			player->kartstuff[k_growshrinktimer]--;
-		if (player->kartstuff[k_growshrinktimer] < 0)
-			player->kartstuff[k_growshrinktimer]++;
-
-		// Back to normal
-		if (player->kartstuff[k_growshrinktimer] == 0)
-			K_RemoveGrowShrink(player);
-	}
-
-	if (player->kartstuff[k_stealingtimer] == 0 && player->kartstuff[k_stolentimer] == 0
-		&& player->kartstuff[k_rocketsneakertimer])
-		player->kartstuff[k_rocketsneakertimer]--;
-
-	if (player->kartstuff[k_hyudorotimer])
-		player->kartstuff[k_hyudorotimer]--;
-
-	if (player->kartstuff[k_sadtimer])
-		player->kartstuff[k_sadtimer]--;
-
-	if (player->kartstuff[k_stealingtimer])
-		player->kartstuff[k_stealingtimer]--;
-
-	if (player->kartstuff[k_stolentimer])
-		player->kartstuff[k_stolentimer]--;
-
-	if (player->kartstuff[k_squishedtimer])
-		player->kartstuff[k_squishedtimer]--;
-
-	if (player->kartstuff[k_justbumped])
-		player->kartstuff[k_justbumped]--;
-
-	// This doesn't go in HUD update because it has potential gameplay ramifications
-	if (player->kartstuff[k_itemblink] && player->kartstuff[k_itemblink]-- <= 0)
-	{
-		player->kartstuff[k_itemblinkmode] = 0;
-		player->kartstuff[k_itemblink] = 0;
-	}
-
-	K_KartPlayerHUDUpdate(player);
-
-	if (player->kartstuff[k_voices])
-		player->kartstuff[k_voices]--;
-
-	if (player->kartstuff[k_tauntvoices])
-		player->kartstuff[k_tauntvoices]--;
-
-	if (G_BattleGametype() && player->kartstuff[k_bumper] > 0)
-		player->kartstuff[k_wanted]++;
-
-	if (P_IsObjectOnGround(player->mo))
-		player->kartstuff[k_waterskip] = 0;
-
-	if (player->kartstuff[k_instashield])
-		player->kartstuff[k_instashield]--;
-
-	if (player->kartstuff[k_eggmanexplode])
-	{
-		if (player->spectator || (G_BattleGametype() && !player->kartstuff[k_bumper]))
-			player->kartstuff[k_eggmanexplode] = 0;
-		else
-		{
-			player->kartstuff[k_eggmanexplode]--;
-			if (player->kartstuff[k_eggmanexplode] <= 0)
-			{
-				mobj_t *eggsexplode;
-				//player->powers[pw_flashing] = 0;
-				eggsexplode = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SPBEXPLOSION);
-				if (player->kartstuff[k_eggmanblame] >= 0
-				&& player->kartstuff[k_eggmanblame] < MAXPLAYERS
-				&& playeringame[player->kartstuff[k_eggmanblame]]
-				&& !players[player->kartstuff[k_eggmanblame]].spectator
-				&& players[player->kartstuff[k_eggmanblame]].mo)
-					P_SetTarget(&eggsexplode->target, players[player->kartstuff[k_eggmanblame]].mo);
-			}
-		}
-	}
-
-	// ???
-	/*
-	if (player->kartstuff[k_jmp] > 1 && onground)
-	{
-		S_StartSound(player->mo, sfx_spring);
-		P_DoJump(player, false);
-		player->mo->momz *= player->kartstuff[k_jmp];
-		player->kartstuff[k_jmp] = 0;
-	}
-	*/
-
-	if (player->kartstuff[k_comebacktimer])
-		player->kartstuff[k_comebackmode] = 0;
-
-	if (P_IsObjectOnGround(player->mo) && player->mo->momz <= 0 && player->kartstuff[k_pogospring])
-		player->kartstuff[k_pogospring] = 0;
-
-	if (cmd->buttons & BT_DRIFT)
-		player->kartstuff[k_jmp] = 1;
-	else
-		player->kartstuff[k_jmp] = 0;
-
-	// Respawn Checker
-	if (player->kartstuff[k_respawn])
-		K_RespawnChecker(player);
-
-	// Roulette Code
-	K_KartItemRoulette(player, cmd);
-
-	// Handle invincibility sfx
-	K_UpdateInvincibilitySounds(player); // Also thanks, VAda!
-
-	// Plays the music after the starting countdown.
-	if (P_IsLocalPlayer(player) && leveltime == (starttime + (TICRATE/2)))
-	{
-		S_ChangeMusic(mapmusname, mapmusflags, true);
-		S_ShowMusicCredit();
-	}
-}
-
-void K_KartPlayerAfterThink(player_t *player)
-{
-	if (player->kartstuff[k_curshield]
-		|| player->kartstuff[k_invincibilitytimer]
-		|| (player->kartstuff[k_growshrinktimer] != 0 && player->kartstuff[k_growshrinktimer] % 5 == 4)) // 4 instead of 0 because this is afterthink!
-	{
-		player->mo->frame |= FF_FULLBRIGHT;
-	}
-	else
-	{
-		if (!(player->mo->state->frame & FF_FULLBRIGHT))
-			player->mo->frame &= ~FF_FULLBRIGHT;
-	}
-
-	// Move held objects (Bananas, Orbinaut, etc)
-	K_MoveHeldObjects(player);
-
-	// Jawz reticule (seeking)
-	if (player->kartstuff[k_itemtype] == KITEM_JAWZ && player->kartstuff[k_itemheld])
-	{
-		INT32 lasttarg = player->kartstuff[k_lastjawztarget];
-		player_t *targ;
-		mobj_t *ret;
-
-		if (player->kartstuff[k_jawztargetdelay] && playeringame[lasttarg] && !players[lasttarg].spectator)
-		{
-			targ = &players[lasttarg];
-			player->kartstuff[k_jawztargetdelay]--;
-		}
-		else
-			targ = K_FindJawzTarget(player->mo, player);
-
-		if (!targ || !targ->mo || P_MobjWasRemoved(targ->mo))
-		{
-			player->kartstuff[k_lastjawztarget] = -1;
-			player->kartstuff[k_jawztargetdelay] = 0;
-			return;
-		}
-
-		ret = P_SpawnMobj(targ->mo->x, targ->mo->y, targ->mo->z, MT_PLAYERRETICULE);
-		P_SetTarget(&ret->target, targ->mo);
-		ret->frame |= ((leveltime % 10) / 2);
-		ret->tics = 1;
-		ret->color = player->skincolor;
-
-		if (targ-players != lasttarg)
-		{
-			if (P_IsLocalPlayer(player) || P_IsLocalPlayer(targ))
-				S_StartSound(NULL, sfx_s3k89);
-			else
-				S_StartSound(targ->mo, sfx_s3k89);
-
-			player->kartstuff[k_lastjawztarget] = targ-players;
-			player->kartstuff[k_jawztargetdelay] = 5;
-		}
-	}
-	else
-	{
-		player->kartstuff[k_lastjawztarget] = -1;
-		player->kartstuff[k_jawztargetdelay] = 0;
-	}
-}
-
-// Returns false if this player being placed here causes them to collide with any other player
-// Used in g_game.c for match etc. respawning
-// This does not check along the z because the z is not correctly set for the spawnee at this point
-boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y)
-{
-	INT32 i;
-	fixed_t p1radius = players[playernum].mo->radius;
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (playernum == i || !playeringame[i] || players[i].spectator || !players[i].mo || players[i].mo->health <= 0
-			|| players[i].playerstate != PST_LIVE || (players[i].mo->flags & MF_NOCLIP) || (players[i].mo->flags & MF_NOCLIPTHING))
-			continue;
-
-		if (abs(x - players[i].mo->x) < (p1radius + players[i].mo->radius)
-			&& abs(y - players[i].mo->y) < (p1radius + players[i].mo->radius))
-		{
-			return false;
-		}
-	}
-	return true;
-}
-
-// countersteer is how strong the controls are telling us we are turning
-// turndir is the direction the controls are telling us to turn, -1 if turning right and 1 if turning left
-static INT16 K_GetKartDriftValue(player_t *player, fixed_t countersteer)
-{
-	INT16 basedrift, driftangle;
-	fixed_t driftweight = player->kartweight*14; // 12
-
-	// If they aren't drifting or on the ground this doesn't apply
-	if (player->kartstuff[k_drift] == 0 || !P_IsObjectOnGround(player->mo))
-		return 0;
-
-	if (player->kartstuff[k_driftend] != 0)
-	{
-		return -266*player->kartstuff[k_drift]; // Drift has ended and we are tweaking their angle back a bit
-	}
-
-	//basedrift = 90*player->kartstuff[k_drift]; // 450
-	//basedrift = 93*player->kartstuff[k_drift] - driftweight*3*player->kartstuff[k_drift]/10; // 447 - 303
-	basedrift = 83*player->kartstuff[k_drift] - (driftweight - 14)*player->kartstuff[k_drift]/5; // 415 - 303
-	driftangle = abs((252 - driftweight)*player->kartstuff[k_drift]/5);
-
-	return basedrift + FixedMul(driftangle, countersteer);
-}
-
-INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue)
-{
-	fixed_t p_maxspeed = FixedMul(K_GetKartSpeed(player, false), 3*FRACUNIT);
-	fixed_t adjustangle = FixedDiv((p_maxspeed>>16) - (player->speed>>16), (p_maxspeed>>16) + player->kartweight);
-
-	if (player->spectator)
-		return turnvalue;
-
-	if (player->kartstuff[k_drift] != 0 && P_IsObjectOnGround(player->mo))
-	{
-		// If we're drifting we have a completely different turning value
-		if (player->kartstuff[k_driftend] == 0)
-		{
-			// 800 is the max set in g_game.c with angleturn
-			fixed_t countersteer = FixedDiv(turnvalue*FRACUNIT, 800*FRACUNIT);
-			turnvalue = K_GetKartDriftValue(player, countersteer);
-		}
-		else
-			turnvalue = (INT16)(turnvalue + K_GetKartDriftValue(player, FRACUNIT));
-
-		return turnvalue;
-	}
-
-	turnvalue = FixedMul(turnvalue, adjustangle); // Weight has a small effect on turning
-
-	if (player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_sneakertimer] || player->kartstuff[k_growshrinktimer] > 0)
-		turnvalue = FixedMul(turnvalue, FixedDiv(5*FRACUNIT, 4*FRACUNIT));
-
-	return turnvalue;
-}
-
-INT32 K_GetKartDriftSparkValue(player_t *player)
-{
-	UINT8 kartspeed = (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)
-		? 1
-		: player->kartspeed;
-	return (26*4 + kartspeed*2 + (9 - player->kartweight))*8;
-}
-
-static void K_KartDrift(player_t *player, boolean onground)
-{
-	fixed_t minspeed = (10 * player->mo->scale);
-	INT32 dsone = K_GetKartDriftSparkValue(player);
-	INT32 dstwo = dsone*2;
-	INT32 dsthree = dstwo*2;
-
-	// Drifting is actually straffing + automatic turning.
-	// Holding the Jump button will enable drifting.
-
-	// Drift Release (Moved here so you can't "chain" drifts)
-	if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5)
-		// || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1))
-		&& player->kartstuff[k_driftcharge] < dsone
-		&& onground)
-	{
-		player->kartstuff[k_driftcharge] = 0;
-	}
-	else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5)
-		// || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1))
-		&& (player->kartstuff[k_driftcharge] >= dsone && player->kartstuff[k_driftcharge] < dstwo)
-		&& onground)
-	{
-		if (player->kartstuff[k_driftboost] < 20)
-			player->kartstuff[k_driftboost] = 20;
-		S_StartSound(player->mo, sfx_s23c);
-		//K_SpawnDashDustRelease(player);
-		player->kartstuff[k_driftcharge] = 0;
-	}
-	else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5)
-		// || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1))
-		&& player->kartstuff[k_driftcharge] < dsthree
-		&& onground)
-	{
-		if (player->kartstuff[k_driftboost] < 50)
-			player->kartstuff[k_driftboost] = 50;
-		S_StartSound(player->mo, sfx_s23c);
-		//K_SpawnDashDustRelease(player);
-		player->kartstuff[k_driftcharge] = 0;
-	}
-	else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5)
-		// || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1))
-		&& player->kartstuff[k_driftcharge] >= dsthree
-		&& onground)
-	{
-		if (player->kartstuff[k_driftboost] < 125)
-			player->kartstuff[k_driftboost] = 125;
-		S_StartSound(player->mo, sfx_s23c);
-		//K_SpawnDashDustRelease(player);
-		player->kartstuff[k_driftcharge] = 0;
-	}
-
-	// Drifting: left or right?
-	if ((player->cmd.driftturn > 0) && player->speed > minspeed && player->kartstuff[k_jmp] == 1
-		&& (player->kartstuff[k_drift] == 0 || player->kartstuff[k_driftend] == 1)) // && player->kartstuff[k_drift] != 1)
-	{
-		// Starting left drift
-		player->kartstuff[k_drift] = 1;
-		player->kartstuff[k_driftend] = player->kartstuff[k_driftcharge] = 0;
-	}
-	else if ((player->cmd.driftturn < 0) && player->speed > minspeed && player->kartstuff[k_jmp] == 1
-		&& (player->kartstuff[k_drift] == 0 || player->kartstuff[k_driftend] == 1)) // && player->kartstuff[k_drift] != -1)
-	{
-		// Starting right drift
-		player->kartstuff[k_drift] = -1;
-		player->kartstuff[k_driftend] = player->kartstuff[k_driftcharge] = 0;
-	}
-	else if (player->kartstuff[k_jmp] == 0) // || player->kartstuff[k_turndir] == 0)
-	{
-		// drift is not being performed so if we're just finishing set driftend and decrement counters
-		if (player->kartstuff[k_drift] > 0)
-		{
-			player->kartstuff[k_drift]--;
-			player->kartstuff[k_driftend] = 1;
-		}
-		else if (player->kartstuff[k_drift] < 0)
-		{
-			player->kartstuff[k_drift]++;
-			player->kartstuff[k_driftend] = 1;
-		}
-		else
-			player->kartstuff[k_driftend] = 0;
-	}
-
-	// Incease/decrease the drift value to continue drifting in that direction
-	if (player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_jmp] == 1 && onground && player->kartstuff[k_drift] != 0)
-	{
-		fixed_t driftadditive = 24;
-
-		if (player->kartstuff[k_drift] >= 1) // Drifting to the left
-		{
-			player->kartstuff[k_drift]++;
-			if (player->kartstuff[k_drift] > 5)
-				player->kartstuff[k_drift] = 5;
-
-			if (player->cmd.driftturn > 0) // Inward
-				driftadditive += abs(player->cmd.driftturn)/100;
-			if (player->cmd.driftturn < 0) // Outward
-				driftadditive -= abs(player->cmd.driftturn)/75;
-		}
-		else if (player->kartstuff[k_drift] <= -1) // Drifting to the right
-		{
-			player->kartstuff[k_drift]--;
-			if (player->kartstuff[k_drift] < -5)
-				player->kartstuff[k_drift] = -5;
-
-			if (player->cmd.driftturn < 0) // Inward
-				driftadditive += abs(player->cmd.driftturn)/100;
-			if (player->cmd.driftturn > 0) // Outward
-				driftadditive -= abs(player->cmd.driftturn)/75;
-		}
-
-		// Disable drift-sparks until you're going fast enough
-		if (player->kartstuff[k_getsparks] == 0 || player->kartstuff[k_offroad])
-			driftadditive = 0;
-		if (player->speed > minspeed*2)
-			player->kartstuff[k_getsparks] = 1;
-
-		// This spawns the drift sparks
-		if (player->kartstuff[k_driftcharge] + driftadditive >= dsone)
-			K_SpawnDriftSparks(player);
-
-		// Sound whenever you get a different tier of sparks
-		if (P_IsLocalPlayer(player) // UGHGHGH...
-			&& ((player->kartstuff[k_driftcharge] < dsone && player->kartstuff[k_driftcharge]+driftadditive >= dsone)
-			|| (player->kartstuff[k_driftcharge] < dstwo && player->kartstuff[k_driftcharge]+driftadditive >= dstwo)
-			|| (player->kartstuff[k_driftcharge] < dsthree && player->kartstuff[k_driftcharge]+driftadditive >= dsthree)))
-		{
-			//S_StartSound(player->mo, sfx_s3ka2);
-			S_StartSoundAtVolume(player->mo, sfx_s3ka2, 192); // Ugh...
-		}
-
-		player->kartstuff[k_driftcharge] += driftadditive;
-		player->kartstuff[k_driftend] = 0;
-	}
-
-	// Stop drifting
-	if (player->kartstuff[k_spinouttimer] > 0 || player->speed < minspeed)
-	{
-		player->kartstuff[k_drift] = player->kartstuff[k_driftcharge] = 0;
-		player->kartstuff[k_aizdriftstrat] = player->kartstuff[k_brakedrift] = 0;
-		player->kartstuff[k_getsparks] = 0;
-	}
-
-	if ((!player->kartstuff[k_sneakertimer])
-	|| (!player->cmd.driftturn)
-	|| (player->cmd.driftturn > 0) != (player->kartstuff[k_aizdriftstrat] > 0))
-	{
-		if (!player->kartstuff[k_drift])
-			player->kartstuff[k_aizdriftstrat] = 0;
-		else
-			player->kartstuff[k_aizdriftstrat] = ((player->kartstuff[k_drift] > 0) ? 1 : -1);
-	}
-	else if (player->kartstuff[k_aizdriftstrat] && !player->kartstuff[k_drift])
-		K_SpawnAIZDust(player);
-
-	if (player->kartstuff[k_drift]
-		&& ((player->cmd.buttons & BT_BRAKE)
-		|| !(player->cmd.buttons & BT_ACCELERATE))
-		&& P_IsObjectOnGround(player->mo))
-	{
-		if (!player->kartstuff[k_brakedrift])
-			K_SpawnBrakeDriftSparks(player);
-		player->kartstuff[k_brakedrift] = 1;
-	}
-	else
-		player->kartstuff[k_brakedrift] = 0;
-}
-//
-// K_KartUpdatePosition
-//
-void K_KartUpdatePosition(player_t *player)
-{
-	fixed_t position = 1;
-	fixed_t oldposition = player->kartstuff[k_position];
-	fixed_t i, ppcd, pncd, ipcd, incd;
-	fixed_t pmo, imo;
-	mobj_t *mo;
-
-	if (player->spectator || !player->mo)
-		return;
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i] || players[i].spectator || !players[i].mo)
-			continue;
-
-		if (G_RaceGametype())
-		{
-			if ((((players[i].starpostnum) + (numstarposts + 1) * players[i].laps) >
-				((player->starpostnum) + (numstarposts + 1) * player->laps)))
-				position++;
-			else if (((players[i].starpostnum) + (numstarposts+1)*players[i].laps) ==
-				((player->starpostnum) + (numstarposts+1)*player->laps))
-			{
-				ppcd = pncd = ipcd = incd = 0;
-
-				player->kartstuff[k_prevcheck] = players[i].kartstuff[k_prevcheck] = 0;
-				player->kartstuff[k_nextcheck] = players[i].kartstuff[k_nextcheck] = 0;
-
-				// This checks every thing on the map, and looks for MT_BOSS3WAYPOINT (the thing we're using for checkpoint wp's, for now)
-				for (mo = waypointcap; mo != NULL; mo = mo->tracer)
-				{
-					pmo = P_AproxDistance(P_AproxDistance(	mo->x - player->mo->x,
-															mo->y - player->mo->y),
-															mo->z - player->mo->z) / FRACUNIT;
-					imo = P_AproxDistance(P_AproxDistance(	mo->x - players[i].mo->x,
-															mo->y - players[i].mo->y),
-															mo->z - players[i].mo->z) / FRACUNIT;
-
-					if (mo->health == player->starpostnum && (!mo->movecount || mo->movecount == player->laps+1))
-					{
-						player->kartstuff[k_prevcheck] += pmo;
-						ppcd++;
-					}
-					if (mo->health == (player->starpostnum + 1) && (!mo->movecount || mo->movecount == player->laps+1))
-					{
-						player->kartstuff[k_nextcheck] += pmo;
-						pncd++;
-					}
-					if (mo->health == players[i].starpostnum && (!mo->movecount || mo->movecount == players[i].laps+1))
-					{
-						players[i].kartstuff[k_prevcheck] += imo;
-						ipcd++;
-					}
-					if (mo->health == (players[i].starpostnum + 1) && (!mo->movecount || mo->movecount == players[i].laps+1))
-					{
-						players[i].kartstuff[k_nextcheck] += imo;
-						incd++;
-					}
-				}
-
-				if (ppcd > 1) player->kartstuff[k_prevcheck] /= ppcd;
-				if (pncd > 1) player->kartstuff[k_nextcheck] /= pncd;
-				if (ipcd > 1) players[i].kartstuff[k_prevcheck] /= ipcd;
-				if (incd > 1) players[i].kartstuff[k_nextcheck] /= incd;
-
-				if ((players[i].kartstuff[k_nextcheck] > 0 || player->kartstuff[k_nextcheck] > 0) && !player->exiting)
-				{
-					if ((players[i].kartstuff[k_nextcheck] - players[i].kartstuff[k_prevcheck]) <
-						(player->kartstuff[k_nextcheck] - player->kartstuff[k_prevcheck]))
-						position++;
-				}
-				else if (!player->exiting)
-				{
-					if (players[i].kartstuff[k_prevcheck] > player->kartstuff[k_prevcheck])
-						position++;
-				}
-				else
-				{
-					if (players[i].starposttime < player->starposttime)
-						position++;
-				}
-			}
-		}
-		else if (G_BattleGametype())
-		{
-			if (player->exiting) // End of match standings
-			{
-				if (players[i].marescore > player->marescore) // Only score matters
-					position++;
-			}
-			else
-			{
-				if (players[i].kartstuff[k_bumper] == player->kartstuff[k_bumper] && players[i].marescore > player->marescore)
-					position++;
-				else if (players[i].kartstuff[k_bumper] > player->kartstuff[k_bumper])
-					position++;
-			}
-		}
-	}
-
-	if (leveltime < starttime || oldposition == 0)
-		oldposition = position;
-
-	if (oldposition != position) // Changed places?
-		player->kartstuff[k_positiondelay] = 10; // Position number growth
-
-	player->kartstuff[k_position] = position;
-}
-
-//
-// K_StripItems
-//
-void K_StripItems(player_t *player)
-{
-	player->kartstuff[k_itemtype] = KITEM_NONE;
-	player->kartstuff[k_itemamount] = 0;
-	player->kartstuff[k_itemheld] = 0;
-
-	player->kartstuff[k_rocketsneakertimer] = 0;
-
-	if (!player->kartstuff[k_itemroulette] || player->kartstuff[k_roulettetype] != 2)
-	{
-		player->kartstuff[k_itemroulette] = 0;
-		player->kartstuff[k_roulettetype] = 0;
-	}
-	player->kartstuff[k_eggmanheld] = 0;
-
-	player->kartstuff[k_hyudorotimer] = 0;
-	player->kartstuff[k_stealingtimer] = 0;
-	player->kartstuff[k_stolentimer] = 0;
-
-	player->kartstuff[k_curshield] = 0;
-	//player->kartstuff[k_thunderanim] = 0;
-	player->kartstuff[k_bananadrag] = 0;
-
-	player->kartstuff[k_sadtimer] = 0;
-
-	K_UpdateHnextList(player, true);
-}
-
-void K_StripOther(player_t *player)
-{
-	player->kartstuff[k_itemroulette] = 0;
-	player->kartstuff[k_roulettetype] = 0;
-
-	player->kartstuff[k_invincibilitytimer] = 0;
-	K_RemoveGrowShrink(player);
-
-	if (player->kartstuff[k_eggmanexplode])
-	{
-		player->kartstuff[k_eggmanexplode] = 0;
-		player->kartstuff[k_eggmanblame] = -1;
-	}
-}
-
-//
-// K_MoveKartPlayer
-//
-void K_MoveKartPlayer(player_t *player, boolean onground)
-{
-	ticcmd_t *cmd = &player->cmd;
-	boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->pflags & PF_ATTACKDOWN));
-	boolean HOLDING_ITEM = (player->kartstuff[k_itemheld] || player->kartstuff[k_eggmanheld]);
-	boolean NO_HYUDORO = (player->kartstuff[k_stolentimer] == 0 && player->kartstuff[k_stealingtimer] == 0);
-
-	K_KartUpdatePosition(player);
-
-	if (!player->exiting)
-	{
-		if (player->kartstuff[k_oldposition] < player->kartstuff[k_position]) // But first, if you lost a place,
-		{
-			player->kartstuff[k_oldposition] = player->kartstuff[k_position]; // then the other player taunts.
-			K_RegularVoiceTimers(player); // and you can't for a bit
-		}
-		else if (player->kartstuff[k_oldposition] > player->kartstuff[k_position]) // Otherwise,
-		{
-			K_PlayOvertakeSound(player->mo); // Say "YOU'RE TOO SLOW!"
-			player->kartstuff[k_oldposition] = player->kartstuff[k_position]; // Restore the old position,
-		}
-	}
-
-	if (player->kartstuff[k_positiondelay])
-		player->kartstuff[k_positiondelay]--;
-
-	if ((player->pflags & PF_ATTACKDOWN) && !(cmd->buttons & BT_ATTACK))
-		player->pflags &= ~PF_ATTACKDOWN;
-	else if (cmd->buttons & BT_ATTACK)
-		player->pflags |= PF_ATTACKDOWN;
-
-	if (player && player->mo && player->mo->health > 0 && !player->spectator && !(player->exiting || mapreset)
-		&& player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_squishedtimer] == 0 && player->kartstuff[k_respawn] == 0)
-	{
-		// First, the really specific, finicky items that function without the item being directly in your item slot.
-		// Karma item dropping
-		if (ATTACK_IS_DOWN && player->kartstuff[k_comebackmode] && !player->kartstuff[k_comebacktimer])
-		{
-			mobj_t *newitem;
-
-			if (player->kartstuff[k_comebackmode] == 1)
-			{
-				newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RANDOMITEM);
-				newitem->threshold = 69; // selected "randomly".
-			}
-			else
-			{
-				newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM);
-				if (player->kartstuff[k_eggmanblame] >= 0
-				&& player->kartstuff[k_eggmanblame] < MAXPLAYERS
-				&& playeringame[player->kartstuff[k_eggmanblame]]
-				&& !players[player->kartstuff[k_eggmanblame]].spectator
-				&& players[player->kartstuff[k_eggmanblame]].mo)
-					P_SetTarget(&newitem->target, players[player->kartstuff[k_eggmanblame]].mo);
-				player->kartstuff[k_eggmanblame] = -1;
-			}
-
-			newitem->flags2 = (player->mo->flags2 & MF2_OBJECTFLIP);
-			newitem->fuse = 15*TICRATE; // selected randomly.
-
-			player->kartstuff[k_comebackmode] = 0;
-			player->kartstuff[k_comebacktimer] = comebacktime;
-			S_StartSound(player->mo, sfx_s254);
-		}
-		// Eggman Monitor exploding
-		else if (player->kartstuff[k_eggmanexplode])
-		{
-			if (ATTACK_IS_DOWN && player->kartstuff[k_eggmanexplode] <= 3*TICRATE && player->kartstuff[k_eggmanexplode] > 1)
-				player->kartstuff[k_eggmanexplode] = 1;
-		}
-		// Eggman Monitor throwing
-		else if (ATTACK_IS_DOWN && player->kartstuff[k_eggmanheld])
-		{
-			K_ThrowKartItem(player, false, MT_EGGMANITEM, -1, 0);
-			K_PlayAttackTaunt(player->mo);
-			player->kartstuff[k_eggmanheld] = 0;
-			K_UpdateHnextList(player, true);
-		}
-		// Rocket Sneaker
-		else if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO
-			&& player->kartstuff[k_rocketsneakertimer] > 1)
-		{
-			K_DoSneaker(player, 2);
-			K_PlayBoostTaunt(player->mo);
-			player->kartstuff[k_rocketsneakertimer] -= 2*TICRATE;
-			if (player->kartstuff[k_rocketsneakertimer] < 1)
-				player->kartstuff[k_rocketsneakertimer] = 1;
-		}
-		else if (player->kartstuff[k_itemamount] <= 0)
-		{
-			player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0;
-		}
-		else
-		{
-			switch (player->kartstuff[k_itemtype])
-			{
-				case KITEM_SNEAKER:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO)
-					{
-						K_DoSneaker(player, 1);
-						K_PlayBoostTaunt(player->mo);
-						player->kartstuff[k_itemamount]--;
-					}
-					break;
-				case KITEM_ROCKETSNEAKER:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO
-						&& player->kartstuff[k_rocketsneakertimer] == 0)
-					{
-						INT32 moloop;
-						mobj_t *mo = NULL;
-						mobj_t *prev = player->mo;
-
-						K_PlayBoostTaunt(player->mo);
-						//player->kartstuff[k_itemheld] = 1;
-						S_StartSound(player->mo, sfx_s3k3a);
-
-						//K_DoSneaker(player, 2);
-
-						player->kartstuff[k_rocketsneakertimer] = (itemtime*3);
-						player->kartstuff[k_itemamount]--;
-						K_UpdateHnextList(player, true);
-
-						for (moloop = 0; moloop < 2; moloop++)
-						{
-							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ROCKETSNEAKER);
-							mo->flags |= MF_NOCLIPTHING;
-							mo->angle = player->mo->angle;
-							mo->threshold = 10;
-							mo->movecount = moloop%2;
-							mo->movedir = mo->lastlook = moloop+1;
-							P_SetTarget(&mo->target, player->mo);
-							P_SetTarget(&mo->hprev, prev);
-							P_SetTarget(&prev->hnext, mo);
-							prev = mo;
-						}
-					}
-					break;
-				case KITEM_INVINCIBILITY:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage normally, so you're free to waste it if you have multiple
-					{
-						if (!player->kartstuff[k_invincibilitytimer])
-						{
-							mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INVULNFLASH);
-							P_SetTarget(&overlay->target, player->mo);
-							overlay->destscale = player->mo->scale;
-							P_SetScale(overlay, player->mo->scale);
-						}
-						player->kartstuff[k_invincibilitytimer] = itemtime+(2*TICRATE); // 10 seconds
-						P_RestoreMusic(player);
-						if (!P_IsLocalPlayer(player))
-							S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmi : sfx_kinvnc));
-						K_PlayPowerGloatSound(player->mo);
-						player->kartstuff[k_itemamount]--;
-					}
-					break;
-				case KITEM_BANANA:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						INT32 moloop;
-						mobj_t *mo;
-						mobj_t *prev = player->mo;
-
-						//K_PlayAttackTaunt(player->mo);
-						player->kartstuff[k_itemheld] = 1;
-						S_StartSound(player->mo, sfx_s254);
-
-						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
-						{
-							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD);
-							if (!mo)
-							{
-								player->kartstuff[k_itemamount] = moloop;
-								break;
-							}
-							mo->flags |= MF_NOCLIPTHING;
-							mo->threshold = 10;
-							mo->movecount = player->kartstuff[k_itemamount];
-							mo->movedir = moloop+1;
-							P_SetTarget(&mo->target, player->mo);
-							P_SetTarget(&mo->hprev, prev);
-							P_SetTarget(&prev->hnext, mo);
-							prev = mo;
-						}
-					}
-					else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) // Banana x3 thrown
-					{
-						K_ThrowKartItem(player, false, MT_BANANA, -1, 0);
-						K_PlayAttackTaunt(player->mo);
-						player->kartstuff[k_itemamount]--;
-						K_UpdateHnextList(player, false);
-					}
-					break;
-				case KITEM_EGGMAN:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						mobj_t *mo;
-						player->kartstuff[k_itemamount]--;
-						player->kartstuff[k_eggmanheld] = 1;
-						S_StartSound(player->mo, sfx_s254);
-						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD);
-						if (mo)
-						{
-							mo->flags |= MF_NOCLIPTHING;
-							mo->threshold = 10;
-							mo->movecount = 1;
-							mo->movedir = 1;
-							P_SetTarget(&mo->target, player->mo);
-							P_SetTarget(&player->mo->hnext, mo);
-						}
-					}
-					break;
-				case KITEM_ORBINAUT:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						angle_t newangle;
-						INT32 moloop;
-						mobj_t *mo = NULL;
-						mobj_t *prev = player->mo;
-
-						//K_PlayAttackTaunt(player->mo);
-						player->kartstuff[k_itemheld] = 1;
-						S_StartSound(player->mo, sfx_s3k3a);
-
-						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
-						{
-							newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90;
-							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD);
-							if (!mo)
-							{
-								player->kartstuff[k_itemamount] = moloop;
-								break;
-							}
-							mo->flags |= MF_NOCLIPTHING;
-							mo->angle = newangle;
-							mo->threshold = 10;
-							mo->movecount = player->kartstuff[k_itemamount];
-							mo->movedir = mo->lastlook = moloop+1;
-							mo->color = player->skincolor;
-							P_SetTarget(&mo->target, player->mo);
-							P_SetTarget(&mo->hprev, prev);
-							P_SetTarget(&prev->hnext, mo);
-							prev = mo;
-						}
-					}
-					else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) // Orbinaut x3 thrown
-					{
-						K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0);
-						K_PlayAttackTaunt(player->mo);
-						player->kartstuff[k_itemamount]--;
-						K_UpdateHnextList(player, false);
-					}
-					break;
-				case KITEM_JAWZ:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						angle_t newangle;
-						INT32 moloop;
-						mobj_t *mo = NULL;
-						mobj_t *prev = player->mo;
-
-						//K_PlayAttackTaunt(player->mo);
-						player->kartstuff[k_itemheld] = 1;
-						S_StartSound(player->mo, sfx_s3k3a);
-
-						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
-						{
-							newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90;
-							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD);
-							if (!mo)
-							{
-								player->kartstuff[k_itemamount] = moloop;
-								break;
-							}
-							mo->flags |= MF_NOCLIPTHING;
-							mo->angle = newangle;
-							mo->threshold = 10;
-							mo->movecount = player->kartstuff[k_itemamount];
-							mo->movedir = mo->lastlook = moloop+1;
-							P_SetTarget(&mo->target, player->mo);
-							P_SetTarget(&mo->hprev, prev);
-							P_SetTarget(&prev->hnext, mo);
-							prev = mo;
-						}
-					}
-					else if (ATTACK_IS_DOWN && HOLDING_ITEM && player->kartstuff[k_itemheld]) // Jawz thrown
-					{
-						if (player->kartstuff[k_throwdir] == 1 || player->kartstuff[k_throwdir] == 0)
-							K_ThrowKartItem(player, true, MT_JAWZ, 1, 0);
-						else if (player->kartstuff[k_throwdir] == -1) // Throwing backward gives you a dud that doesn't home in
-							K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, 0);
-						K_PlayAttackTaunt(player->mo);
-						player->kartstuff[k_itemamount]--;
-						K_UpdateHnextList(player, false);
-					}
-					break;
-				case KITEM_MINE:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						mobj_t *mo;
-						player->kartstuff[k_itemheld] = 1;
-						S_StartSound(player->mo, sfx_s254);
-						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD);
-						if (mo)
-						{
-							mo->flags |= MF_NOCLIPTHING;
-							mo->threshold = 10;
-							mo->movecount = 1;
-							mo->movedir = 1;
-							P_SetTarget(&mo->target, player->mo);
-							P_SetTarget(&player->mo->hnext, mo);
-						}
-					}
-					else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld])
-					{
-						K_ThrowKartItem(player, false, MT_SSMINE, 1, 1);
-						K_PlayAttackTaunt(player->mo);
-						player->kartstuff[k_itemamount]--;
-						player->kartstuff[k_itemheld] = 0;
-						K_UpdateHnextList(player, true);
-					}
-					break;
-				case KITEM_BALLHOG:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						player->kartstuff[k_itemamount]--;
-						K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0);
-						K_PlayAttackTaunt(player->mo);
-					}
-					break;
-				case KITEM_SPB:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						player->kartstuff[k_itemamount]--;
-						K_ThrowKartItem(player, true, MT_SPB, 1, 0);
-						K_PlayAttackTaunt(player->mo);
-					}
-					break;
-				case KITEM_GROW:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
-						&& player->kartstuff[k_growshrinktimer] <= 0) // Grow holds the item box hostage
-					{
-						K_PlayPowerGloatSound(player->mo);
-						player->mo->scalespeed = mapobjectscale/TICRATE;
-						player->mo->destscale = (3*mapobjectscale)/2;
-						if (cv_kartdebugshrink.value && !modeattacking && !player->bot)
-							player->mo->destscale = (6*player->mo->destscale)/8;
-						player->kartstuff[k_growshrinktimer] = itemtime+(4*TICRATE); // 12 seconds
-						P_RestoreMusic(player);
-						if (!P_IsLocalPlayer(player))
-							S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow));
-						S_StartSound(player->mo, sfx_kc5a);
-						player->kartstuff[k_itemamount]--;
-					}
-					break;
-				case KITEM_SHRINK:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						K_DoShrink(player);
-						player->kartstuff[k_itemamount]--;
-						K_PlayPowerGloatSound(player->mo);
-					}
-					break;
-				case KITEM_THUNDERSHIELD:
-					if (player->kartstuff[k_curshield] != 1)
-					{
-						mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD);
-						P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2));
-						P_SetTarget(&shield->target, player->mo);
-						S_StartSound(shield, sfx_s3k41);
-						player->kartstuff[k_curshield] = 1;
-					}
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						K_DoThunderShield(player);
-						player->kartstuff[k_itemamount]--;
-						K_PlayAttackTaunt(player->mo);
-					}
-					break;
-				case KITEM_HYUDORO:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						player->kartstuff[k_itemamount]--;
-						K_DoHyudoroSteal(player); // yes. yes they do.
-					}
-					break;
-				case KITEM_POGOSPRING:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO
-						&& !player->kartstuff[k_pogospring])
-					{
-						K_PlayBoostTaunt(player->mo);
-						K_DoPogoSpring(player->mo, 32<<FRACBITS, 2);
-						player->kartstuff[k_pogospring] = 1;
-						player->kartstuff[k_itemamount]--;
-					}
-					break;
-				case KITEM_KITCHENSINK:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
-					{
-						mobj_t *mo;
-						player->kartstuff[k_itemheld] = 1;
-						S_StartSound(player->mo, sfx_s254);
-						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SINK_SHIELD);
-						if (mo)
-						{
-							mo->flags |= MF_NOCLIPTHING;
-							mo->threshold = 10;
-							mo->movecount = 1;
-							mo->movedir = 1;
-							P_SetTarget(&mo->target, player->mo);
-							P_SetTarget(&player->mo->hnext, mo);
-						}
-					}
-					else if (ATTACK_IS_DOWN && HOLDING_ITEM && player->kartstuff[k_itemheld]) // Sink thrown
-					{
-						K_ThrowKartItem(player, false, MT_SINK, 1, 2);
-						K_PlayAttackTaunt(player->mo);
-						player->kartstuff[k_itemamount]--;
-						player->kartstuff[k_itemheld] = 0;
-						K_UpdateHnextList(player, true);
-					}
-					break;
-				case KITEM_SAD:
-					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
-						&& !player->kartstuff[k_sadtimer])
-					{
-						player->kartstuff[k_sadtimer] = stealtime;
-						player->kartstuff[k_itemamount]--;
-					}
-					break;
-				default:
-					break;
-			}
-		}
-
-		// No more!
-		if (!player->kartstuff[k_itemamount])
-		{
-			player->kartstuff[k_itemheld] = 0;
-			player->kartstuff[k_itemtype] = KITEM_NONE;
-		}
-
-		if (player->kartstuff[k_itemtype] != KITEM_THUNDERSHIELD)
-			player->kartstuff[k_curshield] = 0;
-
-		if (player->kartstuff[k_itemtype] == KITEM_SPB
-			|| player->kartstuff[k_itemtype] == KITEM_SHRINK
-			|| player->kartstuff[k_growshrinktimer] < 0)
-			indirectitemcooldown = 20*TICRATE;
-
-		if (player->kartstuff[k_hyudorotimer] > 0)
-		{
-			if (splitscreen)
-			{
-				if (leveltime & 1)
-					player->mo->flags2 |= MF2_DONTDRAW;
-				else
-					player->mo->flags2 &= ~MF2_DONTDRAW;
-
-				if (player->kartstuff[k_hyudorotimer] >= (1*TICRATE/2) && player->kartstuff[k_hyudorotimer] <= hyudorotime-(1*TICRATE/2))
-				{
-					if (player == &players[secondarydisplayplayer])
-						player->mo->eflags |= MFE_DRAWONLYFORP2;
-					else if (player == &players[thirddisplayplayer] && splitscreen > 1)
-						player->mo->eflags |= MFE_DRAWONLYFORP3;
-					else if (player == &players[fourthdisplayplayer] && splitscreen > 2)
-						player->mo->eflags |= MFE_DRAWONLYFORP4;
-					else if (player == &players[consoleplayer])
-						player->mo->eflags |= MFE_DRAWONLYFORP1;
-					else
-						player->mo->flags2 |= MF2_DONTDRAW;
-				}
-				else
-					player->mo->eflags &= ~(MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4);
-			}
-			else
-			{
-				if (player == &players[displayplayer]
-					|| (player != &players[displayplayer] && (player->kartstuff[k_hyudorotimer] < (1*TICRATE/2) || player->kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2))))
-				{
-					if (leveltime & 1)
-						player->mo->flags2 |= MF2_DONTDRAW;
-					else
-						player->mo->flags2 &= ~MF2_DONTDRAW;
-				}
-				else
-					player->mo->flags2 |= MF2_DONTDRAW;
-			}
-
-			player->powers[pw_flashing] = player->kartstuff[k_hyudorotimer]; // We'll do this for now, let's people know about the invisible people through subtle hints
-		}
-		else if (player->kartstuff[k_hyudorotimer] == 0)
-		{
-			player->mo->flags2 &= ~MF2_DONTDRAW;
-			player->mo->eflags &= ~(MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4);
-		}
-
-		if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) // dead in match? you da bomb
-		{
-			K_DropItems(player); //K_StripItems(player);
-			K_StripOther(player);
-			player->mo->flags2 |= MF2_SHADOW;
-			player->powers[pw_flashing] = player->kartstuff[k_comebacktimer];
-		}
-		else if (G_RaceGametype() || player->kartstuff[k_bumper] > 0)
-		{
-			player->mo->flags2 &= ~MF2_SHADOW;
-		}
-	}
-
-	// Friction
-	if (!player->kartstuff[k_offroad])
-	{
-		if (player->speed > 0 && cmd->forwardmove == 0 && player->mo->friction == 59392)
-			player->mo->friction += 4608;
-		if (player->speed > 0 && cmd->forwardmove < 0 && player->mo->friction == 59392)
-			player->mo->friction += 1608;
-	}
-
-	// Karma ice physics
-	if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)
-	{
-		player->mo->friction += 1228;
-
-		if (player->mo->friction > FRACUNIT)
-			player->mo->friction = FRACUNIT;
-		if (player->mo->friction < 0)
-			player->mo->friction = 0;
-
-		player->mo->movefactor = FixedDiv(ORIG_FRICTION, player->mo->friction);
-
-		if (player->mo->movefactor < FRACUNIT)
-			player->mo->movefactor = 19*player->mo->movefactor - 18*FRACUNIT;
-		else
-			player->mo->movefactor = FRACUNIT; //player->mo->movefactor = ((player->mo->friction - 0xDB34)*(0xA))/0x80;
-
-		if (player->mo->movefactor < 32)
-			player->mo->movefactor = 32;
-	}
-
-	// Wipeout slowdown
-	if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow])
-	{
-		if (player->kartstuff[k_offroad])
-			player->mo->friction -= 4912;
-		if (player->kartstuff[k_wipeoutslow] == 1)
-			player->mo->friction -= 9824;
-	}
-
-	K_KartDrift(player, onground);
-
-	// Quick Turning
-	// You can't turn your kart when you're not moving.
-	// So now it's time to burn some rubber!
-	if (player->speed < 2 && leveltime > starttime && cmd->buttons & BT_ACCELERATE && cmd->buttons & BT_BRAKE && cmd->driftturn != 0)
-	{
-		if (leveltime % 8 == 0)
-			S_StartSound(player->mo, sfx_s224);
-	}
-
-	// Squishing
-	// If a Grow player or a sector crushes you, get flattened instead of being killed.
-
-	if (player->kartstuff[k_squishedtimer] <= 0)
-	{
-		player->mo->flags &= ~MF_NOCLIP;
-	}
-	else
-	{
-		player->mo->flags |= MF_NOCLIP;
-		player->mo->momx = 0;
-		player->mo->momy = 0;
-	}
-
-	// Play the starting countdown sounds
-	if (player == &players[displayplayer]) // Don't play louder in splitscreen
-	{
-		if ((leveltime == starttime-(3*TICRATE)) || (leveltime == starttime-(2*TICRATE)) || (leveltime == starttime-TICRATE))
-			S_StartSound(NULL, sfx_s3ka7);
-		if (leveltime == starttime)
-		{
-			S_StartSound(NULL, sfx_s3kad);
-			S_StopMusic(); // The GO! sound stops the level start ambience
-		}
-	}
-
-	// Start charging once you're given the opportunity.
-	if (leveltime >= starttime-(2*TICRATE) && leveltime <= starttime)
-	{
-		if (cmd->buttons & BT_ACCELERATE)
-		{
-			if (player->kartstuff[k_boostcharge] == 0)
-				player->kartstuff[k_boostcharge] = cmd->latency;
-
-			player->kartstuff[k_boostcharge]++;
-		}
-		else
-			player->kartstuff[k_boostcharge] = 0;
-	}
-
-	// Increase your size while charging your engine.
-	if (leveltime < starttime+10)
-	{
-		player->mo->scalespeed = mapobjectscale/12;
-		player->mo->destscale = mapobjectscale + (player->kartstuff[k_boostcharge]*131);
-		if (cv_kartdebugshrink.value && !modeattacking && !player->bot)
-			player->mo->destscale = (6*player->mo->destscale)/8;
-	}
-
-	// Determine the outcome of your charge.
-	if (leveltime > starttime && player->kartstuff[k_boostcharge])
-	{
-		// Not even trying?
-		if (player->kartstuff[k_boostcharge] < 35)
-		{
-			if (player->kartstuff[k_boostcharge] > 17)
-				S_StartSound(player->mo, sfx_cdfm00); // chosen instead of a conventional skid because it's more engine-like
-		}
-		// Get an instant boost!
-		else if (player->kartstuff[k_boostcharge] <= 50)
-		{
-			player->kartstuff[k_startboost] = (50-player->kartstuff[k_boostcharge])+20;
-
-			if (player->kartstuff[k_boostcharge] <= 36)
-			{
-				player->kartstuff[k_startboost] = 0;
-				K_DoSneaker(player, 0);
-				player->kartstuff[k_sneakertimer] = 70; // PERFECT BOOST!!
-
-				if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) // Let everyone hear this one
-					S_StartSound(player->mo, sfx_s25f);
-			}
-			else
-			{
-				K_SpawnDashDustRelease(player); // already handled for perfect boosts by K_DoSneaker
-				if ((!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) && P_IsLocalPlayer(player))
-				{
-					if (player->kartstuff[k_boostcharge] <= 40)
-						S_StartSound(player->mo, sfx_cdfm01); // You were almost there!
-					else
-						S_StartSound(player->mo, sfx_s23c); // Nope, better luck next time.
-				}
-			}
-		}
-		// You overcharged your engine? Those things are expensive!!!
-		else if (player->kartstuff[k_boostcharge] > 50)
-		{
-			player->powers[pw_nocontrol] = 40;
-			//S_StartSound(player->mo, sfx_kc34);
-			S_StartSound(player->mo, sfx_s3k83);
-			player->pflags |= PF_SKIDDOWN; // cheeky pflag reuse
-		}
-
-		player->kartstuff[k_boostcharge] = 0;
-	}
-}
-
-void K_CalculateBattleWanted(void)
-{
-	UINT8 numingame = 0, numplaying = 0, numwanted = 0;
-	SINT8 bestbumperplayer = -1, bestbumper = -1;
-	SINT8 camppos[MAXPLAYERS]; // who is the biggest camper
-	UINT8 ties = 0, nextcamppos = 0;
-	boolean setbumper = false;
-	UINT8 i, j;
-
-	if (!G_BattleGametype())
-	{
-		for (i = 0; i < 4; i++)
-			battlewanted[i] = -1;
-		return;
-	}
-
-	wantedcalcdelay = wantedfrequency;
-
-	for (i = 0; i < MAXPLAYERS; i++)
-		camppos[i] = -1; // initialize
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		UINT8 position = 1;
-
-		if (!playeringame[i] || players[i].spectator) // Not playing
-			continue;
-
-		if (players[i].exiting) // We're done, don't calculate.
-			return;
-
-		numplaying++;
-
-		if (players[i].kartstuff[k_bumper] <= 0) // Not alive, so don't do anything else
-			continue;
-
-		numingame++;
-
-		if (bestbumper == -1 || players[i].kartstuff[k_bumper] > bestbumper)
-		{
-			bestbumper = players[i].kartstuff[k_bumper];
-			bestbumperplayer = i;
-		}
-		else if (players[i].kartstuff[k_bumper] == bestbumper)
-			bestbumperplayer = -1; // Tie, no one has best bumper.
-
-		for (j = 0; j < MAXPLAYERS; j++)
-		{
-			if (!playeringame[j] || players[j].spectator)
-				continue;
-			if (players[j].kartstuff[k_bumper] <= 0)
-				continue;
-			if (j == i)
-				continue;
-			if (players[j].kartstuff[k_wanted] == players[i].kartstuff[k_wanted] && players[j].marescore > players[i].marescore)
-				position++;
-			else if (players[j].kartstuff[k_wanted] > players[i].kartstuff[k_wanted])
-				position++;
-		}
-
-		position--; // Make zero based
-
-		while (camppos[position] != -1) // Port priority!
-			position++;
-
-		camppos[position] = i;
-	}
-
-	if (numplaying <= 2 || (numingame <= 2 && bestbumper == 1)) // In 1v1s then there's no need for WANTED. In bigger netgames, don't show anyone as WANTED when they're equally matched.
-		numwanted = 0;
-	else
-		numwanted = min(4, 1 + ((numingame-2) / 4));
-
-	for (i = 0; i < 4; i++)
-	{
-		if (i+1 > numwanted) // Not enough players for this slot to be wanted!
-			battlewanted[i] = -1;
-		else if (bestbumperplayer != -1 && !setbumper) // If there's a player who has an untied bumper lead over everyone else, they are the first to be wanted.
-		{
-			battlewanted[i] = bestbumperplayer;
-			setbumper = true; // Don't set twice
-		}
-		else
-		{
-			// Don't accidentally set the same player, if the bestbumperplayer is also a huge camper.
-			while (bestbumperplayer != -1 && camppos[nextcamppos] != -1
-				&& bestbumperplayer == camppos[nextcamppos])
-				nextcamppos++;
-
-			// Do not add *any* more people if there's too many times that are tied with others.
-			// This could theoretically happen very easily if people don't hit each other for a while after the start of a match.
-			// (I will be sincerely impressed if more than 2 people tie after people start hitting each other though)
-
-			if (camppos[nextcamppos] == -1 // Out of entries
-				|| ties >= (numwanted-i)) // Already counted ties
-			{
-				battlewanted[i] = -1;
-				continue;
-			}
-
-			if (ties < (numwanted-i))
-			{
-				ties = 0; // Reset
-				for (j = 0; j < 2; j++)
-				{
-					if (camppos[nextcamppos+(j+1)] == -1) // Nothing beyond, cancel
-						break;
-					if (players[camppos[nextcamppos]].kartstuff[k_wanted] == players[camppos[nextcamppos+(j+1)]].kartstuff[k_wanted])
-						ties++;
-				}
-			}
-
-			if (ties < (numwanted-i)) // Is it still low enough after counting?
-			{
-				battlewanted[i] = camppos[nextcamppos];
-				nextcamppos++;
-			}
-			else
-				battlewanted[i] = -1;
-		}
-	}
-}
-
-void K_CheckBumpers(void)
-{
-	UINT8 i;
-	UINT8 numingame = 0;
-	SINT8 winnernum = -1;
-	INT32 winnerscoreadd = 0;
-
-	if (!multiplayer)
-		return;
-
-	if (!G_BattleGametype())
-		return;
-
-	if (gameaction == ga_completed)
-		return;
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i] || players[i].spectator) // not even in-game
-			continue;
-
-		if (players[i].exiting) // we're already exiting! stop!
-			return;
-
-		numingame++;
-		winnerscoreadd += players[i].marescore;
-
-		if (players[i].kartstuff[k_bumper] <= 0) // if you don't have any bumpers, you're probably not a winner
-			continue;
-		else if (winnernum > -1) // TWO winners? that's dumb :V
-			return;
-
-		winnernum = i;
-		winnerscoreadd -= players[i].marescore;
-	}
-
-	if (numingame <= 1)
-		return;
-
-	if (winnernum > -1 && playeringame[winnernum])
-	{
-		players[winnernum].marescore += winnerscoreadd;
-		CONS_Printf(M_GetText("%s recieved %d point%s for winning!\n"), player_names[winnernum], winnerscoreadd, (winnerscoreadd == 1 ? "" : "s"));
-	}
-
-	for (i = 0; i < MAXPLAYERS; i++) // This can't go in the earlier loop because winning adds points
-		K_KartUpdatePosition(&players[i]);
-
-	for (i = 0; i < MAXPLAYERS; i++) // and it can't be merged with this loop because it needs to be all updated before exiting... multi-loops suck...
-		P_DoPlayerExit(&players[i]);
-}
-
-void K_CheckSpectateStatus(void)
-{
-	UINT8 respawnlist[MAXPLAYERS];
-	UINT8 i, j, numingame = 0, numjoiners = 0;
-
-	// Maintain spectate wait timer
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i])
-			continue;
-		if (players[i].spectator && (players[i].pflags & PF_WANTSTOJOIN))
-			players[i].kartstuff[k_spectatewait]++;
-		else
-			players[i].kartstuff[k_spectatewait] = 0;
-	}
-
-	// No one's allowed to join
-	if (!cv_allowteamchange.value)
-		return;
-
-	// Get the number of players in game, and the players to be de-spectated.
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i])
-			continue;
-
-		if (!players[i].spectator)
-		{
-			numingame++;
-			if (cv_ingamecap.value && numingame >= cv_ingamecap.value) // DON'T allow if you've hit the in-game player cap
-				return;
-			if (gamestate != GS_LEVEL) // Allow if you're not in a level
-				continue;
-			if (players[i].exiting) // DON'T allow if anyone's exiting
-				return;
-			if (numingame < 2 || leveltime < starttime || mapreset) // Allow if the match hasn't started yet
-				continue;
-			if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in
-				return;
-			if (G_RaceGametype() && players[i].laps) // DON'T allow if the race is at 2 laps
-				return;
-			continue;
-		}
-		else if (!(players[i].pflags & PF_WANTSTOJOIN))
-			continue;
-
-		respawnlist[numjoiners++] = i;
-	}
-
-	// literally zero point in going any further if nobody is joining
-	if (!numjoiners)
-		return;
-
-	// Organize by spectate wait timer
-	if (cv_ingamecap.value)
-	{
-		UINT8 oldrespawnlist[MAXPLAYERS];
-		memcpy(oldrespawnlist, respawnlist, numjoiners);
-		for (i = 0; i < numjoiners; i++)
-		{
-			UINT8 pos = 0;
-			INT32 ispecwait = players[oldrespawnlist[i]].kartstuff[k_spectatewait];
-
-			for (j = 0; j < numjoiners; j++)
-			{
-				INT32 jspecwait = players[oldrespawnlist[j]].kartstuff[k_spectatewait];
-				if (j == i)
-					continue;
-				if (jspecwait > ispecwait)
-					pos++;
-				else if (jspecwait == ispecwait && j < i)
-					pos++;
-			}
-
-			respawnlist[pos] = oldrespawnlist[i];
-		}
-	}
-
-	// Finally, we can de-spectate everyone!
-	for (i = 0; i < numjoiners; i++)
-	{
-		if (cv_ingamecap.value && numingame+i >= cv_ingamecap.value) // Hit the in-game player cap while adding people?
-			break;
-		P_SpectatorJoinGame(&players[respawnlist[i]]);
-	}
-
-	// Reset the match if you're in an empty server
-	if (!mapreset && gamestate == GS_LEVEL && leveltime >= starttime && (numingame < 2 && numingame+i >= 2)) // use previous i value
-	{
-		S_ChangeMusicInternal("chalng", false); // COME ON
-		mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD
-	}
-}
-
-//}
-
-//{ SRB2kart HUD Code
-
-#define NUMPOSNUMS 10
-#define NUMPOSFRAMES 7 // White, three blues, three reds
-#define NUMWINFRAMES 6 // Red, yellow, green, cyan, blue, purple
-
-//{ 	Patch Definitions
-static patch_t *kp_nodraw;
-
-static patch_t *kp_timesticker;
-static patch_t *kp_timestickerwide;
-static patch_t *kp_lapsticker;
-static patch_t *kp_lapstickerwide;
-static patch_t *kp_lapstickernarrow;
-static patch_t *kp_splitlapflag;
-static patch_t *kp_bumpersticker;
-static patch_t *kp_bumperstickerwide;
-static patch_t *kp_karmasticker;
-static patch_t *kp_splitkarmabomb;
-static patch_t *kp_timeoutsticker;
-
-static patch_t *kp_startcountdown[16];
-static patch_t *kp_racefinish[6];
-
-static patch_t *kp_positionnum[NUMPOSNUMS][NUMPOSFRAMES];
-static patch_t *kp_winnernum[NUMPOSFRAMES];
-
-static patch_t *kp_facenum[MAXPLAYERS+1];
-static patch_t *kp_facehighlight[8];
-
-static patch_t *kp_rankbumper;
-static patch_t *kp_tinybumper[2];
-static patch_t *kp_ranknobumpers;
-
-static patch_t *kp_battlewin;
-static patch_t *kp_battlecool;
-static patch_t *kp_battlelose;
-static patch_t *kp_battlewait;
-static patch_t *kp_battleinfo;
-static patch_t *kp_wanted;
-static patch_t *kp_wantedsplit;
-static patch_t *kp_wantedreticle;
-
-static patch_t *kp_itembg[4];
-static patch_t *kp_itemtimer[2];
-static patch_t *kp_itemmulsticker[2];
-static patch_t *kp_itemx;
-
-static patch_t *kp_sneaker[2];
-static patch_t *kp_rocketsneaker[2];
-static patch_t *kp_invincibility[13];
-static patch_t *kp_banana[2];
-static patch_t *kp_eggman[2];
-static patch_t *kp_orbinaut[5];
-static patch_t *kp_jawz[2];
-static patch_t *kp_mine[2];
-static patch_t *kp_ballhog[2];
-static patch_t *kp_selfpropelledbomb[2];
-static patch_t *kp_grow[2];
-static patch_t *kp_shrink[2];
-static patch_t *kp_thundershield[2];
-static patch_t *kp_hyudoro[2];
-static patch_t *kp_pogospring[2];
-static patch_t *kp_kitchensink[2];
-static patch_t *kp_sadface[2];
-
-static patch_t *kp_check[6];
-
-static patch_t *kp_eggnum[4];
-
-static patch_t *kp_fpview[3];
-static patch_t *kp_inputwheel[5];
-
-static patch_t *kp_challenger[25];
-
-static patch_t *kp_lapanim_lap[7];
-static patch_t *kp_lapanim_final[11];
-static patch_t *kp_lapanim_number[10][3];
-static patch_t *kp_lapanim_emblem[2];
-static patch_t *kp_lapanim_hand[3];
-
-static patch_t *kp_yougotem;
-
-void K_LoadKartHUDGraphics(void)
-{
-	INT32 i, j;
-	char buffer[9];
-
-	// Null Stuff
-	kp_nodraw = 				W_CachePatchName("K_TRNULL", PU_HUDGFX);
-
-	// Stickers
-	kp_timesticker = 			W_CachePatchName("K_STTIME", PU_HUDGFX);
-	kp_timestickerwide = 		W_CachePatchName("K_STTIMW", PU_HUDGFX);
-	kp_lapsticker = 			W_CachePatchName("K_STLAPS", PU_HUDGFX);
-	kp_lapstickerwide = 		W_CachePatchName("K_STLAPW", PU_HUDGFX);
-	kp_lapstickernarrow = 		W_CachePatchName("K_STLAPN", PU_HUDGFX);
-	kp_splitlapflag = 			W_CachePatchName("K_SPTLAP", PU_HUDGFX);
-	kp_bumpersticker = 			W_CachePatchName("K_STBALN", PU_HUDGFX);
-	kp_bumperstickerwide = 		W_CachePatchName("K_STBALW", PU_HUDGFX);
-	kp_karmasticker = 			W_CachePatchName("K_STKARM", PU_HUDGFX);
-	kp_splitkarmabomb = 		W_CachePatchName("K_SPTKRM", PU_HUDGFX);
-	kp_timeoutsticker = 		W_CachePatchName("K_STTOUT", PU_HUDGFX);
-
-	// Starting countdown
-	kp_startcountdown[0] = 		W_CachePatchName("K_CNT3A", PU_HUDGFX);
-	kp_startcountdown[1] = 		W_CachePatchName("K_CNT2A", PU_HUDGFX);
-	kp_startcountdown[2] = 		W_CachePatchName("K_CNT1A", PU_HUDGFX);
-	kp_startcountdown[3] = 		W_CachePatchName("K_CNTGOA", PU_HUDGFX);
-	kp_startcountdown[4] = 		W_CachePatchName("K_CNT3B", PU_HUDGFX);
-	kp_startcountdown[5] = 		W_CachePatchName("K_CNT2B", PU_HUDGFX);
-	kp_startcountdown[6] = 		W_CachePatchName("K_CNT1B", PU_HUDGFX);
-	kp_startcountdown[7] = 		W_CachePatchName("K_CNTGOB", PU_HUDGFX);
-	// Splitscreen
-	kp_startcountdown[8] = 		W_CachePatchName("K_SMC3A", PU_HUDGFX);
-	kp_startcountdown[9] = 		W_CachePatchName("K_SMC2A", PU_HUDGFX);
-	kp_startcountdown[10] = 	W_CachePatchName("K_SMC1A", PU_HUDGFX);
-	kp_startcountdown[11] = 	W_CachePatchName("K_SMCGOA", PU_HUDGFX);
-	kp_startcountdown[12] = 	W_CachePatchName("K_SMC3B", PU_HUDGFX);
-	kp_startcountdown[13] = 	W_CachePatchName("K_SMC2B", PU_HUDGFX);
-	kp_startcountdown[14] = 	W_CachePatchName("K_SMC1B", PU_HUDGFX);
-	kp_startcountdown[15] = 	W_CachePatchName("K_SMCGOB", PU_HUDGFX);
-
-	// Finish
-	kp_racefinish[0] = 			W_CachePatchName("K_FINA", PU_HUDGFX);
-	kp_racefinish[1] = 			W_CachePatchName("K_FINB", PU_HUDGFX);
-	// Splitscreen
-	kp_racefinish[2] = 			W_CachePatchName("K_SMFINA", PU_HUDGFX);
-	kp_racefinish[3] = 			W_CachePatchName("K_SMFINB", PU_HUDGFX);
-	// 2P splitscreen
-	kp_racefinish[4] = 			W_CachePatchName("K_2PFINA", PU_HUDGFX);
-	kp_racefinish[5] = 			W_CachePatchName("K_2PFINB", PU_HUDGFX);
-
-	// Position numbers
-	sprintf(buffer, "K_POSNxx");
-	for (i = 0; i < NUMPOSNUMS; i++)
-	{
-		buffer[6] = '0'+i;
-		for (j = 0; j < NUMPOSFRAMES; j++)
-		{
-			//sprintf(buffer, "K_POSN%d%d", i, j);
-			buffer[7] = '0'+j;
-			kp_positionnum[i][j] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-		}
-	}
-
-	sprintf(buffer, "K_POSNWx");
-	for (i = 0; i < NUMWINFRAMES; i++)
-	{
-		buffer[7] = '0'+i;
-		kp_winnernum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	sprintf(buffer, "OPPRNKxx");
-	for (i = 0; i <= MAXPLAYERS; i++)
-	{
-		buffer[6] = '0'+(i/10);
-		buffer[7] = '0'+(i%10);
-		kp_facenum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	sprintf(buffer, "K_CHILIx");
-	for (i = 0; i < 8; i++)
-	{
-		buffer[7] = '0'+(i+1);
-		kp_facehighlight[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	// Extra ranking icons
-	kp_rankbumper =				W_CachePatchName("K_BLNICO", PU_HUDGFX);
-	kp_tinybumper[0] =			W_CachePatchName("K_BLNA", PU_HUDGFX);
-	kp_tinybumper[1] =			W_CachePatchName("K_BLNB", PU_HUDGFX);
-	kp_ranknobumpers =			W_CachePatchName("K_NOBLNS", PU_HUDGFX);
-
-	// Battle graphics
-	kp_battlewin = 				W_CachePatchName("K_BWIN", PU_HUDGFX);
-	kp_battlecool = 			W_CachePatchName("K_BCOOL", PU_HUDGFX);
-	kp_battlelose = 			W_CachePatchName("K_BLOSE", PU_HUDGFX);
-	kp_battlewait = 			W_CachePatchName("K_BWAIT", PU_HUDGFX);
-	kp_battleinfo = 			W_CachePatchName("K_BINFO", PU_HUDGFX);
-	kp_wanted = 				W_CachePatchName("K_WANTED", PU_HUDGFX);
-	kp_wantedsplit = 			W_CachePatchName("4PWANTED", PU_HUDGFX);
-	kp_wantedreticle =			W_CachePatchName("MMAPWANT", PU_HUDGFX);
-
-	// Kart Item Windows
-	kp_itembg[0] = 				W_CachePatchName("K_ITBG", PU_HUDGFX);
-	kp_itembg[1] = 				W_CachePatchName("K_ITBGD", PU_HUDGFX);
-	kp_itemtimer[0] = 			W_CachePatchName("K_ITIMER", PU_HUDGFX);
-	kp_itemmulsticker[0] = 		W_CachePatchName("K_ITMUL", PU_HUDGFX);
-	kp_itemx = 					W_CachePatchName("K_ITX", PU_HUDGFX);
-
-	kp_sneaker[0] =				W_CachePatchName("K_ITSHOE", PU_HUDGFX);
-	kp_rocketsneaker[0] =		W_CachePatchName("K_ITRSHE", PU_HUDGFX);
-
-	sprintf(buffer, "K_ITINVx");
-	for (i = 0; i < 7; i++)
-	{
-		buffer[7] = '1'+i;
-		kp_invincibility[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-	kp_banana[0] =				W_CachePatchName("K_ITBANA", PU_HUDGFX);
-	kp_eggman[0] =				W_CachePatchName("K_ITEGGM", PU_HUDGFX);
-	sprintf(buffer, "K_ITORBx");
-	for (i = 0; i < 4; i++)
-	{
-		buffer[7] = '1'+i;
-		kp_orbinaut[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-	kp_jawz[0] =				W_CachePatchName("K_ITJAWZ", PU_HUDGFX);
-	kp_mine[0] =				W_CachePatchName("K_ITMINE", PU_HUDGFX);
-	kp_ballhog[0] =				W_CachePatchName("K_ITBHOG", PU_HUDGFX);
-	kp_selfpropelledbomb[0] =	W_CachePatchName("K_ITSPB", PU_HUDGFX);
-	kp_grow[0] =				W_CachePatchName("K_ITGROW", PU_HUDGFX);
-	kp_shrink[0] =				W_CachePatchName("K_ITSHRK", PU_HUDGFX);
-	kp_thundershield[0] =		W_CachePatchName("K_ITTHNS", PU_HUDGFX);
-	kp_hyudoro[0] = 			W_CachePatchName("K_ITHYUD", PU_HUDGFX);
-	kp_pogospring[0] = 			W_CachePatchName("K_ITPOGO", PU_HUDGFX);
-	kp_kitchensink[0] = 		W_CachePatchName("K_ITSINK", PU_HUDGFX);
-	kp_sadface[0] = 			W_CachePatchName("K_ITSAD", PU_HUDGFX);
-
-	// Splitscreen
-	kp_itembg[2] = 				W_CachePatchName("K_ISBG", PU_HUDGFX);
-	kp_itembg[3] = 				W_CachePatchName("K_ISBGD", PU_HUDGFX);
-	kp_itemtimer[1] = 			W_CachePatchName("K_ISIMER", PU_HUDGFX);
-	kp_itemmulsticker[1] = 		W_CachePatchName("K_ISMUL", PU_HUDGFX);
-
-	kp_sneaker[1] =				W_CachePatchName("K_ISSHOE", PU_HUDGFX);
-	kp_rocketsneaker[1] =		W_CachePatchName("K_ISRSHE", PU_HUDGFX);
-	sprintf(buffer, "K_ISINVx");
-	for (i = 0; i < 6; i++)
-	{
-		buffer[7] = '1'+i;
-		kp_invincibility[i+7] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-	kp_banana[1] =				W_CachePatchName("K_ISBANA", PU_HUDGFX);
-	kp_eggman[1] =				W_CachePatchName("K_ISEGGM", PU_HUDGFX);
-	kp_orbinaut[4] =			W_CachePatchName("K_ISORBN", PU_HUDGFX);
-	kp_jawz[1] =				W_CachePatchName("K_ISJAWZ", PU_HUDGFX);
-	kp_mine[1] =				W_CachePatchName("K_ISMINE", PU_HUDGFX);
-	kp_ballhog[1] =				W_CachePatchName("K_ISBHOG", PU_HUDGFX);
-	kp_selfpropelledbomb[1] =	W_CachePatchName("K_ISSPB", PU_HUDGFX);
-	kp_grow[1] =				W_CachePatchName("K_ISGROW", PU_HUDGFX);
-	kp_shrink[1] =				W_CachePatchName("K_ISSHRK", PU_HUDGFX);
-	kp_thundershield[1] =		W_CachePatchName("K_ISTHNS", PU_HUDGFX);
-	kp_hyudoro[1] = 			W_CachePatchName("K_ISHYUD", PU_HUDGFX);
-	kp_pogospring[1] = 			W_CachePatchName("K_ISPOGO", PU_HUDGFX);
-	kp_kitchensink[1] = 		W_CachePatchName("K_ISSINK", PU_HUDGFX);
-	kp_sadface[1] = 			W_CachePatchName("K_ISSAD", PU_HUDGFX);
-
-	// CHECK indicators
-	sprintf(buffer, "K_CHECKx");
-	for (i = 0; i < 6; i++)
-	{
-		buffer[7] = '1'+i;
-		kp_check[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	// Eggman warning numbers
-	sprintf(buffer, "K_EGGNx");
-	for (i = 0; i < 4; i++)
-	{
-		buffer[6] = '0'+i;
-		kp_eggnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	// First person mode
-	kp_fpview[0] = 				W_CachePatchName("VIEWA0", PU_HUDGFX);
-	kp_fpview[1] =				W_CachePatchName("VIEWB0D0", PU_HUDGFX);
-	kp_fpview[2] = 				W_CachePatchName("VIEWC0E0", PU_HUDGFX);
-
-	// Input UI Wheel
-	sprintf(buffer, "K_WHEELx");
-	for (i = 0; i < 5; i++)
-	{
-		buffer[7] = '0'+i;
-		kp_inputwheel[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	// HERE COMES A NEW CHALLENGER
-	sprintf(buffer, "K_CHALxx");
-	for (i = 0; i < 25; i++)
-	{
-		buffer[6] = '0'+((i+1)/10);
-		buffer[7] = '0'+((i+1)%10);
-		kp_challenger[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	// Lap start animation
-	sprintf(buffer, "K_LAP0x");
-	for (i = 0; i < 7; i++)
-	{
-		buffer[6] = '0'+(i+1);
-		kp_lapanim_lap[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	sprintf(buffer, "K_LAPFxx");
-	for (i = 0; i < 11; i++)
-	{
-		buffer[6] = '0'+((i+1)/10);
-		buffer[7] = '0'+((i+1)%10);
-		kp_lapanim_final[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	sprintf(buffer, "K_LAPNxx");
-	for (i = 0; i < 10; i++)
-	{
-		buffer[6] = '0'+i;
-		for (j = 0; j < 3; j++)
-		{
-			buffer[7] = '0'+(j+1);
-			kp_lapanim_number[i][j] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-		}
-	}
-
-	sprintf(buffer, "K_LAPE0x");
-	for (i = 0; i < 2; i++)
-	{
-		buffer[7] = '0'+(i+1);
-		kp_lapanim_emblem[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	sprintf(buffer, "K_LAPH0x");
-	for (i = 0; i < 3; i++)
-	{
-		buffer[7] = '0'+(i+1);
-		kp_lapanim_hand[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
-	}
-
-	kp_yougotem = (patch_t *) W_CachePatchName("YOUGOTEM", PU_HUDGFX);
-}
-
-// For the item toggle menu
-const char *K_GetItemPatch(UINT8 item, boolean tiny)
-{
-	switch (item)
-	{
-		case KITEM_SNEAKER:
-		case KRITEM_TRIPLESNEAKER:
-			return (tiny ? "K_ISSHOE" : "K_ITSHOE");
-		case KITEM_ROCKETSNEAKER:
-			return (tiny ? "K_ISRSHE" : "K_ITRSHE");
-		case KITEM_INVINCIBILITY:
-			return (tiny ? "K_ISINV1" : "K_ITINV1");
-		case KITEM_BANANA:
-		case KRITEM_TRIPLEBANANA:
-		case KRITEM_TENFOLDBANANA:
-			return (tiny ? "K_ISBANA" : "K_ITBANA");
-		case KITEM_EGGMAN:
-			return (tiny ? "K_ISEGGM" : "K_ITEGGM");
-		case KITEM_ORBINAUT:
-			return (tiny ? "K_ISORBN" : "K_ITORB1");
-		case KITEM_JAWZ:
-		case KRITEM_DUALJAWZ:
-			return (tiny ? "K_ISJAWZ" : "K_ITJAWZ");
-		case KITEM_MINE:
-			return (tiny ? "K_ISMINE" : "K_ITMINE");
-		case KITEM_BALLHOG:
-			return (tiny ? "K_ISBHOG" : "K_ITBHOG");
-		case KITEM_SPB:
-			return (tiny ? "K_ISSPB" : "K_ITSPB");
-		case KITEM_GROW:
-			return (tiny ? "K_ISGROW" : "K_ITGROW");
-		case KITEM_SHRINK:
-			return (tiny ? "K_ISSHRK" : "K_ITSHRK");
-		case KITEM_THUNDERSHIELD:
-			return (tiny ? "K_ISTHNS" : "K_ITTHNS");
-		case KITEM_HYUDORO:
-			return (tiny ? "K_ISHYUD" : "K_ITHYUD");
-		case KITEM_POGOSPRING:
-			return (tiny ? "K_ISPOGO" : "K_ITPOGO");
-		case KITEM_KITCHENSINK:
-			return (tiny ? "K_ISSINK" : "K_ITSINK");
-		case KRITEM_TRIPLEORBINAUT:
-			return (tiny ? "K_ISORBN" : "K_ITORB3");
-		case KRITEM_QUADORBINAUT:
-			return (tiny ? "K_ISORBN" : "K_ITORB4");
-		default:
-			return (tiny ? "K_ISSAD" : "K_ITSAD");
-	}
-}
-
-//}
-
-INT32 ITEM_X, ITEM_Y;	// Item Window
-INT32 TIME_X, TIME_Y;	// Time Sticker
-INT32 LAPS_X, LAPS_Y;	// Lap Sticker
-INT32 SPDM_X, SPDM_Y;	// Speedometer
-INT32 POSI_X, POSI_Y;	// Position Number
-INT32 FACE_X, FACE_Y;	// Top-four Faces
-INT32 STCD_X, STCD_Y;	// Starting countdown
-INT32 CHEK_Y;			// CHECK graphic
-INT32 MINI_X, MINI_Y;	// Minimap
-INT32 WANT_X, WANT_Y;	// Battle WANTED poster
-
-// This is for the P2 and P4 side of splitscreen. Then we'll flip P1's and P2's to the bottom with V_SPLITSCREEN.
-INT32 ITEM2_X, ITEM2_Y;
-INT32 LAPS2_X, LAPS2_Y;
-INT32 POSI2_X, POSI2_Y;
-
-
-static void K_initKartHUD(void)
-{
-	/*
-		BASEVIDWIDTH  = 320
-		BASEVIDHEIGHT = 200
-
-		Item window graphic is 41 x 33
-
-		Time Sticker graphic is 116 x 11
-		Time Font is a solid block of (8 x [12) x 14], equal to 96 x 14
-		Therefore, timestamp is 116 x 14 altogether
-
-		Lap Sticker is 80 x 11
-		Lap flag is 22 x 20
-		Lap Font is a solid block of (3 x [12) x 14], equal to 36 x 14
-		Therefore, lapstamp is 80 x 20 altogether
-
-		Position numbers are 43 x 53
-
-		Faces are 32 x 32
-		Faces draw downscaled at 16 x 16
-		Therefore, the allocated space for them is 16 x 67 altogether
-
-		----
-
-		ORIGINAL CZ64 SPLITSCREEN:
-
-		Item window:
-		if (!splitscreen) 	{ ICONX = 139; 				ICONY = 20; }
-		else 				{ ICONX = BASEVIDWIDTH-315; ICONY = 60; }
-
-		Time: 			   236, STRINGY(			   12)
-		Lap:  BASEVIDWIDTH-304, STRINGY(BASEVIDHEIGHT-189)
-
-	*/
-
-	// Single Screen (defaults)
-	// Item Window
-	ITEM_X = 5;						//   5
-	ITEM_Y = 5;						//   5
-	// Level Timer
-	TIME_X = BASEVIDWIDTH - 148;	// 172
-	TIME_Y = 9;						//   9
-	// Level Laps
-	LAPS_X = 9;						//   9
-	LAPS_Y = BASEVIDHEIGHT - 29;	// 171
-	// Speedometer
-	SPDM_X = 9;						//   9
-	SPDM_Y = BASEVIDHEIGHT - 45;	// 155
-	// Position Number
-	POSI_X = BASEVIDWIDTH  - 9;		// 268
-	POSI_Y = BASEVIDHEIGHT - 9;		// 138
-	// Top-Four Faces
-	FACE_X = 9;						//   9
-	FACE_Y = 92;					//  92
-	// Starting countdown
-	STCD_X = BASEVIDWIDTH/2;		//   9
-	STCD_Y = BASEVIDHEIGHT/2;		//  92
-	// CHECK graphic
-	CHEK_Y = BASEVIDHEIGHT;			// 200
-	// Minimap
-	MINI_X = BASEVIDWIDTH - 50;		// 270
-	MINI_Y = (BASEVIDHEIGHT/2)-16; //  84
-	// Battle WANTED poster
-	WANT_X = BASEVIDWIDTH - 55;		// 270
-	WANT_Y = BASEVIDHEIGHT- 71;		// 176
-
-	if (splitscreen)	// Splitscreen
-	{
-		ITEM_X = 5;
-		ITEM_Y = 3;
-
-		LAPS_Y = (BASEVIDHEIGHT/2)-24;
-
-		POSI_Y = (BASEVIDHEIGHT/2)- 2;
-
-		STCD_Y = BASEVIDHEIGHT/4;
-
-		MINI_Y = (BASEVIDHEIGHT/2);
-
-		if (splitscreen > 1)	// 3P/4P Small Splitscreen
-		{
-			// 1P (top left)
-			ITEM_X = -9;
-			ITEM_Y = -8;
-
-			LAPS_X = 3;
-			LAPS_Y = (BASEVIDHEIGHT/2)-13;
-
-			POSI_X = 24;
-			POSI_Y = (BASEVIDHEIGHT/2)- 16;
-
-			// 2P (top right)
-			ITEM2_X = BASEVIDWIDTH-39;
-			ITEM2_Y = -8;
-
-			LAPS2_X = BASEVIDWIDTH-40;
-			LAPS2_Y = (BASEVIDHEIGHT/2)-13;
-
-			POSI2_X = BASEVIDWIDTH -4;
-			POSI2_Y = (BASEVIDHEIGHT/2)- 16;
-
-			// Reminder that 3P and 4P are just 1P and 2P splitscreen'd to the bottom.
-
-			STCD_X = BASEVIDWIDTH/4;
-
-			MINI_X = (3*BASEVIDWIDTH/4);
-			MINI_Y = (3*BASEVIDHEIGHT/4);
-
-			if (splitscreen > 2) // 4P-only
-			{
-				MINI_X = (BASEVIDWIDTH/2);
-				MINI_Y = (BASEVIDHEIGHT/2);
-			}
-		}
-	}
-
-	if (timeinmap > 113)
-		hudtrans = cv_translucenthud.value;
-	else if (timeinmap > 105)
-		hudtrans = ((((INT32)timeinmap) - 105)*cv_translucenthud.value)/(113-105);
-	else
-		hudtrans = 0;
-}
-
-INT32 K_calcSplitFlags(INT32 snapflags)
-{
-	INT32 splitflags = 0;
-
-	if (splitscreen == 0)
-		return snapflags;
-
-	if (stplyr != &players[displayplayer])
-	{
-		if (splitscreen == 1 && stplyr == &players[secondarydisplayplayer])
-		{
-			splitflags |= V_SPLITSCREEN;
-		}
-		else if (splitscreen > 1)
-		{
-			if (stplyr == &players[thirddisplayplayer] || stplyr == &players[fourthdisplayplayer])
-				splitflags |= V_SPLITSCREEN;
-			if (stplyr == &players[secondarydisplayplayer] || stplyr == &players[fourthdisplayplayer])
-				splitflags |= V_HORZSCREEN;
-		}
-	}
-
-	if (splitflags & V_SPLITSCREEN)
-		snapflags &= ~V_SNAPTOTOP;
-	else
-		snapflags &= ~V_SNAPTOBOTTOM;
-
-	if (splitscreen > 1)
-	{
-		if (splitflags & V_HORZSCREEN)
-			snapflags &= ~V_SNAPTOLEFT;
-		else
-			snapflags &= ~V_SNAPTORIGHT;
-	}
-
-	return (splitflags|snapflags);
-}
-
-static void K_drawKartItem(void)
-{
-	// ITEM_X = BASEVIDWIDTH-50;	// 270
-	// ITEM_Y = 24;					//  24
-
-	// Why write V_DrawScaledPatch calls over and over when they're all the same?
-	// Set to 'no item' just in case.
-	const UINT8 offset = ((splitscreen > 1) ? 1 : 0);
-	patch_t *localpatch = kp_nodraw;
-	patch_t *localbg = ((offset) ? kp_itembg[2] : kp_itembg[0]);
-	patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]);
-	INT32 fx = 0, fy = 0, fflags = 0;	// final coords for hud and flags...
-	//INT32 splitflags = K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOLEFT);
-	const INT32 numberdisplaymin = ((!offset && stplyr->kartstuff[k_itemtype] == KITEM_ORBINAUT) ? 5 : 2);
-	INT32 itembar = 0;
-	UINT8 localcolor = SKINCOLOR_NONE;
-	SINT8 colormode = TC_RAINBOW;
-	UINT8 *colmap = NULL;
-	boolean flipamount = false;	// Used for 3P/4P splitscreen to flip item amount stuff
-
-	if (stplyr->kartstuff[k_itemroulette])
-	{
-		if (stplyr->skincolor)
-			localcolor = stplyr->skincolor;
-
-		switch((stplyr->kartstuff[k_itemroulette] % (14*3)) / 3)
-		{
-			// Each case is handled in threes, to give three frames of in-game time to see the item on the roulette
-			case 0: // Sneaker
-				localpatch = kp_sneaker[offset];
-				//localcolor = SKINCOLOR_RASPBERRY;
-				break;
-			case 1: // Banana
-				localpatch = kp_banana[offset];
-				//localcolor = SKINCOLOR_YELLOW;
-				break;
-			case 2: // Orbinaut
-				localpatch = kp_orbinaut[3+offset];
-				//localcolor = SKINCOLOR_STEEL;
-				break;
-			case 3: // Mine
-				localpatch = kp_mine[offset];
-				//localcolor = SKINCOLOR_JET;
-				break;
-			case 4: // Grow
-				localpatch = kp_grow[offset];
-				//localcolor = SKINCOLOR_TEAL;
-				break;
-			case 5: // Hyudoro
-				localpatch = kp_hyudoro[offset];
-				//localcolor = SKINCOLOR_STEEL;
-				break;
-			case 6: // Rocket Sneaker
-				localpatch = kp_rocketsneaker[offset];
-				//localcolor = SKINCOLOR_TANGERINE;
-				break;
-			case 7: // Jawz
-				localpatch = kp_jawz[offset];
-				//localcolor = SKINCOLOR_JAWZ;
-				break;
-			case 8: // Self-Propelled Bomb
-				localpatch = kp_selfpropelledbomb[offset];
-				//localcolor = SKINCOLOR_JET;
-				break;
-			case 9: // Shrink
-				localpatch = kp_shrink[offset];
-				//localcolor = SKINCOLOR_ORANGE;
-				break;
-			case 10: // Invincibility
-				localpatch = localinv;
-				//localcolor = SKINCOLOR_GREY;
-				break;
-			case 11: // Eggman Monitor
-				localpatch = kp_eggman[offset];
-				//localcolor = SKINCOLOR_ROSE;
-				break;
-			case 12: // Ballhog
-				localpatch = kp_ballhog[offset];
-				//localcolor = SKINCOLOR_LILAC;
-				break;
-			case 13: // Thunder Shield
-				localpatch = kp_thundershield[offset];
-				//localcolor = SKINCOLOR_CYAN;
-				break;
-			/*case 14: // Pogo Spring
-				localpatch = kp_pogospring[offset];
-				localcolor = SKINCOLOR_TANGERINE;
-				break;
-			case 15: // Kitchen Sink
-				localpatch = kp_kitchensink[offset];
-				localcolor = SKINCOLOR_STEEL;
-				break;*/
-			default:
-				break;
-		}
-	}
-	else
-	{
-		// I'm doing this a little weird and drawing mostly in reverse order
-		// The only actual reason is to make sneakers line up this way in the code below
-		// This shouldn't have any actual baring over how it functions
-		// Hyudoro is first, because we're drawing it on top of the player's current item
-		if (stplyr->kartstuff[k_stolentimer] > 0)
-		{
-			if (leveltime & 2)
-				localpatch = kp_hyudoro[offset];
-			else
-				localpatch = kp_nodraw;
-		}
-		else if ((stplyr->kartstuff[k_stealingtimer] > 0) && (leveltime & 2))
-		{
-			localpatch = kp_hyudoro[offset];
-		}
-		else if (stplyr->kartstuff[k_eggmanexplode] > 1)
-		{
-			if (leveltime & 1)
-				localpatch = kp_eggman[offset];
-			else
-				localpatch = kp_nodraw;
-		}
-		else if (stplyr->kartstuff[k_rocketsneakertimer] > 1)
-		{
-			itembar = stplyr->kartstuff[k_rocketsneakertimer];
-			if (leveltime & 1)
-				localpatch = kp_rocketsneaker[offset];
-			else
-				localpatch = kp_nodraw;
-		}
-		else if (stplyr->kartstuff[k_growshrinktimer] > 0)
-		{
-			if (leveltime & 1)
-				localpatch = kp_grow[offset];
-			else
-				localpatch = kp_nodraw;
-		}
-		else if (stplyr->kartstuff[k_sadtimer] > 0)
-		{
-			if (leveltime & 2)
-				localpatch = kp_sadface[offset];
-			else
-				localpatch = kp_nodraw;
-		}
-		else
-		{
-			if (stplyr->kartstuff[k_itemamount] <= 0)
-				return;
-
-			switch(stplyr->kartstuff[k_itemtype])
-			{
-				case KITEM_SNEAKER:
-					localpatch = kp_sneaker[offset];
-					break;
-				case KITEM_ROCKETSNEAKER:
-					localpatch = kp_rocketsneaker[offset];
-					break;
-				case KITEM_INVINCIBILITY:
-					localpatch = localinv;
-					localbg = kp_itembg[offset+1];
-					break;
-				case KITEM_BANANA:
-					localpatch = kp_banana[offset];
-					break;
-				case KITEM_EGGMAN:
-					localpatch = kp_eggman[offset];
-					break;
-				case KITEM_ORBINAUT:
-					localpatch = kp_orbinaut[(offset ? 4 : min(stplyr->kartstuff[k_itemamount]-1, 3))];
-					break;
-				case KITEM_JAWZ:
-					localpatch = kp_jawz[offset];
-					break;
-				case KITEM_MINE:
-					localpatch = kp_mine[offset];
-					break;
-				case KITEM_BALLHOG:
-					localpatch = kp_ballhog[offset];
-					break;
-				case KITEM_SPB:
-					localpatch = kp_selfpropelledbomb[offset];
-					localbg = kp_itembg[offset+1];
-					break;
-				case KITEM_GROW:
-					localpatch = kp_grow[offset];
-					break;
-				case KITEM_SHRINK:
-					localpatch = kp_shrink[offset];
-					break;
-				case KITEM_THUNDERSHIELD:
-					localpatch = kp_thundershield[offset];
-					localbg = kp_itembg[offset+1];
-					break;
-				case KITEM_HYUDORO:
-					localpatch = kp_hyudoro[offset];
-					break;
-				case KITEM_POGOSPRING:
-					localpatch = kp_pogospring[offset];
-					break;
-				case KITEM_KITCHENSINK:
-					localpatch = kp_kitchensink[offset];
-					break;
-				case KITEM_SAD:
-					localpatch = kp_sadface[offset];
-					break;
-				default:
-					return;
-			}
-
-			if (stplyr->kartstuff[k_itemheld] && !(leveltime & 1))
-				localpatch = kp_nodraw;
-		}
-
-		if (stplyr->kartstuff[k_itemblink] && (leveltime & 1))
-		{
-			colormode = TC_BLINK;
-
-			switch (stplyr->kartstuff[k_itemblinkmode])
-			{
-				case 2:
-					localcolor = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1)));
-					break;
-				case 1:
-					localcolor = SKINCOLOR_RED;
-					break;
-				default:
-					localcolor = SKINCOLOR_WHITE;
-					break;
-			}
-		}
-	}
-
-	// pain and suffering defined below
-	if (splitscreen < 2)	// don't change shit for THIS splitscreen.
-	{
-		fx = ITEM_X;
-		fy = ITEM_Y;
-		fflags = K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOLEFT);
-	}
-	else				// now we're having a fun game.
-	{
-		if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer])	// If we are P1 or P3...
-		{
-			fx = ITEM_X;
-			fy = ITEM_Y;
-			fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN : V_SNAPTOTOP);	// flip P3 to the bottom.
-		}
-		else // else, that means we're P2 or P4.
-		{
-			fx = ITEM2_X;
-			fy = ITEM2_Y;
-			fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN : V_SNAPTOTOP);	// flip P4 to the bottom
-			flipamount = true;
-		}
-	}
-
-	if (localcolor != SKINCOLOR_NONE)
-		colmap = R_GetTranslationColormap(colormode, localcolor, 0);
-
-	V_DrawScaledPatch(fx, fy, V_HUDTRANS|fflags, localbg);
-
-	// Then, the numbers:
-	if (stplyr->kartstuff[k_itemamount] >= numberdisplaymin && !stplyr->kartstuff[k_itemroulette])
-	{
-		V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]);	// flip this graphic for p2 and p4 in split and shift it.
-		V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, FRACUNIT, V_HUDTRANS|fflags, localpatch, colmap);
-		if (offset)
-			if (flipamount)	// reminder that this is for 3/4p's right end of the screen.
-				V_DrawString(fx+2, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|fflags, va("x%d", stplyr->kartstuff[k_itemamount]));
-			else
-				V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|fflags, va("x%d", stplyr->kartstuff[k_itemamount]));
-		else
-		{
-			V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|fflags, kp_itemx);
-			V_DrawKartString(fx+38, fy+36, V_HUDTRANS|fflags, va("%d", stplyr->kartstuff[k_itemamount]));
-		}
-	}
-	else
-		V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, FRACUNIT, V_HUDTRANS|fflags, localpatch, colmap);
-
-	// Extensible meter, currently only used for rocket sneaker...
-	if (itembar && hudtrans)
-	{
-		const INT32 barlength = (splitscreen > 1 ? 12 : 26);
-		const INT32 maxl = (itemtime*3) - barlength; // timer's normal highest value
-		const INT32 fill = ((itembar*barlength)/maxl);
-		const INT32 length = min(barlength, fill);
-		const INT32 height = (offset ? 1 : 2);
-		const INT32 x = (offset ? 17 : 11), y = (offset ? 27 : 35);
-
-		V_DrawScaledPatch(fx+x, fy+y, V_HUDTRANS|fflags, kp_itemtimer[offset]);
-		// The left dark "AA" edge
-		V_DrawFill(fx+x+1, fy+y+1, (length == 2 ? 2 : 1), height, 12|fflags);
-		// The bar itself
-		if (length > 2)
-		{
-			V_DrawFill(fx+x+length, fy+y+1, 1, height, 12|fflags); // the right one
-			if (height == 2)
-				V_DrawFill(fx+x+2, fy+y+2, length-2, 1, 8|fflags); // the dulled underside
-			V_DrawFill(fx+x+2, fy+y+1, length-2, 1, 120|fflags); // the shine
-		}
-	}
-
-	// Quick Eggman numbers
-	if (stplyr->kartstuff[k_eggmanexplode] > 1 /*&& stplyr->kartstuff[k_eggmanexplode] <= 3*TICRATE*/)
-		V_DrawScaledPatch(fx+17, fy+13-offset, V_HUDTRANS|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->kartstuff[k_eggmanexplode]))]);
-
-}
-
-void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode)
-{
-	// TIME_X = BASEVIDWIDTH-124;	// 196
-	// TIME_Y = 6;					//   6
-
-	tic_t worktime;
-
-	INT32 splitflags = 0;
-	if (!mode)
-	{
-		splitflags = V_HUDTRANS|K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTORIGHT);
-		if (cv_timelimit.value && timelimitintics > 0)
-		{
-			if (drawtime >= timelimitintics)
-				drawtime = 0;
-			else
-				drawtime = timelimitintics - drawtime;
-		}
-	}
-
-	V_DrawScaledPatch(TX, TY, splitflags, ((mode == 2) ? kp_lapstickerwide : kp_timestickerwide));
-
-	TX += 33;
-
-	worktime = drawtime/(60*TICRATE);
-
-	if (mode && !drawtime)
-		V_DrawKartString(TX, TY+3, splitflags, va("--'--\"--"));
-	else if (worktime < 100) // 99:99:99 only
-	{
-		// zero minute
-		if (worktime < 10)
-		{
-			V_DrawKartString(TX, TY+3, splitflags, va("0"));
-			// minutes time       0 __ __
-			V_DrawKartString(TX+12, TY+3, splitflags, va("%d", worktime));
-		}
-		// minutes time       0 __ __
-		else
-			V_DrawKartString(TX, TY+3, splitflags, va("%d", worktime));
-
-		// apostrophe location     _'__ __
-		V_DrawKartString(TX+24, TY+3, splitflags, va("'"));
-
-		worktime = (drawtime/TICRATE % 60);
-
-		// zero second        _ 0_ __
-		if (worktime < 10)
-		{
-			V_DrawKartString(TX+36, TY+3, splitflags, va("0"));
-		// seconds time       _ _0 __
-			V_DrawKartString(TX+48, TY+3, splitflags, va("%d", worktime));
-		}
-		// zero second        _ 00 __
-		else
-			V_DrawKartString(TX+36, TY+3, splitflags, va("%d", worktime));
-
-		// quotation mark location    _ __"__
-		V_DrawKartString(TX+60, TY+3, splitflags, va("\""));
-
-		worktime = G_TicsToCentiseconds(drawtime);
-
-		// zero tick          _ __ 0_
-		if (worktime < 10)
-		{
-			V_DrawKartString(TX+72, TY+3, splitflags, va("0"));
-		// tics               _ __ _0
-			V_DrawKartString(TX+84, TY+3, splitflags, va("%d", worktime));
-		}
-		// zero tick          _ __ 00
-		else
-			V_DrawKartString(TX+72, TY+3, splitflags, va("%d", worktime));
-	}
-	else if ((drawtime/TICRATE) & 1)
-		V_DrawKartString(TX, TY+3, splitflags, va("99'59\"99"));
-
-	if (emblemmap && (modeattacking || (mode == 1)) && !demoplayback) // emblem time!
-	{
-		INT32 workx = TX + 96, worky = TY+18;
-		SINT8 curemb = 0;
-		patch_t *emblempic[3] = {NULL, NULL, NULL};
-		UINT8 *emblemcol[3] = {NULL, NULL, NULL};
-
-		emblem_t *emblem = M_GetLevelEmblems(emblemmap);
-		while (emblem)
-		{
-			char targettext[9];
-
-			switch (emblem->type)
-			{
-				case ET_TIME:
-					{
-						static boolean canplaysound = true;
-						tic_t timetoreach = emblem->var;
-
-						if (emblem->collected)
-						{
-							emblempic[curemb] = W_CachePatchName(M_GetEmblemPatch(emblem), PU_CACHE);
-							emblemcol[curemb] = R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE);
-							if (++curemb == 3)
-								break;
-							goto bademblem;
-						}
-
-						snprintf(targettext, 9, "%i'%02i\"%02i",
-							G_TicsToMinutes(timetoreach, false),
-							G_TicsToSeconds(timetoreach),
-							G_TicsToCentiseconds(timetoreach));
-
-						if (!mode)
-						{
-							if (stplyr->realtime > timetoreach)
-							{
-								splitflags = (splitflags &~ V_HUDTRANS)|V_HUDTRANSHALF;
-								if (canplaysound)
-								{
-									S_StartSound(NULL, sfx_s3k72); //sfx_s26d); -- you STOLE fizzy lifting drinks
-									canplaysound = false;
-								}
-							}
-							else if (!canplaysound)
-								canplaysound = true;
-						}
-
-						targettext[8] = 0;
-					}
-					break;
-				default:
-					goto bademblem;
-			}
-
-			V_DrawRightAlignedString(workx, worky, splitflags, targettext);
-			workx -= 67;
-			V_DrawSmallScaledPatch(workx + 4, worky, splitflags, W_CachePatchName("NEEDIT", PU_CACHE));
-
-			break;
-
-			bademblem:
-			emblem = M_GetLevelEmblems(-1);
-		}
-
-		if (!mode)
-			splitflags = (splitflags &~ V_HUDTRANSHALF)|V_HUDTRANS;
-		while (curemb--)
-		{
-			workx -= 12;
-			V_DrawSmallMappedPatch(workx + 4, worky, splitflags, emblempic[curemb], emblemcol[curemb]);
-		}
-	}
-}
-
-static void K_DrawKartPositionNum(INT32 num)
-{
-	// POSI_X = BASEVIDWIDTH - 51;	// 269
-	// POSI_Y = BASEVIDHEIGHT- 64;	// 136
-
-	boolean win = (stplyr->exiting && num == 1);
-	//INT32 X = POSI_X;
-	INT32 W = SHORT(kp_positionnum[0][0]->width);
-	fixed_t scale = FRACUNIT;
-	patch_t *localpatch = kp_positionnum[0][0];
-	//INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTORIGHT);
-	INT32 fx = 0, fy = 0, fflags = 0;
-	boolean flipdraw = false;	// flip the order we draw it in for MORE splitscreen bs. fun.
-	boolean flipvdraw = false;	// used only for 2p splitscreen so overtaking doesn't make 1P's position fly off the screen.
-	boolean overtake = false;
-
-	if (stplyr->kartstuff[k_positiondelay] || stplyr->exiting)
-	{
-		scale *= 2;
-		overtake = true;	// this is used for splitscreen stuff in conjunction with flipdraw.
-	}
-	if (splitscreen)
-		scale /= 2;
-
-	W = FixedMul(W<<FRACBITS, scale)>>FRACBITS;
-
-	// pain and suffering defined below
-	if (!splitscreen)
-	{
-		fx = POSI_X;
-		fy = BASEVIDHEIGHT - 8;
-		fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT;
-	}
-	else if (splitscreen == 1)	// for this splitscreen, we'll use case by case because it's a bit different.
-	{
-		fx = POSI_X;
-		if (stplyr == &players[displayplayer])	// for player 1: display this at the top right, above the minimap.
-		{
-			fy = 30;
-			fflags = V_SNAPTOTOP|V_SNAPTORIGHT;
-			if (overtake)
-				flipvdraw = true;	// make sure overtaking doesn't explode us
-		}
-		else	// if we're not p1, that means we're p2. display this at the bottom right, below the minimap.
-		{
-			fy = BASEVIDHEIGHT - 8;
-			fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT;
-		}
-	}
-	else
-	{
-		if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer])	// If we are P1 or P3...
-		{
-			fx = POSI_X;
-			fy = POSI_Y;
-			fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P3 to the bottom.
-			flipdraw = true;
-			if (num && num >= 10)
-				fx += W;	// this seems dumb, but we need to do this in order for positions above 10 going off screen.
-		}
-		else // else, that means we're P2 or P4.
-		{
-			fx = POSI2_X;
-			fy = POSI2_Y;
-			fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P4 to the bottom
-		}
-	}
-
-	// Special case for 0
-	if (!num)
-	{
-		V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, scale, V_HUDTRANSHALF|fflags, kp_positionnum[0][0], NULL);
-		return;
-	}
-
-	I_Assert(num >= 0); // This function does not draw negative numbers
-
-	// Draw the number
-	while (num)
-	{
-		if (win) // 1st place winner? You get rainbows!!
-			localpatch = kp_winnernum[(leveltime % (NUMWINFRAMES*3)) / 3];
-		else if (stplyr->laps+1 >= cv_numlaps.value || stplyr->exiting) // Check for the final lap, or won
-		{
-			// Alternate frame every three frames
-			switch (leveltime % 9)
-			{
-				case 1: case 2: case 3:
-					if (K_IsPlayerLosing(stplyr))
-						localpatch = kp_positionnum[num % 10][4];
-					else
-						localpatch = kp_positionnum[num % 10][1];
-					break;
-				case 4: case 5: case 6:
-					if (K_IsPlayerLosing(stplyr))
-						localpatch = kp_positionnum[num % 10][5];
-					else
-						localpatch = kp_positionnum[num % 10][2];
-					break;
-				case 7: case 8: case 9:
-					if (K_IsPlayerLosing(stplyr))
-						localpatch = kp_positionnum[num % 10][6];
-					else
-						localpatch = kp_positionnum[num % 10][3];
-					break;
-				default:
-					localpatch = kp_positionnum[num % 10][0];
-					break;
-			}
-		}
-		else
-			localpatch = kp_positionnum[num % 10][0];
-
-		V_DrawFixedPatch((fx<<FRACBITS) + ((overtake && flipdraw) ? (SHORT(localpatch->width)*scale/2) : 0), (fy<<FRACBITS) + ((overtake && flipvdraw) ? (SHORT(localpatch->height)*scale/2) : 0), scale, V_HUDTRANSHALF|fflags, localpatch, NULL);
-		// ^ if we overtake as p1 or p3 in splitscren, we shift it so that it doesn't go off screen.
-		// ^ if we overtake as p1 in 2p splits, shift vertically so that this doesn't happen either.
-
-		fx -= W;
-		num /= 10;
-	}
-}
-
-static boolean K_drawKartPositionFaces(void)
-{
-	// FACE_X = 15;				//  15
-	// FACE_Y = 72;				//  72
-
-	INT32 Y = FACE_Y+9; // +9 to offset where it's being drawn if there are more than one
-	INT32 i, j, ranklines, strank = -1;
-	boolean completed[MAXPLAYERS];
-	INT32 rankplayer[MAXPLAYERS];
-	INT32 bumperx, numplayersingame = 0;
-	UINT8 *colormap;
-
-	ranklines = 0;
-	memset(completed, 0, sizeof (completed));
-	memset(rankplayer, 0, sizeof (rankplayer));
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		rankplayer[i] = -1;
-
-		if (!playeringame[i] || players[i].spectator || !players[i].mo)
-			continue;
-
-		numplayersingame++;
-	}
-
-	if (numplayersingame <= 1)
-		return true;
-
-#ifdef HAVE_BLUA
-	if (!LUA_HudEnabled(hud_minirankings))
-		return false;	// Don't proceed but still return true for free play above if HUD is disabled.
-#endif
-
-	for (j = 0; j < numplayersingame; j++)
-	{
-		UINT8 lowestposition = MAXPLAYERS+1;
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (completed[i] || !playeringame[i] || players[i].spectator || !players[i].mo)
-				continue;
-
-			if (players[i].kartstuff[k_position] >= lowestposition)
-				continue;
-
-			rankplayer[ranklines] = i;
-			lowestposition = players[i].kartstuff[k_position];
-		}
-
-		i = rankplayer[ranklines];
-
-		completed[i] = true;
-
-		if (players+i == stplyr)
-			strank = ranklines;
-
-		//if (ranklines == 5)
-			//break; // Only draw the top 5 players -- we do this a different way now...
-
-		ranklines++;
-	}
-
-	if (ranklines < 5)
-		Y -= (9*ranklines);
-	else
-		Y -= (9*5);
-
-	if (G_BattleGametype() || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2)
-	{
-		i = 0;
-		if (ranklines > 5) // could be both...
-			ranklines = 5;
-	}
-	else if (strank+3 > ranklines) // too close to the bottom?
-	{
-		i = ranklines - 5;
-		if (i < 0)
-			i = 0;
-	}
-	else
-	{
-		i = strank-2;
-		ranklines = strank+3;
-	}
-
-	for (; i < ranklines; i++)
-	{
-		if (!playeringame[rankplayer[i]]) continue;
-		if (players[rankplayer[i]].spectator) continue;
-		if (!players[rankplayer[i]].mo) continue;
-
-		bumperx = FACE_X+19;
-
-		if (players[rankplayer[i]].mo->color)
-		{
-			colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE);
-			if (players[rankplayer[i]].mo->colorized)
-				colormap = R_GetTranslationColormap(TC_RAINBOW, players[rankplayer[i]].mo->color, GTC_CACHE);
-			else
-				colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE);
-
-			V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, facerankprefix[players[rankplayer[i]].skin], colormap);
-
-#ifdef HAVE_BLUA
-			if (LUA_HudEnabled(hud_battlebumpers))
-			{
-#endif
-				if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] > 0)
-				{
-					V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[0], colormap);
-					for (j = 1; j < players[rankplayer[i]].kartstuff[k_bumper]; j++)
-					{
-						bumperx += 5;
-						V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[1], colormap);
-					}
-				}
-#ifdef HAVE_BLUA
-			}	// A new level of stupidity: checking if lua is enabled to close a bracket. :Fascinating:
-#endif
-		}
-
-		if (i == strank)
-			V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]);
-
-		if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] <= 0)
-			V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SNAPTOLEFT, kp_ranknobumpers);
-		else
-		{
-			INT32 pos = players[rankplayer[i]].kartstuff[k_position];
-			if (pos < 0 || pos > MAXPLAYERS)
-				pos = 0;
-			// Draws the little number over the face
-			V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SNAPTOLEFT, kp_facenum[pos]);
-		}
-
-		Y += 18;
-	}
-
-	return false;
-}
-
-//
-// HU_DrawTabRankings -- moved here to take advantage of kart stuff!
-//
-void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol)
-{
-	INT32 i, rightoffset = 240;
-	const UINT8 *colormap;
-	INT32 dupadjust = (vid.width/vid.dupx), duptweak = (dupadjust - BASEVIDWIDTH)/2;
-
-	//this function is designed for 9 or less score lines only
-	//I_Assert(scorelines <= 9); -- not today bitch, kart fixed it up
-
-	V_DrawFill(1-duptweak, 26, dupadjust-2, 1, 0); // Draw a horizontal line because it looks nice!
-	if (scorelines > 8)
-	{
-		V_DrawFill(160, 26, 1, 147, 0); // Draw a vertical line to separate the two sides.
-		V_DrawFill(1-duptweak, 173, dupadjust-2, 1, 0); // And a horizontal line near the bottom.
-		rightoffset = (BASEVIDWIDTH/2) - 4 - x;
-	}
-
-	for (i = 0; i < scorelines; i++)
-	{
-		char strtime[MAXPLAYERNAME+1];
-
-		if (players[tab[i].num].spectator || !players[tab[i].num].mo)
-			continue; //ignore them.
-
-		if (netgame // don't draw it offline
-		&& tab[i].num != serverplayer)
-			HU_drawPing(x + ((i < 8) ? -19 : rightoffset + 13), y+2, playerpingtable[tab[i].num], false);
-
-		STRBUFCPY(strtime, tab[i].name);
-
-		if (scorelines > 8)
-			V_DrawThinString(x + 20, y, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, strtime);
-		else
-			V_DrawString(x + 20, y, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE, strtime);
-
-		if (players[tab[i].num].mo->color)
-		{
-			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
-			if (players[tab[i].num].mo->colorized)
-				colormap = R_GetTranslationColormap(TC_RAINBOW, players[tab[i].num].mo->color, GTC_CACHE);
-			else
-				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
-
-			V_DrawMappedPatch(x, y-4, 0, facerankprefix[players[tab[i].num].skin], colormap);
-			/*if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] > 0) -- not enough space for this
-			{
-				INT32 bumperx = x+19;
-				V_DrawMappedPatch(bumperx-2, y-4, 0, kp_tinybumper[0], colormap);
-				for (j = 1; j < players[tab[i].num].kartstuff[k_bumper]; j++)
-				{
-					bumperx += 5;
-					V_DrawMappedPatch(bumperx, y-4, 0, kp_tinybumper[1], colormap);
-				}
-			}*/
-		}
-
-		if (tab[i].num == whiteplayer)
-			V_DrawScaledPatch(x, y-4, 0, kp_facehighlight[(leveltime / 4) % 8]);
-
-		if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] <= 0)
-			V_DrawScaledPatch(x-4, y-7, 0, kp_ranknobumpers);
-		else
-		{
-			INT32 pos = players[tab[i].num].kartstuff[k_position];
-			if (pos < 0 || pos > MAXPLAYERS)
-				pos = 0;
-			// Draws the little number over the face
-			V_DrawScaledPatch(x-5, y+6, 0, kp_facenum[pos]);
-		}
-
-		if (G_RaceGametype())
-		{
-#define timestring(time) va("%i'%02i\"%02i", G_TicsToMinutes(time, true), G_TicsToSeconds(time), G_TicsToCentiseconds(time))
-			if (scorelines > 8)
-			{
-				if (players[tab[i].num].exiting)
-					V_DrawRightAlignedThinString(x+rightoffset, y-1, hilicol|V_6WIDTHSPACE, timestring(players[tab[i].num].realtime));
-				else if (players[tab[i].num].pflags & PF_TIMEOVER)
-					V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, "NO CONTEST.");
-				else if (circuitmap)
-					V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, va("Lap %d", tab[i].count));
-			}
-			else
-			{
-				if (players[tab[i].num].exiting)
-					V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime));
-				else if (players[tab[i].num].pflags & PF_TIMEOVER)
-					V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "NO CONTEST.");
-				else if (circuitmap)
-					V_DrawRightAlignedString(x+rightoffset, y, 0, va("Lap %d", tab[i].count));
-			}
-#undef timestring
-		}
-		else
-			V_DrawRightAlignedString(x+rightoffset, y, 0, va("%u", tab[i].count));
-
-		y += 18;
-		if (i == 7)
-		{
-			y = 33;
-			x = (BASEVIDWIDTH/2) + 4;
-		}
-	}
-}
-
-static void K_drawKartLaps(void)
-{
-	INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT);
-	INT32 fx = 0, fy = 0, fflags = 0;	// stuff for 3p / 4p splitscreen.
-	boolean flipstring = false;	// used for 3p or 4p
-	INT32 stringw = 0;	// used with the above
-
-	if (splitscreen > 1)
-	{
-
-		// pain and suffering defined below
-		if (splitscreen < 2)	// don't change shit for THIS splitscreen.
-		{
-			fx = LAPS_X;
-			fy = LAPS_Y;
-			fflags = splitflags;
-		}
-		else
-		{
-			if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer])	// If we are P1 or P3...
-			{
-				fx = LAPS_X;
-				fy = LAPS_Y;
-				fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P3 to the bottom.
-			}
-			else // else, that means we're P2 or P4.
-			{
-				fx = LAPS2_X;
-				fy = LAPS2_Y;
-				fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P4 to the bottom
-				flipstring = true;	// make the string right aligned and other shit
-			}
-		}
-
-
-
-		if (stplyr->exiting)	// draw stuff as god intended.
-		{
-			V_DrawScaledPatch(fx, fy, V_HUDTRANS|fflags, kp_splitlapflag);
-			V_DrawString(fx+13, fy+1, V_HUDTRANS|fflags, "FIN");
-		}
-		else					// take flipstring into account here since we may have more laps than just 10
-			if (flipstring)
-			{
-				stringw = V_StringWidth(va("%d/%d", stplyr->laps+1, cv_numlaps.value), 0);
-
-				V_DrawScaledPatch(BASEVIDWIDTH-stringw-16, fy, V_HUDTRANS|fflags, kp_splitlapflag);
-				V_DrawRightAlignedString(BASEVIDWIDTH-3, fy+1, V_HUDTRANS|fflags, va("%d/%d", stplyr->laps+1, cv_numlaps.value));
-			}
-			else	// draw stuff NORMALLY.
-			{
-				V_DrawScaledPatch(fx, fy, V_HUDTRANS|fflags, kp_splitlapflag);
-				V_DrawString(fx+13, fy+1, V_HUDTRANS|fflags, va("%d/%d", stplyr->laps+1, cv_numlaps.value));
-			}
-	}
-	else
-	{
-		V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_lapsticker);
-
-		if (stplyr->exiting)
-			V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, "FIN");
-		else
-			V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->laps+1, cv_numlaps.value));
-	}
-}
-
-static void K_drawKartSpeedometer(void)
-{
-	fixed_t convSpeed;
-	INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT);
-
-	if (cv_kartspeedometer.value == 1) // Kilometers
-	{
-		convSpeed = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale)/FRACUNIT; // 2.172409058
-		V_DrawKartString(SPDM_X, SPDM_Y, V_HUDTRANS|splitflags, va("%3d km/h", convSpeed));
-	}
-	else if (cv_kartspeedometer.value == 2) // Miles
-	{
-		convSpeed = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale)/FRACUNIT; // 1.349868774
-		V_DrawKartString(SPDM_X, SPDM_Y, V_HUDTRANS|splitflags, va("%3d mph", convSpeed));
-	}
-	else if (cv_kartspeedometer.value == 3) // Fracunits
-	{
-		convSpeed = FixedDiv(stplyr->speed, mapobjectscale)/FRACUNIT;
-		V_DrawKartString(SPDM_X, SPDM_Y, V_HUDTRANS|splitflags, va("%3d fu/t", convSpeed));
-	}
-}
-
-static void K_drawKartBumpersOrKarma(void)
-{
-	UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, 0);
-	INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT);
-	INT32 fx = 0, fy = 0, fflags = 0;
-	boolean flipstring = false;	// same as laps, used for splitscreen
-	INT32 stringw = 0;	// used with the above
-
-	if (splitscreen > 1)
-	{
-
-		// we will reuse lap coords here since it's essentially the same shit.
-
-		if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer])	// If we are P1 or P3...
-		{
-			fx = LAPS_X;
-			fy = LAPS_Y;
-			fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P3 to the bottom.
-		}
-		else // else, that means we're P2 or P4.
-		{
-			fx = LAPS2_X;
-			fy = LAPS2_Y;
-			fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P4 to the bottom
-			flipstring = true;
-		}
-
-		if (stplyr->kartstuff[k_bumper] <= 0)
-		{
-			V_DrawMappedPatch(fx, fy-1, V_HUDTRANS|fflags, kp_splitkarmabomb, colormap);
-			V_DrawString(fx+13, fy+1, V_HUDTRANS|fflags, va("%d/2", stplyr->kartstuff[k_comebackpoints]));
-		}
-		else	// the above doesn't need to account for weird stuff since the max amount of karma necessary is always 2 ^^^^
-		{
-			if (flipstring)	// for p2 and p4, assume we can have more than 10 bumpers. It's retarded but who knows.
-			{
-				stringw = V_StringWidth(va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value), 0);
-
-				V_DrawMappedPatch(BASEVIDWIDTH-stringw-16, fy-1, V_HUDTRANS|fflags, kp_rankbumper, colormap);
-				V_DrawRightAlignedString(BASEVIDWIDTH-3, fy+1, V_HUDTRANS|fflags, va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value));
-			}
-			else	// draw bumpers normally.
-			{
-				V_DrawMappedPatch(fx, fy-1, V_HUDTRANS|fflags, kp_rankbumper, colormap);
-				V_DrawString(fx+13, fy+1, V_HUDTRANS|fflags, va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value));
-			}
-		}
-	}
-	else
-	{
-		if (stplyr->kartstuff[k_bumper] <= 0)
-		{
-			V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_karmasticker, colormap);
-			V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/2", stplyr->kartstuff[k_comebackpoints]));
-		}
-		else
-		{
-			if (stplyr->kartstuff[k_bumper] > 9 && cv_kartbumpers.value > 9)
-				V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_bumperstickerwide, colormap);
-			else
-				V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_bumpersticker, colormap);
-			V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value));
-		}
-	}
-}
-
-static fixed_t K_FindCheckX(fixed_t px, fixed_t py, angle_t ang, fixed_t mx, fixed_t my)
-{
-	fixed_t dist, x;
-	fixed_t range = RING_DIST/3;
-	angle_t diff;
-
-	range *= gamespeed+1;
-
-	dist = abs(R_PointToDist2(px, py, mx, my));
-	if (dist > range)
-		return -320;
-
-	diff = R_PointToAngle2(px, py, mx, my) - ang;
-
-	if (diff < ANGLE_90 || diff > ANGLE_270)
-		return -320;
-	else
-		x = (FixedMul(FINETANGENT(((diff+ANGLE_90)>>ANGLETOFINESHIFT) & 4095), 160<<FRACBITS) + (160<<FRACBITS))>>FRACBITS;
-
-	if (encoremode)
-		x = 320-x;
-
-	if (splitscreen > 1)
-		x /= 2;
-
-	return x;
-}
-
-static void K_drawKartWanted(void)
-{
-	UINT8 i, numwanted = 0;
-	UINT8 *colormap = NULL;
-	INT32 basex = 0, basey = 0;
-
-	if (stplyr != &players[displayplayer])
-		return;
-
-	for (i = 0; i < 4; i++)
-	{
-		if (battlewanted[i] == -1)
-			break;
-		numwanted++;
-	}
-
-	if (numwanted <= 0)
-		return;
-
-	// set X/Y coords depending on splitscreen.
-	if (splitscreen < 3)		// 1P and 2P use the same code.
-	{
-		basex = WANT_X;
-		basey = WANT_Y;
-		if (splitscreen == 2)
-		{
-			basey += 16;	// slight adjust for 3P
-			basex -= 6;
-		}
-	}
-	else if (splitscreen == 3)	// 4P splitscreen...
-	{
-		basex = BASEVIDWIDTH/2 - (SHORT(kp_wantedsplit->width)/2);	// center on screen
-		basey = BASEVIDHEIGHT - 55;
-		//basey2 = 4;
-	}
-
-	if (battlewanted[0] != -1)
-		colormap = R_GetTranslationColormap(0, players[battlewanted[0]].skincolor, GTC_CACHE);
-	V_DrawFixedPatch(basex<<FRACBITS, basey<<FRACBITS, FRACUNIT, V_HUDTRANS|(splitscreen < 3 ? V_SNAPTORIGHT : 0)|V_SNAPTOBOTTOM, (splitscreen > 1 ? kp_wantedsplit : kp_wanted), colormap);
-	/*if (basey2)
-		V_DrawFixedPatch(basex<<FRACBITS, basey2<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SNAPTOTOP, (splitscreen == 3 ? kp_wantedsplit : kp_wanted), colormap);	// < used for 4p splits.*/
-
-	for (i = 0; i < numwanted; i++)
-	{
-		INT32 x = basex+(splitscreen > 1 ? 13 : 8), y = basey+(splitscreen > 1 ? 16 : 21);
-		fixed_t scale = FRACUNIT/2;
-		player_t *p = &players[battlewanted[i]];
-
-		if (battlewanted[i] == -1)
-			break;
-
-		if (numwanted == 1)
-			scale = FRACUNIT;
-		else
-		{
-			if (i & 1)
-				x += 16;
-			if (i > 1)
-				y += 16;
-		}
-
-		if (players[battlewanted[i]].skincolor)
-		{
-			colormap = R_GetTranslationColormap(TC_RAINBOW, p->skincolor, GTC_CACHE);
-			V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT, V_HUDTRANS|(splitscreen < 3 ? V_SNAPTORIGHT : 0)|V_SNAPTOBOTTOM, (scale == FRACUNIT ? facewantprefix[p->skin] : facerankprefix[p->skin]), colormap);
-			/*if (basey2)	// again with 4p stuff
-				V_DrawFixedPatch(x<<FRACBITS, (y - (basey-basey2))<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SNAPTOTOP, (scale == FRACUNIT ? facewantprefix[p->skin] : facerankprefix[p->skin]), colormap);*/
-		}
-	}
-}
-
-static void K_drawKartPlayerCheck(void)
-{
-	INT32 i;
-	UINT8 *colormap;
-	INT32 x;
-
-	INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM);
-
-	if (!stplyr->mo || stplyr->spectator)
-		return;
-
-	if (stplyr->awayviewtics)
-		return;
-
-	if (camspin)
-		return;
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		UINT8 pnum = 0;
-
-		if (&players[i] == stplyr)
-			continue;
-		if (!playeringame[i] || players[i].spectator)
-			continue;
-		if (!players[i].mo)
-			continue;
-
-		if ((players[i].kartstuff[k_invincibilitytimer] <= 0) && (leveltime & 2))
-			pnum++; // white frames
-
-		if (players[i].kartstuff[k_itemtype] == KITEM_GROW || players[i].kartstuff[k_growshrinktimer] > 0)
-			pnum += 4;
-		else if (players[i].kartstuff[k_itemtype] == KITEM_INVINCIBILITY || players[i].kartstuff[k_invincibilitytimer])
-			pnum += 2;
-
-		x = K_FindCheckX(stplyr->mo->x, stplyr->mo->y, stplyr->mo->angle, players[i].mo->x, players[i].mo->y);
-		if (x <= 320 && x >= 0)
-		{
-			if (x < 14)
-				x = 14;
-			else if (x > 306)
-				x = 306;
-
-			colormap = R_GetTranslationColormap(TC_DEFAULT, players[i].mo->color, 0);
-			V_DrawMappedPatch(x, CHEK_Y, V_HUDTRANS|splitflags, kp_check[pnum], colormap);
-		}
-	}
-}
-
-static void K_drawKartMinimapHead(mobj_t *mo, INT32 x, INT32 y, INT32 flags, patch_t *AutomapPic)
-{
-	// amnum xpos & ypos are the icon's speed around the HUD.
-	// The number being divided by is for how fast it moves.
-	// The higher the number, the slower it moves.
-
-	// am xpos & ypos are the icon's starting position. Withouht
-	// it, they wouldn't 'spawn' on the top-right side of the HUD.
-
-	UINT8 skin = 0;
-
-	fixed_t amnumxpos, amnumypos;
-	INT32 amxpos, amypos;
-
-	node_t *bsp = &nodes[numnodes-1];
-	fixed_t maxx, minx, maxy, miny;
-
-	fixed_t mapwidth, mapheight;
-	fixed_t xoffset, yoffset;
-	fixed_t xscale, yscale, zoom;
-
-	if (mo->skin)
-		skin = ((skin_t*)mo->skin)-skins;
-
-	maxx = maxy = INT32_MAX;
-	minx = miny = INT32_MIN;
-	minx = bsp->bbox[0][BOXLEFT];
-	maxx = bsp->bbox[0][BOXRIGHT];
-	miny = bsp->bbox[0][BOXBOTTOM];
-	maxy = bsp->bbox[0][BOXTOP];
-
-	if (bsp->bbox[1][BOXLEFT] < minx)
-		minx = bsp->bbox[1][BOXLEFT];
-	if (bsp->bbox[1][BOXRIGHT] > maxx)
-		maxx = bsp->bbox[1][BOXRIGHT];
-	if (bsp->bbox[1][BOXBOTTOM] < miny)
-		miny = bsp->bbox[1][BOXBOTTOM];
-	if (bsp->bbox[1][BOXTOP] > maxy)
-		maxy = bsp->bbox[1][BOXTOP];
-
-	// You might be wondering why these are being bitshift here
-	// it's because mapwidth and height would otherwise overflow for maps larger than half the size possible...
-	// map boundaries and sizes will ALWAYS be whole numbers thankfully
-	// later calculations take into consideration that these are actually not in terms of FRACUNIT though
-	minx >>= FRACBITS;
-	maxx >>= FRACBITS;
-	miny >>= FRACBITS;
-	maxy >>= FRACBITS;
-
-	mapwidth = maxx - minx;
-	mapheight = maxy - miny;
-
-	// These should always be small enough to be bitshift back right now
-	xoffset = (minx + mapwidth/2)<<FRACBITS;
-	yoffset = (miny + mapheight/2)<<FRACBITS;
-
-	xscale = FixedDiv(AutomapPic->width, mapwidth);
-	yscale = FixedDiv(AutomapPic->height, mapheight);
-	zoom = FixedMul(min(xscale, yscale), FRACUNIT-FRACUNIT/20);
-
-	amnumxpos = (FixedMul(mo->x, zoom) - FixedMul(xoffset, zoom));
-	amnumypos = -(FixedMul(mo->y, zoom) - FixedMul(yoffset, zoom));
-
-	if (encoremode)
-		amnumxpos = -amnumxpos;
-
-	amxpos = amnumxpos + ((x + AutomapPic->width/2 - (facemmapprefix[skin]->width/2))<<FRACBITS);
-	amypos = amnumypos + ((y + AutomapPic->height/2 - (facemmapprefix[skin]->height/2))<<FRACBITS);
-
-	// do we want this? it feels unnecessary. easier to just modify the amnumxpos?
-	/*if (encoremode)
-	{
-		flags |= V_FLIP;
-		amxpos = -amnumxpos + ((x + AutomapPic->width/2 + (facemmapprefix[skin]->width/2))<<FRACBITS);
-	}*/
-
-	if (!mo->color) // 'default' color
-		V_DrawSciencePatch(amxpos, amypos, flags, facemmapprefix[skin], FRACUNIT);
-	else
-	{
-		UINT8 *colormap;
-		if (mo->colorized)
-			colormap = R_GetTranslationColormap(TC_RAINBOW, mo->color, GTC_CACHE);
-		else
-			colormap = R_GetTranslationColormap(skin, mo->color, GTC_CACHE);
-		V_DrawFixedPatch(amxpos, amypos, FRACUNIT, flags, facemmapprefix[skin], colormap);
-		if (mo->player && K_IsPlayerWanted(mo->player))
-			V_DrawFixedPatch(amxpos - (4<<FRACBITS), amypos - (4<<FRACBITS), FRACUNIT, flags, kp_wantedreticle, NULL);
-	}
-}
-
-static void K_drawKartMinimap(void)
-{
-	INT32 lumpnum;
-	patch_t *AutomapPic;
-	INT32 i = 0;
-	INT32 x, y;
-	INT32 minimaptrans, splitflags = (splitscreen == 3 ? 0 : V_SNAPTORIGHT);	// flags should only be 0 when it's centered (4p split)
-	SINT8 localplayers[4];
-	SINT8 numlocalplayers = 0;
-
-	// Draw the HUD only when playing in a level.
-	// hu_stuff needs this, unlike st_stuff.
-	if (gamestate != GS_LEVEL)
-		return;
-
-	if (stplyr != &players[displayplayer])
-		return;
-
-	lumpnum = W_CheckNumForName(va("%sR", G_BuildMapName(gamemap)));
-
-	if (lumpnum != -1)
-		AutomapPic = W_CachePatchName(va("%sR", G_BuildMapName(gamemap)), PU_HUDGFX);
-	else
-		return; // no pic, just get outta here
-
-	x = MINI_X - (AutomapPic->width/2);
-	y = MINI_Y - (AutomapPic->height/2);
-
-	if (timeinmap > 105)
-	{
-		minimaptrans = cv_kartminimap.value;
-		if (timeinmap <= 113)
-			minimaptrans = ((((INT32)timeinmap) - 105)*minimaptrans)/(113-105);
-		if (!minimaptrans)
-			return;
-	}
-	else
-		return;
-
-	minimaptrans = ((10-minimaptrans)<<FF_TRANSSHIFT);
-	splitflags |= minimaptrans;
-
-	if (encoremode)
-		V_DrawScaledPatch(x+(AutomapPic->width), y, splitflags|V_FLIP, AutomapPic);
-	else
-		V_DrawScaledPatch(x, y, splitflags, AutomapPic);
-
-	if (!(splitscreen == 2))
-	{
-		splitflags &= ~minimaptrans;
-		splitflags |= V_HUDTRANSHALF;
-	}
-
-	// let offsets transfer to the heads, too!
-	if (encoremode)
-		x += SHORT(AutomapPic->leftoffset);
-	else
-		x -= SHORT(AutomapPic->leftoffset);
-	y -= SHORT(AutomapPic->topoffset);
-
-	// initialize
-	for (i = 0; i < 4; i++)
-		localplayers[i] = -1;
-
-	// Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen)
-	if (ghosts)
-	{
-		demoghost *g = ghosts;
-		while (g)
-		{
-			K_drawKartMinimapHead(g->mo, x, y, splitflags, AutomapPic);
-			g = g->next;
-		}
-
-		if (!stplyr->mo || stplyr->spectator) // do we need the latter..?
-			return;
-
-		localplayers[numlocalplayers] = stplyr-players;
-		numlocalplayers++;
-	}
-	else
-	{
-		for (i = MAXPLAYERS-1; i >= 0; i--)
-		{
-			if (!playeringame[i])
-				continue;
-			if (!players[i].mo || players[i].spectator)
-				continue;
-
-			if (i != displayplayer || splitscreen)
-			{
-				if (G_BattleGametype() && players[i].kartstuff[k_bumper] <= 0)
-					continue;
-
-				if (players[i].kartstuff[k_hyudorotimer] > 0)
-				{
-					if (!((players[i].kartstuff[k_hyudorotimer] < 1*TICRATE/2
-						|| players[i].kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2))
-						&& !(leveltime & 1)))
-						continue;
-				}
-			}
-
-			if (i == displayplayer || i == secondarydisplayplayer || i == thirddisplayplayer || i == fourthdisplayplayer)
-			{
-				// Draw display players on top of everything else
-				localplayers[numlocalplayers] = i;
-				numlocalplayers++;
-				continue;
-			}
-
-			K_drawKartMinimapHead(players[i].mo, x, y, splitflags, AutomapPic);
-		}
-	}
-
-	// draw our local players here, opaque.
-	splitflags &= ~V_HUDTRANSHALF;
-	splitflags |= V_HUDTRANS;
-
-	for (i = 0; i < numlocalplayers; i++)
-	{
-		if (i == -1)
-			continue; // this doesn't interest us
-		K_drawKartMinimapHead(players[localplayers[i]].mo, x, y, splitflags, AutomapPic);
-	}
-}
-
-static void K_drawKartStartCountdown(void)
-{
-	INT32 pnum = 0, splitflags = K_calcSplitFlags(0); // 3
-
-	if (leveltime >= starttime-(2*TICRATE)) // 2
-		pnum++;
-	if (leveltime >= starttime-TICRATE) // 1
-		pnum++;
-	if (leveltime >= starttime) // GO!
-		pnum++;
-	if ((leveltime % (2*5)) / 5) // blink
-		pnum += 4;
-	if (splitscreen) // splitscreen
-		pnum += 8;
-
-	V_DrawScaledPatch(STCD_X - (SHORT(kp_startcountdown[pnum]->width)/2), STCD_Y - (SHORT(kp_startcountdown[pnum]->height)/2), splitflags, kp_startcountdown[pnum]);
-}
-
-static void K_drawKartFinish(void)
-{
-	INT32 pnum = 0, splitflags = K_calcSplitFlags(0);
-
-	if (!stplyr->kartstuff[k_cardanimation] || stplyr->kartstuff[k_cardanimation] >= 2*TICRATE)
-		return;
-
-	if ((stplyr->kartstuff[k_cardanimation] % (2*5)) / 5) // blink
-		pnum = 1;
-
-	if (splitscreen > 1) // 3/4p, stationary FIN
-	{
-		pnum += 2;
-		V_DrawScaledPatch(STCD_X - (SHORT(kp_racefinish[pnum]->width)/2), STCD_Y - (SHORT(kp_racefinish[pnum]->height)/2), splitflags, kp_racefinish[pnum]);
-		return;
-	}
-
-	//else -- 1/2p, scrolling FINISH
-	{
-		INT32 x, xval;
-
-		if (splitscreen) // wide splitscreen
-			pnum += 4;
-
-		x = ((vid.width<<FRACBITS)/vid.dupx);
-		xval = (SHORT(kp_racefinish[pnum]->width)<<FRACBITS);
-		x = ((TICRATE - stplyr->kartstuff[k_cardanimation])*(xval > x ? xval : x))/TICRATE;
-
-		if (splitscreen && stplyr == &players[secondarydisplayplayer])
-			x = -x;
-
-		V_DrawFixedPatch(x + (STCD_X<<FRACBITS) - (xval>>1),
-			(STCD_Y<<FRACBITS) - (SHORT(kp_racefinish[pnum]->height)<<(FRACBITS-1)),
-			FRACUNIT,
-			splitflags, kp_racefinish[pnum], NULL);
-	}
-}
-
-static void K_drawBattleFullscreen(void)
-{
-	INT32 x = BASEVIDWIDTH/2;
-	INT32 y = -64+(stplyr->kartstuff[k_cardanimation]); // card animation goes from 0 to 164, 164 is the middle of the screen
-	INT32 splitflags = V_SNAPTOTOP; // I don't feel like properly supporting non-green resolutions, so you can have a misuse of SNAPTO instead
-	fixed_t scale = FRACUNIT;
-
-	if (splitscreen)
-	{
-		if ((splitscreen == 1 && stplyr == &players[secondarydisplayplayer])
-			|| (splitscreen > 1 && (stplyr == &players[thirddisplayplayer]
-			|| (stplyr == &players[fourthdisplayplayer] && splitscreen > 2))))
-		{
-			y = 232-(stplyr->kartstuff[k_cardanimation]/2);
-			splitflags = V_SNAPTOBOTTOM;
-		}
-		else
-			y = -32+(stplyr->kartstuff[k_cardanimation]/2);
-
-		if (splitscreen > 1)
-		{
-			scale /= 2;
-
-			if (stplyr == &players[secondarydisplayplayer]
-				|| (stplyr == &players[fourthdisplayplayer] && splitscreen > 2))
-				x = 3*BASEVIDWIDTH/4;
-			else
-				x = BASEVIDWIDTH/4;
-		}
-		else
-		{
-			if (stplyr->exiting)
-			{
-				if (stplyr == &players[secondarydisplayplayer])
-					x = BASEVIDWIDTH-96;
-				else
-					x = 96;
-			}
-			else
-				scale /= 2;
-		}
-	}
-
-	if (stplyr->exiting)
-	{
-		if (stplyr == &players[displayplayer])
-			V_DrawFadeScreen(0xFF00, 16);
-		if (stplyr->exiting < 6*TICRATE && !stplyr->spectator)
-		{
-			if (stplyr->kartstuff[k_position] == 1)
-				V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, kp_battlewin, NULL);
-			else
-				V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, (K_IsPlayerLosing(stplyr) ? kp_battlelose : kp_battlecool), NULL);
-		}
-		else
-			K_drawKartFinish();
-	}
-	else if (stplyr->kartstuff[k_bumper] <= 0 && stplyr->kartstuff[k_comebacktimer] && comeback && !stplyr->spectator)
-	{
-		UINT16 t = stplyr->kartstuff[k_comebacktimer]/(10*TICRATE);
-		INT32 txoff, adjust = (splitscreen > 1) ? 4 : 6; // normal string is 8, kart string is 12, half of that for ease
-		INT32 ty = (BASEVIDHEIGHT/2)+66;
-
-		txoff = adjust;
-
-		while (t)
-		{
-			txoff += adjust;
-			t /= 10;
-		}
-
-		if (splitscreen)
-		{
-			if (splitscreen > 1)
-				ty = (BASEVIDHEIGHT/4)+33;
-			if ((splitscreen == 1 && stplyr == &players[secondarydisplayplayer])
-				|| (stplyr == &players[thirddisplayplayer] && splitscreen > 1)
-				|| (stplyr == &players[fourthdisplayplayer] && splitscreen > 2))
-				ty += (BASEVIDHEIGHT/2);
-		}
-		else
-			V_DrawFadeScreen(0xFF00, 16);
-
-		if (!comebackshowninfo)
-			V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, kp_battleinfo, NULL);
-		else
-			V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, kp_battlewait, NULL);
-
-		if (splitscreen > 1)
-			V_DrawString(x-txoff, ty, 0, va("%d", stplyr->kartstuff[k_comebacktimer]/TICRATE));
-		else
-		{
-			V_DrawFixedPatch(x<<FRACBITS, ty<<FRACBITS, scale, 0, kp_timeoutsticker, NULL);
-			V_DrawKartString(x-txoff, ty, 0, va("%d", stplyr->kartstuff[k_comebacktimer]/TICRATE));
-		}
-	}
-
-	if (netgame && !stplyr->spectator && timeinmap > 113) // FREE PLAY?
-	{
-		UINT8 i;
-
-		// check to see if there's anyone else at all
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (i == displayplayer)
-				continue;
-			if (playeringame[i] && !stplyr->spectator)
-				return;
-		}
-
-#ifdef HAVE_BLUA
-		if (LUA_HudEnabled(hud_freeplay))
-#endif
-			K_drawKartFreePlay(leveltime);
-	}
-}
-
-static void K_drawKartFirstPerson(void)
-{
-	static INT32 pnum[4], turn[4], drift[4];
-	INT32 pn = 0, tn = 0, dr = 0;
-	INT32 target = 0, splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM);
-	INT32 x = BASEVIDWIDTH/2, y = BASEVIDHEIGHT;
-	fixed_t scale;
-	UINT8 *colmap = NULL;
-	ticcmd_t *cmd = &stplyr->cmd;
-
-	if (stplyr->spectator || !stplyr->mo || (stplyr->mo->flags2 & MF2_DONTDRAW))
-		return;
-
-	if (stplyr == &players[secondarydisplayplayer] && splitscreen)
-		{ pn = pnum[1]; tn = turn[1]; dr = drift[1]; }
-	else if (stplyr == &players[thirddisplayplayer] && splitscreen > 1)
-		{ pn = pnum[2]; tn = turn[2]; dr = drift[2]; }
-	else if (stplyr == &players[fourthdisplayplayer] && splitscreen > 2)
-		{ pn = pnum[3]; tn = turn[3]; dr = drift[3]; }
-	else
-		{ pn = pnum[0]; tn = turn[0]; dr = drift[0]; }
-
-	if (splitscreen)
-	{
-		y >>= 1;
-		if (splitscreen > 1)
-			x >>= 1;
-	}
-
-	{
-		if (stplyr->speed < FixedMul(stplyr->runspeed, stplyr->mo->scale) && (leveltime & 1) && !splitscreen)
-			y++;
-		// the following isn't EXPLICITLY right, it just gets the result we want, but i'm too lazy to look up the right way to do it
-		if (stplyr->mo->flags2 & MF2_SHADOW)
-			splitflags |= FF_TRANS80;
-		else if (stplyr->mo->frame & FF_TRANSMASK)
-			splitflags |= (stplyr->mo->frame & FF_TRANSMASK);
-	}
-
-	if (cmd->driftturn > 400) // strong left turn
-		target = 2;
-	else if (cmd->driftturn < -400) // strong right turn
-		target = -2;
-	else if (cmd->driftturn > 0) // weak left turn
-		target = 1;
-	else if (cmd->driftturn < 0) // weak right turn
-		target = -1;
-	else // forward
-		target = 0;
-
-	if (encoremode)
-		target = -target;
-
-	if (pn < target)
-		pn++;
-	else if (pn > target)
-		pn--;
-
-	if (pn < 0)
-		splitflags |= V_FLIP; // right turn
-
-	target = abs(pn);
-	if (target > 2)
-		target = 2;
-
-	x <<= FRACBITS;
-	y <<= FRACBITS;
-
-	if (tn != cmd->driftturn/50)
-		tn -= (tn - (cmd->driftturn/50))/8;
-
-	if (dr != stplyr->kartstuff[k_drift]*16)
-		dr -= (dr - (stplyr->kartstuff[k_drift]*16))/8;
-
-	if (splitscreen == 1)
-	{
-		scale = (2*FRACUNIT)/3;
-		y += FRACUNIT/(vid.dupx < vid.dupy ? vid.dupx : vid.dupy); // correct a one-pixel gap on the screen view (not the basevid view)
-	}
-	else if (splitscreen)
-		scale = FRACUNIT/2;
-	else
-		scale = FRACUNIT;
-
-	if (stplyr->mo)
-	{
-		INT32 dsone = K_GetKartDriftSparkValue(stplyr);
-		INT32 dstwo = dsone*2;
-		INT32 dsthree = dstwo*2;
-
-#ifndef DONTLIKETOASTERSFPTWEAKS
-		{
-			const angle_t ang = R_PointToAngle2(0, 0, stplyr->rmomx, stplyr->rmomy) - stplyr->frameangle;
-			// yes, the following is correct. no, you do not need to swap the x and y.
-			fixed_t xoffs = -P_ReturnThrustY(stplyr->mo, ang, (BASEVIDWIDTH<<(FRACBITS-2))/2);
-			fixed_t yoffs = -(P_ReturnThrustX(stplyr->mo, ang, 4*FRACUNIT) - 4*FRACUNIT);
-
-			if (splitscreen)
-				xoffs = FixedMul(xoffs, scale);
-
-			xoffs -= (tn)*scale;
-			xoffs -= (dr)*scale;
-
-			if (stplyr->frameangle == stplyr->mo->angle)
-			{
-				const fixed_t mag = FixedDiv(stplyr->speed, 10*stplyr->mo->scale);
-
-				if (mag < FRACUNIT)
-				{
-					xoffs = FixedMul(xoffs, mag);
-					if (!splitscreen)
-						yoffs = FixedMul(yoffs, mag);
-				}
-			}
-
-			if (stplyr->mo->momz > 0) // TO-DO: Draw more of the kart so we can remove this if!
-				yoffs += stplyr->mo->momz/3;
-
-			if (encoremode)
-				x -= xoffs;
-			else
-				x += xoffs;
-			if (!splitscreen)
-				y += yoffs;
-		}
-
-		// drift sparks!
-		if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsthree))
-			colmap = R_GetTranslationColormap(TC_RAINBOW, (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))), 0);
-		else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dstwo))
-			colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_KETCHUP, 0);
-		else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsone))
-			colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SAPPHIRE, 0);
-		else
-#endif
-		// invincibility/grow/shrink!
-		if (stplyr->mo->colorized && stplyr->mo->color)
-			colmap = R_GetTranslationColormap(TC_RAINBOW, stplyr->mo->color, 0);
-	}
-
-	V_DrawFixedPatch(x, y, scale, splitflags, kp_fpview[target], colmap);
-
-	if (stplyr == &players[secondarydisplayplayer] && splitscreen)
-		{ pnum[1] = pn; turn[1] = tn; drift[1] = dr; }
-	else if (stplyr == &players[thirddisplayplayer] && splitscreen > 1)
-		{ pnum[2] = pn; turn[2] = tn; drift[2] = dr; }
-	else if (stplyr == &players[fourthdisplayplayer] && splitscreen > 2)
-		{ pnum[3] = pn; turn[3] = tn; drift[3] = dr; }
-	else
-		{ pnum[0] = pn; turn[0] = tn; drift[0] = dr; }
-}
-
-// doesn't need to ever support 4p
-static void K_drawInput(void)
-{
-	static INT32 pn = 0;
-	INT32 target = 0, splitflags = (V_SNAPTOBOTTOM|V_SNAPTORIGHT);
-	INT32 x = BASEVIDWIDTH - 32, y = BASEVIDHEIGHT-24, offs, col;
-	const INT32 accent1 = splitflags|colortranslations[stplyr->skincolor][5];
-	const INT32 accent2 = splitflags|colortranslations[stplyr->skincolor][9];
-	ticcmd_t *cmd = &stplyr->cmd;
-
-	if (timeinmap <= 105)
-		return;
-
-	if (timeinmap < 113)
-	{
-		INT32 count = ((INT32)(timeinmap) - 105);
-		offs = (titledemo ? 128 : 64);
-		while (count-- > 0)
-			offs >>= 1;
-		x += offs;
-	}
-
-	if (titledemo)
-	{
-		V_DrawTinyScaledPatch(x-54, 128, splitflags, W_CachePatchName("TTKBANNR", PU_CACHE));
-		V_DrawTinyScaledPatch(x-54, 128+25, splitflags, W_CachePatchName("TTKART", PU_CACHE));
-		return;
-	}
-
-#define BUTTW 8
-#define BUTTH 11
-
-#define drawbutt(xoffs, butt, symb)\
-	if (stplyr->cmd.buttons & butt)\
-	{\
-		offs = 2;\
-		col = accent1;\
-	}\
-	else\
-	{\
-		offs = 0;\
-		col = accent2;\
-		V_DrawFill(x+(xoffs), y+BUTTH, BUTTW-1, 2, splitflags|31);\
-	}\
-	V_DrawFill(x+(xoffs), y+offs, BUTTW-1, BUTTH, col);\
-	V_DrawFixedPatch((x+1+(xoffs))<<FRACBITS, (y+offs+1)<<FRACBITS, FRACUNIT, splitflags, tny_font[symb-HU_FONTSTART], NULL)
-
-	drawbutt(-2*BUTTW, BT_ACCELERATE, 'A');
-	drawbutt(  -BUTTW, BT_BRAKE,      'B');
-	drawbutt(       0, BT_DRIFT,      'D');
-	drawbutt(   BUTTW, BT_ATTACK,     'I');
-
-#undef drawbutt
-
-#undef BUTTW
-#undef BUTTH
-
-	y -= 1;
-
-	if (!cmd->driftturn) // no turn
-		target = 0;
-	else // turning of multiple strengths!
-	{
-		target = ((abs(cmd->driftturn) - 1)/125)+1;
-		if (target > 4)
-			target = 4;
-		if (cmd->driftturn < 0)
-			target = -target;
-	}
-
-	if (pn != target)
-	{
-		if (abs(pn - target) == 1)
-			pn = target;
-		else if (pn < target)
-			pn += 2;
-		else //if (pn > target)
-			pn -= 2;
-	}
-
-	if (pn < 0)
-	{
-		splitflags |= V_FLIP; // right turn
-		x--;
-	}
-
-	target = abs(pn);
-	if (target > 4)
-		target = 4;
-
-	if (!stplyr->skincolor)
-		V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT, splitflags, kp_inputwheel[target], NULL);
-	else
-	{
-		UINT8 *colormap;
-		colormap = R_GetTranslationColormap(0, stplyr->skincolor, 0);
-		V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT, splitflags, kp_inputwheel[target], colormap);
-	}
-}
-
-static void K_drawChallengerScreen(void)
-{
-	// This is an insanely complicated animation.
-	static UINT8 anim[52] = {
-		0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13, // frame 1-14, 2 tics: HERE COMES A NEW slides in
-		14,14,14,14,14,14, // frame 15, 6 tics: pause on the W
-		15,16,17,18, // frame 16-19, 1 tic: CHALLENGER approaches screen
-		19,20,19,20,19,20,19,20,19,20, // frame 20-21, 1 tic, 5 alternating: all text vibrates from impact
-		21,22,23,24 // frame 22-25, 1 tic: CHALLENGER turns gold
-	};
-	const UINT8 offset = min(52-1, (3*TICRATE)-mapreset);
-
-	V_DrawFadeScreen(0xFF00, 16); // Fade out
-	V_DrawScaledPatch(0, 0, 0, kp_challenger[anim[offset]]);
-}
-
-static void K_drawLapStartAnim(void)
-{
-	// This is an EVEN MORE insanely complicated animation.
-	const UINT8 progress = 80-stplyr->kartstuff[k_lapanimation];
-	UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, 0);
-
-	V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->kartstuff[k_lapanimation]-76)))*FRACUNIT,
-		(48 - (32*max(0, progress-76)))*FRACUNIT,
-		FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
-		(modeattacking ? kp_lapanim_emblem[1] : kp_lapanim_emblem[0]), colormap);
-
-	if (stplyr->kartstuff[k_laphand] >= 1 && stplyr->kartstuff[k_laphand] <= 3)
-	{
-		V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->kartstuff[k_lapanimation]-76)))*FRACUNIT,
-			(48 - (32*max(0, progress-76))
-				+ 4 - abs((signed)((leveltime % 8) - 4)))*FRACUNIT,
-			FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
-			kp_lapanim_hand[stplyr->kartstuff[k_laphand]-1], NULL);
-	}
-
-	if (stplyr->laps == (UINT8)(cv_numlaps.value - 1))
-	{
-		V_DrawFixedPatch((62 - (32*max(0, progress-76)))*FRACUNIT, // 27
-			30*FRACUNIT, // 24
-			FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
-			kp_lapanim_final[min(progress/2, 10)], NULL);
-
-		if (progress/2-12 >= 0)
-		{
-			V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194
-				30*FRACUNIT, // 24
-				FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
-				kp_lapanim_lap[min(progress/2-12, 6)], NULL);
-		}
-	}
-	else
-	{
-		V_DrawFixedPatch((82 - (32*max(0, progress-76)))*FRACUNIT, // 61
-			30*FRACUNIT, // 24
-			FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
-			kp_lapanim_lap[min(progress/2, 6)], NULL);
-
-		if (progress/2-8 >= 0)
-		{
-			V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194
-				30*FRACUNIT, // 24
-				FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
-				kp_lapanim_number[(((UINT32)stplyr->laps+1) / 10)][min(progress/2-8, 2)], NULL);
-
-			if (progress/2-10 >= 0)
-			{
-				V_DrawFixedPatch((208 + (32*max(0, progress-76)))*FRACUNIT, // 221
-					30*FRACUNIT, // 24
-					FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
-					kp_lapanim_number[(((UINT32)stplyr->laps+1) % 10)][min(progress/2-10, 2)], NULL);
-			}
-		}
-	}
-}
-
-void K_drawKartFreePlay(UINT32 flashtime)
-{
-	// no splitscreen support because it's not FREE PLAY if you have more than one player in-game
-
-	if ((flashtime % TICRATE) < TICRATE/2)
-		return;
-
-	V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - (12*9), // mirror the laps thingy
-		LAPS_Y+3, V_SNAPTOBOTTOM|V_SNAPTORIGHT, "FREE PLAY");
-}
-
-static void K_drawDistributionDebugger(void)
-{
-	patch_t *items[NUMKARTRESULTS] = {
-		kp_sadface[1],
-		kp_sneaker[1],
-		kp_rocketsneaker[1],
-		kp_invincibility[7],
-		kp_banana[1],
-		kp_eggman[1],
-		kp_orbinaut[4],
-		kp_jawz[1],
-		kp_mine[1],
-		kp_ballhog[1],
-		kp_selfpropelledbomb[1],
-		kp_grow[1],
-		kp_shrink[1],
-		kp_thundershield[1],
-		kp_hyudoro[1],
-		kp_pogospring[1],
-		kp_kitchensink[1],
-
-		kp_sneaker[1],
-		kp_banana[1],
-		kp_banana[1],
-		kp_orbinaut[4],
-		kp_orbinaut[4],
-		kp_jawz[1]
-	};
-	INT32 useodds = 0;
-	INT32 pingame = 0, bestbumper = 0;
-	INT32 i;
-	INT32 x = -9, y = -9;
-	boolean dontforcespb = false;
-
-	if (stplyr != &players[displayplayer]) // only for p1
-		return;
-
-	// The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i] || players[i].spectator)
-			continue;
-		pingame++;
-		if (players[i].exiting)
-			dontforcespb = true;
-		if (players[i].kartstuff[k_bumper] > bestbumper)
-			bestbumper = players[i].kartstuff[k_bumper];
-	}
-
-	useodds = K_FindUseodds(stplyr, 0, pingame, bestbumper, (spbplace != -1 && stplyr->kartstuff[k_position] == spbplace+1), dontforcespb);
-
-	for (i = 1; i < NUMKARTRESULTS; i++)
-	{
-		const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0);
-		if (itemodds <= 0)
-			continue;
-
-		V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOTOP, items[i]);
-		V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SNAPTOTOP, va("%d", itemodds));
-
-		// Display amount for multi-items
-		if (i >= NUMKARTITEMS)
-		{
-			INT32 amount;
-			switch (i)
-			{
-				case KRITEM_TENFOLDBANANA:
-					amount = 10;
-					break;
-				case KRITEM_QUADORBINAUT:
-					amount = 4;
-					break;
-				case KRITEM_DUALJAWZ:
-					amount = 2;
-					break;
-				default:
-					amount = 3;
-					break;
-			}
-			V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", amount));
-		}
-
-		x += 32;
-		if (x >= 297)
-		{
-			x = -9;
-			y += 32;
-		}
-	}
-
-	V_DrawString(0, 0, V_HUDTRANS|V_SNAPTOTOP, va("USEODDS %d", useodds));
-}
-
-static void K_drawCheckpointDebugger(void)
-{
-	if (stplyr != &players[displayplayer]) // only for p1
-		return;
-
-	if (stplyr->starpostnum >= (numstarposts - (numstarposts/2)))
-		V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Can finish)", stplyr->starpostnum, numstarposts));
-	else
-		V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Skip: %d)", stplyr->starpostnum, numstarposts, ((numstarposts/2) + stplyr->starpostnum)));
-	V_DrawString(8, 192, 0, va("Waypoint dist: Prev %d, Next %d", stplyr->kartstuff[k_prevcheck], stplyr->kartstuff[k_nextcheck]));
-}
-
-void K_drawKartHUD(void)
-{
-	boolean isfreeplay = false;
-	boolean battlefullscreen = false;
-
-	// Define the X and Y for each drawn object
-	// This is handled by console/menu values
-	K_initKartHUD();
-
-	// Draw that fun first person HUD! Drawn ASAP so it looks more "real".
-	if ((stplyr == &players[displayplayer] && !camera.chase)
-		|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase)
-		|| ((splitscreen > 1 && stplyr == &players[thirddisplayplayer]) && !camera3.chase)
-		|| ((splitscreen > 2 && stplyr == &players[fourthdisplayplayer]) && !camera4.chase))
-		K_drawKartFirstPerson();
-
-	// Draw full screen stuff that turns off the rest of the HUD
-	if (mapreset && stplyr == &players[displayplayer])
-	{
-		K_drawChallengerScreen();
-		return;
-	}
-
-	battlefullscreen = ((G_BattleGametype())
-		&& (stplyr->exiting
-		|| (stplyr->kartstuff[k_bumper] <= 0
-		&& stplyr->kartstuff[k_comebacktimer]
-		&& comeback
-		&& stplyr->playerstate == PST_LIVE)));
-
-	if (!battlefullscreen || splitscreen)
-	{
-		// Draw the CHECK indicator before the other items, so it's overlapped by everything else
-		if (cv_kartcheck.value && !splitscreen && !players[displayplayer].exiting)
-			K_drawKartPlayerCheck();
-
-		// Draw WANTED status
-		if (G_BattleGametype())
-		{
-#ifdef HAVE_BLUA
-			if (LUA_HudEnabled(hud_wanted))
-#endif
-				K_drawKartWanted();
-		}
-
-		if (cv_kartminimap.value && !titledemo)
-		{
-#ifdef HAVE_BLUA
-			if (LUA_HudEnabled(hud_minimap))
-#endif
-				K_drawKartMinimap();
-		}
-	}
-
-	if (battlefullscreen)
-	{
-		K_drawBattleFullscreen();
-		return;
-	}
-
-	// Draw the item window
-#ifdef HAVE_BLUA
-	if (LUA_HudEnabled(hud_item))
-#endif
-		K_drawKartItem();
-
-	// If not splitscreen, draw...
-	if (!splitscreen && !titledemo)
-	{
-		// Draw the timestamp
-#ifdef HAVE_BLUA
-		if (LUA_HudEnabled(hud_time))
-#endif
-			K_drawKartTimestamp(stplyr->realtime, TIME_X, TIME_Y, gamemap, 0);
-
-		if (!modeattacking)
-		{
-			// The top-four faces on the left
-			/*#ifdef HAVE_BLUA
-			if (LUA_HudEnabled(hud_minirankings))
-			#endif*/
-				isfreeplay = K_drawKartPositionFaces();
-		}
-	}
-
-	if (!stplyr->spectator) // Bottom of the screen elements, don't need in spectate mode
-	{
-		if (G_RaceGametype()) // Race-only elements
-		{
-			if (!titledemo)
-			{
-				// Draw the lap counter
-#ifdef HAVE_BLUA
-				if (LUA_HudEnabled(hud_gametypeinfo))
-#endif
-					K_drawKartLaps();
-
-				if (!splitscreen)
-				{
-					// Draw the speedometer
-					// TODO: Make a better speedometer.
-#ifdef HAVE_BLUA
-				if (LUA_HudEnabled(hud_speedometer))
-#endif
-					K_drawKartSpeedometer();
-				}
-			}
-
-			if (isfreeplay)
-				;
-			else if (!modeattacking)
-			{
-				// Draw the numerical position
-#ifdef HAVE_BLUA
-				if (LUA_HudEnabled(hud_position))
-#endif
-					K_DrawKartPositionNum(stplyr->kartstuff[k_position]);
-			}
-			else //if (!(demoplayback && hu_showscores))
-			{
-				// Draw the input UI
-#ifdef HAVE_BLUA
-				if (LUA_HudEnabled(hud_position))
-#endif
-					K_drawInput();
-			}
-		}
-		else if (G_BattleGametype()) // Battle-only
-		{
-			// Draw the hits left!
-#ifdef HAVE_BLUA
-			if (LUA_HudEnabled(hud_gametypeinfo))
-#endif
-				K_drawKartBumpersOrKarma();
-		}
-	}
-
-	// Draw the countdowns after everything else.
-	if (leveltime >= starttime-(3*TICRATE)
-		&& leveltime < starttime+TICRATE)
-		K_drawKartStartCountdown();
-	else if (countdown && (!splitscreen || !stplyr->exiting))
-	{
-		char *countstr = va("%d", countdown/TICRATE);
-
-		if (splitscreen > 1)
-			V_DrawCenteredString(BASEVIDWIDTH/4, LAPS_Y+1, K_calcSplitFlags(0), countstr);
-		else
-		{
-			INT32 karlen = strlen(countstr)*6; // half of 12
-			V_DrawKartString((BASEVIDWIDTH/2)-karlen, LAPS_Y+3, K_calcSplitFlags(0), countstr);
-		}
-	}
-
-	// Race overlays
-	if (G_RaceGametype())
-	{
-		if (stplyr->exiting)
-			K_drawKartFinish();
-		else if (stplyr->kartstuff[k_lapanimation] && !splitscreen)
-			K_drawLapStartAnim();
-	}
-
-	if (modeattacking) // everything after here is MP and debug only
-		return;
-
-	if (G_BattleGametype() && !splitscreen && (stplyr->kartstuff[k_yougotem] % 2)) // * YOU GOT EM *
-		V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem);
-
-	// Draw FREE PLAY.
-	if (isfreeplay && !stplyr->spectator && timeinmap > 113)
-	{
-#ifdef HAVE_BLUA
-		if (LUA_HudEnabled(hud_freeplay))
-#endif
-			K_drawKartFreePlay(leveltime);
-	}
-
-	if (cv_kartdebugdistribution.value)
-		K_drawDistributionDebugger();
-
-	if (cv_kartdebugcheckpoint.value)
-		K_drawCheckpointDebugger();
-
-	if (cv_kartdebugnodes.value)
-	{
-		UINT8 p;
-		for (p = 0; p < MAXPLAYERS; p++)
-			V_DrawString(8, 64+(8*p), V_YELLOWMAP, va("%d - %d (%dl)", p, playernode[p], players[p].cmd.latency));
-	}
-}
-
-//}
+// SONIC ROBO BLAST 2 KART ~ ZarroTsu
+//-----------------------------------------------------------------------------
+/// \file  k_kart.c
+/// \brief SRB2kart general.
+///        All of the SRB2kart-unique stuff.
+
+#include "doomdef.h"
+#include "hu_stuff.h"
+#include "g_game.h"
+#include "m_random.h"
+#include "p_local.h"
+#include "p_slopes.h"
+#include "r_draw.h"
+#include "r_local.h"
+#include "s_sound.h"
+#include "st_stuff.h"
+#include "v_video.h"
+#include "z_zone.h"
+#include "m_misc.h"
+#include "m_cond.h"
+#include "k_kart.h"
+#include "f_finale.h"
+#include "lua_hud.h"	// For Lua hud checks
+#include "lua_hook.h"	// For MobjDamage and ShouldDamage
+
+// SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H:
+// gamespeed is cc (0 for easy, 1 for normal, 2 for hard)
+// franticitems is Frantic Mode items, bool
+// encoremode is Encore Mode (duh), bool
+// comeback is Battle Mode's karma comeback, also bool
+// battlewanted is an array of the WANTED player nums, -1 for no player in that slot
+// indirectitemcooldown is timer before anyone's allowed another Shrink/SPB
+// mapreset is set when enough players fill an empty server
+// nospectategrief is the players in-game needed to eliminate the person in last
+
+
+//{ SRB2kart Color Code
+
+#define SKIN_RAMP_LENGTH 16
+#define DEFAULT_STARTTRANSCOLOR 160
+#define NUM_PALETTE_ENTRIES 256
+
+// These should be within 14 characters to fit on the character select screen
+const char *KartColor_Names[MAXSKINCOLORS] =
+{
+	"None",           // 00 // SKINCOLOR_NONE
+	"White",          // 01 // SKINCOLOR_WHITE
+	"Silver",         // 02 // SKINCOLOR_SILVER
+	"Grey",           // 03 // SKINCOLOR_GREY
+	"Nickel",         // 04 // SKINCOLOR_NICKEL
+	"Black",          // 05 // SKINCOLOR_BLACK
+	"Sepia",          // 06 // SKINCOLOR_SEPIA
+	"Beige",          // 07 // SKINCOLOR_BEIGE
+	"Brown",          // 08 // SKINCOLOR_BROWN
+	"Leather",        // 09 // SKINCOLOR_LEATHER
+	"Salmon",         // 10 // SKINCOLOR_SALMON
+	"Pink",           // 11 // SKINCOLOR_PINK
+	"Rose",           // 12 // SKINCOLOR_ROSE
+	"Ruby",           // 13 // SKINCOLOR_RUBY
+	"Raspberry",      // 14 // SKINCOLOR_RASPBERRY
+	"Red",            // 15 // SKINCOLOR_RED
+	"Crimson",        // 16 // SKINCOLOR_CRIMSON
+	"Ketchup",        // 17 // SKINCOLOR_KETCHUP
+	"Dawn",           // 18 // SKINCOLOR_DAWN
+	"Creamsicle",     // 19 // SKINCOLOR_CREAMSICLE
+	"Orange",         // 20 // SKINCOLOR_ORANGE
+	"Pumpkin",        // 21 // SKINCOLOR_PUMPKIN
+	"Rosewood",       // 22 // SKINCOLOR_ROSEWOOD
+	"Burgundy",       // 23 // SKINCOLOR_BURGUNDY
+	"Tangerine",      // 24 // SKINCOLOR_TANGERINE
+	"Peach",          // 25 // SKINCOLOR_PEACH
+	"Caramel",        // 26 // SKINCOLOR_CARAMEL
+	"Gold",           // 27 // SKINCOLOR_GOLD
+	"Bronze",         // 28 // SKINCOLOR_BRONZE
+	"Yellow",         // 29 // SKINCOLOR_YELLOW
+	"Mustard",        // 30 // SKINCOLOR_MUSTARD
+	"Olive",          // 31 // SKINCOLOR_OLIVE
+	"Vomit",          // 32 // SKINCOLOR_VOMIT
+	"Garden",         // 33 // SKINCOLOR_GARDEN
+	"Lime",           // 34 // SKINCOLOR_LIME
+	"Tea",            // 35 // SKINCOLOR_TEA
+	"Pistachio",      // 36 // SKINCOLOR_PISTACHIO
+	"Robo-Hood",      // 37 // SKINCOLOR_ROBOHOOD
+	"Moss",           // 38 // SKINCOLOR_MOSS
+	"Mint",           // 39 // SKINCOLOR_MINT
+	"Green",          // 40 // SKINCOLOR_GREEN
+	"Pinetree",       // 41 // SKINCOLOR_PINETREE
+	"Emerald",        // 42 // SKINCOLOR_EMERALD
+	"Swamp",          // 43 // SKINCOLOR_SWAMP
+	"Dream",          // 44 // SKINCOLOR_DREAM
+	"Aqua",           // 45 // SKINCOLOR_AQUA
+	"Teal",           // 46 // SKINCOLOR_TEAL
+	"Cyan",           // 47 // SKINCOLOR_CYAN
+	"Jawz",           // 48 // SKINCOLOR_JAWZ
+	"Cerulean",       // 49 // SKINCOLOR_CERULEAN
+	"Navy",           // 50 // SKINCOLOR_NAVY
+	"Slate",          // 51 // SKINCOLOR_SLATE
+	"Steel",          // 52 // SKINCOLOR_STEEL
+	"Jet",            // 53 // SKINCOLOR_JET
+	"Sapphire",       // 54 // SKINCOLOR_SAPPHIRE
+	"Periwinkle",     // 55 // SKINCOLOR_PERIWINKLE
+	"Blue",           // 56 // SKINCOLOR_BLUE
+	"Blueberry",      // 57 // SKINCOLOR_BLUEBERRY
+	"Dusk",           // 58 // SKINCOLOR_DUSK
+	"Purple",         // 59 // SKINCOLOR_PURPLE
+	"Lavender",       // 60 // SKINCOLOR_LAVENDER
+	"Byzantium",      // 61 // SKINCOLOR_BYZANTIUM
+	"Pomegranate",    // 62 // SKINCOLOR_POMEGRANATE
+	"Lilac"           // 63 // SKINCOLOR_LILAC
+};
+
+// Color_Opposite replacement; frame setting has not been changed from 8 for most, should be done later
+const UINT8 KartColor_Opposite[MAXSKINCOLORS*2] =
+{
+	SKINCOLOR_NONE,8,         // 00 // SKINCOLOR_NONE
+	SKINCOLOR_BLACK,8,        // 01 // SKINCOLOR_WHITE
+	SKINCOLOR_NICKEL,8,       // 02 // SKINCOLOR_SILVER
+	SKINCOLOR_GREY,8,         // 03 // SKINCOLOR_GREY
+	SKINCOLOR_SILVER,8,       // 04 // SKINCOLOR_NICKEL
+	SKINCOLOR_WHITE,8,        // 05 // SKINCOLOR_BLACK
+	SKINCOLOR_LEATHER,6,      // 06 // SKINCOLOR_SEPIA
+	SKINCOLOR_BROWN,2,        // 07 // SKINCOLOR_BEIGE
+	SKINCOLOR_BEIGE,8,        // 08 // SKINCOLOR_BROWN
+	SKINCOLOR_SEPIA,8,        // 09 // SKINCOLOR_LEATHER
+	SKINCOLOR_TEA,8,          // 10 // SKINCOLOR_SALMON
+	SKINCOLOR_PISTACHIO,8,    // 11 // SKINCOLOR_PINK
+	SKINCOLOR_MOSS,8,         // 12 // SKINCOLOR_ROSE
+	SKINCOLOR_SAPPHIRE,8,     // 13 // SKINCOLOR_RUBY
+	SKINCOLOR_MINT,8,         // 14 // SKINCOLOR_RASPBERRY
+	SKINCOLOR_GREEN,6,        // 15 // SKINCOLOR_RED
+	SKINCOLOR_PINETREE,6,     // 16 // SKINCOLOR_CRIMSON
+	SKINCOLOR_MUSTARD,10,     // 17 // SKINCOLOR_KETCHUP
+	SKINCOLOR_DUSK,8,         // 18 // SKINCOLOR_DAWN
+	SKINCOLOR_PERIWINKLE,8,   // 19 // SKINCOLOR_CREAMSICLE
+	SKINCOLOR_BLUE,8,         // 20 // SKINCOLOR_ORANGE
+	SKINCOLOR_BLUEBERRY,8,    // 21 // SKINCOLOR_PUMPKIN
+	SKINCOLOR_NAVY,6,         // 22 // SKINCOLOR_ROSEWOOD
+	SKINCOLOR_JET,8,          // 23 // SKINCOLOR_BURGUNDY
+	SKINCOLOR_LIME,8,         // 24 // SKINCOLOR_TANGERINE
+	SKINCOLOR_CYAN,8,         // 25 // SKINCOLOR_PEACH
+	SKINCOLOR_CERULEAN,8,     // 26 // SKINCOLOR_CARAMEL
+	SKINCOLOR_SLATE,8,        // 27 // SKINCOLOR_GOLD
+	SKINCOLOR_STEEL,8,        // 28 // SKINCOLOR_BRONZE
+	SKINCOLOR_AQUA,8,         // 29 // SKINCOLOR_YELLOW
+	SKINCOLOR_KETCHUP,8,      // 30 // SKINCOLOR_MUSTARD
+	SKINCOLOR_TEAL,8,         // 31 // SKINCOLOR_OLIVE
+	SKINCOLOR_ROBOHOOD,8,     // 32 // SKINCOLOR_VOMIT
+	SKINCOLOR_LAVENDER,6,     // 33 // SKINCOLOR_GARDEN
+	SKINCOLOR_TANGERINE,8,    // 34 // SKINCOLOR_LIME
+	SKINCOLOR_SALMON,8,       // 35 // SKINCOLOR_TEA
+	SKINCOLOR_PINK,6,         // 36 // SKINCOLOR_PISTACHIO
+	SKINCOLOR_VOMIT,8,        // 37 // SKINCOLOR_ROBOHOOD
+	SKINCOLOR_ROSE,8,         // 38 // SKINCOLOR_MOSS
+	SKINCOLOR_RASPBERRY,8,    // 39 // SKINCOLOR_MINT
+	SKINCOLOR_RED,8,          // 40 // SKINCOLOR_GREEN
+	SKINCOLOR_CRIMSON,8,      // 41 // SKINCOLOR_PINETREE
+	SKINCOLOR_PURPLE,8,       // 42 // SKINCOLOR_EMERALD
+	SKINCOLOR_BYZANTIUM,8,    // 43 // SKINCOLOR_SWAMP
+	SKINCOLOR_POMEGRANATE,8,  // 44 // SKINCOLOR_DREAM
+	SKINCOLOR_YELLOW,8,       // 45 // SKINCOLOR_AQUA
+	SKINCOLOR_OLIVE,8,        // 46 // SKINCOLOR_TEAL
+	SKINCOLOR_PEACH,8,        // 47 // SKINCOLOR_CYAN
+	SKINCOLOR_LILAC,10,       // 48 // SKINCOLOR_JAWZ
+	SKINCOLOR_CARAMEL,8,      // 49 // SKINCOLOR_CERULEAN
+	SKINCOLOR_ROSEWOOD,8,     // 50 // SKINCOLOR_NAVY
+	SKINCOLOR_GOLD,10,        // 51 // SKINCOLOR_SLATE
+	SKINCOLOR_BRONZE,10,      // 52 // SKINCOLOR_STEEL
+	SKINCOLOR_BURGUNDY,8,     // 53 // SKINCOLOR_JET
+	SKINCOLOR_RUBY,6,         // 54 // SKINCOLOR_SAPPHIRE
+	SKINCOLOR_CREAMSICLE,8,   // 55 // SKINCOLOR_PERIWINKLE
+	SKINCOLOR_ORANGE,8,       // 56 // SKINCOLOR_BLUE
+	SKINCOLOR_PUMPKIN,8,      // 57 // SKINCOLOR_BLUEBERRY
+	SKINCOLOR_DAWN,6,         // 58 // SKINCOLOR_DUSK
+	SKINCOLOR_EMERALD,8,      // 59 // SKINCOLOR_PURPLE
+	SKINCOLOR_GARDEN,6,       // 60 // SKINCOLOR_LAVENDER
+	SKINCOLOR_SWAMP,8,        // 61 // SKINCOLOR_BYZANTIUM
+	SKINCOLOR_DREAM,8,        // 62 // SKINCOLOR_POMEGRANATE
+	SKINCOLOR_JAWZ,6          // 63 // SKINCOLOR_LILAC
+};
+
+UINT8 colortranslations[MAXSKINCOLORS][16] = {
+	{  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0}, // SKINCOLOR_NONE
+	{120, 120, 120, 120,   0,   1,   3,   4,   6,   7,  10,  14,  18,  22,  25,  28}, // SKINCOLOR_WHITE
+	{  0,   1,   2,   4,   5,   7,   8,  10,  13,  15,  18,  20,  23,  25,  28,  30}, // SKINCOLOR_SILVER
+	{  1,   3,   5,   7,   9,  11,  13,  15,  17,  19,  21,  23,  25,  27,  29,  31}, // SKINCOLOR_GREY
+	{ 12,  14,  16,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31}, // SKINCOLOR_NICKEL
+	{ 16,  17,  19,  21,  22,  24,  26,  27,  27,  28,  28,  29,  29,  30,  30,  31}, // SKINCOLOR_BLACK
+	{  0,   1,   3,   5,   7,   9,  34,  36,  38,  40,  42,  44,  60,  61,  62,  63}, // SKINCOLOR_SEPIA
+	{ 32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47}, // SKINCOLOR_BEIGE
+	{ 48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63}, // SKINCOLOR_BROWN
+	{ 51,  52,  53,  55,  56,  57,  58,  60,  61,  63,  28,  28,  29,  29,  30,  31}, // SKINCOLOR_LEATHER
+	{120, 120, 120, 121, 121, 122, 122, 123, 124, 125, 126, 128, 129, 131, 133, 135}, // SKINCOLOR_SALMON
+	{120, 121, 121, 122, 144, 145, 146, 147, 148, 149, 150, 151, 134, 136, 138, 140}, // SKINCOLOR_PINK
+	{144, 145, 146, 147, 148, 149, 150, 151, 134, 135, 136, 137, 138, 139, 140, 141}, // SKINCOLOR_ROSE
+	{121, 122, 145, 146, 147, 149, 131, 132, 133, 134, 135, 197, 197, 198, 199, 255}, // SKINCOLOR_RUBY
+	{120, 121, 122, 123, 124, 125, 126, 127, 128, 130, 131, 133, 134, 136, 137, 139}, // SKINCOLOR_RASPBERRY
+	{125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140}, // SKINCOLOR_RED
+	{130, 131, 132, 133, 134, 136, 137, 138, 139, 139, 140, 140, 141, 141, 142, 143}, // SKINCOLOR_CRIMSON
+	{104, 113, 113,  85,  86,  88, 128, 129, 131, 133, 134, 136, 138, 139, 141, 143}, // SKINCOLOR_KETCHUP
+	{120, 121, 122, 123, 124, 147, 147, 148,  90,  91,  92,  93,  94,  95, 152, 154}, // SKINCOLOR_DAWN
+	{120, 120,  80,  80,  81,  82,  83,  83,  84,  85,  86,  88,  89,  91,  93,  95}, // SKINCOLOR_CREAMSICLE
+	{ 80,  81,  82,  83,  84,  85,  86,  88,  89,  91,  94,  95, 154, 156, 158, 159}, // SKINCOLOR_ORANGE
+	{ 84,  85,  86,  87,  88,  90,  92,  93,  94,  95, 152, 153, 154, 156, 157, 159}, // SKINCOLOR_PUMPKIN
+	{ 90,  91,  92,  93,  94, 152, 153, 154, 155, 156, 157, 158, 159, 139, 141, 143}, // SKINCOLOR_ROSEWOOD
+	{ 94,  95, 152, 153, 154, 156, 157, 159, 141, 141, 141, 142, 142, 143, 143,  31}, // SKINCOLOR_BURGUNDY
+	{ 98,  98, 112, 112, 113, 113,  84,  85,  87,  89,  91,  93,  95, 153, 156, 159}, // SKINCOLOR_TANGERINE
+	{ 64,  65,  67,  68,  70,  71,  73,  74,  76,  77,  79,  48,  50,  53,  56,  59}, // SKINCOLOR_PEACH
+	{ 64,  66,  68,  70,  72,  74,  76,  78,  48,  50,  52,  54,  56,  58,  60,  62}, // SKINCOLOR_CARAMEL
+	{112, 112, 112, 113, 113, 114, 114, 115, 115, 116, 116, 117, 117, 118, 118, 119}, // SKINCOLOR_GOLD
+	{112, 113, 114, 115, 116, 117, 118, 119, 156, 157, 158, 159, 141, 141, 142, 143}, // SKINCOLOR_BRONZE
+	{ 96,  97,  98, 100, 101, 102, 104, 113, 114, 115, 116, 117, 118, 119, 156, 159}, // SKINCOLOR_YELLOW
+	{ 96,  98,  99, 112, 113, 114, 114, 106, 106, 107, 107, 108, 108, 109, 110, 111}, // SKINCOLOR_MUSTARD
+	{105, 105, 105, 106, 106, 107, 107, 108, 108, 109, 109, 110, 110, 111, 111,  31}, // SKINCOLOR_OLIVE
+	{121, 144, 145,  72,  73,  84, 114, 115, 107, 108, 109, 183, 223, 207,  30, 246}, // SKINCOLOR_VOMIT
+	{ 98,  99, 112, 101, 113, 114, 106, 179, 180, 180, 181, 182, 183, 173, 174, 175}, // SKINCOLOR_GARDEN
+	{ 96,  97,  99, 100, 102, 104, 160, 162, 164, 166, 168, 171, 223, 223, 207,  31}, // SKINCOLOR_LIME
+	{120, 120, 176, 176, 176, 177, 177, 178, 178, 179, 179, 180, 180, 181, 182, 183}, // SKINCOLOR_TEA
+	{120, 120, 176, 176, 177, 177, 178, 179, 165, 166, 167, 168, 169, 170, 171, 172}, // SKINCOLOR_PISTACHIO
+	{176, 176, 177, 178, 165, 166, 167, 167, 168, 169, 182, 182, 182, 183, 183, 183}, // SKINCOLOR_ROBOHOOD
+	{178, 178, 178, 179, 179, 180, 181, 182, 183, 172, 172, 173, 173, 174, 174, 175}, // SKINCOLOR_MOSS
+	{120, 176, 176, 176, 177, 163, 164, 165, 167, 221, 221, 222, 223, 207, 207,  31}, // SKINCOLOR_MINT
+	{160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175}, // SKINCOLOR_GREEN
+	{160, 161, 162, 164, 165, 167, 169, 170, 171, 171, 172, 173, 174, 175,  30,  31}, // SKINCOLOR_PINETREE
+	{160, 184, 184, 185, 185, 186, 186, 187, 187, 188, 188, 189, 189, 190, 191, 175}, // SKINCOLOR_EMERALD
+	{186, 187, 188, 188, 188, 189, 189, 190, 190, 191, 175, 175,  30,  30,  31,  31}, // SKINCOLOR_SWAMP
+	{120, 120,  80,  80,  81, 177, 162, 164, 228, 228, 204, 204, 205, 205, 206, 207}, // SKINCOLOR_DREAM
+	{120, 208, 208, 210, 212, 214, 220, 220, 220, 221, 221, 222, 222, 223, 223, 191}, // SKINCOLOR_AQUA
+	{210, 213, 220, 220, 220, 216, 216, 221, 221, 221, 222, 222, 223, 223, 191,  31}, // SKINCOLOR_TEAL
+	{120, 120, 208, 208, 209, 210, 211, 212, 213, 215, 216, 217, 218, 219, 222, 223}, // SKINCOLOR_CYAN
+	{120, 120, 208, 209, 210, 226, 215, 216, 217, 229, 229, 205, 205, 206, 207,  31}, // SKINCOLOR_JAWZ
+	{208, 209, 211, 213, 215, 216, 216, 217, 217, 218, 218, 219, 205, 206, 207, 207}, // SKINCOLOR_CERULEAN
+	{211, 212, 213, 215, 216, 218, 219, 205, 206, 206, 207, 207,  28,  29,  30,  31}, // SKINCOLOR_NAVY
+	{120, 120, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 204, 205, 206, 207}, // SKINCOLOR_SLATE
+	{120, 200, 200, 201, 201, 202, 202, 203, 203, 204, 204, 205, 205, 206, 207,  31}, // SKINCOLOR_STEEL
+	{225, 226, 227, 228, 229, 205, 205, 206, 207, 207,  28,  28,  29,  29,  30,  31}, // SKINCOLOR_JET
+	{208, 209, 211, 213, 215, 217, 229, 230, 232, 234, 236, 238, 240, 242, 244, 246}, // SKINCOLOR_SAPPHIRE
+	{120, 224, 225, 226, 226, 227, 228, 228, 229, 230, 231, 234, 235, 237, 239, 241}, // SKINCOLOR_PERIWINKLE
+	{224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239}, // SKINCOLOR_BLUE
+	{228, 229, 230, 231, 232, 233, 234, 235, 237, 238, 239, 240, 242, 243, 244, 245}, // SKINCOLOR_BLUEBERRY
+	{192, 192, 248, 249, 250, 251, 204, 204, 205, 205, 206, 206, 207,  29,  30,  31}, // SKINCOLOR_DUSK
+	{192, 192, 192, 193, 193, 194, 194, 195, 195, 196, 196, 197, 197, 198, 198, 199}, // SKINCOLOR_PURPLE
+	{248, 248, 248, 249, 249, 250, 250, 251, 251, 252, 252, 253, 253, 254, 254, 255}, // SKINCOLOR_LAVENDER
+	{192, 248, 249, 250, 251, 252, 253, 254, 255, 255,  29,  29,  30,  30,  31,  31}, // SKINCOLOR_BYZANTIUM
+	{144, 145, 146, 147, 148, 149, 150, 251, 251, 252, 252, 253, 254, 255,  29,  30}, // SKINCOLOR_POMEGRANATE
+	{120, 120, 120, 121, 121, 122, 122, 123, 192, 248, 249, 250, 251, 252, 253, 254}, // SKINCOLOR_LILAC
+	/* Removed Colours
+		{120, 121, 123, 124, 126, 127, 129, 130, 132, 133, 135, 136, 138, 139, 141, 143}, // old SKINCOLOR_RUBY, removed for other colors
+		{224, 225, 226, 228, 229, 231, 232, 234, 235, 237, 238, 240, 241, 243, 244, 246}, // old SKINCOLOR_SAPPHIRE, removed for other colors
+		{ 72,  73,  74,  75,  76,  77,  78,  79,  48,  49,  50,  51,  52,  53,  54,  55}, // old SKINCOLOR_CARAMEL, new Caramel was previously Shiny Caramel
+		{215, 216, 217, 218, 204, 205, 206, 237, 238, 239, 240, 241, 242, 243, 244, 245}, // old SKINCOLOR_NAVY, too similar to Jet
+		{ 80,  81,  83,  85,  86,  88,  90,  91,  93,  95, 152, 153, 154, 156, 157, 159}, // SKINCOLOR_AMBER, removed for other colors
+		{160, 160, 160, 184, 184, 184, 185, 185, 185, 186, 187, 187, 188, 188, 189, 190}, // SKINCOLOR_JADE, removed for other colors
+		{224, 225, 226, 212, 213, 213, 214, 215, 220, 221, 172, 222, 173, 223, 174, 175}, // SKINCOLOR_FROST, merged into Aqua
+		{ 96,  97,  99, 100, 102, 104, 105, 105, 106, 107, 107, 108, 109, 109, 110, 111}, // SKINCOLOR_CANARY, replaced with Mustard
+		{192, 193, 194, 195, 196, 197, 198, 199, 255, 255,  29,  29,  30,  30,  31,  31}, // SKINCOLOR_INDIGO, too similar to Byzantium
+		{  1, 145, 125,  73,  83, 114, 106, 180, 187, 168, 219, 205, 236, 206, 199, 255}, // SKINCOLOR_RAINBOW, is Vomit 2.0
+	*/
+};
+
+// Define for getting accurate color brightness readings according to how the human eye sees them.
+// https://en.wikipedia.org/wiki/Relative_luminance
+// 0.2126 to red
+// 0.7152 to green
+// 0.0722 to blue
+// (See this same define in hw_md2.c!)
+#define SETBRIGHTNESS(brightness,r,g,b) \
+	brightness = (UINT8)(((1063*((UINT16)r)/5000) + (3576*((UINT16)g)/5000) + (361*((UINT16)b)/5000)) / 3)
+
+/** \brief	Generates the rainbow colourmaps that are used when a player has the invincibility power
+
+	\param	dest_colormap	colormap to populate
+	\param	skincolor		translation color
+*/
+void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor)
+{
+	INT32 i;
+	RGBA_t color;
+	UINT8 brightness;
+	INT32 j;
+	UINT8 colorbrightnesses[16];
+	UINT16 brightdif;
+	INT32 temp;
+
+	// first generate the brightness of all the colours of that skincolour
+	for (i = 0; i < 16; i++)
+	{
+		color = V_GetColor(colortranslations[skincolor][i]);
+		SETBRIGHTNESS(colorbrightnesses[i], color.s.red, color.s.green, color.s.blue);
+	}
+
+	// next, for every colour in the palette, choose the transcolor that has the closest brightness
+	for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
+	{
+		if (i == 0 || i == 31 || i == 120) // pure black and pure white don't change
+		{
+			dest_colormap[i] = (UINT8)i;
+			continue;
+		}
+		color = V_GetColor(i);
+		SETBRIGHTNESS(brightness, color.s.red, color.s.green, color.s.blue);
+		brightdif = 256;
+		for (j = 0; j < 16; j++)
+		{
+			temp = abs((INT16)brightness - (INT16)colorbrightnesses[j]);
+			if (temp < brightdif)
+			{
+				brightdif = (UINT16)temp;
+				dest_colormap[i] = colortranslations[skincolor][j];
+			}
+		}
+	}
+}
+
+#undef SETBRIGHTNESS
+
+/**	\brief	Generates a translation colormap for Kart, to replace R_GenerateTranslationColormap in r_draw.c
+
+	\param	dest_colormap	colormap to populate
+	\param	skinnum			number of skin, TC_DEFAULT or TC_BOSS
+	\param	color			translation color
+
+	\return	void
+*/
+void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color)
+{
+	INT32 i;
+	INT32 starttranscolor;
+
+	// Handle a couple of simple special cases
+	if (skinnum == TC_BOSS
+		|| skinnum == TC_ALLWHITE
+		|| skinnum == TC_METALSONIC
+		|| skinnum == TC_BLINK
+		|| color == SKINCOLOR_NONE)
+	{
+		for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
+		{
+			if (skinnum == TC_ALLWHITE)
+				dest_colormap[i] = 0;
+			else if (skinnum == TC_BLINK)
+				dest_colormap[i] = colortranslations[color][3];
+			else
+				dest_colormap[i] = (UINT8)i;
+		}
+
+		// White!
+		if (skinnum == TC_BOSS)
+			dest_colormap[31] = 0;
+		else if (skinnum == TC_METALSONIC)
+			dest_colormap[239] = 0;
+
+		return;
+	}
+	else if (skinnum == TC_RAINBOW)
+	{
+		K_RainbowColormap(dest_colormap, color);
+		return;
+	}
+
+	starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum].starttranscolor : DEFAULT_STARTTRANSCOLOR;
+
+	// Fill in the entries of the palette that are fixed
+	for (i = 0; i < starttranscolor; i++)
+		dest_colormap[i] = (UINT8)i;
+
+	for (i = (UINT8)(starttranscolor + 16); i < NUM_PALETTE_ENTRIES; i++)
+		dest_colormap[i] = (UINT8)i;
+
+	// Build the translated ramp
+	for (i = 0; i < SKIN_RAMP_LENGTH; i++)
+	{
+		// Sryder 2017-10-26: What was here before was most definitely not particularly readable, check above for new color translation table
+		dest_colormap[starttranscolor + i] = colortranslations[color][i];
+	}
+}
+
+/**	\brief	Pulls kart color by name, to replace R_GetColorByName in r_draw.c
+
+	\param	name	color name
+
+	\return	0
+*/
+UINT8 K_GetKartColorByName(const char *name)
+{
+	UINT8 color = (UINT8)atoi(name);
+	if (color > 0 && color < MAXSKINCOLORS)
+		return color;
+	for (color = 1; color < MAXSKINCOLORS; color++)
+		if (!stricmp(KartColor_Names[color], name))
+			return color;
+	return 0;
+}
+
+//}
+
+//{ SRB2kart Net Variables
+
+void K_RegisterKartStuff(void)
+{
+	CV_RegisterVar(&cv_sneaker);
+	CV_RegisterVar(&cv_rocketsneaker);
+	CV_RegisterVar(&cv_invincibility);
+	CV_RegisterVar(&cv_banana);
+	CV_RegisterVar(&cv_eggmanmonitor);
+	CV_RegisterVar(&cv_orbinaut);
+	CV_RegisterVar(&cv_jawz);
+	CV_RegisterVar(&cv_mine);
+	CV_RegisterVar(&cv_ballhog);
+	CV_RegisterVar(&cv_selfpropelledbomb);
+	CV_RegisterVar(&cv_grow);
+	CV_RegisterVar(&cv_shrink);
+	CV_RegisterVar(&cv_thundershield);
+	CV_RegisterVar(&cv_hyudoro);
+	CV_RegisterVar(&cv_pogospring);
+	CV_RegisterVar(&cv_kitchensink);
+
+	CV_RegisterVar(&cv_triplesneaker);
+	CV_RegisterVar(&cv_triplebanana);
+	CV_RegisterVar(&cv_decabanana);
+	CV_RegisterVar(&cv_tripleorbinaut);
+	CV_RegisterVar(&cv_quadorbinaut);
+	CV_RegisterVar(&cv_dualjawz);
+
+	CV_RegisterVar(&cv_kartminimap);
+	CV_RegisterVar(&cv_kartcheck);
+	CV_RegisterVar(&cv_kartinvinsfx);
+	CV_RegisterVar(&cv_kartspeed);
+	CV_RegisterVar(&cv_kartbumpers);
+	CV_RegisterVar(&cv_kartfrantic);
+	CV_RegisterVar(&cv_kartcomeback);
+	CV_RegisterVar(&cv_kartencore);
+	CV_RegisterVar(&cv_kartvoterulechanges);
+	CV_RegisterVar(&cv_kartspeedometer);
+	CV_RegisterVar(&cv_kartvoices);
+	CV_RegisterVar(&cv_karteliminatelast);
+	CV_RegisterVar(&cv_votetime);
+
+	CV_RegisterVar(&cv_kartdebugitem);
+	CV_RegisterVar(&cv_kartdebugamount);
+	CV_RegisterVar(&cv_kartdebugshrink);
+	CV_RegisterVar(&cv_kartdebugdistribution);
+	CV_RegisterVar(&cv_kartdebughuddrop);
+
+	CV_RegisterVar(&cv_kartdebugcheckpoint);
+	CV_RegisterVar(&cv_kartdebugnodes);
+}
+
+//}
+
+boolean K_IsPlayerLosing(player_t *player)
+{
+	INT32 winningpos = 1;
+	UINT8 i, pcount = 0;
+
+	if (player->kartstuff[k_position] == 1)
+		return false;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i] || players[i].spectator)
+			continue;
+		if (players[i].kartstuff[k_position] > pcount)
+			pcount = players[i].kartstuff[k_position];
+	}
+
+	if (pcount <= 1)
+		return false;
+
+	winningpos = pcount/2;
+	if (pcount % 2) // any remainder?
+		winningpos++;
+
+	return (player->kartstuff[k_position] > winningpos);
+}
+
+boolean K_IsPlayerWanted(player_t *player)
+{
+	UINT8 i;
+	if (!(G_BattleGametype()))
+		return false;
+	for (i = 0; i < 4; i++)
+	{
+		if (battlewanted[i] == -1)
+			break;
+		if (player == &players[battlewanted[i]])
+			return true;
+	}
+	return false;
+}
+
+//{ SRB2kart Roulette Code - Position Based
+
+#define NUMKARTODDS 	80
+
+// Less ugly 2D arrays
+static INT32 K_KartItemOddsRace[NUMKARTRESULTS][10] =
+{
+				//P-Odds	 0  1  2  3  4  5  6  7  8  9
+			   /*Sneaker*/ {20, 0, 0, 4, 6, 6, 0, 0, 0, 0 }, // Sneaker
+		/*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 1, 3, 5, 3, 0 }, // Rocket Sneaker
+		 /*Invincibility*/ { 0, 0, 0, 0, 0, 1, 4, 6,14, 0 }, // Invincibility
+				/*Banana*/ { 0,10, 4, 2, 1, 0, 0, 0, 0, 0 }, // Banana
+		/*Eggman Monitor*/ { 0, 3, 2, 1, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor
+			  /*Orbinaut*/ { 0, 8, 6, 4, 2, 0, 0, 0, 0, 0 }, // Orbinaut
+				  /*Jawz*/ { 0, 0, 3, 2, 1, 1, 0, 0, 0, 0 }, // Jawz
+				  /*Mine*/ { 0, 0, 2, 2, 1, 0, 0, 0, 0, 0 }, // Mine
+			   /*Ballhog*/ { 0, 0, 0, 2, 1, 0, 0, 0, 0, 0 }, // Ballhog
+   /*Self-Propelled Bomb*/ { 0, 0, 1, 2, 3, 4, 2, 2, 0,20 }, // Self-Propelled Bomb
+				  /*Grow*/ { 0, 0, 0, 0, 0, 1, 3, 5, 3, 0 }, // Grow
+				/*Shrink*/ { 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 }, // Shrink
+		/*Thunder Shield*/ { 0, 1, 2, 0, 0, 0, 0, 0, 0, 0 }, // Thunder Shield
+			   /*Hyudoro*/ { 0, 0, 0, 0, 1, 2, 1, 0, 0, 0 }, // Hyudoro
+		   /*Pogo Spring*/ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring
+		  /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink
+			/*Sneaker x3*/ { 0, 0, 0, 0, 3, 7, 9, 2, 0, 0 }, // Sneaker x3
+			 /*Banana x3*/ { 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, // Banana x3
+			/*Banana x10*/ { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, // Banana x10
+		   /*Orbinaut x3*/ { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // Orbinaut x3
+		   /*Orbinaut x4*/ { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // Orbinaut x4
+			   /*Jawz x2*/ { 0, 0, 0, 1, 2, 0, 0, 0, 0, 0 }  // Jawz x2
+};
+
+static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][6] =
+{
+				//P-Odds	 0  1  2  3  4  5
+			   /*Sneaker*/ { 3, 2, 2, 2, 0, 2 }, // Sneaker
+		/*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 0 }, // Rocket Sneaker
+		 /*Invincibility*/ { 0, 1, 2, 3, 4, 2 }, // Invincibility
+				/*Banana*/ { 2, 1, 0, 0, 0, 0 }, // Banana
+		/*Eggman Monitor*/ { 1, 1, 0, 0, 0, 0 }, // Eggman Monitor
+			  /*Orbinaut*/ { 6, 2, 1, 0, 0, 0 }, // Orbinaut
+				  /*Jawz*/ { 3, 3, 3, 2, 0, 2 }, // Jawz
+				  /*Mine*/ { 2, 3, 3, 1, 0, 2 }, // Mine
+			   /*Ballhog*/ { 0, 1, 2, 1, 0, 2 }, // Ballhog
+   /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 0 }, // Self-Propelled Bomb
+				  /*Grow*/ { 0, 0, 1, 2, 4, 2 }, // Grow
+				/*Shrink*/ { 0, 0, 0, 0, 0, 0 }, // Shrink
+		/*Thunder Shield*/ { 0, 0, 0, 0, 0, 0 }, // Thunder Shield
+			   /*Hyudoro*/ { 1, 1, 0, 0, 0, 0 }, // Hyudoro
+		   /*Pogo Spring*/ { 1, 1, 0, 0, 0, 0 }, // Pogo Spring
+		  /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0 }, // Kitchen Sink
+			/*Sneaker x3*/ { 0, 0, 0, 2, 4, 2 }, // Sneaker x3
+			 /*Banana x3*/ { 1, 2, 1, 0, 0, 0 }, // Banana x3
+			/*Banana x10*/ { 0, 0, 1, 1, 0, 2 }, // Banana x10
+		   /*Orbinaut x3*/ { 0, 1, 2, 1, 0, 0 }, // Orbinaut x3
+		   /*Orbinaut x4*/ { 0, 0, 1, 3, 4, 2 }, // Orbinaut x4
+			   /*Jawz x2*/ { 0, 0, 1, 2, 4, 2 }  // Jawz x2
+};
+
+/**	\brief	Item Roulette for Kart
+
+	\param	player		player
+	\param	getitem		what item we're looking for
+
+	\return	void
+*/
+static void K_KartGetItemResult(player_t *player, SINT8 getitem)
+{
+	switch (getitem)
+	{
+		// Special roulettes first, then the generic ones are handled by default
+		case KRITEM_TRIPLESNEAKER: // Sneaker x3
+			player->kartstuff[k_itemtype] = KITEM_SNEAKER;
+			player->kartstuff[k_itemamount] = 3;
+			break;
+		case KRITEM_TRIPLEBANANA: // Banana x3
+			player->kartstuff[k_itemtype] = KITEM_BANANA;
+			player->kartstuff[k_itemamount] = 3;
+			break;
+		case KRITEM_TENFOLDBANANA: // Banana x10
+			player->kartstuff[k_itemtype] = KITEM_BANANA;
+			player->kartstuff[k_itemamount] = 10;
+			break;
+		case KRITEM_TRIPLEORBINAUT: // Orbinaut x3
+			player->kartstuff[k_itemtype] = KITEM_ORBINAUT;
+			player->kartstuff[k_itemamount] = 3;
+			break;
+		case KRITEM_QUADORBINAUT: // Orbinaut x4
+			player->kartstuff[k_itemtype] = KITEM_ORBINAUT;
+			player->kartstuff[k_itemamount] = 4;
+			break;
+		case KRITEM_DUALJAWZ: // Jawz x2
+			player->kartstuff[k_itemtype] = KITEM_JAWZ;
+			player->kartstuff[k_itemamount] = 2;
+			break;
+		case KITEM_SPB:
+		case KITEM_SHRINK: // Indirect items
+			indirectitemcooldown = 20*TICRATE;
+			/* FALLTHRU */
+		default:
+			if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback)
+			{
+				if (getitem != 0)
+					CONS_Printf("ERROR: P_KartGetItemResult - Item roulette gave bad item (%d) :(\n", getitem);
+				player->kartstuff[k_itemtype] = KITEM_SAD;
+			}
+			else
+				player->kartstuff[k_itemtype] = getitem;
+			player->kartstuff[k_itemamount] = 1;
+			break;
+	}
+}
+
+/**	\brief	Item Roulette for Kart
+
+	\param	player	player object passed from P_KartPlayerThink
+
+	\return	void
+*/
+
+static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
+{
+	const INT32 distvar = (64*14);
+	INT32 newodds;
+	INT32 i;
+	UINT8 pingame = 0, pexiting = 0, pinvin = 0;
+	SINT8 first = -1, second = -1;
+	INT32 secondist = 0;
+	boolean itemenabled[NUMKARTRESULTS] = {
+		cv_sneaker.value,
+		cv_rocketsneaker.value,
+		cv_invincibility.value,
+		cv_banana.value,
+		cv_eggmanmonitor.value,
+		cv_orbinaut.value,
+		cv_jawz.value,
+		cv_mine.value,
+		cv_ballhog.value,
+		cv_selfpropelledbomb.value,
+		cv_grow.value,
+		cv_shrink.value,
+		cv_thundershield.value,
+		cv_hyudoro.value,
+		cv_kitchensink.value,
+		cv_triplesneaker.value,
+		cv_triplebanana.value,
+		cv_decabanana.value,
+		cv_tripleorbinaut.value,
+		cv_quadorbinaut.value,
+		cv_dualjawz.value
+	};
+
+	if (!itemenabled[item] && !modeattacking)
+		return 0;
+
+	if (G_BattleGametype())
+		newodds = K_KartItemOddsBattle[item-1][pos];
+	else
+		newodds = K_KartItemOddsRace[item-1][pos];
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i] || players[i].spectator)
+			continue;
+		if (!G_BattleGametype() || players[i].kartstuff[k_bumper])
+			pingame++;
+		if (players[i].exiting)
+			pexiting++;
+		if (players[i].mo)
+		{
+			if (players[i].kartstuff[k_itemtype] == KITEM_INVINCIBILITY
+				|| players[i].kartstuff[k_itemtype] == KITEM_GROW
+				|| players[i].kartstuff[k_invincibilitytimer]
+				|| players[i].kartstuff[k_growshrinktimer] > 0)
+				pinvin++;
+			if (!G_BattleGametype())
+			{
+				if (players[i].kartstuff[k_position] == 1 && first == -1)
+					first = i;
+				if (players[i].kartstuff[k_position] == 2 && second == -1)
+					second = i;
+			}
+		}
+	}
+
+	if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB
+	{
+		secondist = P_AproxDistance(P_AproxDistance(players[first].mo->x - players[second].mo->x,
+													players[first].mo->y - players[second].mo->y),
+													players[first].mo->z - players[second].mo->z) / mapobjectscale;
+		if (franticitems)
+			secondist = (15 * secondist) / 14;
+		secondist = ((28 + (8-pingame)) * secondist) / 28;
+	}
+
+	// POWERITEMODDS handles all of the "frantic item" related functionality, for all of our powerful items.
+	// First, it multiplies it by 2 if franticitems is true; easy-peasy.
+	// Next, it multiplies it again if it's in SPB mode and 2nd needs to apply pressure to 1st.
+	// Then, it multiplies it further if there's less than 5 players in game.
+	// This is done to make low player count races more fair & interesting. (2P normal would be about halfway between 8P normal and 8P frantic)
+	// Lastly, it *divides* it by your mashed value, which was determined in K_KartItemRoulette, to punish those who are impatient.
+#define POWERITEMODDS(odds) \
+	if (franticitems) \
+		odds <<= 1; \
+	odds = FixedMul(odds<<FRACBITS, FRACUNIT + ((8-pingame) * (FRACUNIT/25))) >> FRACBITS; \
+	if (mashed > 0) \
+		odds = FixedDiv(odds<<FRACBITS, FRACUNIT + mashed) >> FRACBITS \
+
+	switch (item)
+	{
+		case KITEM_INVINCIBILITY:
+		case KITEM_GROW:
+			if (pinvin >= max(1, (pingame+2) / 4))
+				newodds = 0;
+			else
+			/* FALLTHRU */
+		case KITEM_ROCKETSNEAKER:
+		case KITEM_JAWZ:
+		case KITEM_MINE:
+		case KITEM_BALLHOG:
+		case KITEM_THUNDERSHIELD:
+		case KRITEM_TRIPLESNEAKER:
+		case KRITEM_TRIPLEBANANA:
+		case KRITEM_TENFOLDBANANA:
+		case KRITEM_TRIPLEORBINAUT:
+		case KRITEM_QUADORBINAUT:
+		case KRITEM_DUALJAWZ:
+			POWERITEMODDS(newodds);
+			break;
+		case KITEM_SPB:
+			//POWERITEMODDS(newodds);
+			if (((indirectitemcooldown > 0) || (pexiting > 0) || (secondist/distvar < 3))
+				&& (pos != 9)) // Force SPB
+				newodds = 0;
+			else
+				newodds *= min((secondist/distvar)-4, 3);
+			break;
+		case KITEM_SHRINK:
+			POWERITEMODDS(newodds);
+			if ((indirectitemcooldown > 0) || (pingame-1 <= pexiting))
+				newodds = 0;
+			break;
+		default:
+			break;
+	}
+
+#undef POWERITEMODDS
+
+	return newodds;
+}
+
+//{ SRB2kart Roulette Code - Distance Based, no waypoints
+
+static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT32 bestbumper, boolean spbrush, boolean dontforcespb)
+{
+	const INT32 distvar = (64*14);
+	INT32 i;
+	INT32 pdis = 0, useodds = 0;
+	UINT8 disttable[14];
+	UINT8 distlen = 0;
+	boolean oddsvalid[10];
+
+	for (i = 0; i < 10; i++)
+	{
+		INT32 j;
+		boolean available = false;
+
+		if (G_BattleGametype() && i > 5)
+		{
+			oddsvalid[i] = false;
+			break;
+		}
+
+		for (j = 0; j < NUMKARTRESULTS; j++)
+		{
+			if (K_KartGetItemOdds(i, j, mashed) > 0)
+			{
+				available = true;
+				break;
+			}
+		}
+
+		oddsvalid[i] = available;
+	}
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playeringame[i] && !players[i].spectator && players[i].mo
+			&& players[i].kartstuff[k_position] < player->kartstuff[k_position])
+			pdis += P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x,
+													players[i].mo->y - player->mo->y),
+													players[i].mo->z - player->mo->z) / mapobjectscale
+													* (pingame - players[i].kartstuff[k_position])
+													/ max(1, ((pingame - 1) * (pingame + 1) / 3));
+	}
+
+#define SETUPDISTTABLE(odds, num) \
+	for (i = num; i; --i) disttable[distlen++] = odds
+
+	if (G_BattleGametype()) // Battle Mode
+	{
+		if (oddsvalid[0]) SETUPDISTTABLE(0,1);
+		if (oddsvalid[1]) SETUPDISTTABLE(1,1);
+		if (oddsvalid[2]) SETUPDISTTABLE(2,1);
+		if (oddsvalid[3]) SETUPDISTTABLE(3,1);
+		if (oddsvalid[4]) SETUPDISTTABLE(4,1);
+
+		if (player->kartstuff[k_roulettetype] == 1 && oddsvalid[5]) // 5 is the extreme odds of player-controlled "Karma" items
+			useodds = 5;
+		else
+		{
+			SINT8 wantedpos = (bestbumper-player->kartstuff[k_bumper]); // 0 is the best player's bumper count, 1 is a bumper below best, 2 is two bumpers below, etc
+			if (K_IsPlayerWanted(player))
+				wantedpos++;
+			if (wantedpos > 4) // Don't run off into karma items
+				wantedpos = 4;
+			if (wantedpos < 0) // Don't go below somehow
+				wantedpos = 0;
+			useodds = disttable[(wantedpos * distlen) / 5];
+		}
+	}
+	else
+	{
+		if (oddsvalid[1]) SETUPDISTTABLE(1,1);
+		if (oddsvalid[2]) SETUPDISTTABLE(2,1);
+		if (oddsvalid[3]) SETUPDISTTABLE(3,1);
+		if (oddsvalid[4]) SETUPDISTTABLE(4,2);
+		if (oddsvalid[5]) SETUPDISTTABLE(5,2);
+		if (oddsvalid[6]) SETUPDISTTABLE(6,3);
+		if (oddsvalid[7]) SETUPDISTTABLE(7,3);
+		if (oddsvalid[8]) SETUPDISTTABLE(8,1);
+
+		if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items
+			pdis = (15 * pdis) / 14;
+
+		if (spbrush) // SPB Rush Mode: It's 2nd place's job to catch-up items and make 1st place's job hell
+			pdis = (3 * pdis) / 2;
+
+		pdis = ((28 + (8-pingame)) * pdis) / 28;
+
+		if (pingame == 1 && oddsvalid[0])					// Record Attack, or just alone
+			useodds = 0;
+		else if (pdis <= 0)									// (64*14) *  0 =     0
+			useodds = disttable[0];
+		else if (player->kartstuff[k_position] == 2 && pdis > (distvar*6)
+			&& spbplace == -1 && !indirectitemcooldown && !dontforcespb
+			&& oddsvalid[9])								// Force SPB in 2nd
+			useodds = 9;
+		else if (pdis > distvar * ((12 * distlen) / 14))	// (64*14) * 12 = 10752
+			useodds = disttable[distlen-1];
+		else
+		{
+			for (i = 1; i < 13; i++)
+			{
+				if (pdis <= distvar * ((i * distlen) / 14))
+				{
+					useodds = disttable[((i * distlen) / 14)];
+					break;
+				}
+			}
+		}
+	}
+
+#undef SETUPDISTTABLE
+
+	//CONS_Printf("Got useodds %d. (position: %d, distance: %d)\n", useodds, player->kartstuff[k_position], pdis);
+
+	return useodds;
+}
+
+static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
+{
+	INT32 i;
+	UINT8 pingame = 0;
+	UINT8 roulettestop;
+	INT32 useodds = 0;
+	INT32 spawnchance[NUMKARTRESULTS * NUMKARTODDS];
+	INT32 chance = 0, numchoices = 0;
+	INT32 bestbumper = 0;
+	fixed_t mashed = 0;
+	boolean dontforcespb = false;
+
+	// This makes the roulette cycle through items - if this is 0, you shouldn't be here.
+	if (player->kartstuff[k_itemroulette])
+		player->kartstuff[k_itemroulette]++;
+	else
+		return;
+
+	// Gotta check how many players are active at this moment.
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i] || players[i].spectator)
+			continue;
+		pingame++;
+		if (players[i].exiting)
+			dontforcespb = true;
+		if (players[i].kartstuff[k_bumper] > bestbumper)
+			bestbumper = players[i].kartstuff[k_bumper];
+	}
+
+	// This makes the roulette produce the random noises.
+	if ((player->kartstuff[k_itemroulette] % 3) == 1 && P_IsLocalPlayer(player))
+	{
+#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->kartstuff[k_itemroulette] / 3) % 8));
+		if (splitscreen)
+		{
+			if (players[displayplayer].kartstuff[k_itemroulette])
+			{
+				if (player == &players[displayplayer])
+					PLAYROULETTESND;
+			}
+			else if (players[secondarydisplayplayer].kartstuff[k_itemroulette])
+			{
+				if (player == &players[secondarydisplayplayer])
+					PLAYROULETTESND;
+			}
+			else if (players[thirddisplayplayer].kartstuff[k_itemroulette] && splitscreen > 1)
+			{
+				if (player == &players[thirddisplayplayer])
+					PLAYROULETTESND;
+			}
+			else if (players[fourthdisplayplayer].kartstuff[k_itemroulette] && splitscreen > 2)
+			{
+				if (player == &players[fourthdisplayplayer])
+					PLAYROULETTESND;
+			}
+		}
+		else
+			PLAYROULETTESND;
+#undef PLAYROULETTESND
+	}
+
+	roulettestop = TICRATE + (3*(pingame - player->kartstuff[k_position]));
+
+	// If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item.
+	// I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think.
+	// Finally, if you get past this check, now you can actually start calculating what item you get.
+	if ((cmd->buttons & BT_ATTACK) && !(player->kartstuff[k_eggmanheld] || player->kartstuff[k_itemheld]) && player->kartstuff[k_itemroulette] >= roulettestop && !modeattacking)
+	{
+		// Mashing reduces your chances for the good items
+		mashed = FixedDiv((player->kartstuff[k_itemroulette])*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT;
+	}
+	else if (!(player->kartstuff[k_itemroulette] >= (TICRATE*3)))
+		return;
+
+	if (cmd->buttons & BT_ATTACK)
+		player->pflags |= PF_ATTACKDOWN;
+
+	if (player->kartstuff[k_roulettetype] == 2) // Fake items
+	{
+		player->kartstuff[k_eggmanexplode] = 4*TICRATE;
+		//player->kartstuff[k_itemblink] = TICRATE;
+		//player->kartstuff[k_itemblinkmode] = 1;
+		player->kartstuff[k_itemroulette] = 0;
+		player->kartstuff[k_roulettetype] = 0;
+		if (P_IsLocalPlayer(player))
+			S_StartSound(NULL, sfx_itrole);
+		return;
+	}
+
+	if (cv_kartdebugitem.value != 0 && !modeattacking)
+	{
+		K_KartGetItemResult(player, cv_kartdebugitem.value);
+		player->kartstuff[k_itemamount] = cv_kartdebugamount.value;
+		player->kartstuff[k_itemblink] = TICRATE;
+		player->kartstuff[k_itemblinkmode] = 2;
+		player->kartstuff[k_itemroulette] = 0;
+		player->kartstuff[k_roulettetype] = 0;
+		if (P_IsLocalPlayer(player))
+			S_StartSound(NULL, sfx_dbgsal);
+		return;
+	}
+
+	// Initializes existing spawnchance values
+	for (i = 0; i < (NUMKARTRESULTS * NUMKARTODDS); i++)
+		spawnchance[i] = 0;
+
+	// Split into another function for a debug function below
+	useodds = K_FindUseodds(player, mashed, pingame, bestbumper, (spbplace != -1 && player->kartstuff[k_position] == spbplace+1), dontforcespb);
+
+#define SETITEMRESULT(itemnum) \
+	for (chance = 0; chance < K_KartGetItemOdds(useodds, itemnum, mashed); chance++) \
+		spawnchance[numchoices++] = itemnum
+
+	for (i = 1; i < NUMKARTRESULTS; i++)
+		SETITEMRESULT(i);
+
+#undef SETITEMRESULT
+
+	// Award the player whatever power is rolled
+	if (numchoices > 0)
+		K_KartGetItemResult(player, spawnchance[P_RandomKey(numchoices)]);
+	else
+	{
+		player->kartstuff[k_itemtype] = KITEM_SAD;
+		player->kartstuff[k_itemamount] = 1;
+	}
+
+	if (P_IsLocalPlayer(player))
+		S_StartSound(NULL, ((player->kartstuff[k_roulettetype] == 1) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf)));
+
+	player->kartstuff[k_itemblink] = TICRATE;
+	player->kartstuff[k_itemblinkmode] = ((player->kartstuff[k_roulettetype] == 1) ? 2 : (mashed ? 1 : 0));
+
+	player->kartstuff[k_itemroulette] = 0; // Since we're done, clear the roulette number
+	player->kartstuff[k_roulettetype] = 0; // This too
+}
+
+//}
+
+//{ SRB2kart p_user.c Stuff
+
+static fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against)
+{
+	fixed_t weight = 5<<FRACBITS;
+
+	switch (mobj->type)
+	{
+		case MT_PLAYER:
+			if (!mobj->player)
+				break;
+			if (against->player && !against->player->kartstuff[k_spinouttimer] && mobj->player->kartstuff[k_spinouttimer])
+				weight = 0; // Do not bump
+			else
+			{
+				weight = (mobj->player->kartweight)<<FRACBITS;
+				if (mobj->player->speed > K_GetKartSpeed(mobj->player, false))
+					weight += (mobj->player->speed - K_GetKartSpeed(mobj->player, false))/8;
+			}
+			break;
+		case MT_FALLINGROCK:
+			if (against->player)
+			{
+				if (against->player->kartstuff[k_invincibilitytimer]
+					|| against->player->kartstuff[k_growshrinktimer] > 0)
+					weight = 0;
+				else
+					weight = (against->player->kartweight)<<FRACBITS;
+			}
+			break;
+		case MT_ORBINAUT:
+		case MT_ORBINAUT_SHIELD:
+			if (against->player)
+				weight = (against->player->kartweight)<<FRACBITS;
+			break;
+		case MT_JAWZ:
+		case MT_JAWZ_DUD:
+		case MT_JAWZ_SHIELD:
+			if (against->player)
+				weight = (against->player->kartweight+3)<<FRACBITS;
+			else
+				weight = 8<<FRACBITS;
+			break;
+		default:
+			break;
+	}
+
+	return weight;
+}
+
+void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid)
+{
+	mobj_t *fx;
+	fixed_t momdifx, momdify;
+	fixed_t distx, disty;
+	fixed_t dot, p;
+	fixed_t mass1, mass2;
+
+	if (!mobj1 || !mobj2)
+		return;
+
+	// Don't bump when you're being reborn
+	if ((mobj1->player && mobj1->player->playerstate != PST_LIVE)
+		|| (mobj2->player && mobj2->player->playerstate != PST_LIVE))
+		return;
+
+	if ((mobj1->player && mobj1->player->kartstuff[k_respawn])
+		|| (mobj2->player && mobj2->player->kartstuff[k_respawn]))
+		return;
+
+	{ // Don't bump if you're flashing
+		INT32 flash;
+
+		flash = K_GetKartFlashing(mobj1->player);
+		if (mobj1->player && mobj1->player->powers[pw_flashing] > 0 && mobj1->player->powers[pw_flashing] < flash)
+		{
+			if (mobj1->player->powers[pw_flashing] < flash-1)
+				mobj1->player->powers[pw_flashing]++;
+			return;
+		}
+
+		flash = K_GetKartFlashing(mobj2->player);
+		if (mobj2->player && mobj2->player->powers[pw_flashing] > 0 && mobj2->player->powers[pw_flashing] < flash)
+		{
+			if (mobj2->player->powers[pw_flashing] < flash-1)
+				mobj2->player->powers[pw_flashing]++;
+			return;
+		}
+	}
+
+	// Don't bump if you've recently bumped
+	if (mobj1->player && mobj1->player->kartstuff[k_justbumped])
+	{
+		mobj1->player->kartstuff[k_justbumped] = bumptime;
+		return;
+	}
+
+	if (mobj2->player && mobj2->player->kartstuff[k_justbumped])
+	{
+		mobj2->player->kartstuff[k_justbumped] = bumptime;
+		return;
+	}
+
+	mass1 = K_GetMobjWeight(mobj1, mobj2);
+
+	if (solid == true && mass1 > 0)
+		mass2 = mass1;
+	else
+		mass2 = K_GetMobjWeight(mobj2, mobj1);
+
+	momdifx = mobj1->momx - mobj2->momx;
+	momdify = mobj1->momy - mobj2->momy;
+
+	// 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))
+	{
+		fixed_t momdiflength = P_AproxDistance(momdifx, momdify);
+		fixed_t normalisedx = FixedDiv(momdifx, momdiflength);
+		fixed_t normalisedy = FixedDiv(momdify, momdiflength);
+		momdifx = FixedMul((25*mapobjectscale), normalisedx);
+		momdify = FixedMul((25*mapobjectscale), normalisedy);
+	}
+
+	// Adds the OTHER player's momentum, so that it reduces the chance of you being "inside" the other object
+	distx = (mobj1->x + mobj2->momx) - (mobj2->x + mobj1->momx);
+	disty = (mobj1->y + mobj2->momy) - (mobj2->y + mobj1->momy);
+
+	{ // Don't allow dist to get WAY too low, that it pushes you stupidly huge amounts, or backwards...
+		fixed_t dist = P_AproxDistance(distx, disty);
+		fixed_t nx = FixedDiv(distx, dist);
+		fixed_t ny = FixedDiv(disty, dist);
+
+		if (P_AproxDistance(distx, disty) < (3*mobj1->radius)/4)
+		{
+			distx = FixedMul((3*mobj1->radius)/4, nx);
+			disty = FixedMul((3*mobj1->radius)/4, ny);
+		}
+
+		if (P_AproxDistance(distx, disty) < (3*mobj2->radius)/4)
+		{
+			distx = FixedMul((3*mobj2->radius)/4, nx);
+			disty = FixedMul((3*mobj2->radius)/4, ny);
+		}
+	}
+
+	if (distx == 0 && disty == 0)
+	{
+		// if there's no distance between the 2, they're directly on top of each other, don't run this
+		return;
+	}
+
+	dot = FixedMul(momdifx, distx) + FixedMul(momdify, disty);
+
+	if (dot >= 0)
+	{
+		// They're moving away from each other
+		return;
+	}
+
+	p = FixedDiv(dot, FixedMul(distx, distx)+FixedMul(disty, disty));
+
+	if (bounce == true && mass2 > 0) // Perform a Goomba Bounce.
+		mobj1->momz = -mobj1->momz;
+	else
+	{
+		fixed_t newz = mobj1->momz;
+		if (mass2 > 0)
+			mobj1->momz = mobj2->momz;
+		if (mass1 > 0 && solid == false)
+			mobj2->momz = newz;
+	}
+
+	if (mass2 > 0)
+	{
+		mobj1->momx = mobj1->momx - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), p), distx);
+		mobj1->momy = mobj1->momy - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), p), disty);
+	}
+
+	if (mass1 > 0 && solid == false)
+	{
+		mobj2->momx = mobj2->momx - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), p), -distx);
+		mobj2->momy = mobj2->momy - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), p), -disty);
+	}
+
+	// Do the bump fx when we've CONFIRMED we can bump.
+	S_StartSound(mobj1, sfx_s3k49);
+
+	fx = P_SpawnMobj(mobj1->x/2 + mobj2->x/2, mobj1->y/2 + mobj2->y/2, mobj1->z/2 + mobj2->z/2, MT_BUMP);
+	if (mobj1->eflags & MFE_VERTICALFLIP)
+		fx->eflags |= MFE_VERTICALFLIP;
+	else
+		fx->eflags &= ~MFE_VERTICALFLIP;
+	P_SetScale(fx, mobj1->scale);
+
+	// 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)
+	{
+		mobj1->player->rmomx = mobj1->momx - mobj1->player->cmomx;
+		mobj1->player->rmomy = mobj1->momy - mobj1->player->cmomy;
+		mobj1->player->kartstuff[k_justbumped] = bumptime;
+		if (mobj1->player->kartstuff[k_spinouttimer])
+		{
+			mobj1->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1;
+			mobj1->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj1->player->kartstuff[k_spinouttimer]);
+		}
+	}
+
+	if (mobj2->player)
+	{
+		mobj2->player->rmomx = mobj2->momx - mobj2->player->cmomx;
+		mobj2->player->rmomy = mobj2->momy - mobj2->player->cmomy;
+		mobj2->player->kartstuff[k_justbumped] = bumptime;
+		if (mobj2->player->kartstuff[k_spinouttimer])
+		{
+			mobj2->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1;
+			mobj2->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj2->player->kartstuff[k_spinouttimer]);
+		}
+	}
+}
+
+/**	\brief	Checks that the player is on an offroad subsector for realsies
+
+	\param	mo	player mobj object
+
+	\return	boolean
+*/
+static UINT8 K_CheckOffroadCollide(mobj_t *mo, sector_t *sec)
+{
+	UINT8 i;
+	sector_t *sec2;
+
+	I_Assert(mo != NULL);
+	I_Assert(!P_MobjWasRemoved(mo));
+
+	sec2 = P_ThingOnSpecial3DFloor(mo);
+
+	for (i = 2; i < 5; i++)
+	{
+		if ((sec2 && GETSECSPECIAL(sec2->special, 1) == i)
+			|| (P_IsObjectOnRealGround(mo, sec) && GETSECSPECIAL(sec->special, 1) == i))
+			return i-1;
+	}
+
+	return 0;
+}
+
+/**	\brief	Updates the Player's offroad value once per frame
+
+	\param	player	player object passed from K_KartPlayerThink
+
+	\return	void
+*/
+static void K_UpdateOffroad(player_t *player)
+{
+	fixed_t offroad;
+	sector_t *nextsector = R_PointInSubsector(
+		player->mo->x + player->mo->momx*2, player->mo->y + player->mo->momy*2)->sector;
+	UINT8 offroadstrength = K_CheckOffroadCollide(player->mo, nextsector);
+
+	// If you are in offroad, a timer starts.
+	if (offroadstrength)
+	{
+		if (K_CheckOffroadCollide(player->mo, player->mo->subsector->sector) && player->kartstuff[k_offroad] == 0)
+			player->kartstuff[k_offroad] = (TICRATE/2);
+
+		if (player->kartstuff[k_offroad] > 0)
+		{
+			offroad = (offroadstrength << FRACBITS) / (TICRATE/2);
+
+			//if (player->kartstuff[k_growshrinktimer] > 1) // grow slows down half as fast
+			//	offroad /= 2;
+
+			player->kartstuff[k_offroad] += offroad;
+		}
+
+		if (player->kartstuff[k_offroad] > (offroadstrength << FRACBITS))
+			player->kartstuff[k_offroad] = (offroadstrength << FRACBITS);
+	}
+	else
+		player->kartstuff[k_offroad] = 0;
+}
+
+// These have to go earlier than its sisters because of K_RespawnChecker...
+void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master)
+{
+	// flipping
+	mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP)|(master->eflags & MFE_VERTICALFLIP);
+	// visibility (usually for hyudoro)
+	mo->flags2 = (mo->flags2 & ~MF2_DONTDRAW)|(master->flags2 & MF2_DONTDRAW);
+	mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP1)|(master->eflags & MFE_DRAWONLYFORP1);
+	mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP2)|(master->eflags & MFE_DRAWONLYFORP2);
+	mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP3)|(master->eflags & MFE_DRAWONLYFORP3);
+	mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP4)|(master->eflags & MFE_DRAWONLYFORP4);
+}
+
+static void K_SpawnDashDustRelease(player_t *player)
+{
+	fixed_t newx;
+	fixed_t newy;
+	mobj_t *dust;
+	angle_t travelangle;
+	INT32 i;
+
+	I_Assert(player != NULL);
+	I_Assert(player->mo != NULL);
+	I_Assert(!P_MobjWasRemoved(player->mo));
+
+	if (!P_IsObjectOnGround(player->mo))
+		return;
+
+	if (!player->speed && !player->kartstuff[k_startboost])
+		return;
+
+	travelangle = player->mo->angle;
+
+	if (player->kartstuff[k_drift] || player->kartstuff[k_driftend])
+		travelangle -= (ANGLE_45/5)*player->kartstuff[k_drift];
+
+	for (i = 0; i < 2; i++)
+	{
+		newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale));
+		newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale));
+		dust = P_SpawnMobj(newx, newy, player->mo->z, MT_FASTDUST);
+
+		P_SetTarget(&dust->target, player->mo);
+		dust->angle = travelangle - ((i&1) ? -1 : 1)*ANGLE_45;
+		dust->destscale = player->mo->scale;
+		P_SetScale(dust, player->mo->scale);
+
+		dust->momx = 3*player->mo->momx/5;
+		dust->momy = 3*player->mo->momy/5;
+		//dust->momz = 3*player->mo->momz/5;
+
+		K_MatchGenericExtraFlags(dust, player->mo);
+	}
+}
+
+static void K_SpawnBrakeDriftSparks(player_t *player) // Be sure to update the mobj thinker case too!
+{
+	mobj_t *sparks;
+
+	I_Assert(player != NULL);
+	I_Assert(player->mo != NULL);
+	I_Assert(!P_MobjWasRemoved(player->mo));
+
+	// Position & etc are handled in its thinker, and its spawned invisible.
+	// This avoids needing to dupe code if we don't need it.
+	sparks = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BRAKEDRIFT);
+	P_SetTarget(&sparks->target, player->mo);
+	P_SetScale(sparks, (sparks->destscale = player->mo->scale));
+	K_MatchGenericExtraFlags(sparks, player->mo);
+	sparks->flags2 |= MF2_DONTDRAW;
+}
+
+/**	\brief	Calculates the respawn timer and drop-boosting
+
+	\param	player	player object passed from K_KartPlayerThink
+
+	\return	void
+*/
+void K_RespawnChecker(player_t *player)
+{
+	ticcmd_t *cmd = &player->cmd;
+
+	if (player->spectator)
+		return;
+
+	if (player->kartstuff[k_respawn] > 1)
+	{
+		player->kartstuff[k_respawn]--;
+		player->mo->momz = 0;
+		player->powers[pw_flashing] = 2;
+		player->powers[pw_nocontrol] = 2;
+		if (leveltime % 8 == 0)
+		{
+			INT32 i;
+			if (!mapreset)
+				S_StartSound(player->mo, sfx_s3kcas);
+
+			for (i = 0; i < 8; i++)
+			{
+				mobj_t *mo;
+				angle_t newangle;
+				fixed_t newx, newy, newz;
+
+				newangle = FixedAngle(((360/8)*i)*FRACUNIT);
+				newx = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31<<FRACBITS); // does NOT use scale, since this effect doesn't scale properly
+				newy = player->mo->y + P_ReturnThrustY(player->mo, newangle, 31<<FRACBITS);
+				if (player->mo->eflags & MFE_VERTICALFLIP)
+					newz = player->mo->z + player->mo->height;
+				else
+					newz = player->mo->z;
+
+				mo = P_SpawnMobj(newx, newy, newz, MT_DEZLASER);
+				if (mo)
+				{
+					if (player->mo->eflags & MFE_VERTICALFLIP)
+						mo->eflags |= MFE_VERTICALFLIP;
+					P_SetTarget(&mo->target, player->mo);
+					mo->angle = newangle+ANGLE_90;
+					mo->momz = (8*FRACUNIT)*P_MobjFlip(player->mo);
+					P_SetScale(mo, (mo->destscale = FRACUNIT));
+				}
+			}
+		}
+	}
+	else if (player->kartstuff[k_respawn] == 1)
+	{
+		if (player->kartstuff[k_growshrinktimer] < 0)
+		{
+			player->mo->scalespeed = mapobjectscale/TICRATE;
+			player->mo->destscale = (6*mapobjectscale)/8;
+			if (cv_kartdebugshrink.value && !modeattacking && !player->bot)
+				player->mo->destscale = (6*player->mo->destscale)/8;
+		}
+
+		if (!P_IsObjectOnGround(player->mo) && !mapreset)
+		{
+			player->powers[pw_flashing] = 2;
+
+			// Sal: The old behavior was stupid and prone to accidental usage.
+			// Let's rip off Mania instead, and turn this into a Drop Dash!
+
+			if (cmd->buttons & BT_ACCELERATE)
+				player->kartstuff[k_dropdash]++;
+			else
+				player->kartstuff[k_dropdash] = 0;
+
+			if (player->kartstuff[k_dropdash] == TICRATE/4)
+				S_StartSound(player->mo, sfx_ddash);
+
+			if ((player->kartstuff[k_dropdash] >= TICRATE/4)
+				&& (player->kartstuff[k_dropdash] & 1))
+				player->mo->colorized = true;
+			else
+				player->mo->colorized = false;
+		}
+		else
+		{
+			if ((cmd->buttons & BT_ACCELERATE) && (player->kartstuff[k_dropdash] >= TICRATE/4))
+			{
+				S_StartSound(player->mo, sfx_s23c);
+				player->kartstuff[k_startboost] = 50;
+				K_SpawnDashDustRelease(player);
+			}
+			player->mo->colorized = false;
+			player->kartstuff[k_dropdash] = 0;
+			player->kartstuff[k_respawn] = 0;
+		}
+	}
+}
+
+/**	\brief Handles the state changing for moving players, moved here to eliminate duplicate code
+
+	\param	player	player data
+
+	\return	void
+*/
+void K_KartMoveAnimation(player_t *player)
+{
+	ticcmd_t *cmd = &player->cmd;
+	// Standing frames - S_KART_STND1   S_KART_STND1_L   S_KART_STND1_R
+	if (player->speed == 0)
+	{
+		if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_STND1_R] && player->mo->state <= &states[S_KART_STND2_R]))
+			P_SetPlayerMobjState(player->mo, S_KART_STND1_R);
+		else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_STND1_L] && player->mo->state <= &states[S_KART_STND2_L]))
+			P_SetPlayerMobjState(player->mo, S_KART_STND1_L);
+		else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_STND1] && player->mo->state <= &states[S_KART_STND2]))
+			P_SetPlayerMobjState(player->mo, S_KART_STND1);
+	}
+	// Drifting Left - S_KART_DRIFT1_L
+	else if (player->kartstuff[k_drift] > 0 && P_IsObjectOnGround(player->mo))
+	{
+		if (!(player->mo->state >= &states[S_KART_DRIFT1_L] && player->mo->state <= &states[S_KART_DRIFT2_L]))
+			P_SetPlayerMobjState(player->mo, S_KART_DRIFT1_L);
+	}
+	// Drifting Right - S_KART_DRIFT1_R
+	else if (player->kartstuff[k_drift] < 0 && P_IsObjectOnGround(player->mo))
+	{
+		if (!(player->mo->state >= &states[S_KART_DRIFT1_R] && player->mo->state <= &states[S_KART_DRIFT2_R]))
+			P_SetPlayerMobjState(player->mo, S_KART_DRIFT1_R);
+	}
+	// Run frames - S_KART_RUN1   S_KART_RUN1_L   S_KART_RUN1_R
+	else if (player->speed > FixedMul(player->runspeed, player->mo->scale))
+	{
+		if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_RUN1_R] && player->mo->state <= &states[S_KART_RUN2_R]))
+			P_SetPlayerMobjState(player->mo, S_KART_RUN1_R);
+		else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_RUN1_L] && player->mo->state <= &states[S_KART_RUN2_L]))
+			P_SetPlayerMobjState(player->mo, S_KART_RUN1_L);
+		else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_RUN1] && player->mo->state <= &states[S_KART_RUN2]))
+			P_SetPlayerMobjState(player->mo, S_KART_RUN1);
+	}
+	// Walk frames - S_KART_WALK1   S_KART_WALK1_L   S_KART_WALK1_R
+	else if (player->speed <= FixedMul(player->runspeed, player->mo->scale))
+	{
+		if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_WALK1_R] && player->mo->state <= &states[S_KART_WALK2_R]))
+			P_SetPlayerMobjState(player->mo, S_KART_WALK1_R);
+		else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_WALK1_L] && player->mo->state <= &states[S_KART_WALK2_L]))
+			P_SetPlayerMobjState(player->mo, S_KART_WALK1_L);
+		else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_WALK1] && player->mo->state <= &states[S_KART_WALK2]))
+			P_SetPlayerMobjState(player->mo, S_KART_WALK1);
+	}
+}
+
+static void K_TauntVoiceTimers(player_t *player)
+{
+	if (!player)
+		return;
+
+	player->kartstuff[k_tauntvoices] = 6*TICRATE;
+	player->kartstuff[k_voices] = 4*TICRATE;
+}
+
+static void K_RegularVoiceTimers(player_t *player)
+{
+	if (!player)
+		return;
+
+	player->kartstuff[k_voices] = 4*TICRATE;
+
+	if (player->kartstuff[k_tauntvoices] < 4*TICRATE)
+		player->kartstuff[k_tauntvoices] = 4*TICRATE;
+}
+
+void K_PlayAttackTaunt(mobj_t *source)
+{
+	sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons
+	boolean tasteful = (!source->player || !source->player->kartstuff[k_tauntvoices]);
+
+	if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2))
+		S_StartSound(source, sfx_kattk1+pick);
+
+	if (!tasteful)
+		return;
+
+	K_TauntVoiceTimers(source->player);
+}
+
+void K_PlayBoostTaunt(mobj_t *source)
+{
+	sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons
+	boolean tasteful = (!source->player || !source->player->kartstuff[k_tauntvoices]);
+
+	if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2))
+		S_StartSound(source, sfx_kbost1+pick);
+
+	if (!tasteful)
+		return;
+
+	K_TauntVoiceTimers(source->player);
+}
+
+void K_PlayOvertakeSound(mobj_t *source)
+{
+	boolean tasteful = (!source->player || !source->player->kartstuff[k_voices]);
+
+	if (!G_RaceGametype()) // Only in race
+		return;
+
+	// 4 seconds from before race begins, 10 seconds afterwards
+	if (leveltime < starttime+(10*TICRATE))
+		return;
+
+	if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2))
+		S_StartSound(source, sfx_kslow);
+
+	if (!tasteful)
+		return;
+
+	K_RegularVoiceTimers(source->player);
+}
+
+void K_PlayHitEmSound(mobj_t *source)
+{
+	if (cv_kartvoices.value)
+		S_StartSound(source, sfx_khitem);
+	else
+		S_StartSound(source, sfx_s1c9); // The only lost gameplay functionality with voices disabled
+
+	K_RegularVoiceTimers(source->player);
+}
+
+void K_PlayPowerGloatSound(mobj_t *source)
+{
+	if (cv_kartvoices.value)
+		S_StartSound(source, sfx_kgloat);
+
+	K_RegularVoiceTimers(source->player);
+}
+
+void K_MomentumToFacing(player_t *player)
+{
+	angle_t dangle = player->mo->angle - R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
+
+	if (dangle > ANGLE_180)
+		dangle = InvAngle(dangle);
+
+	// If you aren't on the ground or are moving in too different of a direction don't do this
+	if (player->mo->eflags & MFE_JUSTHITFLOOR)
+		; // Just hit floor ALWAYS redirects
+	else if (!P_IsObjectOnGround(player->mo) || dangle > ANGLE_90)
+		return;
+
+	P_Thrust(player->mo, player->mo->angle, player->speed - FixedMul(player->speed, player->mo->friction));
+	player->mo->momx = FixedMul(player->mo->momx - player->cmomx, player->mo->friction) + player->cmomx;
+	player->mo->momy = FixedMul(player->mo->momy - player->cmomy, player->mo->friction) + player->cmomy;
+}
+
+// sets k_boostpower, k_speedboost, and k_accelboost to whatever we need it to be
+static void K_GetKartBoostPower(player_t *player)
+{
+	fixed_t boostpower = FRACUNIT;
+	fixed_t speedboost = 0, accelboost = 0;
+
+	if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow] == 1) // Slow down after you've been bumped
+	{
+		player->kartstuff[k_boostpower] = player->kartstuff[k_speedboost] = player->kartstuff[k_accelboost] = 0;
+		return;
+	}
+
+	// Offroad is separate, it's difficult to factor it in with a variable value anyway.
+	if (!(player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || player->kartstuff[k_sneakertimer])
+		&& player->kartstuff[k_offroad] >= 0)
+		boostpower = FixedDiv(boostpower, player->kartstuff[k_offroad] + FRACUNIT);
+
+	if (player->kartstuff[k_bananadrag] > TICRATE)
+		boostpower = (4*boostpower)/5;
+
+	// Banana drag/offroad dust
+	if (boostpower < FRACUNIT
+		&& player->mo && P_IsObjectOnGround(player->mo)
+		&& player->speed > 0
+		&& !player->spectator)
+	{
+		K_SpawnWipeoutTrail(player->mo, true);
+		if (leveltime % 6 == 0)
+			S_StartSound(player->mo, sfx_cdfm70);
+	}
+
+	if (player->kartstuff[k_sneakertimer]) // Sneaker
+	{
+		switch (gamespeed)
+		{
+			case 0:
+				speedboost = max(speedboost, 53740+768);
+				break;
+			case 2:
+				speedboost = max(speedboost, 17294+768);
+				break;
+			default:
+				speedboost = max(speedboost, 32768);
+				break;
+		}
+		accelboost = max(accelboost, 8*FRACUNIT); // + 800%
+	}
+
+	if (player->kartstuff[k_invincibilitytimer]) // Invincibility
+	{
+		speedboost = max(speedboost, 3*FRACUNIT/8); // + 37.5%
+		accelboost = max(accelboost, 3*FRACUNIT); // + 300%
+	}
+
+	if (player->kartstuff[k_growshrinktimer] > 0) // Grow
+	{
+		speedboost = max(speedboost, FRACUNIT/5); // + 20%
+	}
+
+	if (player->kartstuff[k_driftboost]) // Drift Boost
+	{
+		speedboost = max(speedboost, FRACUNIT/4); // + 25%
+		accelboost = max(accelboost, 4*FRACUNIT); // + 400%
+	}
+
+	if (player->kartstuff[k_startboost]) // Startup Boost
+	{
+		speedboost = max(speedboost, FRACUNIT/4); // + 25%
+		accelboost = max(accelboost, 6*FRACUNIT); // + 300%
+	}
+
+	// don't average them anymore, this would make a small boost and a high boost less useful
+	// just take the highest we want instead
+
+	player->kartstuff[k_boostpower] = boostpower;
+
+	// value smoothing
+	if (speedboost > player->kartstuff[k_speedboost])
+		player->kartstuff[k_speedboost] = speedboost;
+	else
+		player->kartstuff[k_speedboost] += (speedboost - player->kartstuff[k_speedboost])/(TICRATE/2);
+
+	player->kartstuff[k_accelboost] = accelboost;
+}
+
+fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower)
+{
+	fixed_t k_speed = 150;
+	fixed_t g_cc = FRACUNIT;
+	fixed_t xspd = 3072;		// 4.6875 aka 3/64
+	UINT8 kartspeed = player->kartspeed;
+	fixed_t finalspeed;
+
+	if (doboostpower && !player->kartstuff[k_pogospring] && !P_IsObjectOnGround(player->mo))
+		return (75*mapobjectscale); // air speed cap
+
+	switch (gamespeed)
+	{
+		case 0:
+			g_cc = 53248 + xspd; //  50cc =  81.25 + 4.69 =  85.94%
+			break;
+		case 2:
+			g_cc = 77824 + xspd; // 150cc = 118.75 + 4.69 = 123.44%
+			break;
+		default:
+			g_cc = 65536 + xspd; // 100cc = 100.00 + 4.69 = 104.69%
+			break;
+	}
+
+	if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)
+		kartspeed = 1;
+
+	k_speed += kartspeed*3; // 153 - 177
+
+	finalspeed = FixedMul(FixedMul(k_speed<<14, g_cc), player->mo->scale);
+
+	if (doboostpower)
+		return FixedMul(finalspeed, player->kartstuff[k_boostpower]+player->kartstuff[k_speedboost]);
+	return finalspeed;
+}
+
+fixed_t K_GetKartAccel(player_t *player)
+{
+	fixed_t k_accel = 32; // 36;
+	UINT8 kartspeed = player->kartspeed;
+
+	if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)
+		kartspeed = 1;
+
+	//k_accel += 3 * (9 - kartspeed); // 36 - 60
+	k_accel += 4 * (9 - kartspeed); // 32 - 64
+
+	return FixedMul(k_accel, FRACUNIT+player->kartstuff[k_accelboost]);
+}
+
+UINT16 K_GetKartFlashing(player_t *player)
+{
+    UINT16 tics = flashingtics;
+    if (G_BattleGametype())
+        tics *= 2;
+    tics += (flashingtics/8) * (player->kartspeed);
+    return tics;
+}
+
+fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove)
+{
+	fixed_t accelmax = 4000;
+	fixed_t newspeed, oldspeed, finalspeed;
+	fixed_t p_speed = K_GetKartSpeed(player, true);
+	fixed_t p_accel = K_GetKartAccel(player);
+
+	if (!onground) return 0; // If the player isn't on the ground, there is no change in speed
+
+	// ACCELCODE!!!1!11!
+	oldspeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy); // FixedMul(P_AproxDistance(player->rmomx, player->rmomy), player->mo->scale);
+	newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), ORIG_FRICTION);
+
+	if (player->kartstuff[k_pogospring]) // Pogo Spring minimum/maximum thrust
+	{
+		const fixed_t hscale = mapobjectscale /*+ (mapobjectscale - player->mo->scale)*/;
+		const fixed_t minspeed = 24*hscale;
+		const fixed_t maxspeed = 28*hscale;
+
+		if (newspeed > maxspeed && player->kartstuff[k_pogospring] == 2)
+			newspeed = maxspeed;
+		if (newspeed < minspeed)
+			newspeed = minspeed;
+	}
+
+	finalspeed = newspeed - oldspeed;
+
+	// forwardmove is:
+	//  50 while accelerating,
+	//  25 while clutching,
+	//   0 with no gas, and
+	// -25 when only braking.
+
+	finalspeed *= forwardmove/25;
+	finalspeed /= 2;
+
+	if (forwardmove < 0 && finalspeed > FRACUNIT*2)
+		return finalspeed/2;
+	else if (forwardmove < 0)
+		return -FRACUNIT/2;
+
+	if (finalspeed < 0)
+		finalspeed = 0;
+
+	return finalspeed;
+}
+
+void K_DoInstashield(player_t *player)
+{
+	mobj_t *layera;
+	mobj_t *layerb;
+
+	if (player->kartstuff[k_instashield] > 0)
+		return;
+
+	player->kartstuff[k_instashield] = 15; // length of instashield animation
+	S_StartSound(player->mo, sfx_cdpcm9);
+
+	layera = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDA);
+	P_SetTarget(&layera->target, player->mo);
+
+	layerb = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDB);
+	P_SetTarget(&layerb->target, player->mo);
+}
+
+void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount)
+{
+	statenum_t st;
+	mobj_t *pt;
+
+	if (!source || !source->mo)
+		return;
+
+	if (amount == 1)
+		st = S_BATTLEPOINT1A;
+	else if (amount == 2)
+		st = S_BATTLEPOINT2A;
+	else if (amount == 3)
+		st = S_BATTLEPOINT3A;
+	else
+		return; // NO STATE!
+
+	pt = P_SpawnMobj(source->mo->x, source->mo->y, source->mo->z, MT_BATTLEPOINT);
+	P_SetTarget(&pt->target, source->mo);
+	P_SetMobjState(pt, st);
+	if (victim && victim->skincolor)
+		pt->color = victim->skincolor;
+	else
+		pt->color = source->skincolor;
+}
+
+void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, mobj_t *inflictor, boolean trapitem)
+{
+	UINT8 scoremultiply = 1;
+	// PS: Inflictor is unused for all purposes here and is actually only ever relevant to Lua. It may be nil too.
+#ifdef HAVE_BLUA
+	boolean force = false;	// Used to check if Lua ShouldSpin should get us damaged reguardless of flashtics or heck knows what.
+	UINT8 shouldForce = LUAh_ShouldSpin(player, inflictor, source);
+	if (P_MobjWasRemoved(player->mo))
+		return; // mobj was removed (in theory that shouldn't happen)
+	if (shouldForce == 1)
+		force = true;
+	else if (shouldForce == 2)
+		return;
+#else
+	static const boolean force = false;
+	(void)inflictor;	// in case some weirdo doesn't want Lua.
+#endif
+
+
+	if (!trapitem && G_BattleGametype())
+	{
+		if (K_IsPlayerWanted(player))
+			scoremultiply = 3;
+		else if (player->kartstuff[k_bumper] == 1)
+			scoremultiply = 2;
+	}
+
+	if (player->health <= 0)
+		return;
+
+	if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0
+		|| player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
+		|| (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1)))
+	{
+		if (!force)	// if shoulddamage force, we go THROUGH that.
+		{
+			K_DoInstashield(player);
+			return;
+		}
+	}
+
+	if (LUAh_PlayerSpin(player, inflictor, source))	// Let Lua do its thing or overwrite if it wants to. Make sure to let any possible instashield happen because we didn't get "damaged" in this case.
+		return;
+
+	if (source && source != player->mo && source->player)
+		K_PlayHitEmSound(source);
+
+	//player->kartstuff[k_sneakertimer] = 0;
+	player->kartstuff[k_driftboost] = 0;
+
+	player->kartstuff[k_drift] = 0;
+	player->kartstuff[k_driftcharge] = 0;
+	player->kartstuff[k_pogospring] = 0;
+
+	if (G_BattleGametype())
+	{
+		if (source && source->player && player != source->player)
+		{
+			P_AddPlayerScore(source->player, scoremultiply);
+			K_SpawnBattlePoints(source->player, player, scoremultiply);
+			if (!trapitem)
+			{
+				source->player->kartstuff[k_wanted] -= wantedreduce;
+				player->kartstuff[k_wanted] -= (wantedreduce/2);
+			}
+		}
+
+		if (player->kartstuff[k_bumper] > 0)
+		{
+			if (player->kartstuff[k_bumper] == 1)
+			{
+				mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!!
+				P_SetTarget(&karmahitbox->target, player->mo);
+				karmahitbox->destscale = player->mo->scale;
+				P_SetScale(karmahitbox, player->mo->scale);
+				CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]);
+			}
+			player->kartstuff[k_bumper]--;
+			if (K_IsPlayerWanted(player))
+				K_CalculateBattleWanted();
+		}
+
+		if (!player->kartstuff[k_bumper])
+		{
+			player->kartstuff[k_comebacktimer] = comebacktime;
+			if (player->kartstuff[k_comebackmode] == 2)
+			{
+				mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE);
+				S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound);
+				player->kartstuff[k_comebackmode] = 0;
+			}
+		}
+
+		K_CheckBumpers();
+	}
+
+	player->kartstuff[k_spinouttype] = type;
+
+	if (player->kartstuff[k_spinouttype] <= 0) // type 0 is spinout, type 1 is wipeout
+	{
+		// At spinout, player speed is increased to 1/4 their regular speed, moving them forward
+		if (player->speed < K_GetKartSpeed(player, true)/4)
+			P_InstaThrust(player->mo, player->mo->angle, FixedMul(K_GetKartSpeed(player, true)/4, player->mo->scale));
+		S_StartSound(player->mo, sfx_slip);
+	}
+
+	player->kartstuff[k_spinouttimer] = (3*TICRATE/2)+2;
+	player->powers[pw_flashing] = K_GetKartFlashing(player);
+
+	if (player->mo->state != &states[S_KART_SPIN])
+		P_SetPlayerMobjState(player->mo, S_KART_SPIN);
+
+	player->kartstuff[k_instashield] = 15;
+	if (cv_kartdebughuddrop.value && !modeattacking)
+		K_DropItems(player);
+	else
+		K_DropHnextList(player);
+	return;
+}
+
+static void K_RemoveGrowShrink(player_t *player)
+{
+	player->kartstuff[k_growshrinktimer] = 0;
+	if (player->kartstuff[k_invincibilitytimer] == 0)
+		player->mo->color = player->skincolor;
+	player->mo->scalespeed = mapobjectscale/TICRATE;
+	player->mo->destscale = mapobjectscale;
+	if (cv_kartdebugshrink.value && !modeattacking && !player->bot)
+		player->mo->destscale = (6*player->mo->destscale)/8;
+	P_RestoreMusic(player);
+}
+
+void K_SquishPlayer(player_t *player, mobj_t *source, mobj_t *inflictor)
+{
+	UINT8 scoremultiply = 1;
+	// PS: Inflictor is unused for all purposes here and is actually only ever relevant to Lua. It may be nil too.
+#ifdef HAVE_BLUA
+	boolean force = false;	// Used to check if Lua ShouldSquish should get us damaged reguardless of flashtics or heck knows what.
+	UINT8 shouldForce = LUAh_ShouldSquish(player, inflictor, source);
+	if (P_MobjWasRemoved(player->mo))
+		return; // mobj was removed (in theory that shouldn't happen)
+	if (shouldForce == 1)
+		force = true;
+	else if (shouldForce == 2)
+		return;
+#else
+	static const boolean force = false;
+	(void)inflictor;	// Please stop forgetting to put inflictor in yer functions thank -Lat'
+#endif
+
+	if (G_BattleGametype())
+	{
+		if (K_IsPlayerWanted(player))
+			scoremultiply = 3;
+		else if (player->kartstuff[k_bumper] == 1)
+			scoremultiply = 2;
+	}
+
+	if (player->health <= 0)
+		return;
+
+	if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_invincibilitytimer] > 0
+		|| player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
+		|| (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1)))
+	{
+		if (!force)	// You know the drill by now.
+		{
+			K_DoInstashield(player);
+			return;
+		}
+	}
+
+	if (LUAh_PlayerSquish(player, inflictor, source))	// Let Lua do its thing or overwrite if it wants to. Make sure to let any possible instashield happen because we didn't get "damaged" in this case.
+		return;
+
+	player->kartstuff[k_sneakertimer] = 0;
+	player->kartstuff[k_driftboost] = 0;
+
+	player->kartstuff[k_drift] = 0;
+	player->kartstuff[k_driftcharge] = 0;
+	player->kartstuff[k_pogospring] = 0;
+
+	if (G_BattleGametype())
+	{
+		if (source && source->player && player != source->player)
+		{
+			P_AddPlayerScore(source->player, scoremultiply);
+			K_SpawnBattlePoints(source->player, player, scoremultiply);
+			source->player->kartstuff[k_wanted] -= wantedreduce;
+			player->kartstuff[k_wanted] -= (wantedreduce/2);
+		}
+
+		if (player->kartstuff[k_bumper] > 0)
+		{
+			if (player->kartstuff[k_bumper] == 1)
+			{
+				mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!!
+				P_SetTarget(&karmahitbox->target, player->mo);
+				karmahitbox->destscale = player->mo->scale;
+				P_SetScale(karmahitbox, player->mo->scale);
+				CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]);
+			}
+			player->kartstuff[k_bumper]--;
+			if (K_IsPlayerWanted(player))
+				K_CalculateBattleWanted();
+		}
+
+		if (!player->kartstuff[k_bumper])
+		{
+			player->kartstuff[k_comebacktimer] = comebacktime;
+			if (player->kartstuff[k_comebackmode] == 2)
+			{
+				mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE);
+				S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound);
+				player->kartstuff[k_comebackmode] = 0;
+			}
+		}
+
+		K_CheckBumpers();
+	}
+
+	player->kartstuff[k_squishedtimer] = TICRATE;
+
+	// Reduce Shrink timer
+	if (player->kartstuff[k_growshrinktimer] < 0)
+	{
+		player->kartstuff[k_growshrinktimer] += TICRATE;
+		if (player->kartstuff[k_growshrinktimer] >= 0)
+			K_RemoveGrowShrink(player);
+	}
+
+	player->powers[pw_flashing] = K_GetKartFlashing(player);
+
+	player->mo->flags |= MF_NOCLIP;
+
+	if (player->mo->state != &states[S_KART_SQUISH]) // Squash
+		P_SetPlayerMobjState(player->mo, S_KART_SQUISH);
+
+	P_PlayRinglossSound(player->mo);
+
+	player->kartstuff[k_instashield] = 15;
+	if (cv_kartdebughuddrop.value && !modeattacking)
+		K_DropItems(player);
+	else
+		K_DropHnextList(player);
+	return;
+}
+
+void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor) // A bit of a hack, we just throw the player up higher here and extend their spinout timer
+{
+	UINT8 scoremultiply = 1;
+#ifdef HAVE_BLUA
+	boolean force = false;	// Used to check if Lua ShouldExplode should get us damaged reguardless of flashtics or heck knows what.
+	UINT8 shouldForce = LUAh_ShouldExplode(player, inflictor, source);
+	if (P_MobjWasRemoved(player->mo))
+		return; // mobj was removed (in theory that shouldn't happen)
+	if (shouldForce == 1)
+		force = true;
+	else if (shouldForce == 2)
+		return;
+
+#else
+	static const boolean force = false;
+#endif
+	if (G_BattleGametype())
+	{
+		if (K_IsPlayerWanted(player))
+			scoremultiply = 3;
+		else if (player->kartstuff[k_bumper] == 1)
+			scoremultiply = 2;
+	}
+
+	if (player->health <= 0)
+		return;
+
+	if (/*player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0 // Explosions should combo, because of SPB and Eggman
+		||*/player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0
+		|| (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1)))
+	{
+		if (!force)	// ShouldDamage can bypass that, again.
+		{
+			K_DoInstashield(player);
+			return;
+		}
+	}
+
+	if (LUAh_PlayerExplode(player, inflictor, source))	// Same thing. Also make sure to let Instashield happen blah blah
+		return;
+
+	if (source && source != player->mo && source->player)
+		K_PlayHitEmSound(source);
+
+	player->mo->momz = 18*mapobjectscale;
+	player->mo->momx = player->mo->momy = 0;
+
+	player->kartstuff[k_sneakertimer] = 0;
+	player->kartstuff[k_driftboost] = 0;
+
+	player->kartstuff[k_drift] = 0;
+	player->kartstuff[k_driftcharge] = 0;
+	player->kartstuff[k_pogospring] = 0;
+
+	// This is the only part that SHOULDN'T combo :VVVVV
+	if (G_BattleGametype() && !(player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0))
+	{
+		if (source && source->player && player != source->player)
+		{
+			P_AddPlayerScore(source->player, scoremultiply);
+			K_SpawnBattlePoints(source->player, player, scoremultiply);
+			source->player->kartstuff[k_wanted] -= wantedreduce;
+			player->kartstuff[k_wanted] -= (wantedreduce/2);
+		}
+
+		if (player->kartstuff[k_bumper] > 0)
+		{
+			if (player->kartstuff[k_bumper] == 1)
+			{
+				mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!!
+				P_SetTarget(&karmahitbox->target, player->mo);
+				karmahitbox->destscale = player->mo->scale;
+				P_SetScale(karmahitbox, player->mo->scale);
+				CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]);
+			}
+			player->kartstuff[k_bumper]--;
+			if (K_IsPlayerWanted(player))
+				K_CalculateBattleWanted();
+		}
+
+		if (!player->kartstuff[k_bumper])
+		{
+			player->kartstuff[k_comebacktimer] = comebacktime;
+			if (player->kartstuff[k_comebackmode] == 2)
+			{
+				mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE);
+				S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound);
+				player->kartstuff[k_comebackmode] = 0;
+			}
+		}
+
+		K_CheckBumpers();
+	}
+
+	player->kartstuff[k_spinouttype] = 1;
+	player->kartstuff[k_spinouttimer] = 2*TICRATE+(TICRATE/2);
+
+	player->powers[pw_flashing] = K_GetKartFlashing(player);
+
+	if (inflictor && inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1)
+	{
+		player->kartstuff[k_spinouttimer] = ((3*player->kartstuff[k_spinouttimer])/2)+1;
+		player->mo->momz *= 2;
+	}
+
+	if (player->mo->state != &states[S_KART_SPIN])
+		P_SetPlayerMobjState(player->mo, S_KART_SPIN);
+
+	P_PlayRinglossSound(player->mo);
+
+	if (P_IsLocalPlayer(player))
+	{
+		quake.intensity = 64*FRACUNIT;
+		quake.time = 5;
+	}
+
+	player->kartstuff[k_instashield] = 15;
+	K_DropItems(player);
+
+	return;
+}
+
+void K_StealBumper(player_t *player, player_t *victim, boolean force)
+{
+	INT32 newbumper;
+	angle_t newangle, diff;
+	fixed_t newx, newy;
+	mobj_t *newmo;
+
+	if (!G_BattleGametype())
+		return;
+
+	if (player->health <= 0 || victim->health <= 0)
+		return;
+
+	if (!force)
+	{
+		if (victim->kartstuff[k_bumper] <= 0) // || player->kartstuff[k_bumper] >= cv_kartbumpers.value+2
+			return;
+
+		if (player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0)
+			return;
+
+		if (victim->powers[pw_flashing] > 0 || victim->kartstuff[k_squishedtimer] > 0 || victim->kartstuff[k_spinouttimer] > 0
+			|| victim->kartstuff[k_invincibilitytimer] > 0 || victim->kartstuff[k_growshrinktimer] > 0 || victim->kartstuff[k_hyudorotimer] > 0)
+		{
+			K_DoInstashield(victim);
+			return;
+		}
+	}
+
+	if (netgame && player->kartstuff[k_bumper] <= 0)
+		CONS_Printf(M_GetText("%s is back in the game!\n"), player_names[player-players]);
+
+	newbumper = player->kartstuff[k_bumper];
+	if (newbumper <= 1)
+		diff = 0;
+	else
+		diff = FixedAngle(360*FRACUNIT/newbumper);
+
+	newangle = player->mo->angle;
+	newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, 64*FRACUNIT);
+	newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, 64*FRACUNIT);
+
+	newmo = P_SpawnMobj(newx, newy, player->mo->z, MT_BATTLEBUMPER);
+	newmo->threshold = newbumper;
+	P_SetTarget(&newmo->tracer, victim->mo);
+	P_SetTarget(&newmo->target, player->mo);
+	newmo->angle = (diff * (newbumper-1));
+	newmo->color = victim->skincolor;
+
+	if (newbumper+1 < 2)
+		P_SetMobjState(newmo, S_BATTLEBUMPER3);
+	else if (newbumper+1 < 3)
+		P_SetMobjState(newmo, S_BATTLEBUMPER2);
+	else
+		P_SetMobjState(newmo, S_BATTLEBUMPER1);
+
+	S_StartSound(player->mo, sfx_3db06);
+
+	player->kartstuff[k_bumper]++;
+	player->kartstuff[k_comebackpoints] = 0;
+	player->powers[pw_flashing] = K_GetKartFlashing(player);
+	player->kartstuff[k_comebacktimer] = comebacktime;
+
+	/*victim->powers[pw_flashing] = K_GetKartFlashing(victim);
+	victim->kartstuff[k_comebacktimer] = comebacktime;*/
+
+	victim->kartstuff[k_instashield] = 15;
+	if (cv_kartdebughuddrop.value && !modeattacking)
+		K_DropItems(victim);
+	else
+		K_DropHnextList(victim);
+	return;
+}
+
+// source is the mobj that originally threw the bomb that exploded etc.
+// Spawns the sphere around the explosion that handles spinout
+void K_SpawnKartExplosion(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle, boolean spawncenter, boolean ghostit, mobj_t *source)
+{
+	mobj_t *mobj;
+	mobj_t *ghost = NULL;
+	INT32 i;
+	TVector v;
+	TVector *res;
+	fixed_t finalx, finaly, finalz, dist;
+	//mobj_t hoopcenter;
+	angle_t degrees, fa, closestangle;
+	fixed_t mobjx, mobjy, mobjz;
+
+	//hoopcenter.x = x;
+	//hoopcenter.y = y;
+	//hoopcenter.z = z;
+
+	//hoopcenter.z = z - mobjinfo[type].height/2;
+
+	degrees = FINEANGLES/number;
+
+	closestangle = 0;
+
+	// Create the hoop!
+	for (i = 0; i < number; i++)
+	{
+		fa = (i*degrees);
+		v[0] = FixedMul(FINECOSINE(fa),radius);
+		v[1] = 0;
+		v[2] = FixedMul(FINESINE(fa),radius);
+		v[3] = FRACUNIT;
+
+		res = VectorMatrixMultiply(v, *RotateXMatrix(rotangle));
+		M_Memcpy(&v, res, sizeof (v));
+		res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle));
+		M_Memcpy(&v, res, sizeof (v));
+
+		finalx = x + v[0];
+		finaly = y + v[1];
+		finalz = z + v[2];
+
+		mobj = P_SpawnMobj(finalx, finaly, finalz, type);
+
+		mobj->z -= mobj->height>>1;
+
+		// change angle
+		mobj->angle = R_PointToAngle2(mobj->x, mobj->y, x, y);
+
+		// change slope
+		dist = P_AproxDistance(P_AproxDistance(x - mobj->x, y - mobj->y), z - mobj->z);
+
+		if (dist < 1)
+			dist = 1;
+
+		mobjx = mobj->x;
+		mobjy = mobj->y;
+		mobjz = mobj->z;
+
+		if (ghostit)
+		{
+			ghost = P_SpawnGhostMobj(mobj);
+			P_SetMobjState(mobj, S_NULL);
+			mobj = ghost;
+		}
+
+		if (spawncenter)
+		{
+			mobj->x = x;
+			mobj->y = y;
+			mobj->z = z;
+		}
+
+		mobj->momx = FixedMul(FixedDiv(mobjx - x, dist), FixedDiv(dist, 6*FRACUNIT));
+		mobj->momy = FixedMul(FixedDiv(mobjy - y, dist), FixedDiv(dist, 6*FRACUNIT));
+		mobj->momz = FixedMul(FixedDiv(mobjz - z, dist), FixedDiv(dist, 6*FRACUNIT));
+
+		if (source && !P_MobjWasRemoved(source))
+			P_SetTarget(&mobj->target, source);
+	}
+}
+
+// Spawns the purely visual explosion
+void K_SpawnMineExplosion(mobj_t *source, UINT8 color)
+{
+	INT32 i, radius, height;
+	mobj_t *smoldering = P_SpawnMobj(source->x, source->y, source->z, MT_SMOLDERING);
+	mobj_t *dust;
+	mobj_t *truc;
+	INT32 speed, speed2;
+
+	smoldering->tics = TICRATE*3;
+	radius = source->radius>>FRACBITS;
+	height = source->height>>FRACBITS;
+
+	if (!color)
+		color = SKINCOLOR_KETCHUP;
+
+	for (i = 0; i < 32; i++)
+	{
+		dust = P_SpawnMobj(source->x, source->y, source->z, MT_SMOKE);
+		dust->angle = (ANGLE_180/16) * i;
+		P_SetScale(dust, source->scale);
+		dust->destscale = source->scale*10;
+		dust->scalespeed = source->scale/12;
+		P_InstaThrust(dust, dust->angle, FixedMul(20*FRACUNIT, source->scale));
+
+		truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT,
+			source->y + P_RandomRange(-radius, radius)*FRACUNIT,
+			source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMEXPLODE);
+		P_SetScale(truc, source->scale);
+		truc->destscale = source->scale*6;
+		truc->scalespeed = source->scale/12;
+		speed = FixedMul(10*FRACUNIT, source->scale)>>FRACBITS;
+		truc->momx = P_RandomRange(-speed, speed)*FRACUNIT;
+		truc->momy = P_RandomRange(-speed, speed)*FRACUNIT;
+		speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS;
+		truc->momz = P_RandomRange(-speed, speed)*FRACUNIT;
+		truc->color = color;
+	}
+
+	for (i = 0; i < 16; i++)
+	{
+		dust = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT,
+			source->y + P_RandomRange(-radius, radius)*FRACUNIT,
+			source->z + P_RandomRange(0, height)*FRACUNIT, MT_SMOKE);
+		P_SetScale(dust, source->scale);
+		dust->destscale = source->scale*10;
+		dust->scalespeed = source->scale/12;
+		dust->tics = 30;
+		dust->momz = P_RandomRange(FixedMul(3*FRACUNIT, source->scale)>>FRACBITS, FixedMul(7*FRACUNIT, source->scale)>>FRACBITS)*FRACUNIT;
+
+		truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT,
+			source->y + P_RandomRange(-radius, radius)*FRACUNIT,
+			source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMPARTICLE);
+		P_SetScale(truc, source->scale);
+		truc->destscale = source->scale*5;
+		truc->scalespeed = source->scale/12;
+		speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS;
+		truc->momx = P_RandomRange(-speed, speed)*FRACUNIT;
+		truc->momy = P_RandomRange(-speed, speed)*FRACUNIT;
+		speed = FixedMul(15*FRACUNIT, source->scale)>>FRACBITS;
+		speed2 = FixedMul(45*FRACUNIT, source->scale)>>FRACBITS;
+		truc->momz = P_RandomRange(speed, speed2)*FRACUNIT;
+		if (P_RandomChance(FRACUNIT/2))
+			truc->momz = -truc->momz;
+		truc->tics = TICRATE*2;
+		truc->color = color;
+	}
+}
+
+static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed)
+{
+	mobj_t *th;
+	fixed_t x, y, z;
+	fixed_t finalspeed = speed;
+	mobj_t *throwmo;
+
+	if (source->player && source->player->speed > K_GetKartSpeed(source->player, false))
+	{
+		angle_t input = source->angle - an;
+		boolean invert = (input > ANGLE_180);
+		if (invert)
+			input = InvAngle(input);
+
+		finalspeed = max(speed, FixedMul(speed, FixedMul(
+			FixedDiv(source->player->speed, K_GetKartSpeed(source->player, false)), // Multiply speed to be proportional to your own, boosted maxspeed.
+			(((180<<FRACBITS) - AngleFixed(input)) / 180) // multiply speed based on angle diff... i.e: don't do this for firing backward :V
+			)));
+	}
+
+	x = source->x + source->momx + FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT));
+	y = source->y + source->momy + FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT));
+	z = source->z; // spawn on the ground please
+
+	if (P_MobjFlip(source) < 0)
+	{
+		z = source->z+source->height - mobjinfo[type].height;
+	}
+
+	th = P_SpawnMobj(x, y, z, type);
+
+	th->flags2 |= flags2;
+
+	th->threshold = 10;
+
+	if (th->info->seesound)
+		S_StartSound(source, th->info->seesound);
+
+	P_SetTarget(&th->target, source);
+
+	if (P_IsObjectOnGround(source))
+	{
+		// floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn
+		// This should set it for FOFs
+		P_TeleportMove(th, th->x, th->y, th->z);
+		// spawn on the ground if the player is on the ground
+		if (P_MobjFlip(source) < 0)
+		{
+			th->z = th->ceilingz - th->height;
+			th->eflags |= MFE_VERTICALFLIP;
+		}
+		else
+			th->z = th->floorz;
+	}
+
+	th->angle = an;
+	th->momx = FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT));
+	th->momy = FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT));
+
+	switch (type)
+	{
+		case MT_ORBINAUT:
+			if (source && source->player)
+				th->color = source->player->skincolor;
+			else
+				th->color = SKINCOLOR_GREY;
+			th->movefactor = finalspeed;
+			break;
+		case MT_JAWZ:
+			if (source && source->player)
+			{
+				INT32 lasttarg = source->player->kartstuff[k_lastjawztarget];
+				th->cvmem = source->player->skincolor;
+				if ((lasttarg >= 0 && lasttarg < MAXPLAYERS)
+					&& playeringame[lasttarg]
+					&& !players[lasttarg].spectator
+					&& players[lasttarg].mo)
+				{
+					P_SetTarget(&th->tracer, players[lasttarg].mo);
+				}
+			}
+			else
+				th->cvmem = SKINCOLOR_KETCHUP;
+			/* FALLTHRU */
+		case MT_JAWZ_DUD:
+			S_StartSound(th, th->info->activesound);
+			/* FALLTHRU */
+		case MT_SPB:
+			th->movefactor = finalspeed;
+			break;
+		default:
+			break;
+	}
+
+	x = x + P_ReturnThrustX(source, an, source->radius + th->radius);
+	y = y + P_ReturnThrustY(source, an, source->radius + th->radius);
+	throwmo = P_SpawnMobj(x, y, z, MT_FIREDITEM);
+	throwmo->movecount = 1;
+	throwmo->movedir = source->angle - an;
+	P_SetTarget(&throwmo->target, source);
+
+	return NULL;
+}
+
+static void K_SpawnDriftSparks(player_t *player)
+{
+	fixed_t newx;
+	fixed_t newy;
+	mobj_t *spark;
+	angle_t travelangle;
+	INT32 i;
+
+	I_Assert(player != NULL);
+	I_Assert(player->mo != NULL);
+	I_Assert(!P_MobjWasRemoved(player->mo));
+
+	if (leveltime % 2 == 1)
+		return;
+
+	if (!P_IsObjectOnGround(player->mo))
+		return;
+
+	if (!player->kartstuff[k_drift] || player->kartstuff[k_driftcharge] < K_GetKartDriftSparkValue(player))
+		return;
+
+	travelangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift];
+
+	for (i = 0; i < 2; i++)
+	{
+		newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale));
+		newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale));
+		spark = P_SpawnMobj(newx, newy, player->mo->z, MT_DRIFTSPARK);
+
+		P_SetTarget(&spark->target, player->mo);
+		spark->angle = travelangle-(ANGLE_45/5)*player->kartstuff[k_drift];
+		spark->destscale = player->mo->scale;
+		P_SetScale(spark, player->mo->scale);
+
+		spark->momx = player->mo->momx/2;
+		spark->momy = player->mo->momy/2;
+		//spark->momz = player->mo->momz/2;
+
+		if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*4)
+		{
+			spark->color = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1)));
+		}
+		else if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*2)
+		{
+			if (player->kartstuff[k_driftcharge] <= (K_GetKartDriftSparkValue(player)*2)+(24*3))
+				spark->color = SKINCOLOR_RASPBERRY; // transition
+			else
+				spark->color = SKINCOLOR_KETCHUP;
+		}
+		else
+		{
+			spark->color = SKINCOLOR_SAPPHIRE;
+		}
+
+		if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn > 0) // Inward drifts
+			|| (player->kartstuff[k_drift] < 0 && player->cmd.driftturn < 0))
+		{
+			if ((player->kartstuff[k_drift] < 0 && (i & 1))
+				|| (player->kartstuff[k_drift] > 0 && !(i & 1)))
+				P_SetMobjState(spark, S_DRIFTSPARK_A1);
+			else if ((player->kartstuff[k_drift] < 0 && !(i & 1))
+				|| (player->kartstuff[k_drift] > 0 && (i & 1)))
+				P_SetMobjState(spark, S_DRIFTSPARK_C1);
+		}
+		else if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn < 0) // Outward drifts
+			|| (player->kartstuff[k_drift] < 0 && player->cmd.driftturn > 0))
+		{
+			if ((player->kartstuff[k_drift] < 0 && (i & 1))
+				|| (player->kartstuff[k_drift] > 0 && !(i & 1)))
+				P_SetMobjState(spark, S_DRIFTSPARK_C1);
+			else if ((player->kartstuff[k_drift] < 0 && !(i & 1))
+				|| (player->kartstuff[k_drift] > 0 && (i & 1)))
+				P_SetMobjState(spark, S_DRIFTSPARK_A1);
+		}
+
+		K_MatchGenericExtraFlags(spark, player->mo);
+	}
+}
+
+static void K_SpawnAIZDust(player_t *player)
+{
+	fixed_t newx;
+	fixed_t newy;
+	mobj_t *spark;
+	angle_t travelangle;
+
+	I_Assert(player != NULL);
+	I_Assert(player->mo != NULL);
+	I_Assert(!P_MobjWasRemoved(player->mo));
+
+	if (leveltime % 2 == 1)
+		return;
+
+	if (!P_IsObjectOnGround(player->mo))
+		return;
+
+	travelangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
+	//S_StartSound(player->mo, sfx_s3k47);
+
+	{
+		newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle - (player->kartstuff[k_aizdriftstrat]*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale));
+		newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle - (player->kartstuff[k_aizdriftstrat]*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale));
+		spark = P_SpawnMobj(newx, newy, player->mo->z, MT_AIZDRIFTSTRAT);
+
+		spark->angle = travelangle+(player->kartstuff[k_aizdriftstrat]*ANGLE_90);
+		P_SetScale(spark, (spark->destscale = (3*player->mo->scale)>>2));
+
+		spark->momx = (6*player->mo->momx)/5;
+		spark->momy = (6*player->mo->momy)/5;
+		//spark->momz = player->mo->momz/2;
+
+		K_MatchGenericExtraFlags(spark, player->mo);
+	}
+}
+
+void K_SpawnBoostTrail(player_t *player)
+{
+	fixed_t newx;
+	fixed_t newy;
+	fixed_t ground;
+	mobj_t *flame;
+	angle_t travelangle;
+	INT32 i;
+
+	I_Assert(player != NULL);
+	I_Assert(player->mo != NULL);
+	I_Assert(!P_MobjWasRemoved(player->mo));
+
+	if (!P_IsObjectOnGround(player->mo)
+		|| player->kartstuff[k_hyudorotimer] != 0
+		|| (G_BattleGametype() && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]))
+		return;
+
+	if (player->mo->eflags & MFE_VERTICALFLIP)
+		ground = player->mo->ceilingz - FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale);
+	else
+		ground = player->mo->floorz;
+
+	if (player->kartstuff[k_drift] != 0)
+		travelangle = player->mo->angle;
+	else
+		travelangle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy);
+
+	for (i = 0; i < 2; i++)
+	{
+		newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale));
+		newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale));
+#ifdef ESLOPE
+		if (player->mo->standingslope)
+		{
+			ground = P_GetZAt(player->mo->standingslope, newx, newy);
+			if (player->mo->eflags & MFE_VERTICALFLIP)
+				ground -= FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale);
+		}
+#endif
+		flame = P_SpawnMobj(newx, newy, ground, MT_SNEAKERTRAIL);
+
+		P_SetTarget(&flame->target, player->mo);
+		flame->angle = travelangle;
+		flame->fuse = TICRATE*2;
+		flame->destscale = player->mo->scale;
+		P_SetScale(flame, player->mo->scale);
+		flame->eflags = (flame->eflags & ~MFE_VERTICALFLIP)|(player->mo->eflags & MFE_VERTICALFLIP); // not K_MatchGenericExtraFlags so that a stolen sneaker can be seen
+
+		flame->momx = 8;
+		P_XYMovement(flame);
+		if (P_MobjWasRemoved(flame))
+			continue;
+
+		if (player->mo->eflags & MFE_VERTICALFLIP)
+		{
+			if (flame->z + flame->height < flame->ceilingz)
+				P_RemoveMobj(flame);
+		}
+		else if (flame->z > flame->floorz)
+			P_RemoveMobj(flame);
+	}
+}
+
+void K_SpawnSparkleTrail(mobj_t *mo)
+{
+	const INT32 rad = (mo->radius*2)>>FRACBITS;
+	mobj_t *sparkle;
+	INT32 i;
+
+	I_Assert(mo != NULL);
+	I_Assert(!P_MobjWasRemoved(mo));
+
+	for (i = 0; i < 3; i++)
+	{
+		fixed_t newx = mo->x + mo->momx + (P_RandomRange(-rad, rad)<<FRACBITS);
+		fixed_t newy = mo->y + mo->momy + (P_RandomRange(-rad, rad)<<FRACBITS);
+		fixed_t newz = mo->z + mo->momz + (P_RandomRange(0, mo->height>>FRACBITS)<<FRACBITS);
+
+		sparkle = P_SpawnMobj(newx, newy, newz, MT_SPARKLETRAIL);
+
+		//if (i == 0)
+			//P_SetMobjState(sparkle, S_KARTINVULN_LARGE1);
+
+		P_SetTarget(&sparkle->target, mo);
+		sparkle->destscale = mo->destscale;
+		P_SetScale(sparkle, mo->scale);
+		sparkle->eflags = (sparkle->eflags & ~MFE_VERTICALFLIP)|(mo->eflags & MFE_VERTICALFLIP); // not K_MatchGenericExtraFlags so that a stolen invincibility can be seen
+		sparkle->color = mo->color;
+		//sparkle->colorized = mo->colorized;
+	}
+
+	P_SetMobjState(sparkle, S_KARTINVULN_LARGE1);
+}
+
+void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent)
+{
+	mobj_t *dust;
+
+	I_Assert(mo != NULL);
+	I_Assert(!P_MobjWasRemoved(mo));
+
+	dust = P_SpawnMobj(mo->x + (P_RandomRange(-25,25) * mo->scale), mo->y + (P_RandomRange(-25,25) * mo->scale), mo->z, MT_WIPEOUTTRAIL);
+
+	P_SetTarget(&dust->target, mo);
+	dust->angle = R_PointToAngle2(0,0,mo->momx,mo->momy);
+	dust->destscale = mo->scale;
+	P_SetScale(dust, mo->scale);
+	dust->eflags = (dust->eflags & ~MFE_VERTICALFLIP)|(mo->eflags & MFE_VERTICALFLIP); // not K_MatchGenericExtraFlags because hyudoro shouldn't be able to wipeout
+
+	if (translucent) // offroad effect
+	{
+		dust->momx = mo->momx/2;
+		dust->momy = mo->momy/2;
+		dust->momz = mo->momz/2;
+	}
+
+	if (translucent)
+		dust->flags2 |= MF2_SHADOW;
+}
+
+//	K_DriftDustHandling
+//	Parameters:
+//		spawner: The map object that is spawning the drift dust
+//	Description: Spawns the drift dust for objects, players use rmomx/y, other objects use regular momx/y.
+//		Also plays the drift sound.
+//		Other objects should be angled towards where they're trying to go so they don't randomly spawn dust
+//		Do note that most of the function won't run in odd intervals of frames
+void K_DriftDustHandling(mobj_t *spawner)
+{
+	angle_t anglediff;
+	const INT16 spawnrange = spawner->radius>>FRACBITS;
+
+	if (!P_IsObjectOnGround(spawner) || leveltime % 2 != 0)
+		return;
+
+	if (spawner->player)
+	{
+		if (spawner->player->pflags & PF_SKIDDOWN)
+		{
+			anglediff = abs((signed)(spawner->angle - spawner->player->frameangle));
+			if (leveltime % 6 == 0)
+				S_StartSound(spawner, sfx_screec); // repeated here because it doesn't always happen to be within the range when this is the case
+		}
+		else
+		{
+			angle_t playerangle = spawner->angle;
+
+			if (spawner->player->speed < 5<<FRACBITS)
+				return;
+
+			if (spawner->player->cmd.forwardmove < 0)
+				playerangle += ANGLE_180;
+
+			anglediff = abs((signed)(playerangle - R_PointToAngle2(0, 0, spawner->player->rmomx, spawner->player->rmomy)));
+		}
+	}
+	else
+	{
+		if (P_AproxDistance(spawner->momx, spawner->momy) < 5<<FRACBITS)
+			return;
+
+		anglediff = abs((signed)(spawner->angle - R_PointToAngle2(0, 0, spawner->momx, spawner->momy)));
+	}
+
+	if (anglediff > ANGLE_180)
+		anglediff = InvAngle(anglediff);
+
+	if (anglediff > ANG10*4) // Trying to turn further than 40 degrees
+	{
+		fixed_t spawnx = P_RandomRange(-spawnrange, spawnrange)<<FRACBITS;
+		fixed_t spawny = P_RandomRange(-spawnrange, spawnrange)<<FRACBITS;
+		INT32 speedrange = 2;
+		mobj_t *dust = P_SpawnMobj(spawner->x + spawnx, spawner->y + spawny, spawner->z, MT_DRIFTDUST);
+		if (spawner->eflags & MFE_VERTICALFLIP)
+		{
+			dust->z += spawner->height - dust->height;
+		}
+		dust->momx = FixedMul(spawner->momx + (P_RandomRange(-speedrange, speedrange)<<FRACBITS), 3*(spawner->scale)/4);
+		dust->momy = FixedMul(spawner->momy + (P_RandomRange(-speedrange, speedrange)<<FRACBITS), 3*(spawner->scale)/4);
+		dust->momz = P_MobjFlip(spawner) * (P_RandomRange(1, 4) * (spawner->scale));
+		P_SetScale(dust, spawner->scale/2);
+		dust->destscale = spawner->scale * 3;
+		dust->scalespeed = spawner->scale/12;
+
+		if (leveltime % 6 == 0)
+			S_StartSound(spawner, sfx_screec);
+
+		K_MatchGenericExtraFlags(dust, spawner);
+	}
+}
+
+static mobj_t *K_FindLastTrailMobj(player_t *player)
+{
+	mobj_t *trail;
+
+	if (!player || !(trail = player->mo) || !player->mo->hnext || !player->mo->hnext->health)
+		return NULL;
+
+	while (trail->hnext && !P_MobjWasRemoved(trail->hnext) && trail->hnext->health)
+	{
+		trail = trail->hnext;
+	}
+
+	return trail;
+}
+
+static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow)
+{
+	mobj_t *mo;
+	INT32 dir, PROJSPEED;
+	angle_t newangle;
+	fixed_t newx, newy, newz;
+	mobj_t *throwmo;
+
+	if (!player)
+		return NULL;
+
+	// Figure out projectile speed by game speed
+	if (missile && mapthing != MT_BALLHOG) // Trying to keep compatability...
+	{
+		PROJSPEED = mobjinfo[mapthing].speed;
+		if (gamespeed == 0)
+			PROJSPEED = FixedMul(PROJSPEED, FRACUNIT-FRACUNIT/4);
+		else if (gamespeed == 2)
+			PROJSPEED = FixedMul(PROJSPEED, FRACUNIT+FRACUNIT/4);
+		PROJSPEED = FixedMul(PROJSPEED, mapobjectscale);
+	}
+	else
+	{
+		switch (gamespeed)
+		{
+			case 0:
+				PROJSPEED = 68*mapobjectscale; // Avg Speed is 34
+				break;
+			case 2:
+				PROJSPEED = 96*mapobjectscale; // Avg Speed is 48
+				break;
+			default:
+				PROJSPEED = 82*mapobjectscale; // Avg Speed is 41
+				break;
+		}
+	}
+
+	if (altthrow)
+	{
+		if (altthrow == 2) // Kitchen sink throwing
+		{
+			if (player->kartstuff[k_throwdir] == 1)
+				dir = 3;
+			else if (player->kartstuff[k_throwdir] == -1)
+				dir = 1;
+			else
+				dir = 2;
+		}
+		else
+		{
+			if (player->kartstuff[k_throwdir] == 1)
+				dir = 2;
+			else if (player->kartstuff[k_throwdir] == -1)
+				dir = -1;
+			else
+				dir = 1;
+		}
+	}
+	else
+	{
+		if (player->kartstuff[k_throwdir] != 0)
+			dir = player->kartstuff[k_throwdir];
+		else
+			dir = defaultDir;
+	}
+
+	if (missile) // Shootables
+	{
+		if (mapthing == MT_BALLHOG) // Messy
+		{
+			if (dir == -1)
+			{
+				// Shoot backward
+				mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x06000000, 0, PROJSPEED/4);
+				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x03000000, 0, PROJSPEED/4);
+				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/4);
+				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x03000000, 0, PROJSPEED/4);
+				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x06000000, 0, PROJSPEED/4);
+			}
+			else
+			{
+				// Shoot forward
+				mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x06000000, 0, PROJSPEED);
+				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x03000000, 0, PROJSPEED);
+				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED);
+				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x03000000, 0, PROJSPEED);
+				K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x06000000, 0, PROJSPEED);
+			}
+		}
+		else
+		{
+			if (dir == -1 && mapthing != MT_SPB)
+			{
+				// Shoot backward
+				mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/2);
+			}
+			else
+			{
+				// Shoot forward
+				mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED);
+			}
+		}
+	}
+	else
+	{
+		player->kartstuff[k_bananadrag] = 0; // RESET timer, for multiple bananas
+
+		if (dir > 0)
+		{
+			// Shoot forward
+			mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, mapthing);
+
+			mo->threshold = 10;
+			P_SetTarget(&mo->target, player->mo);
+
+			S_StartSound(player->mo, mo->info->seesound);
+
+			if (mo)
+			{
+				angle_t fa = player->mo->angle>>ANGLETOFINESHIFT;
+				INT32 HEIGHT = (20 + (dir*10))*mapobjectscale + player->mo->momz;
+
+				mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), (altthrow == 2 ? 2*PROJSPEED/3 : PROJSPEED));
+				mo->momy = player->mo->momy + FixedMul(FINESINE(fa), (altthrow == 2 ? 2*PROJSPEED/3 : PROJSPEED));
+				mo->momz = P_MobjFlip(player->mo) * HEIGHT;
+
+				if (player->mo->eflags & MFE_VERTICALFLIP)
+					mo->eflags |= MFE_VERTICALFLIP;
+			}
+
+			throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM);
+			P_SetTarget(&throwmo->target, player->mo);
+			throwmo->movecount = 0; // above player
+		}
+		else
+		{
+			mobj_t *lasttrail = K_FindLastTrailMobj(player);
+
+			if (lasttrail)
+			{
+				newx = lasttrail->x;
+				newy = lasttrail->y;
+				newz = lasttrail->z;
+			}
+			else
+			{
+				// Drop it directly behind you.
+				fixed_t dropradius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(mobjinfo[mapthing].radius, mobjinfo[mapthing].radius);
+
+				newangle = player->mo->angle;
+
+				newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, dropradius);
+				newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, dropradius);
+				newz = player->mo->z;
+			}
+
+			mo = P_SpawnMobj(newx, newy, newz, mapthing); // this will never return null because collision isn't processed here
+
+			if (P_MobjFlip(player->mo) < 0)
+				mo->z = player->mo->z + player->mo->height - mo->height;
+
+			mo->threshold = 10;
+			P_SetTarget(&mo->target, player->mo);
+
+			if (P_IsObjectOnGround(player->mo))
+			{
+				// floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn
+				// This should set it for FOFs
+				P_TeleportMove(mo, mo->x, mo->y, mo->z); // however, THIS can fuck up your day. just absolutely ruin you.
+				if (P_MobjWasRemoved(mo))
+					return NULL;
+
+				if (P_MobjFlip(mo) > 0)
+				{
+					if (mo->floorz > mo->target->z - mo->height)
+					{
+						mo->z = mo->floorz;
+					}
+				}
+				else
+				{
+					if (mo->ceilingz < mo->target->z + mo->target->height + mo->height)
+					{
+						mo->z = mo->ceilingz - mo->height;
+					}
+				}
+			}
+
+			if (player->mo->eflags & MFE_VERTICALFLIP)
+				mo->eflags |= MFE_VERTICALFLIP;
+		}
+	}
+
+	return mo;
+}
+
+#define THUNDERRADIUS 320
+
+static void K_DoThunderShield(player_t *player)
+{
+	mobj_t *mo;
+	int i = 0;
+	fixed_t sx;
+	fixed_t sy;
+	angle_t an;
+
+	S_StartSound(player->mo, sfx_zio3);
+	//player->kartstuff[k_thunderanim] = 35;
+	P_NukeEnemies(player->mo, player->mo, RING_DIST/4);
+
+	// spawn vertical bolt
+	mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK);
+	P_SetTarget(&mo->target, player->mo);
+	P_SetMobjState(mo, S_LZIO11);
+	mo->color = SKINCOLOR_TEAL;
+	mo->scale = player->mo->scale*3 + (player->mo->scale/2);
+
+	mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK);
+	P_SetTarget(&mo->target, player->mo);
+	P_SetMobjState(mo, S_LZIO21);
+	mo->color = SKINCOLOR_CYAN;
+	mo->scale = player->mo->scale*3 + (player->mo->scale/2);
+
+	// spawn horizontal bolts;
+	for (i=0; i<7; i++)
+	{
+		mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK);
+		mo->angle = P_RandomRange(0, 359)*ANG1;
+		mo->fuse = P_RandomRange(20, 50);
+		P_SetTarget(&mo->target, player->mo);
+		P_SetMobjState(mo, S_KLIT1);
+	}
+
+	// spawn the radius thing:
+	an = ANGLE_22h;
+	for (i=0; i<15; i++)
+	{
+		sx = player->mo->x + FixedMul((player->mo->scale*THUNDERRADIUS), FINECOSINE((an*i)>>ANGLETOFINESHIFT));
+		sy = player->mo->y + FixedMul((player->mo->scale*THUNDERRADIUS), FINESINE((an*i)>>ANGLETOFINESHIFT));
+		mo = P_SpawnMobj(sx, sy, player->mo->z, MT_THOK);
+		mo-> angle = an*i;
+		mo->extravalue1 = THUNDERRADIUS;	// Used to know whether we should teleport by radius or something.
+		mo->scale = player->mo->scale*3;
+		P_SetTarget(&mo->target, player->mo);
+		P_SetMobjState(mo, S_KSPARK1);
+	}
+}
+
+#undef THUNDERRADIUS
+
+static void K_DoHyudoroSteal(player_t *player)
+{
+	INT32 i, numplayers = 0;
+	INT32 playerswappable[MAXPLAYERS];
+	INT32 stealplayer = -1; // The player that's getting stolen from
+	INT32 prandom = 0;
+	boolean sink = P_RandomChance(FRACUNIT/64);
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playeringame[i] && players[i].mo && players[i].mo->health > 0 && players[i].playerstate == PST_LIVE
+			&& player != &players[i] && !players[i].exiting && !players[i].spectator // Player in-game
+
+			// Can steal from this player
+			&& (G_RaceGametype() //&& players[i].kartstuff[k_position] < player->kartstuff[k_position])
+			|| (G_BattleGametype() && players[i].kartstuff[k_bumper] > 0))
+
+			// Has an item
+			&& (players[i].kartstuff[k_itemtype]
+			&& players[i].kartstuff[k_itemamount]
+			&& !players[i].kartstuff[k_itemheld]
+			&& !players[i].kartstuff[k_itemblink]))
+		{
+			playerswappable[numplayers] = i;
+			numplayers++;
+		}
+	}
+
+	prandom = P_RandomFixed();
+	S_StartSound(player->mo, sfx_s3k92);
+
+	if (sink && numplayers > 0 && cv_kitchensink.value) // BEHOLD THE KITCHEN SINK
+	{
+		player->kartstuff[k_hyudorotimer] = hyudorotime;
+		player->kartstuff[k_stealingtimer] = stealtime;
+
+		player->kartstuff[k_itemtype] = KITEM_KITCHENSINK;
+		player->kartstuff[k_itemamount] = 1;
+		player->kartstuff[k_itemheld] = 0;
+		return;
+	}
+	else if ((G_RaceGametype() && player->kartstuff[k_position] == 1) || numplayers == 0) // No-one can be stolen from? Oh well...
+	{
+		player->kartstuff[k_hyudorotimer] = hyudorotime;
+		player->kartstuff[k_stealingtimer] = stealtime;
+		return;
+	}
+	else if (numplayers == 1) // With just 2 players, we just need to set the other player to be the one to steal from
+	{
+		stealplayer = playerswappable[numplayers-1];
+	}
+	else if (numplayers > 1) // We need to choose between the available candidates for the 2nd player
+	{
+		stealplayer = playerswappable[prandom%(numplayers-1)];
+	}
+
+	if (stealplayer > -1) // Now here's where we do the stealing, has to be done here because we still know the player we're stealing from
+	{
+		player->kartstuff[k_hyudorotimer] = hyudorotime;
+		player->kartstuff[k_stealingtimer] = stealtime;
+		players[stealplayer].kartstuff[k_stolentimer] = stealtime;
+
+		player->kartstuff[k_itemtype] = players[stealplayer].kartstuff[k_itemtype];
+		player->kartstuff[k_itemamount] = players[stealplayer].kartstuff[k_itemamount];
+		player->kartstuff[k_itemheld] = 0;
+
+		players[stealplayer].kartstuff[k_itemtype] = KITEM_NONE;
+		players[stealplayer].kartstuff[k_itemamount] = 0;
+		players[stealplayer].kartstuff[k_itemheld] = 0;
+
+		if (P_IsLocalPlayer(&players[stealplayer]) && !splitscreen)
+			S_StartSound(NULL, sfx_s3k92);
+	}
+}
+
+void K_DoSneaker(player_t *player, INT32 type)
+{
+	fixed_t intendedboost;
+
+	switch (gamespeed)
+	{
+		case 0:
+			intendedboost = 53740+768;
+			break;
+		case 2:
+			intendedboost = 17294+768;
+			break;
+		default:
+			intendedboost = 32768;
+			break;
+	}
+
+	if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3)
+	{
+		S_StartSound(player->mo, sfx_cdfm01);
+		K_SpawnDashDustRelease(player);
+		if (intendedboost > player->kartstuff[k_speedboost])
+			player->kartstuff[k_destboostcam] = FixedMul(FRACUNIT, FixedDiv((intendedboost - player->kartstuff[k_speedboost]), intendedboost));
+	}
+
+	if (!player->kartstuff[k_sneakertimer])
+	{
+		if (type == 2)
+		{
+			if (player->mo->hnext)
+			{
+				mobj_t *cur = player->mo->hnext;
+				while (cur && !P_MobjWasRemoved(cur))
+				{
+					if (!cur->tracer)
+					{
+						mobj_t *overlay = P_SpawnMobj(cur->x, cur->y, cur->z, MT_BOOSTFLAME);
+						P_SetTarget(&overlay->target, cur);
+						P_SetTarget(&cur->tracer, overlay);
+						P_SetScale(overlay, (overlay->destscale = 3*cur->scale/4));
+					}
+					cur = cur->hnext;
+				}
+			}
+		}
+		else
+		{
+			mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BOOSTFLAME);
+			P_SetTarget(&overlay->target, player->mo);
+			P_SetScale(overlay, (overlay->destscale = player->mo->scale));
+		}
+	}
+
+	player->kartstuff[k_sneakertimer] = sneakertime;
+
+	if (type != 0)
+	{
+		player->pflags |= PF_ATTACKDOWN;
+		K_PlayBoostTaunt(player->mo);
+	}
+}
+
+static void K_DoShrink(player_t *user)
+{
+	INT32 i;
+
+	S_StartSound(user->mo, sfx_kc46); // Sound the BANG!
+	user->pflags |= PF_ATTACKDOWN;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i] || players[i].spectator || !players[i].mo)
+			continue;
+		if (&players[i] == user)
+			continue;
+		if (players[i].kartstuff[k_position] < user->kartstuff[k_position])
+		{
+			// Don't hit while invulnerable!
+			if (!players[i].kartstuff[k_invincibilitytimer]
+				&& players[i].kartstuff[k_growshrinktimer] <= 0
+				&& !players[i].kartstuff[k_hyudorotimer])
+			{
+				// Start shrinking!
+				K_DropItems(&players[i]);
+				players[i].mo->scalespeed = mapobjectscale/TICRATE;
+				players[i].mo->destscale = (6*mapobjectscale)/8;
+				if (cv_kartdebugshrink.value && !modeattacking && !players[i].bot)
+					players[i].mo->destscale = (6*players[i].mo->destscale)/8;
+				players[i].kartstuff[k_growshrinktimer] = -(200+(40*(MAXPLAYERS-players[i].kartstuff[k_position])));
+			}
+
+			// Grow should get taken away.
+			if (players[i].kartstuff[k_growshrinktimer] > 0)
+				K_RemoveGrowShrink(&players[i]);
+
+			//P_FlashPal(&players[i], PAL_NUKE, 10);
+			S_StartSound(players[i].mo, sfx_kc59);
+		}
+	}
+}
+
+
+void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound)
+{
+	const fixed_t vscale = mapobjectscale + (mo->scale - mapobjectscale);
+
+	if (mo->player && mo->player->spectator)
+		return;
+
+	if (mo->eflags & MFE_SPRUNG)
+		return;
+
+#ifdef ESLOPE
+	mo->standingslope = NULL;
+#endif
+
+	mo->eflags |= MFE_SPRUNG;
+
+	if (mo->eflags & MFE_VERTICALFLIP)
+		vertispeed *= -1;
+
+	if (vertispeed == 0)
+	{
+		fixed_t thrust;
+
+		if (mo->player)
+		{
+			thrust = 3*mo->player->speed/2;
+			if (thrust < 48<<FRACBITS)
+				thrust = 48<<FRACBITS;
+			if (thrust > 72<<FRACBITS)
+				thrust = 72<<FRACBITS;
+			if (mo->player->kartstuff[k_pogospring] != 2)
+			{
+				if (mo->player->kartstuff[k_sneakertimer])
+					thrust = FixedMul(thrust, 5*FRACUNIT/4);
+				else if (mo->player->kartstuff[k_invincibilitytimer])
+					thrust = FixedMul(thrust, 9*FRACUNIT/8);
+			}
+		}
+		else
+		{
+			thrust = FixedDiv(3*P_AproxDistance(mo->momx, mo->momy)/2, 5*FRACUNIT/2);
+			if (thrust < 16<<FRACBITS)
+				thrust = 16<<FRACBITS;
+			if (thrust > 32<<FRACBITS)
+				thrust = 32<<FRACBITS;
+		}
+
+		mo->momz = FixedMul(FINESINE(ANGLE_22h>>ANGLETOFINESHIFT), FixedMul(thrust, vscale));
+	}
+	else
+		mo->momz = FixedMul(vertispeed, vscale);
+
+	if (sound)
+		S_StartSound(mo, (sound == 1 ? sfx_kc2f : sfx_kpogos));
+}
+
+void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source)
+{
+	mobj_t *cachenext;
+
+killnext:
+	cachenext = banana->hnext;
+
+	if (banana->health)
+	{
+		if (banana->eflags & MFE_VERTICALFLIP)
+			banana->z -= banana->height;
+		else
+			banana->z += banana->height;
+
+		S_StartSound(banana, banana->info->deathsound);
+		P_KillMobj(banana, inflictor, source);
+
+		P_SetObjectMomZ(banana, 8*FRACUNIT, false);
+		if (inflictor)
+			P_InstaThrust(banana, R_PointToAngle2(inflictor->x, inflictor->y, banana->x, banana->y)+ANGLE_90, 16*FRACUNIT);
+	}
+
+	if ((banana = cachenext))
+		goto killnext;
+}
+
+// Just for firing/dropping items.
+void K_UpdateHnextList(player_t *player, boolean clean)
+{
+	mobj_t *work = player->mo, *nextwork;
+
+	if (!work)
+		return;
+
+	nextwork = work->hnext;
+
+	while ((work = nextwork) && !P_MobjWasRemoved(work))
+	{
+		nextwork = work->hnext;
+
+		if (!clean && (!work->movedir || work->movedir <= (UINT16)player->kartstuff[k_itemamount]))
+			continue;
+
+		P_RemoveMobj(work);
+	}
+}
+
+// For getting hit!
+void K_DropHnextList(player_t *player)
+{
+	mobj_t *work = player->mo, *nextwork, *dropwork;
+	INT32 flip;
+	mobjtype_t type;
+	boolean orbit, ponground, dropall = true;
+
+	if (!work)
+		return;
+
+	flip = P_MobjFlip(player->mo);
+	ponground = P_IsObjectOnGround(player->mo);
+
+	if (player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD && player->kartstuff[k_itemamount])
+	{
+		K_DoThunderShield(player);
+		player->kartstuff[k_itemamount] = 0;
+		player->kartstuff[k_itemtype] = KITEM_NONE;
+		player->kartstuff[k_curshield] = 0;
+	}
+
+	nextwork = work->hnext;
+
+	while ((work = nextwork) && !P_MobjWasRemoved(work))
+	{
+		nextwork = work->hnext;
+
+		switch (work->type)
+		{
+			// Kart orbit items
+			case MT_ORBINAUT_SHIELD:
+				orbit = true;
+				type = MT_ORBINAUT;
+				break;
+			case MT_JAWZ_SHIELD:
+				orbit = true;
+				type = MT_JAWZ_DUD;
+				break;
+			// Kart trailing items
+			case MT_BANANA_SHIELD:
+				orbit = false;
+				type = MT_BANANA;
+				break;
+			case MT_SSMINE_SHIELD:
+				orbit = false;
+				dropall = false;
+				type = MT_SSMINE;
+				break;
+			case MT_EGGMANITEM_SHIELD:
+				orbit = false;
+				type = MT_EGGMANITEM;
+				break;
+			// intentionally do nothing
+			case MT_SINK_SHIELD:
+			case MT_ROCKETSNEAKER:
+				return;
+			default:
+				continue;
+		}
+
+		dropwork = P_SpawnMobj(work->x, work->y, work->z, type);
+		P_SetTarget(&dropwork->target, player->mo);
+		dropwork->angle = work->angle;
+		dropwork->flags2 = work->flags2;
+		dropwork->flags |= MF_NOCLIPTHING;
+		dropwork->floorz = work->floorz;
+		dropwork->ceilingz = work->ceilingz;
+
+		if (ponground)
+		{
+			// floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn
+			// This should set it for FOFs
+			//P_TeleportMove(dropwork, dropwork->x, dropwork->y, dropwork->z); -- handled better by above floorz/ceilingz passing
+
+			if (flip == 1)
+			{
+				if (dropwork->floorz > dropwork->target->z - dropwork->height)
+				{
+					dropwork->z = dropwork->floorz;
+				}
+			}
+			else
+			{
+				if (dropwork->ceilingz < dropwork->target->z + dropwork->target->height + dropwork->height)
+				{
+					dropwork->z = dropwork->ceilingz - dropwork->height;
+				}
+			}
+		}
+
+		if (orbit) // splay out
+		{
+			dropwork->flags2 |= MF2_AMBUSH;
+			dropwork->z += flip;
+			dropwork->momx = player->mo->momx>>1;
+			dropwork->momy = player->mo->momy>>1;
+			dropwork->momz = 3*flip*mapobjectscale;
+			P_Thrust(dropwork, work->angle - ANGLE_90, 6*mapobjectscale);
+			dropwork->movecount = 2;
+			dropwork->movedir = work->angle - ANGLE_90;
+			P_SetMobjState(dropwork, dropwork->info->deathstate);
+			dropwork->tics = -1;
+			if (type == MT_JAWZ_DUD)
+				dropwork->z += 20*flip*dropwork->scale;
+			else
+			{
+				dropwork->color = work->color;
+				dropwork->angle -= ANGLE_90;
+			}
+		}
+		else // plop on the ground
+		{
+			dropwork->flags &= ~MF_NOCLIPTHING;
+			dropwork->threshold = 10;
+		}
+
+		P_RemoveMobj(work);
+	}
+
+	{
+		// we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic...
+		P_SetTarget(&player->mo->hnext, NULL);
+		player->kartstuff[k_bananadrag] = 0;
+		if (player->kartstuff[k_eggmanheld])
+			player->kartstuff[k_eggmanheld] = 0;
+		else if (player->kartstuff[k_itemheld]
+			&& (dropall || (--player->kartstuff[k_itemamount] <= 0)))
+		{
+			player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0;
+			player->kartstuff[k_itemtype] = KITEM_NONE;
+		}
+	}
+}
+
+// For getting EXTRA hit!
+void K_DropItems(player_t *player)
+{
+	boolean thunderhack = (player->kartstuff[k_curshield] && player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD);
+
+	if (thunderhack)
+		player->kartstuff[k_itemtype] = KITEM_NONE;
+
+	K_DropHnextList(player);
+
+	if (player->mo && player->kartstuff[k_itemamount])
+	{
+		mobj_t *drop = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FLOATINGITEM);
+		P_SetScale(drop, drop->scale>>4);
+		drop->destscale = (3*drop->destscale)/2;;
+
+		drop->angle = player->mo->angle + ANGLE_90;
+		P_Thrust(drop,
+			FixedAngle(P_RandomFixed()*180) + player->mo->angle + ANGLE_90,
+			16*mapobjectscale);
+		drop->momz = P_MobjFlip(player->mo)*3*mapobjectscale;
+
+		drop->threshold = (thunderhack ? KITEM_THUNDERSHIELD : player->kartstuff[k_itemtype]);
+		drop->movecount = player->kartstuff[k_itemamount];
+
+		drop->flags |= MF_NOCLIPTHING;
+	}
+
+	K_StripItems(player);
+}
+
+// When an item in the hnext chain dies.
+void K_RepairOrbitChain(mobj_t *orbit)
+{
+	mobj_t *cachenext = orbit->hnext;
+
+	// First, repair the chain
+	if (orbit->hnext && orbit->hnext->health && !P_MobjWasRemoved(orbit->hnext))
+	{
+		P_SetTarget(&orbit->hnext->hprev, orbit->hprev);
+		P_SetTarget(&orbit->hnext, NULL);
+	}
+
+	if (orbit->hprev && orbit->hprev->health && !P_MobjWasRemoved(orbit->hprev))
+	{
+		P_SetTarget(&orbit->hprev->hnext, cachenext);
+		P_SetTarget(&orbit->hprev, NULL);
+	}
+
+	// Then recount to make sure item amount is correct
+	if (orbit->target && orbit->target->player)
+	{
+		INT32 num = 0;
+
+		mobj_t *cur = orbit->target->hnext;
+		mobj_t *prev = NULL;
+
+		while (cur && !P_MobjWasRemoved(cur))
+		{
+			prev = cur;
+			cur = cur->hnext;
+			if (++num > orbit->target->player->kartstuff[k_itemamount])
+				P_RemoveMobj(prev);
+			else
+				prev->movedir = num;
+		}
+
+		if (orbit->target->player->kartstuff[k_itemamount] != num)
+			orbit->target->player->kartstuff[k_itemamount] = num;
+	}
+}
+
+// Move the hnext chain!
+static void K_MoveHeldObjects(player_t *player)
+{
+	if (!player->mo)
+		return;
+
+	if (!player->mo->hnext)
+	{
+		player->kartstuff[k_bananadrag] = 0;
+		if (player->kartstuff[k_eggmanheld])
+			player->kartstuff[k_eggmanheld] = 0;
+		else if (player->kartstuff[k_itemheld])
+		{
+			player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0;
+			player->kartstuff[k_itemtype] = KITEM_NONE;
+		}
+		return;
+	}
+
+	if (P_MobjWasRemoved(player->mo->hnext))
+	{
+		// we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic...
+		P_SetTarget(&player->mo->hnext, NULL);
+		player->kartstuff[k_bananadrag] = 0;
+		if (player->kartstuff[k_eggmanheld])
+			player->kartstuff[k_eggmanheld] = 0;
+		else if (player->kartstuff[k_itemheld])
+		{
+			player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0;
+			player->kartstuff[k_itemtype] = KITEM_NONE;
+		}
+		return;
+	}
+
+	switch (player->mo->hnext->type)
+	{
+		case MT_ORBINAUT_SHIELD: // Kart orbit items
+		case MT_JAWZ_SHIELD:
+			{
+				mobj_t *cur = player->mo->hnext;
+				fixed_t speed = ((8 - min(4, player->kartstuff[k_itemamount])) * cur->info->speed) / 7;
+
+				player->kartstuff[k_bananadrag] = 0; // Just to make sure
+
+				while (cur && !P_MobjWasRemoved(cur))
+				{
+					const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); // mobj's distance from its Target, or Radius.
+					fixed_t z;
+
+					if (!cur->health)
+					{
+						cur = cur->hnext;
+						continue;
+					}
+
+					cur->color = player->skincolor;
+
+					cur->angle -= ANGLE_90;
+					cur->angle += FixedAngle(speed);
+
+					if (cur->extravalue1 < radius)
+						cur->extravalue1 += P_AproxDistance(cur->extravalue1, radius) / 12;
+					if (cur->extravalue1 > radius)
+						cur->extravalue1 = radius;
+
+					// If the player is on the ceiling, then flip your items as well.
+					if (player && player->mo->eflags & MFE_VERTICALFLIP)
+						cur->eflags |= MFE_VERTICALFLIP;
+					else
+						cur->eflags &= ~MFE_VERTICALFLIP;
+
+					// Shrink your items if the player shrunk too.
+					P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale)));
+
+					if (P_MobjFlip(cur) > 0)
+						z = player->mo->z;
+					else
+						z = player->mo->z + player->mo->height - cur->height;
+
+					cur->flags |= MF_NOCLIPTHING; // temporarily make them noclip other objects so they can't hit anyone while in the player
+					P_TeleportMove(cur, player->mo->x, player->mo->y, z);
+					cur->momx = FixedMul(FINECOSINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1);
+					cur->momy = FixedMul(FINESINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1);
+					cur->flags &= ~MF_NOCLIPTHING;
+					if (!P_TryMove(cur, player->mo->x + cur->momx, player->mo->y + cur->momy, true))
+						P_SlideMove(cur, true);
+					if (P_IsObjectOnGround(player->mo))
+					{
+						if (P_MobjFlip(cur) > 0)
+						{
+							if (cur->floorz > player->mo->z - cur->height)
+								z = cur->floorz;
+						}
+						else
+						{
+							if (cur->ceilingz < player->mo->z + player->mo->height + cur->height)
+								z = cur->ceilingz - cur->height;
+						}
+					}
+
+					// Center it during the scale up animation
+					z += (FixedMul(mobjinfo[cur->type].height, player->mo->scale - cur->scale)>>1) * P_MobjFlip(cur);
+
+					cur->z = z;
+					cur->momx = cur->momy = 0;
+					cur->angle += ANGLE_90;
+
+					cur = cur->hnext;
+				}
+			}
+			break;
+		case MT_BANANA_SHIELD: // Kart trailing items
+		case MT_SSMINE_SHIELD:
+		case MT_EGGMANITEM_SHIELD:
+		case MT_SINK_SHIELD:
+			{
+				mobj_t *cur = player->mo->hnext;
+				mobj_t *targ = player->mo;
+
+				if (P_IsObjectOnGround(player->mo) && player->speed > 0)
+					player->kartstuff[k_bananadrag]++;
+
+				while (cur && !P_MobjWasRemoved(cur))
+				{
+					const fixed_t radius = FixedHypot(targ->radius, targ->radius) + FixedHypot(cur->radius, cur->radius);
+					angle_t ang;
+					fixed_t targx, targy, targz;
+					fixed_t speed, dist;
+
+					cur->flags &= ~MF_NOCLIPTHING;
+
+					if (!cur->health)
+					{
+						cur = cur->hnext;
+						continue;
+					}
+
+					if (cur->extravalue1 < radius)
+						cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12);
+					if (cur->extravalue1 > radius)
+						cur->extravalue1 = radius;
+
+					if (cur != player->mo->hnext)
+					{
+						targ = cur->hprev;
+						dist = cur->extravalue1/4;
+					}
+					else
+						dist = cur->extravalue1/2;
+
+					if (!targ || P_MobjWasRemoved(targ))
+						continue;
+
+					// Shrink your items if the player shrunk too.
+					P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale)));
+
+					ang = targ->angle;
+					targx = targ->x + P_ReturnThrustX(cur, ang + ANGLE_180, dist);
+					targy = targ->y + P_ReturnThrustY(cur, ang + ANGLE_180, dist);
+					targz = targ->z;
+					speed = FixedMul(R_PointToDist2(cur->x, cur->y, targx, targy), 3*FRACUNIT/4);
+					if (P_IsObjectOnGround(targ))
+						targz = cur->floorz;
+
+					cur->angle = R_PointToAngle2(cur->x, cur->y, targx, targy);
+
+					/*if (P_IsObjectOnGround(player->mo) && player->speed > 0 && player->kartstuff[k_bananadrag] > TICRATE
+						&& P_RandomChance(min(FRACUNIT/2, FixedDiv(player->speed, K_GetKartSpeed(player, false))/2)))
+					{
+						if (leveltime & 1)
+							targz += 8*(2*FRACUNIT)/7;
+						else
+							targz -= 8*(2*FRACUNIT)/7;
+					}*/
+
+					if (speed > dist)
+						P_InstaThrust(cur, cur->angle, speed-dist);
+
+					P_SetObjectMomZ(cur, FixedMul(targz - cur->z, 7*FRACUNIT/8) - gravity, false);
+
+					if (R_PointToDist2(cur->x, cur->y, targx, targy) > 768*FRACUNIT)
+						P_TeleportMove(cur, targx, targy, cur->z);
+
+					cur = cur->hnext;
+				}
+			}
+			break;
+		case MT_ROCKETSNEAKER: // Special rocket sneaker stuff
+			{
+				mobj_t *cur = player->mo->hnext;
+				INT32 num = 0;
+
+				while (cur && !P_MobjWasRemoved(cur))
+				{
+					const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius);
+					boolean vibrate = ((leveltime & 1) && !cur->tracer);
+					angle_t angoffset;
+					fixed_t targx, targy, targz;
+
+					cur->flags &= ~MF_NOCLIPTHING;
+
+					if (player->kartstuff[k_rocketsneakertimer] <= TICRATE && (leveltime & 1))
+						cur->flags2 |= MF2_DONTDRAW;
+					else
+						cur->flags2 &= ~MF2_DONTDRAW;
+
+					if (num & 1)
+						P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_LVIBRATE : S_ROCKETSNEAKER_L));
+					else
+						P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_RVIBRATE : S_ROCKETSNEAKER_R));
+
+					if (!player->kartstuff[k_rocketsneakertimer] || cur->extravalue2 || !cur->health)
+					{
+						num = (num+1) % 2;
+						cur = cur->hnext;
+						continue;
+					}
+
+					if (cur->extravalue1 < radius)
+						cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12);
+					if (cur->extravalue1 > radius)
+						cur->extravalue1 = radius;
+
+					// Shrink your items if the player shrunk too.
+					P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale)));
+
+#if 1
+					{
+						angle_t input = player->frameangle - cur->angle;
+						boolean invert = (input > ANGLE_180);
+						if (invert)
+							input = InvAngle(input);
+
+						input = FixedAngle(AngleFixed(input)/4);
+						if (invert)
+							input = InvAngle(input);
+
+						cur->angle = cur->angle + input;
+					}
+#else
+					cur->angle = player->frameangle;
+#endif
+
+					angoffset = ANGLE_90 + (ANGLE_180 * num);
+
+					targx = player->mo->x + P_ReturnThrustX(cur, cur->angle + angoffset, cur->extravalue1);
+					targy = player->mo->y + P_ReturnThrustY(cur, cur->angle + angoffset, cur->extravalue1);
+
+					{ // bobbing, copy pasted from my kimokawaiii entry
+						const fixed_t pi = (22<<FRACBITS) / 7; // loose approximation, this doesn't need to be incredibly precise
+						fixed_t sine = 8 * FINESINE((((2*pi*(4*TICRATE)) * leveltime)>>ANGLETOFINESHIFT) & FINEMASK);
+						targz = (player->mo->z + (player->mo->height/2)) + sine;
+					}
+
+					if (cur->tracer)
+					{
+						fixed_t diffx, diffy, diffz;
+
+						diffx = targx - cur->x;
+						diffy = targy - cur->y;
+						diffz = targz - cur->z;
+
+						P_TeleportMove(cur->tracer, cur->tracer->x + diffx + P_ReturnThrustX(cur, cur->angle + angoffset, 6*cur->scale),
+							cur->tracer->y + diffy + P_ReturnThrustY(cur, cur->angle + angoffset, 6*cur->scale), cur->tracer->z + diffz);
+						P_SetScale(cur->tracer, (cur->tracer->destscale = 3*cur->scale/4));
+					}
+
+					P_TeleportMove(cur, targx, targy, targz);
+
+					num = (num+1) % 2;
+					cur = cur->hnext;
+				}
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+player_t *K_FindJawzTarget(mobj_t *actor, player_t *source)
+{
+	fixed_t best = -1;
+	player_t *wtarg = NULL;
+	INT32 i;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		angle_t thisang;
+		player_t *player;
+
+		if (!playeringame[i])
+			continue;
+
+		player = &players[i];
+
+		if (player->spectator)
+			continue; // spectator
+
+		if (!player->mo)
+			continue;
+
+		if (player->mo->health <= 0)
+			continue; // dead
+
+		// Don't target yourself, stupid.
+		if (player == source)
+			continue;
+
+		// Don't home in on teammates.
+		if (G_GametypeHasTeams() && source->ctfteam == player->ctfteam)
+			continue;
+
+		// Invisible, don't bother
+		if (player->kartstuff[k_hyudorotimer])
+			continue;
+
+		// Find the angle, see who's got the best.
+		thisang = actor->angle - R_PointToAngle2(actor->x, actor->y, player->mo->x, player->mo->y);
+		if (thisang > ANGLE_180)
+			thisang = InvAngle(thisang);
+
+		// Jawz only go after the person directly ahead of you in race... sort of literally now!
+		if (G_RaceGametype())
+		{
+			// Don't go for people who are behind you
+			if (thisang > ANGLE_67h)
+				continue;
+			// Don't pay attention to people who aren't above your position
+			if (player->kartstuff[k_position] >= source->kartstuff[k_position])
+				continue;
+			if ((best == -1) || (player->kartstuff[k_position] > best))
+			{
+				wtarg = player;
+				best = player->kartstuff[k_position];
+			}
+		}
+		else
+		{
+			fixed_t thisdist;
+			fixed_t thisavg;
+
+			// Don't go for people who are behind you
+			if (thisang > ANGLE_45)
+				continue;
+
+			// Don't pay attention to dead players
+			if (player->kartstuff[k_bumper] <= 0)
+				continue;
+
+			// Z pos too high/low
+			if (abs(player->mo->z - (actor->z + actor->momz)) > RING_DIST/8)
+				continue;
+
+			thisdist = P_AproxDistance(player->mo->x - (actor->x + actor->momx), player->mo->y - (actor->y + actor->momy));
+
+			if (thisdist > 2*RING_DIST) // Don't go for people who are too far away
+				continue;
+
+			thisavg = (AngleFixed(thisang) + thisdist) / 2;
+
+			//CONS_Printf("got avg %d from player # %d\n", thisavg>>FRACBITS, i);
+
+			if ((best == -1) || (thisavg < best))
+			{
+				wtarg = player;
+				best = thisavg;
+			}
+		}
+	}
+
+	return wtarg;
+}
+
+// Engine Sounds.
+static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd)
+{
+	const INT32 numsnds = 13;
+	INT32 class, s, w; // engine class number
+	UINT8 volume = 255;
+	fixed_t volumedampen = 0;
+	INT32 targetsnd = 0;
+	INT32 i;
+
+	s = (player->kartspeed-1)/3;
+	w = (player->kartweight-1)/3;
+
+#define LOCKSTAT(stat) \
+	if (stat < 0) { stat = 0; } \
+	if (stat > 2) { stat = 2; }
+	LOCKSTAT(s);
+	LOCKSTAT(w);
+#undef LOCKSTAT
+
+	class = s+(3*w);
+
+	// Silence the engines
+	if (leveltime < 8 || player->spectator || player->exiting)
+	{
+		player->kartstuff[k_enginesnd] = 0; // Reset sound number
+		return;
+	}
+
+#if 0
+	if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct!
+#else
+	if (leveltime % 8) // .25 seconds of wait time between engine sounds
+#endif
+		return;
+
+	if ((leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) || (player->kartstuff[k_respawn] == 1)) // Startup boosts
+		targetsnd = ((cmd->buttons & BT_ACCELERATE) ? 12 : 0);
+	else
+		targetsnd = (((6*cmd->forwardmove)/25) + ((player->speed / mapobjectscale)/5))/2;
+
+	if (targetsnd < 0)
+		targetsnd = 0;
+	if (targetsnd > 12)
+		targetsnd = 12;
+
+	if (player->kartstuff[k_enginesnd] < targetsnd)
+		player->kartstuff[k_enginesnd]++;
+	if (player->kartstuff[k_enginesnd] > targetsnd)
+		player->kartstuff[k_enginesnd]--;
+
+	if (player->kartstuff[k_enginesnd] < 0)
+		player->kartstuff[k_enginesnd] = 0;
+	if (player->kartstuff[k_enginesnd] > 12)
+		player->kartstuff[k_enginesnd] = 12;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		UINT8 thisvol = 0;
+		fixed_t dist;
+
+		if (!playeringame[i] || !players[i].mo || players[i].spectator || players[i].exiting)
+			continue;
+
+		if ((i == displayplayer)
+			|| (i == secondarydisplayplayer && splitscreen)
+			|| (i == thirddisplayplayer && splitscreen > 1)
+			|| (i == fourthdisplayplayer && splitscreen > 2))
+		{
+			volumedampen += FRACUNIT; // We already know what this is gonna be, let's not waste our time.
+			continue;
+		}
+
+		dist = P_AproxDistance(P_AproxDistance(player->mo->x-players[i].mo->x,
+			player->mo->y-players[i].mo->y), player->mo->z-players[i].mo->z) / 2;
+
+		dist = FixedDiv(dist, mapobjectscale);
+
+		if (dist > 1536<<FRACBITS)
+			continue;
+		else if (dist < 160<<FRACBITS) // engine sounds' approx. range
+			thisvol = 255;
+		else
+			thisvol = (15 * (((160<<FRACBITS) - dist)>>FRACBITS)) / (((1536<<FRACBITS)-(160<<FRACBITS))>>(FRACBITS+4));
+
+		if (thisvol == 0)
+			continue;
+
+		volumedampen += (thisvol * 257); // 255 * 257 = FRACUNIT
+	}
+
+	if (volumedampen > FRACUNIT)
+		volume = FixedDiv(volume<<FRACBITS, volumedampen)>>FRACBITS;
+
+	if (volume <= 0) // Might as well
+		return;
+
+	S_StartSoundAtVolume(player->mo, (sfx_krta00 + player->kartstuff[k_enginesnd]) + (class*numsnds), volume);
+}
+
+static void K_UpdateInvincibilitySounds(player_t *player)
+{
+	INT32 sfxnum = sfx_None;
+
+	if (player->mo->health > 0 && !P_IsLocalPlayer(player))
+	{
+		if (cv_kartinvinsfx.value)
+		{
+			if (player->kartstuff[k_growshrinktimer] > 0) // Prioritize Grow
+				sfxnum = sfx_alarmg;
+			else if (player->kartstuff[k_invincibilitytimer] > 0)
+				sfxnum = sfx_alarmi;
+		}
+		else
+		{
+			if (player->kartstuff[k_growshrinktimer] > 0)
+				sfxnum = sfx_kgrow;
+			else if (player->kartstuff[k_invincibilitytimer] > 0)
+				sfxnum = sfx_kinvnc;
+		}
+	}
+
+	if (sfxnum != sfx_None && !S_SoundPlaying(player->mo, sfxnum))
+		S_StartSound(player->mo, sfxnum);
+
+#define STOPTHIS(this) \
+	if (sfxnum != this && S_SoundPlaying(player->mo, this)) \
+		S_StopSoundByID(player->mo, this);
+	STOPTHIS(sfx_alarmi);
+	STOPTHIS(sfx_alarmg);
+	STOPTHIS(sfx_kinvnc);
+	STOPTHIS(sfx_kgrow);
+#undef STOPTHIS
+}
+
+void K_KartPlayerHUDUpdate(player_t *player)
+{
+	if (player->kartstuff[k_lapanimation])
+		player->kartstuff[k_lapanimation]--;
+
+	if (player->kartstuff[k_yougotem])
+		player->kartstuff[k_yougotem]--;
+
+	if (G_BattleGametype() && (player->exiting || player->kartstuff[k_comebacktimer]))
+	{
+		if (player->exiting)
+		{
+			if (player->exiting < 6*TICRATE)
+				player->kartstuff[k_cardanimation] += ((164-player->kartstuff[k_cardanimation])/8)+1;
+			else if (player->exiting == 6*TICRATE)
+				player->kartstuff[k_cardanimation] = 0;
+			else if (player->kartstuff[k_cardanimation] < 2*TICRATE)
+				player->kartstuff[k_cardanimation]++;
+		}
+		else
+		{
+			if (player->kartstuff[k_comebacktimer] < 6*TICRATE)
+				player->kartstuff[k_cardanimation] -= ((164-player->kartstuff[k_cardanimation])/8)+1;
+			else if (player->kartstuff[k_comebacktimer] < 9*TICRATE)
+				player->kartstuff[k_cardanimation] += ((164-player->kartstuff[k_cardanimation])/8)+1;
+		}
+
+		if (player->kartstuff[k_cardanimation] > 164)
+			player->kartstuff[k_cardanimation] = 164;
+		if (player->kartstuff[k_cardanimation] < 0)
+			player->kartstuff[k_cardanimation] = 0;
+	}
+	else if (G_RaceGametype() && player->exiting)
+	{
+		if (player->kartstuff[k_cardanimation] < 2*TICRATE)
+			player->kartstuff[k_cardanimation]++;
+	}
+	else
+		player->kartstuff[k_cardanimation] = 0;
+}
+
+/**	\brief	Decreases various kart timers and powers per frame. Called in P_PlayerThink in p_user.c
+
+	\param	player	player object passed from P_PlayerThink
+	\param	cmd		control input from player
+
+	\return	void
+*/
+void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
+{
+	K_UpdateOffroad(player);
+	K_UpdateEngineSounds(player, cmd); // Thanks, VAda!
+	K_GetKartBoostPower(player);
+
+	// Speed lines
+	if ((player->kartstuff[k_sneakertimer] || player->kartstuff[k_driftboost] || player->kartstuff[k_startboost]) && player->speed > 0)
+	{
+		mobj_t *fast = P_SpawnMobj(player->mo->x + (P_RandomRange(-36,36) * player->mo->scale),
+			player->mo->y + (P_RandomRange(-36,36) * player->mo->scale),
+			player->mo->z + (player->mo->height/2) + (P_RandomRange(-20,20) * player->mo->scale),
+			MT_FASTLINE);
+		fast->angle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
+		fast->momx = 3*player->mo->momx/4;
+		fast->momy = 3*player->mo->momy/4;
+		fast->momz = 3*player->mo->momz/4;
+		K_MatchGenericExtraFlags(fast, player->mo);
+	}
+
+	if (player->playerstate == PST_DEAD || player->kartstuff[k_respawn] > 1) // Ensure these are set correctly here
+	{
+		player->mo->colorized = false;
+		player->mo->color = player->skincolor;
+	}
+	else if (player->kartstuff[k_eggmanexplode]) // You're gonna diiiiie
+	{
+		const INT32 flashtime = 4<<(player->kartstuff[k_eggmanexplode]/TICRATE);
+		if (player->kartstuff[k_eggmanexplode] == 1 || (player->kartstuff[k_eggmanexplode] % (flashtime/2) != 0))
+		{
+			player->mo->colorized = false;
+			player->mo->color = player->skincolor;
+		}
+		else if (player->kartstuff[k_eggmanexplode] % flashtime == 0)
+		{
+			player->mo->colorized = true;
+			player->mo->color = SKINCOLOR_BLACK;
+		}
+		else
+		{
+			player->mo->colorized = true;
+			player->mo->color = SKINCOLOR_CRIMSON;
+		}
+	}
+	else if (player->kartstuff[k_invincibilitytimer]) // setting players to use the star colormap and spawning afterimages
+	{
+		mobj_t *ghost;
+		player->mo->colorized = true;
+		ghost = P_SpawnGhostMobj(player->mo);
+		ghost->fuse = 4;
+		ghost->frame |= FF_FULLBRIGHT;
+	}
+	else if (player->kartstuff[k_growshrinktimer]) // Ditto, for grow/shrink
+	{
+		if (player->kartstuff[k_growshrinktimer] % 5 == 0)
+		{
+			player->mo->colorized = true;
+			player->mo->color = (player->kartstuff[k_growshrinktimer] < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE);
+		}
+		else
+		{
+			player->mo->colorized = false;
+			player->mo->color = player->skincolor;
+		}
+	}
+	else
+	{
+		player->mo->colorized = false;
+	}
+
+	if (player->kartstuff[k_dashpadcooldown]) // Twinkle Circuit inspired afterimages
+	{
+		mobj_t *ghost;
+		ghost = P_SpawnGhostMobj(player->mo);
+		ghost->fuse = player->kartstuff[k_dashpadcooldown]+1;
+		ghost->momx = player->mo->momx / (player->kartstuff[k_dashpadcooldown]+1);
+		ghost->momy = player->mo->momy / (player->kartstuff[k_dashpadcooldown]+1);
+		ghost->momz = player->mo->momz / (player->kartstuff[k_dashpadcooldown]+1);
+		player->kartstuff[k_dashpadcooldown]--;
+	}
+
+	// DKR style camera for boosting
+	if (player->kartstuff[k_boostcam] != 0 || player->kartstuff[k_destboostcam] != 0)
+	{
+		if (player->kartstuff[k_boostcam] < player->kartstuff[k_destboostcam]
+			&& player->kartstuff[k_destboostcam] != 0)
+		{
+			player->kartstuff[k_boostcam] += FRACUNIT/(TICRATE/4);
+			if (player->kartstuff[k_boostcam] >= player->kartstuff[k_destboostcam])
+				player->kartstuff[k_destboostcam] = 0;
+		}
+		else
+		{
+			player->kartstuff[k_boostcam] -= FRACUNIT/TICRATE;
+			if (player->kartstuff[k_boostcam] < player->kartstuff[k_destboostcam])
+				player->kartstuff[k_boostcam] = player->kartstuff[k_destboostcam] = 0;
+		}
+		//CONS_Printf("cam: %d, dest: %d\n", player->kartstuff[k_boostcam], player->kartstuff[k_destboostcam]);
+	}
+
+	player->kartstuff[k_timeovercam] = 0;
+
+	// Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations.
+	if (player->kartstuff[k_spinouttimer] != 0
+		|| player->kartstuff[k_wipeoutslow] != 0
+		|| player->kartstuff[k_squishedtimer] != 0)
+	{
+		player->powers[pw_flashing] = K_GetKartFlashing(player);
+	}
+	else if (player->powers[pw_flashing] == K_GetKartFlashing(player))
+	{
+		player->powers[pw_flashing]--;
+	}
+
+	if (player->kartstuff[k_spinouttimer])
+	{
+		if ((P_IsObjectOnGround(player->mo) || player->kartstuff[k_spinouttype] == 1)
+			&& (player->kartstuff[k_sneakertimer] == 0))
+		{
+			player->kartstuff[k_spinouttimer]--;
+			if (player->kartstuff[k_wipeoutslow] > 1)
+				player->kartstuff[k_wipeoutslow]--;
+			if (player->kartstuff[k_spinouttimer] == 0)
+				player->kartstuff[k_spinouttype] = 0; // Reset type
+		}
+	}
+	else
+	{
+		if (player->kartstuff[k_wipeoutslow] == 1)
+			player->mo->friction = ORIG_FRICTION;
+		player->kartstuff[k_wipeoutslow] = 0;
+		if (!comeback)
+			player->kartstuff[k_comebacktimer] = comebacktime;
+		else if (player->kartstuff[k_comebacktimer])
+		{
+			player->kartstuff[k_comebacktimer]--;
+			if (P_IsLocalPlayer(player) && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer] <= 0)
+				comebackshowninfo = true; // client has already seen the message
+		}
+	}
+
+	/*if (player->kartstuff[k_thunderanim])
+		player->kartstuff[k_thunderanim]--;*/
+
+	if (player->kartstuff[k_sneakertimer])
+	{
+		player->kartstuff[k_sneakertimer]--;
+		if (player->kartstuff[k_wipeoutslow] > 0 && player->kartstuff[k_wipeoutslow] < wipeoutslowtime+1)
+			player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1;
+	}
+
+	if (player->kartstuff[k_floorboost])
+		player->kartstuff[k_floorboost]--;
+
+	if (player->kartstuff[k_driftboost])
+		player->kartstuff[k_driftboost]--;
+
+	if (player->kartstuff[k_startboost])
+		player->kartstuff[k_startboost]--;
+
+	if (player->kartstuff[k_invincibilitytimer])
+		player->kartstuff[k_invincibilitytimer]--;
+
+	if (!player->kartstuff[k_respawn] && player->kartstuff[k_growshrinktimer] != 0)
+	{
+		if (player->kartstuff[k_growshrinktimer] > 0)
+			player->kartstuff[k_growshrinktimer]--;
+		if (player->kartstuff[k_growshrinktimer] < 0)
+			player->kartstuff[k_growshrinktimer]++;
+
+		// Back to normal
+		if (player->kartstuff[k_growshrinktimer] == 0)
+			K_RemoveGrowShrink(player);
+	}
+
+	if (player->kartstuff[k_stealingtimer] == 0 && player->kartstuff[k_stolentimer] == 0
+		&& player->kartstuff[k_rocketsneakertimer])
+		player->kartstuff[k_rocketsneakertimer]--;
+
+	if (player->kartstuff[k_hyudorotimer])
+		player->kartstuff[k_hyudorotimer]--;
+
+	if (player->kartstuff[k_sadtimer])
+		player->kartstuff[k_sadtimer]--;
+
+	if (player->kartstuff[k_stealingtimer])
+		player->kartstuff[k_stealingtimer]--;
+
+	if (player->kartstuff[k_stolentimer])
+		player->kartstuff[k_stolentimer]--;
+
+	if (player->kartstuff[k_squishedtimer])
+		player->kartstuff[k_squishedtimer]--;
+
+	if (player->kartstuff[k_justbumped])
+		player->kartstuff[k_justbumped]--;
+
+	// This doesn't go in HUD update because it has potential gameplay ramifications
+	if (player->kartstuff[k_itemblink] && player->kartstuff[k_itemblink]-- <= 0)
+	{
+		player->kartstuff[k_itemblinkmode] = 0;
+		player->kartstuff[k_itemblink] = 0;
+	}
+
+	K_KartPlayerHUDUpdate(player);
+
+	if (player->kartstuff[k_voices])
+		player->kartstuff[k_voices]--;
+
+	if (player->kartstuff[k_tauntvoices])
+		player->kartstuff[k_tauntvoices]--;
+
+	if (G_BattleGametype() && player->kartstuff[k_bumper] > 0)
+		player->kartstuff[k_wanted]++;
+
+	if (P_IsObjectOnGround(player->mo))
+		player->kartstuff[k_waterskip] = 0;
+
+	if (player->kartstuff[k_instashield])
+		player->kartstuff[k_instashield]--;
+
+	if (player->kartstuff[k_eggmanexplode])
+	{
+		if (player->spectator || (G_BattleGametype() && !player->kartstuff[k_bumper]))
+			player->kartstuff[k_eggmanexplode] = 0;
+		else
+		{
+			player->kartstuff[k_eggmanexplode]--;
+			if (player->kartstuff[k_eggmanexplode] <= 0)
+			{
+				mobj_t *eggsexplode;
+				//player->powers[pw_flashing] = 0;
+				eggsexplode = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SPBEXPLOSION);
+				if (player->kartstuff[k_eggmanblame] >= 0
+				&& player->kartstuff[k_eggmanblame] < MAXPLAYERS
+				&& playeringame[player->kartstuff[k_eggmanblame]]
+				&& !players[player->kartstuff[k_eggmanblame]].spectator
+				&& players[player->kartstuff[k_eggmanblame]].mo)
+					P_SetTarget(&eggsexplode->target, players[player->kartstuff[k_eggmanblame]].mo);
+			}
+		}
+	}
+
+	// ???
+	/*
+	if (player->kartstuff[k_jmp] > 1 && onground)
+	{
+		S_StartSound(player->mo, sfx_spring);
+		P_DoJump(player, false);
+		player->mo->momz *= player->kartstuff[k_jmp];
+		player->kartstuff[k_jmp] = 0;
+	}
+	*/
+
+	if (player->kartstuff[k_comebacktimer])
+		player->kartstuff[k_comebackmode] = 0;
+
+	if (P_IsObjectOnGround(player->mo) && player->mo->momz <= 0 && player->kartstuff[k_pogospring])
+		player->kartstuff[k_pogospring] = 0;
+
+	if (cmd->buttons & BT_DRIFT)
+		player->kartstuff[k_jmp] = 1;
+	else
+		player->kartstuff[k_jmp] = 0;
+
+	// Respawn Checker
+	if (player->kartstuff[k_respawn])
+		K_RespawnChecker(player);
+
+	// Roulette Code
+	K_KartItemRoulette(player, cmd);
+
+	// Handle invincibility sfx
+	K_UpdateInvincibilitySounds(player); // Also thanks, VAda!
+
+	// Plays the music after the starting countdown.
+	if (P_IsLocalPlayer(player) && leveltime == (starttime + (TICRATE/2)))
+	{
+		S_ChangeMusic(mapmusname, mapmusflags, true);
+		S_ShowMusicCredit();
+	}
+}
+
+void K_KartPlayerAfterThink(player_t *player)
+{
+	if (player->kartstuff[k_curshield]
+		|| player->kartstuff[k_invincibilitytimer]
+		|| (player->kartstuff[k_growshrinktimer] != 0 && player->kartstuff[k_growshrinktimer] % 5 == 4)) // 4 instead of 0 because this is afterthink!
+	{
+		player->mo->frame |= FF_FULLBRIGHT;
+	}
+	else
+	{
+		if (!(player->mo->state->frame & FF_FULLBRIGHT))
+			player->mo->frame &= ~FF_FULLBRIGHT;
+	}
+
+	// Move held objects (Bananas, Orbinaut, etc)
+	K_MoveHeldObjects(player);
+
+	// Jawz reticule (seeking)
+	if (player->kartstuff[k_itemtype] == KITEM_JAWZ && player->kartstuff[k_itemheld])
+	{
+		INT32 lasttarg = player->kartstuff[k_lastjawztarget];
+		player_t *targ;
+		mobj_t *ret;
+
+		if (player->kartstuff[k_jawztargetdelay] && playeringame[lasttarg] && !players[lasttarg].spectator)
+		{
+			targ = &players[lasttarg];
+			player->kartstuff[k_jawztargetdelay]--;
+		}
+		else
+			targ = K_FindJawzTarget(player->mo, player);
+
+		if (!targ || !targ->mo || P_MobjWasRemoved(targ->mo))
+		{
+			player->kartstuff[k_lastjawztarget] = -1;
+			player->kartstuff[k_jawztargetdelay] = 0;
+			return;
+		}
+
+		ret = P_SpawnMobj(targ->mo->x, targ->mo->y, targ->mo->z, MT_PLAYERRETICULE);
+		P_SetTarget(&ret->target, targ->mo);
+		ret->frame |= ((leveltime % 10) / 2);
+		ret->tics = 1;
+		ret->color = player->skincolor;
+
+		if (targ-players != lasttarg)
+		{
+			if (P_IsLocalPlayer(player) || P_IsLocalPlayer(targ))
+				S_StartSound(NULL, sfx_s3k89);
+			else
+				S_StartSound(targ->mo, sfx_s3k89);
+
+			player->kartstuff[k_lastjawztarget] = targ-players;
+			player->kartstuff[k_jawztargetdelay] = 5;
+		}
+	}
+	else
+	{
+		player->kartstuff[k_lastjawztarget] = -1;
+		player->kartstuff[k_jawztargetdelay] = 0;
+	}
+}
+
+// Returns false if this player being placed here causes them to collide with any other player
+// Used in g_game.c for match etc. respawning
+// This does not check along the z because the z is not correctly set for the spawnee at this point
+boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y)
+{
+	INT32 i;
+	fixed_t p1radius = players[playernum].mo->radius;
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playernum == i || !playeringame[i] || players[i].spectator || !players[i].mo || players[i].mo->health <= 0
+			|| players[i].playerstate != PST_LIVE || (players[i].mo->flags & MF_NOCLIP) || (players[i].mo->flags & MF_NOCLIPTHING))
+			continue;
+
+		if (abs(x - players[i].mo->x) < (p1radius + players[i].mo->radius)
+			&& abs(y - players[i].mo->y) < (p1radius + players[i].mo->radius))
+		{
+			return false;
+		}
+	}
+	return true;
+}
+
+// countersteer is how strong the controls are telling us we are turning
+// turndir is the direction the controls are telling us to turn, -1 if turning right and 1 if turning left
+static INT16 K_GetKartDriftValue(player_t *player, fixed_t countersteer)
+{
+	INT16 basedrift, driftangle;
+	fixed_t driftweight = player->kartweight*14; // 12
+
+	// If they aren't drifting or on the ground this doesn't apply
+	if (player->kartstuff[k_drift] == 0 || !P_IsObjectOnGround(player->mo))
+		return 0;
+
+	if (player->kartstuff[k_driftend] != 0)
+	{
+		return -266*player->kartstuff[k_drift]; // Drift has ended and we are tweaking their angle back a bit
+	}
+
+	//basedrift = 90*player->kartstuff[k_drift]; // 450
+	//basedrift = 93*player->kartstuff[k_drift] - driftweight*3*player->kartstuff[k_drift]/10; // 447 - 303
+	basedrift = 83*player->kartstuff[k_drift] - (driftweight - 14)*player->kartstuff[k_drift]/5; // 415 - 303
+	driftangle = abs((252 - driftweight)*player->kartstuff[k_drift]/5);
+
+	return basedrift + FixedMul(driftangle, countersteer);
+}
+
+INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue)
+{
+	fixed_t p_maxspeed = FixedMul(K_GetKartSpeed(player, false), 3*FRACUNIT);
+	fixed_t adjustangle = FixedDiv((p_maxspeed>>16) - (player->speed>>16), (p_maxspeed>>16) + player->kartweight);
+
+	if (player->spectator)
+		return turnvalue;
+
+	if (player->kartstuff[k_drift] != 0 && P_IsObjectOnGround(player->mo))
+	{
+		// If we're drifting we have a completely different turning value
+		if (player->kartstuff[k_driftend] == 0)
+		{
+			// 800 is the max set in g_game.c with angleturn
+			fixed_t countersteer = FixedDiv(turnvalue*FRACUNIT, 800*FRACUNIT);
+			turnvalue = K_GetKartDriftValue(player, countersteer);
+		}
+		else
+			turnvalue = (INT16)(turnvalue + K_GetKartDriftValue(player, FRACUNIT));
+
+		return turnvalue;
+	}
+
+	turnvalue = FixedMul(turnvalue, adjustangle); // Weight has a small effect on turning
+
+	if (player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_sneakertimer] || player->kartstuff[k_growshrinktimer] > 0)
+		turnvalue = FixedMul(turnvalue, FixedDiv(5*FRACUNIT, 4*FRACUNIT));
+
+	return turnvalue;
+}
+
+INT32 K_GetKartDriftSparkValue(player_t *player)
+{
+	UINT8 kartspeed = (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)
+		? 1
+		: player->kartspeed;
+	return (26*4 + kartspeed*2 + (9 - player->kartweight))*8;
+}
+
+static void K_KartDrift(player_t *player, boolean onground)
+{
+	fixed_t minspeed = (10 * player->mo->scale);
+	INT32 dsone = K_GetKartDriftSparkValue(player);
+	INT32 dstwo = dsone*2;
+	INT32 dsthree = dstwo*2;
+
+	// Drifting is actually straffing + automatic turning.
+	// Holding the Jump button will enable drifting.
+
+	// Drift Release (Moved here so you can't "chain" drifts)
+	if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5)
+		// || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1))
+		&& player->kartstuff[k_driftcharge] < dsone
+		&& onground)
+	{
+		player->kartstuff[k_driftcharge] = 0;
+	}
+	else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5)
+		// || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1))
+		&& (player->kartstuff[k_driftcharge] >= dsone && player->kartstuff[k_driftcharge] < dstwo)
+		&& onground)
+	{
+		if (player->kartstuff[k_driftboost] < 20)
+			player->kartstuff[k_driftboost] = 20;
+		S_StartSound(player->mo, sfx_s23c);
+		//K_SpawnDashDustRelease(player);
+		player->kartstuff[k_driftcharge] = 0;
+	}
+	else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5)
+		// || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1))
+		&& player->kartstuff[k_driftcharge] < dsthree
+		&& onground)
+	{
+		if (player->kartstuff[k_driftboost] < 50)
+			player->kartstuff[k_driftboost] = 50;
+		S_StartSound(player->mo, sfx_s23c);
+		//K_SpawnDashDustRelease(player);
+		player->kartstuff[k_driftcharge] = 0;
+	}
+	else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5)
+		// || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1))
+		&& player->kartstuff[k_driftcharge] >= dsthree
+		&& onground)
+	{
+		if (player->kartstuff[k_driftboost] < 125)
+			player->kartstuff[k_driftboost] = 125;
+		S_StartSound(player->mo, sfx_s23c);
+		//K_SpawnDashDustRelease(player);
+		player->kartstuff[k_driftcharge] = 0;
+	}
+
+	// Drifting: left or right?
+	if ((player->cmd.driftturn > 0) && player->speed > minspeed && player->kartstuff[k_jmp] == 1
+		&& (player->kartstuff[k_drift] == 0 || player->kartstuff[k_driftend] == 1)) // && player->kartstuff[k_drift] != 1)
+	{
+		// Starting left drift
+		player->kartstuff[k_drift] = 1;
+		player->kartstuff[k_driftend] = player->kartstuff[k_driftcharge] = 0;
+	}
+	else if ((player->cmd.driftturn < 0) && player->speed > minspeed && player->kartstuff[k_jmp] == 1
+		&& (player->kartstuff[k_drift] == 0 || player->kartstuff[k_driftend] == 1)) // && player->kartstuff[k_drift] != -1)
+	{
+		// Starting right drift
+		player->kartstuff[k_drift] = -1;
+		player->kartstuff[k_driftend] = player->kartstuff[k_driftcharge] = 0;
+	}
+	else if (player->kartstuff[k_jmp] == 0) // || player->kartstuff[k_turndir] == 0)
+	{
+		// drift is not being performed so if we're just finishing set driftend and decrement counters
+		if (player->kartstuff[k_drift] > 0)
+		{
+			player->kartstuff[k_drift]--;
+			player->kartstuff[k_driftend] = 1;
+		}
+		else if (player->kartstuff[k_drift] < 0)
+		{
+			player->kartstuff[k_drift]++;
+			player->kartstuff[k_driftend] = 1;
+		}
+		else
+			player->kartstuff[k_driftend] = 0;
+	}
+
+	// Incease/decrease the drift value to continue drifting in that direction
+	if (player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_jmp] == 1 && onground && player->kartstuff[k_drift] != 0)
+	{
+		fixed_t driftadditive = 24;
+
+		if (player->kartstuff[k_drift] >= 1) // Drifting to the left
+		{
+			player->kartstuff[k_drift]++;
+			if (player->kartstuff[k_drift] > 5)
+				player->kartstuff[k_drift] = 5;
+
+			if (player->cmd.driftturn > 0) // Inward
+				driftadditive += abs(player->cmd.driftturn)/100;
+			if (player->cmd.driftturn < 0) // Outward
+				driftadditive -= abs(player->cmd.driftturn)/75;
+		}
+		else if (player->kartstuff[k_drift] <= -1) // Drifting to the right
+		{
+			player->kartstuff[k_drift]--;
+			if (player->kartstuff[k_drift] < -5)
+				player->kartstuff[k_drift] = -5;
+
+			if (player->cmd.driftturn < 0) // Inward
+				driftadditive += abs(player->cmd.driftturn)/100;
+			if (player->cmd.driftturn > 0) // Outward
+				driftadditive -= abs(player->cmd.driftturn)/75;
+		}
+
+		// Disable drift-sparks until you're going fast enough
+		if (player->kartstuff[k_getsparks] == 0 || player->kartstuff[k_offroad])
+			driftadditive = 0;
+		if (player->speed > minspeed*2)
+			player->kartstuff[k_getsparks] = 1;
+
+		// This spawns the drift sparks
+		if (player->kartstuff[k_driftcharge] + driftadditive >= dsone)
+			K_SpawnDriftSparks(player);
+
+		// Sound whenever you get a different tier of sparks
+		if (P_IsLocalPlayer(player) // UGHGHGH...
+			&& ((player->kartstuff[k_driftcharge] < dsone && player->kartstuff[k_driftcharge]+driftadditive >= dsone)
+			|| (player->kartstuff[k_driftcharge] < dstwo && player->kartstuff[k_driftcharge]+driftadditive >= dstwo)
+			|| (player->kartstuff[k_driftcharge] < dsthree && player->kartstuff[k_driftcharge]+driftadditive >= dsthree)))
+		{
+			//S_StartSound(player->mo, sfx_s3ka2);
+			S_StartSoundAtVolume(player->mo, sfx_s3ka2, 192); // Ugh...
+		}
+
+		player->kartstuff[k_driftcharge] += driftadditive;
+		player->kartstuff[k_driftend] = 0;
+	}
+
+	// Stop drifting
+	if (player->kartstuff[k_spinouttimer] > 0 || player->speed < minspeed)
+	{
+		player->kartstuff[k_drift] = player->kartstuff[k_driftcharge] = 0;
+		player->kartstuff[k_aizdriftstrat] = player->kartstuff[k_brakedrift] = 0;
+		player->kartstuff[k_getsparks] = 0;
+	}
+
+	if ((!player->kartstuff[k_sneakertimer])
+	|| (!player->cmd.driftturn)
+	|| (player->cmd.driftturn > 0) != (player->kartstuff[k_aizdriftstrat] > 0))
+	{
+		if (!player->kartstuff[k_drift])
+			player->kartstuff[k_aizdriftstrat] = 0;
+		else
+			player->kartstuff[k_aizdriftstrat] = ((player->kartstuff[k_drift] > 0) ? 1 : -1);
+	}
+	else if (player->kartstuff[k_aizdriftstrat] && !player->kartstuff[k_drift])
+		K_SpawnAIZDust(player);
+
+	if (player->kartstuff[k_drift]
+		&& ((player->cmd.buttons & BT_BRAKE)
+		|| !(player->cmd.buttons & BT_ACCELERATE))
+		&& P_IsObjectOnGround(player->mo))
+	{
+		if (!player->kartstuff[k_brakedrift])
+			K_SpawnBrakeDriftSparks(player);
+		player->kartstuff[k_brakedrift] = 1;
+	}
+	else
+		player->kartstuff[k_brakedrift] = 0;
+}
+//
+// K_KartUpdatePosition
+//
+void K_KartUpdatePosition(player_t *player)
+{
+	fixed_t position = 1;
+	fixed_t oldposition = player->kartstuff[k_position];
+	fixed_t i, ppcd, pncd, ipcd, incd;
+	fixed_t pmo, imo;
+	mobj_t *mo;
+
+	if (player->spectator || !player->mo)
+		return;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i] || players[i].spectator || !players[i].mo)
+			continue;
+
+		if (G_RaceGametype())
+		{
+			if ((((players[i].starpostnum) + (numstarposts + 1) * players[i].laps) >
+				((player->starpostnum) + (numstarposts + 1) * player->laps)))
+				position++;
+			else if (((players[i].starpostnum) + (numstarposts+1)*players[i].laps) ==
+				((player->starpostnum) + (numstarposts+1)*player->laps))
+			{
+				ppcd = pncd = ipcd = incd = 0;
+
+				player->kartstuff[k_prevcheck] = players[i].kartstuff[k_prevcheck] = 0;
+				player->kartstuff[k_nextcheck] = players[i].kartstuff[k_nextcheck] = 0;
+
+				// This checks every thing on the map, and looks for MT_BOSS3WAYPOINT (the thing we're using for checkpoint wp's, for now)
+				for (mo = waypointcap; mo != NULL; mo = mo->tracer)
+				{
+					pmo = P_AproxDistance(P_AproxDistance(	mo->x - player->mo->x,
+															mo->y - player->mo->y),
+															mo->z - player->mo->z) / FRACUNIT;
+					imo = P_AproxDistance(P_AproxDistance(	mo->x - players[i].mo->x,
+															mo->y - players[i].mo->y),
+															mo->z - players[i].mo->z) / FRACUNIT;
+
+					if (mo->health == player->starpostnum && (!mo->movecount || mo->movecount == player->laps+1))
+					{
+						player->kartstuff[k_prevcheck] += pmo;
+						ppcd++;
+					}
+					if (mo->health == (player->starpostnum + 1) && (!mo->movecount || mo->movecount == player->laps+1))
+					{
+						player->kartstuff[k_nextcheck] += pmo;
+						pncd++;
+					}
+					if (mo->health == players[i].starpostnum && (!mo->movecount || mo->movecount == players[i].laps+1))
+					{
+						players[i].kartstuff[k_prevcheck] += imo;
+						ipcd++;
+					}
+					if (mo->health == (players[i].starpostnum + 1) && (!mo->movecount || mo->movecount == players[i].laps+1))
+					{
+						players[i].kartstuff[k_nextcheck] += imo;
+						incd++;
+					}
+				}
+
+				if (ppcd > 1) player->kartstuff[k_prevcheck] /= ppcd;
+				if (pncd > 1) player->kartstuff[k_nextcheck] /= pncd;
+				if (ipcd > 1) players[i].kartstuff[k_prevcheck] /= ipcd;
+				if (incd > 1) players[i].kartstuff[k_nextcheck] /= incd;
+
+				if ((players[i].kartstuff[k_nextcheck] > 0 || player->kartstuff[k_nextcheck] > 0) && !player->exiting)
+				{
+					if ((players[i].kartstuff[k_nextcheck] - players[i].kartstuff[k_prevcheck]) <
+						(player->kartstuff[k_nextcheck] - player->kartstuff[k_prevcheck]))
+						position++;
+				}
+				else if (!player->exiting)
+				{
+					if (players[i].kartstuff[k_prevcheck] > player->kartstuff[k_prevcheck])
+						position++;
+				}
+				else
+				{
+					if (players[i].starposttime < player->starposttime)
+						position++;
+				}
+			}
+		}
+		else if (G_BattleGametype())
+		{
+			if (player->exiting) // End of match standings
+			{
+				if (players[i].marescore > player->marescore) // Only score matters
+					position++;
+			}
+			else
+			{
+				if (players[i].kartstuff[k_bumper] == player->kartstuff[k_bumper] && players[i].marescore > player->marescore)
+					position++;
+				else if (players[i].kartstuff[k_bumper] > player->kartstuff[k_bumper])
+					position++;
+			}
+		}
+	}
+
+	if (leveltime < starttime || oldposition == 0)
+		oldposition = position;
+
+	if (oldposition != position) // Changed places?
+		player->kartstuff[k_positiondelay] = 10; // Position number growth
+
+	player->kartstuff[k_position] = position;
+}
+
+//
+// K_StripItems
+//
+void K_StripItems(player_t *player)
+{
+	player->kartstuff[k_itemtype] = KITEM_NONE;
+	player->kartstuff[k_itemamount] = 0;
+	player->kartstuff[k_itemheld] = 0;
+
+	player->kartstuff[k_rocketsneakertimer] = 0;
+
+	if (!player->kartstuff[k_itemroulette] || player->kartstuff[k_roulettetype] != 2)
+	{
+		player->kartstuff[k_itemroulette] = 0;
+		player->kartstuff[k_roulettetype] = 0;
+	}
+	player->kartstuff[k_eggmanheld] = 0;
+
+	player->kartstuff[k_hyudorotimer] = 0;
+	player->kartstuff[k_stealingtimer] = 0;
+	player->kartstuff[k_stolentimer] = 0;
+
+	player->kartstuff[k_curshield] = 0;
+	//player->kartstuff[k_thunderanim] = 0;
+	player->kartstuff[k_bananadrag] = 0;
+
+	player->kartstuff[k_sadtimer] = 0;
+
+	K_UpdateHnextList(player, true);
+}
+
+void K_StripOther(player_t *player)
+{
+	player->kartstuff[k_itemroulette] = 0;
+	player->kartstuff[k_roulettetype] = 0;
+
+	player->kartstuff[k_invincibilitytimer] = 0;
+	K_RemoveGrowShrink(player);
+
+	if (player->kartstuff[k_eggmanexplode])
+	{
+		player->kartstuff[k_eggmanexplode] = 0;
+		player->kartstuff[k_eggmanblame] = -1;
+	}
+}
+
+//
+// K_MoveKartPlayer
+//
+void K_MoveKartPlayer(player_t *player, boolean onground)
+{
+	ticcmd_t *cmd = &player->cmd;
+	boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->pflags & PF_ATTACKDOWN));
+	boolean HOLDING_ITEM = (player->kartstuff[k_itemheld] || player->kartstuff[k_eggmanheld]);
+	boolean NO_HYUDORO = (player->kartstuff[k_stolentimer] == 0 && player->kartstuff[k_stealingtimer] == 0);
+
+	K_KartUpdatePosition(player);
+
+	if (!player->exiting)
+	{
+		if (player->kartstuff[k_oldposition] < player->kartstuff[k_position]) // But first, if you lost a place,
+		{
+			player->kartstuff[k_oldposition] = player->kartstuff[k_position]; // then the other player taunts.
+			K_RegularVoiceTimers(player); // and you can't for a bit
+		}
+		else if (player->kartstuff[k_oldposition] > player->kartstuff[k_position]) // Otherwise,
+		{
+			K_PlayOvertakeSound(player->mo); // Say "YOU'RE TOO SLOW!"
+			player->kartstuff[k_oldposition] = player->kartstuff[k_position]; // Restore the old position,
+		}
+	}
+
+	if (player->kartstuff[k_positiondelay])
+		player->kartstuff[k_positiondelay]--;
+
+	if ((player->pflags & PF_ATTACKDOWN) && !(cmd->buttons & BT_ATTACK))
+		player->pflags &= ~PF_ATTACKDOWN;
+	else if (cmd->buttons & BT_ATTACK)
+		player->pflags |= PF_ATTACKDOWN;
+
+	if (player && player->mo && player->mo->health > 0 && !player->spectator && !(player->exiting || mapreset)
+		&& player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_squishedtimer] == 0 && player->kartstuff[k_respawn] == 0)
+	{
+		// First, the really specific, finicky items that function without the item being directly in your item slot.
+		// Karma item dropping
+		if (ATTACK_IS_DOWN && player->kartstuff[k_comebackmode] && !player->kartstuff[k_comebacktimer])
+		{
+			mobj_t *newitem;
+
+			if (player->kartstuff[k_comebackmode] == 1)
+			{
+				newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RANDOMITEM);
+				newitem->threshold = 69; // selected "randomly".
+			}
+			else
+			{
+				newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM);
+				if (player->kartstuff[k_eggmanblame] >= 0
+				&& player->kartstuff[k_eggmanblame] < MAXPLAYERS
+				&& playeringame[player->kartstuff[k_eggmanblame]]
+				&& !players[player->kartstuff[k_eggmanblame]].spectator
+				&& players[player->kartstuff[k_eggmanblame]].mo)
+					P_SetTarget(&newitem->target, players[player->kartstuff[k_eggmanblame]].mo);
+				player->kartstuff[k_eggmanblame] = -1;
+			}
+
+			newitem->flags2 = (player->mo->flags2 & MF2_OBJECTFLIP);
+			newitem->fuse = 15*TICRATE; // selected randomly.
+
+			player->kartstuff[k_comebackmode] = 0;
+			player->kartstuff[k_comebacktimer] = comebacktime;
+			S_StartSound(player->mo, sfx_s254);
+		}
+		// Eggman Monitor exploding
+		else if (player->kartstuff[k_eggmanexplode])
+		{
+			if (ATTACK_IS_DOWN && player->kartstuff[k_eggmanexplode] <= 3*TICRATE && player->kartstuff[k_eggmanexplode] > 1)
+				player->kartstuff[k_eggmanexplode] = 1;
+		}
+		// Eggman Monitor throwing
+		else if (ATTACK_IS_DOWN && player->kartstuff[k_eggmanheld])
+		{
+			K_ThrowKartItem(player, false, MT_EGGMANITEM, -1, 0);
+			K_PlayAttackTaunt(player->mo);
+			player->kartstuff[k_eggmanheld] = 0;
+			K_UpdateHnextList(player, true);
+		}
+		// Rocket Sneaker
+		else if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO
+			&& player->kartstuff[k_rocketsneakertimer] > 1)
+		{
+			K_DoSneaker(player, 2);
+			K_PlayBoostTaunt(player->mo);
+			player->kartstuff[k_rocketsneakertimer] -= 2*TICRATE;
+			if (player->kartstuff[k_rocketsneakertimer] < 1)
+				player->kartstuff[k_rocketsneakertimer] = 1;
+		}
+		else if (player->kartstuff[k_itemamount] <= 0)
+		{
+			player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0;
+		}
+		else
+		{
+			switch (player->kartstuff[k_itemtype])
+			{
+				case KITEM_SNEAKER:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO)
+					{
+						K_DoSneaker(player, 1);
+						K_PlayBoostTaunt(player->mo);
+						player->kartstuff[k_itemamount]--;
+					}
+					break;
+				case KITEM_ROCKETSNEAKER:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO
+						&& player->kartstuff[k_rocketsneakertimer] == 0)
+					{
+						INT32 moloop;
+						mobj_t *mo = NULL;
+						mobj_t *prev = player->mo;
+
+						K_PlayBoostTaunt(player->mo);
+						//player->kartstuff[k_itemheld] = 1;
+						S_StartSound(player->mo, sfx_s3k3a);
+
+						//K_DoSneaker(player, 2);
+
+						player->kartstuff[k_rocketsneakertimer] = (itemtime*3);
+						player->kartstuff[k_itemamount]--;
+						K_UpdateHnextList(player, true);
+
+						for (moloop = 0; moloop < 2; moloop++)
+						{
+							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ROCKETSNEAKER);
+							mo->flags |= MF_NOCLIPTHING;
+							mo->angle = player->mo->angle;
+							mo->threshold = 10;
+							mo->movecount = moloop%2;
+							mo->movedir = mo->lastlook = moloop+1;
+							P_SetTarget(&mo->target, player->mo);
+							P_SetTarget(&mo->hprev, prev);
+							P_SetTarget(&prev->hnext, mo);
+							prev = mo;
+						}
+					}
+					break;
+				case KITEM_INVINCIBILITY:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage normally, so you're free to waste it if you have multiple
+					{
+						if (!player->kartstuff[k_invincibilitytimer])
+						{
+							mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INVULNFLASH);
+							P_SetTarget(&overlay->target, player->mo);
+							overlay->destscale = player->mo->scale;
+							P_SetScale(overlay, player->mo->scale);
+						}
+						player->kartstuff[k_invincibilitytimer] = itemtime+(2*TICRATE); // 10 seconds
+						P_RestoreMusic(player);
+						if (!P_IsLocalPlayer(player))
+							S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmi : sfx_kinvnc));
+						K_PlayPowerGloatSound(player->mo);
+						player->kartstuff[k_itemamount]--;
+					}
+					break;
+				case KITEM_BANANA:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						INT32 moloop;
+						mobj_t *mo;
+						mobj_t *prev = player->mo;
+
+						//K_PlayAttackTaunt(player->mo);
+						player->kartstuff[k_itemheld] = 1;
+						S_StartSound(player->mo, sfx_s254);
+
+						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
+						{
+							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD);
+							if (!mo)
+							{
+								player->kartstuff[k_itemamount] = moloop;
+								break;
+							}
+							mo->flags |= MF_NOCLIPTHING;
+							mo->threshold = 10;
+							mo->movecount = player->kartstuff[k_itemamount];
+							mo->movedir = moloop+1;
+							P_SetTarget(&mo->target, player->mo);
+							P_SetTarget(&mo->hprev, prev);
+							P_SetTarget(&prev->hnext, mo);
+							prev = mo;
+						}
+					}
+					else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) // Banana x3 thrown
+					{
+						K_ThrowKartItem(player, false, MT_BANANA, -1, 0);
+						K_PlayAttackTaunt(player->mo);
+						player->kartstuff[k_itemamount]--;
+						K_UpdateHnextList(player, false);
+					}
+					break;
+				case KITEM_EGGMAN:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						mobj_t *mo;
+						player->kartstuff[k_itemamount]--;
+						player->kartstuff[k_eggmanheld] = 1;
+						S_StartSound(player->mo, sfx_s254);
+						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD);
+						if (mo)
+						{
+							mo->flags |= MF_NOCLIPTHING;
+							mo->threshold = 10;
+							mo->movecount = 1;
+							mo->movedir = 1;
+							P_SetTarget(&mo->target, player->mo);
+							P_SetTarget(&player->mo->hnext, mo);
+						}
+					}
+					break;
+				case KITEM_ORBINAUT:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						angle_t newangle;
+						INT32 moloop;
+						mobj_t *mo = NULL;
+						mobj_t *prev = player->mo;
+
+						//K_PlayAttackTaunt(player->mo);
+						player->kartstuff[k_itemheld] = 1;
+						S_StartSound(player->mo, sfx_s3k3a);
+
+						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
+						{
+							newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90;
+							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD);
+							if (!mo)
+							{
+								player->kartstuff[k_itemamount] = moloop;
+								break;
+							}
+							mo->flags |= MF_NOCLIPTHING;
+							mo->angle = newangle;
+							mo->threshold = 10;
+							mo->movecount = player->kartstuff[k_itemamount];
+							mo->movedir = mo->lastlook = moloop+1;
+							mo->color = player->skincolor;
+							P_SetTarget(&mo->target, player->mo);
+							P_SetTarget(&mo->hprev, prev);
+							P_SetTarget(&prev->hnext, mo);
+							prev = mo;
+						}
+					}
+					else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) // Orbinaut x3 thrown
+					{
+						K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0);
+						K_PlayAttackTaunt(player->mo);
+						player->kartstuff[k_itemamount]--;
+						K_UpdateHnextList(player, false);
+					}
+					break;
+				case KITEM_JAWZ:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						angle_t newangle;
+						INT32 moloop;
+						mobj_t *mo = NULL;
+						mobj_t *prev = player->mo;
+
+						//K_PlayAttackTaunt(player->mo);
+						player->kartstuff[k_itemheld] = 1;
+						S_StartSound(player->mo, sfx_s3k3a);
+
+						for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++)
+						{
+							newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90;
+							mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD);
+							if (!mo)
+							{
+								player->kartstuff[k_itemamount] = moloop;
+								break;
+							}
+							mo->flags |= MF_NOCLIPTHING;
+							mo->angle = newangle;
+							mo->threshold = 10;
+							mo->movecount = player->kartstuff[k_itemamount];
+							mo->movedir = mo->lastlook = moloop+1;
+							P_SetTarget(&mo->target, player->mo);
+							P_SetTarget(&mo->hprev, prev);
+							P_SetTarget(&prev->hnext, mo);
+							prev = mo;
+						}
+					}
+					else if (ATTACK_IS_DOWN && HOLDING_ITEM && player->kartstuff[k_itemheld]) // Jawz thrown
+					{
+						if (player->kartstuff[k_throwdir] == 1 || player->kartstuff[k_throwdir] == 0)
+							K_ThrowKartItem(player, true, MT_JAWZ, 1, 0);
+						else if (player->kartstuff[k_throwdir] == -1) // Throwing backward gives you a dud that doesn't home in
+							K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, 0);
+						K_PlayAttackTaunt(player->mo);
+						player->kartstuff[k_itemamount]--;
+						K_UpdateHnextList(player, false);
+					}
+					break;
+				case KITEM_MINE:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						mobj_t *mo;
+						player->kartstuff[k_itemheld] = 1;
+						S_StartSound(player->mo, sfx_s254);
+						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD);
+						if (mo)
+						{
+							mo->flags |= MF_NOCLIPTHING;
+							mo->threshold = 10;
+							mo->movecount = 1;
+							mo->movedir = 1;
+							P_SetTarget(&mo->target, player->mo);
+							P_SetTarget(&player->mo->hnext, mo);
+						}
+					}
+					else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld])
+					{
+						K_ThrowKartItem(player, false, MT_SSMINE, 1, 1);
+						K_PlayAttackTaunt(player->mo);
+						player->kartstuff[k_itemamount]--;
+						player->kartstuff[k_itemheld] = 0;
+						K_UpdateHnextList(player, true);
+					}
+					break;
+				case KITEM_BALLHOG:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						player->kartstuff[k_itemamount]--;
+						K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0);
+						K_PlayAttackTaunt(player->mo);
+					}
+					break;
+				case KITEM_SPB:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						player->kartstuff[k_itemamount]--;
+						K_ThrowKartItem(player, true, MT_SPB, 1, 0);
+						K_PlayAttackTaunt(player->mo);
+					}
+					break;
+				case KITEM_GROW:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
+						&& player->kartstuff[k_growshrinktimer] <= 0) // Grow holds the item box hostage
+					{
+						K_PlayPowerGloatSound(player->mo);
+						player->mo->scalespeed = mapobjectscale/TICRATE;
+						player->mo->destscale = (3*mapobjectscale)/2;
+						if (cv_kartdebugshrink.value && !modeattacking && !player->bot)
+							player->mo->destscale = (6*player->mo->destscale)/8;
+						player->kartstuff[k_growshrinktimer] = itemtime+(4*TICRATE); // 12 seconds
+						P_RestoreMusic(player);
+						if (!P_IsLocalPlayer(player))
+							S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow));
+						S_StartSound(player->mo, sfx_kc5a);
+						player->kartstuff[k_itemamount]--;
+					}
+					break;
+				case KITEM_SHRINK:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						K_DoShrink(player);
+						player->kartstuff[k_itemamount]--;
+						K_PlayPowerGloatSound(player->mo);
+					}
+					break;
+				case KITEM_THUNDERSHIELD:
+					if (player->kartstuff[k_curshield] != 1)
+					{
+						mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD);
+						P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2));
+						P_SetTarget(&shield->target, player->mo);
+						S_StartSound(shield, sfx_s3k41);
+						player->kartstuff[k_curshield] = 1;
+					}
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						K_DoThunderShield(player);
+						player->kartstuff[k_itemamount]--;
+						K_PlayAttackTaunt(player->mo);
+					}
+					break;
+				case KITEM_HYUDORO:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						player->kartstuff[k_itemamount]--;
+						K_DoHyudoroSteal(player); // yes. yes they do.
+					}
+					break;
+				case KITEM_POGOSPRING:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO
+						&& !player->kartstuff[k_pogospring])
+					{
+						K_PlayBoostTaunt(player->mo);
+						K_DoPogoSpring(player->mo, 32<<FRACBITS, 2);
+						player->kartstuff[k_pogospring] = 1;
+						player->kartstuff[k_itemamount]--;
+					}
+					break;
+				case KITEM_KITCHENSINK:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
+					{
+						mobj_t *mo;
+						player->kartstuff[k_itemheld] = 1;
+						S_StartSound(player->mo, sfx_s254);
+						mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SINK_SHIELD);
+						if (mo)
+						{
+							mo->flags |= MF_NOCLIPTHING;
+							mo->threshold = 10;
+							mo->movecount = 1;
+							mo->movedir = 1;
+							P_SetTarget(&mo->target, player->mo);
+							P_SetTarget(&player->mo->hnext, mo);
+						}
+					}
+					else if (ATTACK_IS_DOWN && HOLDING_ITEM && player->kartstuff[k_itemheld]) // Sink thrown
+					{
+						K_ThrowKartItem(player, false, MT_SINK, 1, 2);
+						K_PlayAttackTaunt(player->mo);
+						player->kartstuff[k_itemamount]--;
+						player->kartstuff[k_itemheld] = 0;
+						K_UpdateHnextList(player, true);
+					}
+					break;
+				case KITEM_SAD:
+					if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
+						&& !player->kartstuff[k_sadtimer])
+					{
+						player->kartstuff[k_sadtimer] = stealtime;
+						player->kartstuff[k_itemamount]--;
+					}
+					break;
+				default:
+					break;
+			}
+		}
+
+		// No more!
+		if (!player->kartstuff[k_itemamount])
+		{
+			player->kartstuff[k_itemheld] = 0;
+			player->kartstuff[k_itemtype] = KITEM_NONE;
+		}
+
+		if (player->kartstuff[k_itemtype] != KITEM_THUNDERSHIELD)
+			player->kartstuff[k_curshield] = 0;
+
+		if (player->kartstuff[k_itemtype] == KITEM_SPB
+			|| player->kartstuff[k_itemtype] == KITEM_SHRINK
+			|| player->kartstuff[k_growshrinktimer] < 0)
+			indirectitemcooldown = 20*TICRATE;
+
+		if (player->kartstuff[k_hyudorotimer] > 0)
+		{
+			if (splitscreen)
+			{
+				if (leveltime & 1)
+					player->mo->flags2 |= MF2_DONTDRAW;
+				else
+					player->mo->flags2 &= ~MF2_DONTDRAW;
+
+				if (player->kartstuff[k_hyudorotimer] >= (1*TICRATE/2) && player->kartstuff[k_hyudorotimer] <= hyudorotime-(1*TICRATE/2))
+				{
+					if (player == &players[secondarydisplayplayer])
+						player->mo->eflags |= MFE_DRAWONLYFORP2;
+					else if (player == &players[thirddisplayplayer] && splitscreen > 1)
+						player->mo->eflags |= MFE_DRAWONLYFORP3;
+					else if (player == &players[fourthdisplayplayer] && splitscreen > 2)
+						player->mo->eflags |= MFE_DRAWONLYFORP4;
+					else if (player == &players[consoleplayer])
+						player->mo->eflags |= MFE_DRAWONLYFORP1;
+					else
+						player->mo->flags2 |= MF2_DONTDRAW;
+				}
+				else
+					player->mo->eflags &= ~(MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4);
+			}
+			else
+			{
+				if (player == &players[displayplayer]
+					|| (player != &players[displayplayer] && (player->kartstuff[k_hyudorotimer] < (1*TICRATE/2) || player->kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2))))
+				{
+					if (leveltime & 1)
+						player->mo->flags2 |= MF2_DONTDRAW;
+					else
+						player->mo->flags2 &= ~MF2_DONTDRAW;
+				}
+				else
+					player->mo->flags2 |= MF2_DONTDRAW;
+			}
+
+			player->powers[pw_flashing] = player->kartstuff[k_hyudorotimer]; // We'll do this for now, let's people know about the invisible people through subtle hints
+		}
+		else if (player->kartstuff[k_hyudorotimer] == 0)
+		{
+			player->mo->flags2 &= ~MF2_DONTDRAW;
+			player->mo->eflags &= ~(MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4);
+		}
+
+		if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) // dead in match? you da bomb
+		{
+			K_DropItems(player); //K_StripItems(player);
+			K_StripOther(player);
+			player->mo->flags2 |= MF2_SHADOW;
+			player->powers[pw_flashing] = player->kartstuff[k_comebacktimer];
+		}
+		else if (G_RaceGametype() || player->kartstuff[k_bumper] > 0)
+		{
+			player->mo->flags2 &= ~MF2_SHADOW;
+		}
+	}
+
+	// Friction
+	if (!player->kartstuff[k_offroad])
+	{
+		if (player->speed > 0 && cmd->forwardmove == 0 && player->mo->friction == 59392)
+			player->mo->friction += 4608;
+		if (player->speed > 0 && cmd->forwardmove < 0 && player->mo->friction == 59392)
+			player->mo->friction += 1608;
+	}
+
+	// Karma ice physics
+	if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0)
+	{
+		player->mo->friction += 1228;
+
+		if (player->mo->friction > FRACUNIT)
+			player->mo->friction = FRACUNIT;
+		if (player->mo->friction < 0)
+			player->mo->friction = 0;
+
+		player->mo->movefactor = FixedDiv(ORIG_FRICTION, player->mo->friction);
+
+		if (player->mo->movefactor < FRACUNIT)
+			player->mo->movefactor = 19*player->mo->movefactor - 18*FRACUNIT;
+		else
+			player->mo->movefactor = FRACUNIT; //player->mo->movefactor = ((player->mo->friction - 0xDB34)*(0xA))/0x80;
+
+		if (player->mo->movefactor < 32)
+			player->mo->movefactor = 32;
+	}
+
+	// Wipeout slowdown
+	if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow])
+	{
+		if (player->kartstuff[k_offroad])
+			player->mo->friction -= 4912;
+		if (player->kartstuff[k_wipeoutslow] == 1)
+			player->mo->friction -= 9824;
+	}
+
+	K_KartDrift(player, onground);
+
+	// Quick Turning
+	// You can't turn your kart when you're not moving.
+	// So now it's time to burn some rubber!
+	if (player->speed < 2 && leveltime > starttime && cmd->buttons & BT_ACCELERATE && cmd->buttons & BT_BRAKE && cmd->driftturn != 0)
+	{
+		if (leveltime % 8 == 0)
+			S_StartSound(player->mo, sfx_s224);
+	}
+
+	// Squishing
+	// If a Grow player or a sector crushes you, get flattened instead of being killed.
+
+	if (player->kartstuff[k_squishedtimer] <= 0)
+	{
+		player->mo->flags &= ~MF_NOCLIP;
+	}
+	else
+	{
+		player->mo->flags |= MF_NOCLIP;
+		player->mo->momx = 0;
+		player->mo->momy = 0;
+	}
+
+	// Play the starting countdown sounds
+	if (player == &players[displayplayer]) // Don't play louder in splitscreen
+	{
+		if ((leveltime == starttime-(3*TICRATE)) || (leveltime == starttime-(2*TICRATE)) || (leveltime == starttime-TICRATE))
+			S_StartSound(NULL, sfx_s3ka7);
+		if (leveltime == starttime)
+		{
+			S_StartSound(NULL, sfx_s3kad);
+			S_StopMusic(); // The GO! sound stops the level start ambience
+		}
+	}
+
+	// Start charging once you're given the opportunity.
+	if (leveltime >= starttime-(2*TICRATE) && leveltime <= starttime)
+	{
+		if (cmd->buttons & BT_ACCELERATE)
+		{
+			if (player->kartstuff[k_boostcharge] == 0)
+				player->kartstuff[k_boostcharge] = cmd->latency;
+
+			player->kartstuff[k_boostcharge]++;
+		}
+		else
+			player->kartstuff[k_boostcharge] = 0;
+	}
+
+	// Increase your size while charging your engine.
+	if (leveltime < starttime+10)
+	{
+		player->mo->scalespeed = mapobjectscale/12;
+		player->mo->destscale = mapobjectscale + (player->kartstuff[k_boostcharge]*131);
+		if (cv_kartdebugshrink.value && !modeattacking && !player->bot)
+			player->mo->destscale = (6*player->mo->destscale)/8;
+	}
+
+	// Determine the outcome of your charge.
+	if (leveltime > starttime && player->kartstuff[k_boostcharge])
+	{
+		// Not even trying?
+		if (player->kartstuff[k_boostcharge] < 35)
+		{
+			if (player->kartstuff[k_boostcharge] > 17)
+				S_StartSound(player->mo, sfx_cdfm00); // chosen instead of a conventional skid because it's more engine-like
+		}
+		// Get an instant boost!
+		else if (player->kartstuff[k_boostcharge] <= 50)
+		{
+			player->kartstuff[k_startboost] = (50-player->kartstuff[k_boostcharge])+20;
+
+			if (player->kartstuff[k_boostcharge] <= 36)
+			{
+				player->kartstuff[k_startboost] = 0;
+				K_DoSneaker(player, 0);
+				player->kartstuff[k_sneakertimer] = 70; // PERFECT BOOST!!
+
+				if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) // Let everyone hear this one
+					S_StartSound(player->mo, sfx_s25f);
+			}
+			else
+			{
+				K_SpawnDashDustRelease(player); // already handled for perfect boosts by K_DoSneaker
+				if ((!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) && P_IsLocalPlayer(player))
+				{
+					if (player->kartstuff[k_boostcharge] <= 40)
+						S_StartSound(player->mo, sfx_cdfm01); // You were almost there!
+					else
+						S_StartSound(player->mo, sfx_s23c); // Nope, better luck next time.
+				}
+			}
+		}
+		// You overcharged your engine? Those things are expensive!!!
+		else if (player->kartstuff[k_boostcharge] > 50)
+		{
+			player->powers[pw_nocontrol] = 40;
+			//S_StartSound(player->mo, sfx_kc34);
+			S_StartSound(player->mo, sfx_s3k83);
+			player->pflags |= PF_SKIDDOWN; // cheeky pflag reuse
+		}
+
+		player->kartstuff[k_boostcharge] = 0;
+	}
+}
+
+void K_CalculateBattleWanted(void)
+{
+	UINT8 numingame = 0, numplaying = 0, numwanted = 0;
+	SINT8 bestbumperplayer = -1, bestbumper = -1;
+	SINT8 camppos[MAXPLAYERS]; // who is the biggest camper
+	UINT8 ties = 0, nextcamppos = 0;
+	boolean setbumper = false;
+	UINT8 i, j;
+
+	if (!G_BattleGametype())
+	{
+		for (i = 0; i < 4; i++)
+			battlewanted[i] = -1;
+		return;
+	}
+
+	wantedcalcdelay = wantedfrequency;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+		camppos[i] = -1; // initialize
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		UINT8 position = 1;
+
+		if (!playeringame[i] || players[i].spectator) // Not playing
+			continue;
+
+		if (players[i].exiting) // We're done, don't calculate.
+			return;
+
+		numplaying++;
+
+		if (players[i].kartstuff[k_bumper] <= 0) // Not alive, so don't do anything else
+			continue;
+
+		numingame++;
+
+		if (bestbumper == -1 || players[i].kartstuff[k_bumper] > bestbumper)
+		{
+			bestbumper = players[i].kartstuff[k_bumper];
+			bestbumperplayer = i;
+		}
+		else if (players[i].kartstuff[k_bumper] == bestbumper)
+			bestbumperplayer = -1; // Tie, no one has best bumper.
+
+		for (j = 0; j < MAXPLAYERS; j++)
+		{
+			if (!playeringame[j] || players[j].spectator)
+				continue;
+			if (players[j].kartstuff[k_bumper] <= 0)
+				continue;
+			if (j == i)
+				continue;
+			if (players[j].kartstuff[k_wanted] == players[i].kartstuff[k_wanted] && players[j].marescore > players[i].marescore)
+				position++;
+			else if (players[j].kartstuff[k_wanted] > players[i].kartstuff[k_wanted])
+				position++;
+		}
+
+		position--; // Make zero based
+
+		while (camppos[position] != -1) // Port priority!
+			position++;
+
+		camppos[position] = i;
+	}
+
+	if (numplaying <= 2 || (numingame <= 2 && bestbumper == 1)) // In 1v1s then there's no need for WANTED. In bigger netgames, don't show anyone as WANTED when they're equally matched.
+		numwanted = 0;
+	else
+		numwanted = min(4, 1 + ((numingame-2) / 4));
+
+	for (i = 0; i < 4; i++)
+	{
+		if (i+1 > numwanted) // Not enough players for this slot to be wanted!
+			battlewanted[i] = -1;
+		else if (bestbumperplayer != -1 && !setbumper) // If there's a player who has an untied bumper lead over everyone else, they are the first to be wanted.
+		{
+			battlewanted[i] = bestbumperplayer;
+			setbumper = true; // Don't set twice
+		}
+		else
+		{
+			// Don't accidentally set the same player, if the bestbumperplayer is also a huge camper.
+			while (bestbumperplayer != -1 && camppos[nextcamppos] != -1
+				&& bestbumperplayer == camppos[nextcamppos])
+				nextcamppos++;
+
+			// Do not add *any* more people if there's too many times that are tied with others.
+			// This could theoretically happen very easily if people don't hit each other for a while after the start of a match.
+			// (I will be sincerely impressed if more than 2 people tie after people start hitting each other though)
+
+			if (camppos[nextcamppos] == -1 // Out of entries
+				|| ties >= (numwanted-i)) // Already counted ties
+			{
+				battlewanted[i] = -1;
+				continue;
+			}
+
+			if (ties < (numwanted-i))
+			{
+				ties = 0; // Reset
+				for (j = 0; j < 2; j++)
+				{
+					if (camppos[nextcamppos+(j+1)] == -1) // Nothing beyond, cancel
+						break;
+					if (players[camppos[nextcamppos]].kartstuff[k_wanted] == players[camppos[nextcamppos+(j+1)]].kartstuff[k_wanted])
+						ties++;
+				}
+			}
+
+			if (ties < (numwanted-i)) // Is it still low enough after counting?
+			{
+				battlewanted[i] = camppos[nextcamppos];
+				nextcamppos++;
+			}
+			else
+				battlewanted[i] = -1;
+		}
+	}
+}
+
+void K_CheckBumpers(void)
+{
+	UINT8 i;
+	UINT8 numingame = 0;
+	SINT8 winnernum = -1;
+	INT32 winnerscoreadd = 0;
+
+	if (!multiplayer)
+		return;
+
+	if (!G_BattleGametype())
+		return;
+
+	if (gameaction == ga_completed)
+		return;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i] || players[i].spectator) // not even in-game
+			continue;
+
+		if (players[i].exiting) // we're already exiting! stop!
+			return;
+
+		numingame++;
+		winnerscoreadd += players[i].marescore;
+
+		if (players[i].kartstuff[k_bumper] <= 0) // if you don't have any bumpers, you're probably not a winner
+			continue;
+		else if (winnernum > -1) // TWO winners? that's dumb :V
+			return;
+
+		winnernum = i;
+		winnerscoreadd -= players[i].marescore;
+	}
+
+	if (numingame <= 1)
+		return;
+
+	if (winnernum > -1 && playeringame[winnernum])
+	{
+		players[winnernum].marescore += winnerscoreadd;
+		CONS_Printf(M_GetText("%s recieved %d point%s for winning!\n"), player_names[winnernum], winnerscoreadd, (winnerscoreadd == 1 ? "" : "s"));
+	}
+
+	for (i = 0; i < MAXPLAYERS; i++) // This can't go in the earlier loop because winning adds points
+		K_KartUpdatePosition(&players[i]);
+
+	for (i = 0; i < MAXPLAYERS; i++) // and it can't be merged with this loop because it needs to be all updated before exiting... multi-loops suck...
+		P_DoPlayerExit(&players[i]);
+}
+
+void K_CheckSpectateStatus(void)
+{
+	UINT8 respawnlist[MAXPLAYERS];
+	UINT8 i, j, numingame = 0, numjoiners = 0;
+
+	// Maintain spectate wait timer
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+		if (players[i].spectator && (players[i].pflags & PF_WANTSTOJOIN))
+			players[i].kartstuff[k_spectatewait]++;
+		else
+			players[i].kartstuff[k_spectatewait] = 0;
+	}
+
+	// No one's allowed to join
+	if (!cv_allowteamchange.value)
+		return;
+
+	// Get the number of players in game, and the players to be de-spectated.
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+
+		if (!players[i].spectator)
+		{
+			numingame++;
+			if (cv_ingamecap.value && numingame >= cv_ingamecap.value) // DON'T allow if you've hit the in-game player cap
+				return;
+			if (gamestate != GS_LEVEL) // Allow if you're not in a level
+				continue;
+			if (players[i].exiting) // DON'T allow if anyone's exiting
+				return;
+			if (numingame < 2 || leveltime < starttime || mapreset) // Allow if the match hasn't started yet
+				continue;
+			if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in
+				return;
+			if (G_RaceGametype() && players[i].laps) // DON'T allow if the race is at 2 laps
+				return;
+			continue;
+		}
+		else if (!(players[i].pflags & PF_WANTSTOJOIN))
+			continue;
+
+		respawnlist[numjoiners++] = i;
+	}
+
+	// literally zero point in going any further if nobody is joining
+	if (!numjoiners)
+		return;
+
+	// Organize by spectate wait timer
+	if (cv_ingamecap.value)
+	{
+		UINT8 oldrespawnlist[MAXPLAYERS];
+		memcpy(oldrespawnlist, respawnlist, numjoiners);
+		for (i = 0; i < numjoiners; i++)
+		{
+			UINT8 pos = 0;
+			INT32 ispecwait = players[oldrespawnlist[i]].kartstuff[k_spectatewait];
+
+			for (j = 0; j < numjoiners; j++)
+			{
+				INT32 jspecwait = players[oldrespawnlist[j]].kartstuff[k_spectatewait];
+				if (j == i)
+					continue;
+				if (jspecwait > ispecwait)
+					pos++;
+				else if (jspecwait == ispecwait && j < i)
+					pos++;
+			}
+
+			respawnlist[pos] = oldrespawnlist[i];
+		}
+	}
+
+	// Finally, we can de-spectate everyone!
+	for (i = 0; i < numjoiners; i++)
+	{
+		if (cv_ingamecap.value && numingame+i >= cv_ingamecap.value) // Hit the in-game player cap while adding people?
+			break;
+		P_SpectatorJoinGame(&players[respawnlist[i]]);
+	}
+
+	// Reset the match if you're in an empty server
+	if (!mapreset && gamestate == GS_LEVEL && leveltime >= starttime && (numingame < 2 && numingame+i >= 2)) // use previous i value
+	{
+		S_ChangeMusicInternal("chalng", false); // COME ON
+		mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD
+	}
+}
+
+//}
+
+//{ SRB2kart HUD Code
+
+#define NUMPOSNUMS 10
+#define NUMPOSFRAMES 7 // White, three blues, three reds
+#define NUMWINFRAMES 6 // Red, yellow, green, cyan, blue, purple
+
+//{ 	Patch Definitions
+static patch_t *kp_nodraw;
+
+static patch_t *kp_timesticker;
+static patch_t *kp_timestickerwide;
+static patch_t *kp_lapsticker;
+static patch_t *kp_lapstickerwide;
+static patch_t *kp_lapstickernarrow;
+static patch_t *kp_splitlapflag;
+static patch_t *kp_bumpersticker;
+static patch_t *kp_bumperstickerwide;
+static patch_t *kp_karmasticker;
+static patch_t *kp_splitkarmabomb;
+static patch_t *kp_timeoutsticker;
+
+static patch_t *kp_startcountdown[16];
+static patch_t *kp_racefinish[6];
+
+static patch_t *kp_positionnum[NUMPOSNUMS][NUMPOSFRAMES];
+static patch_t *kp_winnernum[NUMPOSFRAMES];
+
+static patch_t *kp_facenum[MAXPLAYERS+1];
+static patch_t *kp_facehighlight[8];
+
+static patch_t *kp_rankbumper;
+static patch_t *kp_tinybumper[2];
+static patch_t *kp_ranknobumpers;
+
+static patch_t *kp_battlewin;
+static patch_t *kp_battlecool;
+static patch_t *kp_battlelose;
+static patch_t *kp_battlewait;
+static patch_t *kp_battleinfo;
+static patch_t *kp_wanted;
+static patch_t *kp_wantedsplit;
+static patch_t *kp_wantedreticle;
+
+static patch_t *kp_itembg[4];
+static patch_t *kp_itemtimer[2];
+static patch_t *kp_itemmulsticker[2];
+static patch_t *kp_itemx;
+
+static patch_t *kp_sneaker[2];
+static patch_t *kp_rocketsneaker[2];
+static patch_t *kp_invincibility[13];
+static patch_t *kp_banana[2];
+static patch_t *kp_eggman[2];
+static patch_t *kp_orbinaut[5];
+static patch_t *kp_jawz[2];
+static patch_t *kp_mine[2];
+static patch_t *kp_ballhog[2];
+static patch_t *kp_selfpropelledbomb[2];
+static patch_t *kp_grow[2];
+static patch_t *kp_shrink[2];
+static patch_t *kp_thundershield[2];
+static patch_t *kp_hyudoro[2];
+static patch_t *kp_pogospring[2];
+static patch_t *kp_kitchensink[2];
+static patch_t *kp_sadface[2];
+
+static patch_t *kp_check[6];
+
+static patch_t *kp_eggnum[4];
+
+static patch_t *kp_fpview[3];
+static patch_t *kp_inputwheel[5];
+
+static patch_t *kp_challenger[25];
+
+static patch_t *kp_lapanim_lap[7];
+static patch_t *kp_lapanim_final[11];
+static patch_t *kp_lapanim_number[10][3];
+static patch_t *kp_lapanim_emblem[2];
+static patch_t *kp_lapanim_hand[3];
+
+static patch_t *kp_yougotem;
+
+void K_LoadKartHUDGraphics(void)
+{
+	INT32 i, j;
+	char buffer[9];
+
+	// Null Stuff
+	kp_nodraw = 				W_CachePatchName("K_TRNULL", PU_HUDGFX);
+
+	// Stickers
+	kp_timesticker = 			W_CachePatchName("K_STTIME", PU_HUDGFX);
+	kp_timestickerwide = 		W_CachePatchName("K_STTIMW", PU_HUDGFX);
+	kp_lapsticker = 			W_CachePatchName("K_STLAPS", PU_HUDGFX);
+	kp_lapstickerwide = 		W_CachePatchName("K_STLAPW", PU_HUDGFX);
+	kp_lapstickernarrow = 		W_CachePatchName("K_STLAPN", PU_HUDGFX);
+	kp_splitlapflag = 			W_CachePatchName("K_SPTLAP", PU_HUDGFX);
+	kp_bumpersticker = 			W_CachePatchName("K_STBALN", PU_HUDGFX);
+	kp_bumperstickerwide = 		W_CachePatchName("K_STBALW", PU_HUDGFX);
+	kp_karmasticker = 			W_CachePatchName("K_STKARM", PU_HUDGFX);
+	kp_splitkarmabomb = 		W_CachePatchName("K_SPTKRM", PU_HUDGFX);
+	kp_timeoutsticker = 		W_CachePatchName("K_STTOUT", PU_HUDGFX);
+
+	// Starting countdown
+	kp_startcountdown[0] = 		W_CachePatchName("K_CNT3A", PU_HUDGFX);
+	kp_startcountdown[1] = 		W_CachePatchName("K_CNT2A", PU_HUDGFX);
+	kp_startcountdown[2] = 		W_CachePatchName("K_CNT1A", PU_HUDGFX);
+	kp_startcountdown[3] = 		W_CachePatchName("K_CNTGOA", PU_HUDGFX);
+	kp_startcountdown[4] = 		W_CachePatchName("K_CNT3B", PU_HUDGFX);
+	kp_startcountdown[5] = 		W_CachePatchName("K_CNT2B", PU_HUDGFX);
+	kp_startcountdown[6] = 		W_CachePatchName("K_CNT1B", PU_HUDGFX);
+	kp_startcountdown[7] = 		W_CachePatchName("K_CNTGOB", PU_HUDGFX);
+	// Splitscreen
+	kp_startcountdown[8] = 		W_CachePatchName("K_SMC3A", PU_HUDGFX);
+	kp_startcountdown[9] = 		W_CachePatchName("K_SMC2A", PU_HUDGFX);
+	kp_startcountdown[10] = 	W_CachePatchName("K_SMC1A", PU_HUDGFX);
+	kp_startcountdown[11] = 	W_CachePatchName("K_SMCGOA", PU_HUDGFX);
+	kp_startcountdown[12] = 	W_CachePatchName("K_SMC3B", PU_HUDGFX);
+	kp_startcountdown[13] = 	W_CachePatchName("K_SMC2B", PU_HUDGFX);
+	kp_startcountdown[14] = 	W_CachePatchName("K_SMC1B", PU_HUDGFX);
+	kp_startcountdown[15] = 	W_CachePatchName("K_SMCGOB", PU_HUDGFX);
+
+	// Finish
+	kp_racefinish[0] = 			W_CachePatchName("K_FINA", PU_HUDGFX);
+	kp_racefinish[1] = 			W_CachePatchName("K_FINB", PU_HUDGFX);
+	// Splitscreen
+	kp_racefinish[2] = 			W_CachePatchName("K_SMFINA", PU_HUDGFX);
+	kp_racefinish[3] = 			W_CachePatchName("K_SMFINB", PU_HUDGFX);
+	// 2P splitscreen
+	kp_racefinish[4] = 			W_CachePatchName("K_2PFINA", PU_HUDGFX);
+	kp_racefinish[5] = 			W_CachePatchName("K_2PFINB", PU_HUDGFX);
+
+	// Position numbers
+	sprintf(buffer, "K_POSNxx");
+	for (i = 0; i < NUMPOSNUMS; i++)
+	{
+		buffer[6] = '0'+i;
+		for (j = 0; j < NUMPOSFRAMES; j++)
+		{
+			//sprintf(buffer, "K_POSN%d%d", i, j);
+			buffer[7] = '0'+j;
+			kp_positionnum[i][j] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+		}
+	}
+
+	sprintf(buffer, "K_POSNWx");
+	for (i = 0; i < NUMWINFRAMES; i++)
+	{
+		buffer[7] = '0'+i;
+		kp_winnernum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	sprintf(buffer, "OPPRNKxx");
+	for (i = 0; i <= MAXPLAYERS; i++)
+	{
+		buffer[6] = '0'+(i/10);
+		buffer[7] = '0'+(i%10);
+		kp_facenum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	sprintf(buffer, "K_CHILIx");
+	for (i = 0; i < 8; i++)
+	{
+		buffer[7] = '0'+(i+1);
+		kp_facehighlight[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	// Extra ranking icons
+	kp_rankbumper =				W_CachePatchName("K_BLNICO", PU_HUDGFX);
+	kp_tinybumper[0] =			W_CachePatchName("K_BLNA", PU_HUDGFX);
+	kp_tinybumper[1] =			W_CachePatchName("K_BLNB", PU_HUDGFX);
+	kp_ranknobumpers =			W_CachePatchName("K_NOBLNS", PU_HUDGFX);
+
+	// Battle graphics
+	kp_battlewin = 				W_CachePatchName("K_BWIN", PU_HUDGFX);
+	kp_battlecool = 			W_CachePatchName("K_BCOOL", PU_HUDGFX);
+	kp_battlelose = 			W_CachePatchName("K_BLOSE", PU_HUDGFX);
+	kp_battlewait = 			W_CachePatchName("K_BWAIT", PU_HUDGFX);
+	kp_battleinfo = 			W_CachePatchName("K_BINFO", PU_HUDGFX);
+	kp_wanted = 				W_CachePatchName("K_WANTED", PU_HUDGFX);
+	kp_wantedsplit = 			W_CachePatchName("4PWANTED", PU_HUDGFX);
+	kp_wantedreticle =			W_CachePatchName("MMAPWANT", PU_HUDGFX);
+
+	// Kart Item Windows
+	kp_itembg[0] = 				W_CachePatchName("K_ITBG", PU_HUDGFX);
+	kp_itembg[1] = 				W_CachePatchName("K_ITBGD", PU_HUDGFX);
+	kp_itemtimer[0] = 			W_CachePatchName("K_ITIMER", PU_HUDGFX);
+	kp_itemmulsticker[0] = 		W_CachePatchName("K_ITMUL", PU_HUDGFX);
+	kp_itemx = 					W_CachePatchName("K_ITX", PU_HUDGFX);
+
+	kp_sneaker[0] =				W_CachePatchName("K_ITSHOE", PU_HUDGFX);
+	kp_rocketsneaker[0] =		W_CachePatchName("K_ITRSHE", PU_HUDGFX);
+
+	sprintf(buffer, "K_ITINVx");
+	for (i = 0; i < 7; i++)
+	{
+		buffer[7] = '1'+i;
+		kp_invincibility[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+	kp_banana[0] =				W_CachePatchName("K_ITBANA", PU_HUDGFX);
+	kp_eggman[0] =				W_CachePatchName("K_ITEGGM", PU_HUDGFX);
+	sprintf(buffer, "K_ITORBx");
+	for (i = 0; i < 4; i++)
+	{
+		buffer[7] = '1'+i;
+		kp_orbinaut[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+	kp_jawz[0] =				W_CachePatchName("K_ITJAWZ", PU_HUDGFX);
+	kp_mine[0] =				W_CachePatchName("K_ITMINE", PU_HUDGFX);
+	kp_ballhog[0] =				W_CachePatchName("K_ITBHOG", PU_HUDGFX);
+	kp_selfpropelledbomb[0] =	W_CachePatchName("K_ITSPB", PU_HUDGFX);
+	kp_grow[0] =				W_CachePatchName("K_ITGROW", PU_HUDGFX);
+	kp_shrink[0] =				W_CachePatchName("K_ITSHRK", PU_HUDGFX);
+	kp_thundershield[0] =		W_CachePatchName("K_ITTHNS", PU_HUDGFX);
+	kp_hyudoro[0] = 			W_CachePatchName("K_ITHYUD", PU_HUDGFX);
+	kp_pogospring[0] = 			W_CachePatchName("K_ITPOGO", PU_HUDGFX);
+	kp_kitchensink[0] = 		W_CachePatchName("K_ITSINK", PU_HUDGFX);
+	kp_sadface[0] = 			W_CachePatchName("K_ITSAD", PU_HUDGFX);
+
+	// Splitscreen
+	kp_itembg[2] = 				W_CachePatchName("K_ISBG", PU_HUDGFX);
+	kp_itembg[3] = 				W_CachePatchName("K_ISBGD", PU_HUDGFX);
+	kp_itemtimer[1] = 			W_CachePatchName("K_ISIMER", PU_HUDGFX);
+	kp_itemmulsticker[1] = 		W_CachePatchName("K_ISMUL", PU_HUDGFX);
+
+	kp_sneaker[1] =				W_CachePatchName("K_ISSHOE", PU_HUDGFX);
+	kp_rocketsneaker[1] =		W_CachePatchName("K_ISRSHE", PU_HUDGFX);
+	sprintf(buffer, "K_ISINVx");
+	for (i = 0; i < 6; i++)
+	{
+		buffer[7] = '1'+i;
+		kp_invincibility[i+7] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+	kp_banana[1] =				W_CachePatchName("K_ISBANA", PU_HUDGFX);
+	kp_eggman[1] =				W_CachePatchName("K_ISEGGM", PU_HUDGFX);
+	kp_orbinaut[4] =			W_CachePatchName("K_ISORBN", PU_HUDGFX);
+	kp_jawz[1] =				W_CachePatchName("K_ISJAWZ", PU_HUDGFX);
+	kp_mine[1] =				W_CachePatchName("K_ISMINE", PU_HUDGFX);
+	kp_ballhog[1] =				W_CachePatchName("K_ISBHOG", PU_HUDGFX);
+	kp_selfpropelledbomb[1] =	W_CachePatchName("K_ISSPB", PU_HUDGFX);
+	kp_grow[1] =				W_CachePatchName("K_ISGROW", PU_HUDGFX);
+	kp_shrink[1] =				W_CachePatchName("K_ISSHRK", PU_HUDGFX);
+	kp_thundershield[1] =		W_CachePatchName("K_ISTHNS", PU_HUDGFX);
+	kp_hyudoro[1] = 			W_CachePatchName("K_ISHYUD", PU_HUDGFX);
+	kp_pogospring[1] = 			W_CachePatchName("K_ISPOGO", PU_HUDGFX);
+	kp_kitchensink[1] = 		W_CachePatchName("K_ISSINK", PU_HUDGFX);
+	kp_sadface[1] = 			W_CachePatchName("K_ISSAD", PU_HUDGFX);
+
+	// CHECK indicators
+	sprintf(buffer, "K_CHECKx");
+	for (i = 0; i < 6; i++)
+	{
+		buffer[7] = '1'+i;
+		kp_check[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	// Eggman warning numbers
+	sprintf(buffer, "K_EGGNx");
+	for (i = 0; i < 4; i++)
+	{
+		buffer[6] = '0'+i;
+		kp_eggnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	// First person mode
+	kp_fpview[0] = 				W_CachePatchName("VIEWA0", PU_HUDGFX);
+	kp_fpview[1] =				W_CachePatchName("VIEWB0D0", PU_HUDGFX);
+	kp_fpview[2] = 				W_CachePatchName("VIEWC0E0", PU_HUDGFX);
+
+	// Input UI Wheel
+	sprintf(buffer, "K_WHEELx");
+	for (i = 0; i < 5; i++)
+	{
+		buffer[7] = '0'+i;
+		kp_inputwheel[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	// HERE COMES A NEW CHALLENGER
+	sprintf(buffer, "K_CHALxx");
+	for (i = 0; i < 25; i++)
+	{
+		buffer[6] = '0'+((i+1)/10);
+		buffer[7] = '0'+((i+1)%10);
+		kp_challenger[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	// Lap start animation
+	sprintf(buffer, "K_LAP0x");
+	for (i = 0; i < 7; i++)
+	{
+		buffer[6] = '0'+(i+1);
+		kp_lapanim_lap[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	sprintf(buffer, "K_LAPFxx");
+	for (i = 0; i < 11; i++)
+	{
+		buffer[6] = '0'+((i+1)/10);
+		buffer[7] = '0'+((i+1)%10);
+		kp_lapanim_final[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	sprintf(buffer, "K_LAPNxx");
+	for (i = 0; i < 10; i++)
+	{
+		buffer[6] = '0'+i;
+		for (j = 0; j < 3; j++)
+		{
+			buffer[7] = '0'+(j+1);
+			kp_lapanim_number[i][j] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+		}
+	}
+
+	sprintf(buffer, "K_LAPE0x");
+	for (i = 0; i < 2; i++)
+	{
+		buffer[7] = '0'+(i+1);
+		kp_lapanim_emblem[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	sprintf(buffer, "K_LAPH0x");
+	for (i = 0; i < 3; i++)
+	{
+		buffer[7] = '0'+(i+1);
+		kp_lapanim_hand[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	kp_yougotem = (patch_t *) W_CachePatchName("YOUGOTEM", PU_HUDGFX);
+}
+
+// For the item toggle menu
+const char *K_GetItemPatch(UINT8 item, boolean tiny)
+{
+	switch (item)
+	{
+		case KITEM_SNEAKER:
+		case KRITEM_TRIPLESNEAKER:
+			return (tiny ? "K_ISSHOE" : "K_ITSHOE");
+		case KITEM_ROCKETSNEAKER:
+			return (tiny ? "K_ISRSHE" : "K_ITRSHE");
+		case KITEM_INVINCIBILITY:
+			return (tiny ? "K_ISINV1" : "K_ITINV1");
+		case KITEM_BANANA:
+		case KRITEM_TRIPLEBANANA:
+		case KRITEM_TENFOLDBANANA:
+			return (tiny ? "K_ISBANA" : "K_ITBANA");
+		case KITEM_EGGMAN:
+			return (tiny ? "K_ISEGGM" : "K_ITEGGM");
+		case KITEM_ORBINAUT:
+			return (tiny ? "K_ISORBN" : "K_ITORB1");
+		case KITEM_JAWZ:
+		case KRITEM_DUALJAWZ:
+			return (tiny ? "K_ISJAWZ" : "K_ITJAWZ");
+		case KITEM_MINE:
+			return (tiny ? "K_ISMINE" : "K_ITMINE");
+		case KITEM_BALLHOG:
+			return (tiny ? "K_ISBHOG" : "K_ITBHOG");
+		case KITEM_SPB:
+			return (tiny ? "K_ISSPB" : "K_ITSPB");
+		case KITEM_GROW:
+			return (tiny ? "K_ISGROW" : "K_ITGROW");
+		case KITEM_SHRINK:
+			return (tiny ? "K_ISSHRK" : "K_ITSHRK");
+		case KITEM_THUNDERSHIELD:
+			return (tiny ? "K_ISTHNS" : "K_ITTHNS");
+		case KITEM_HYUDORO:
+			return (tiny ? "K_ISHYUD" : "K_ITHYUD");
+		case KITEM_POGOSPRING:
+			return (tiny ? "K_ISPOGO" : "K_ITPOGO");
+		case KITEM_KITCHENSINK:
+			return (tiny ? "K_ISSINK" : "K_ITSINK");
+		case KRITEM_TRIPLEORBINAUT:
+			return (tiny ? "K_ISORBN" : "K_ITORB3");
+		case KRITEM_QUADORBINAUT:
+			return (tiny ? "K_ISORBN" : "K_ITORB4");
+		default:
+			return (tiny ? "K_ISSAD" : "K_ITSAD");
+	}
+}
+
+//}
+
+INT32 ITEM_X, ITEM_Y;	// Item Window
+INT32 TIME_X, TIME_Y;	// Time Sticker
+INT32 LAPS_X, LAPS_Y;	// Lap Sticker
+INT32 SPDM_X, SPDM_Y;	// Speedometer
+INT32 POSI_X, POSI_Y;	// Position Number
+INT32 FACE_X, FACE_Y;	// Top-four Faces
+INT32 STCD_X, STCD_Y;	// Starting countdown
+INT32 CHEK_Y;			// CHECK graphic
+INT32 MINI_X, MINI_Y;	// Minimap
+INT32 WANT_X, WANT_Y;	// Battle WANTED poster
+
+// This is for the P2 and P4 side of splitscreen. Then we'll flip P1's and P2's to the bottom with V_SPLITSCREEN.
+INT32 ITEM2_X, ITEM2_Y;
+INT32 LAPS2_X, LAPS2_Y;
+INT32 POSI2_X, POSI2_Y;
+
+
+static void K_initKartHUD(void)
+{
+	/*
+		BASEVIDWIDTH  = 320
+		BASEVIDHEIGHT = 200
+
+		Item window graphic is 41 x 33
+
+		Time Sticker graphic is 116 x 11
+		Time Font is a solid block of (8 x [12) x 14], equal to 96 x 14
+		Therefore, timestamp is 116 x 14 altogether
+
+		Lap Sticker is 80 x 11
+		Lap flag is 22 x 20
+		Lap Font is a solid block of (3 x [12) x 14], equal to 36 x 14
+		Therefore, lapstamp is 80 x 20 altogether
+
+		Position numbers are 43 x 53
+
+		Faces are 32 x 32
+		Faces draw downscaled at 16 x 16
+		Therefore, the allocated space for them is 16 x 67 altogether
+
+		----
+
+		ORIGINAL CZ64 SPLITSCREEN:
+
+		Item window:
+		if (!splitscreen) 	{ ICONX = 139; 				ICONY = 20; }
+		else 				{ ICONX = BASEVIDWIDTH-315; ICONY = 60; }
+
+		Time: 			   236, STRINGY(			   12)
+		Lap:  BASEVIDWIDTH-304, STRINGY(BASEVIDHEIGHT-189)
+
+	*/
+
+	// Single Screen (defaults)
+	// Item Window
+	ITEM_X = 5;						//   5
+	ITEM_Y = 5;						//   5
+	// Level Timer
+	TIME_X = BASEVIDWIDTH - 148;	// 172
+	TIME_Y = 9;						//   9
+	// Level Laps
+	LAPS_X = 9;						//   9
+	LAPS_Y = BASEVIDHEIGHT - 29;	// 171
+	// Speedometer
+	SPDM_X = 9;						//   9
+	SPDM_Y = BASEVIDHEIGHT - 45;	// 155
+	// Position Number
+	POSI_X = BASEVIDWIDTH  - 9;		// 268
+	POSI_Y = BASEVIDHEIGHT - 9;		// 138
+	// Top-Four Faces
+	FACE_X = 9;						//   9
+	FACE_Y = 92;					//  92
+	// Starting countdown
+	STCD_X = BASEVIDWIDTH/2;		//   9
+	STCD_Y = BASEVIDHEIGHT/2;		//  92
+	// CHECK graphic
+	CHEK_Y = BASEVIDHEIGHT;			// 200
+	// Minimap
+	MINI_X = BASEVIDWIDTH - 50;		// 270
+	MINI_Y = (BASEVIDHEIGHT/2)-16; //  84
+	// Battle WANTED poster
+	WANT_X = BASEVIDWIDTH - 55;		// 270
+	WANT_Y = BASEVIDHEIGHT- 71;		// 176
+
+	if (splitscreen)	// Splitscreen
+	{
+		ITEM_X = 5;
+		ITEM_Y = 3;
+
+		LAPS_Y = (BASEVIDHEIGHT/2)-24;
+
+		POSI_Y = (BASEVIDHEIGHT/2)- 2;
+
+		STCD_Y = BASEVIDHEIGHT/4;
+
+		MINI_Y = (BASEVIDHEIGHT/2);
+
+		if (splitscreen > 1)	// 3P/4P Small Splitscreen
+		{
+			// 1P (top left)
+			ITEM_X = -9;
+			ITEM_Y = -8;
+
+			LAPS_X = 3;
+			LAPS_Y = (BASEVIDHEIGHT/2)-13;
+
+			POSI_X = 24;
+			POSI_Y = (BASEVIDHEIGHT/2)- 16;
+
+			// 2P (top right)
+			ITEM2_X = BASEVIDWIDTH-39;
+			ITEM2_Y = -8;
+
+			LAPS2_X = BASEVIDWIDTH-40;
+			LAPS2_Y = (BASEVIDHEIGHT/2)-13;
+
+			POSI2_X = BASEVIDWIDTH -4;
+			POSI2_Y = (BASEVIDHEIGHT/2)- 16;
+
+			// Reminder that 3P and 4P are just 1P and 2P splitscreen'd to the bottom.
+
+			STCD_X = BASEVIDWIDTH/4;
+
+			MINI_X = (3*BASEVIDWIDTH/4);
+			MINI_Y = (3*BASEVIDHEIGHT/4);
+
+			if (splitscreen > 2) // 4P-only
+			{
+				MINI_X = (BASEVIDWIDTH/2);
+				MINI_Y = (BASEVIDHEIGHT/2);
+			}
+		}
+	}
+
+	if (timeinmap > 113)
+		hudtrans = cv_translucenthud.value;
+	else if (timeinmap > 105)
+		hudtrans = ((((INT32)timeinmap) - 105)*cv_translucenthud.value)/(113-105);
+	else
+		hudtrans = 0;
+}
+
+INT32 K_calcSplitFlags(INT32 snapflags)
+{
+	INT32 splitflags = 0;
+
+	if (splitscreen == 0)
+		return snapflags;
+
+	if (stplyr != &players[displayplayer])
+	{
+		if (splitscreen == 1 && stplyr == &players[secondarydisplayplayer])
+		{
+			splitflags |= V_SPLITSCREEN;
+		}
+		else if (splitscreen > 1)
+		{
+			if (stplyr == &players[thirddisplayplayer] || stplyr == &players[fourthdisplayplayer])
+				splitflags |= V_SPLITSCREEN;
+			if (stplyr == &players[secondarydisplayplayer] || stplyr == &players[fourthdisplayplayer])
+				splitflags |= V_HORZSCREEN;
+		}
+	}
+
+	if (splitflags & V_SPLITSCREEN)
+		snapflags &= ~V_SNAPTOTOP;
+	else
+		snapflags &= ~V_SNAPTOBOTTOM;
+
+	if (splitscreen > 1)
+	{
+		if (splitflags & V_HORZSCREEN)
+			snapflags &= ~V_SNAPTOLEFT;
+		else
+			snapflags &= ~V_SNAPTORIGHT;
+	}
+
+	return (splitflags|snapflags);
+}
+
+static void K_drawKartItem(void)
+{
+	// ITEM_X = BASEVIDWIDTH-50;	// 270
+	// ITEM_Y = 24;					//  24
+
+	// Why write V_DrawScaledPatch calls over and over when they're all the same?
+	// Set to 'no item' just in case.
+	const UINT8 offset = ((splitscreen > 1) ? 1 : 0);
+	patch_t *localpatch = kp_nodraw;
+	patch_t *localbg = ((offset) ? kp_itembg[2] : kp_itembg[0]);
+	patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]);
+	INT32 fx = 0, fy = 0, fflags = 0;	// final coords for hud and flags...
+	//INT32 splitflags = K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOLEFT);
+	const INT32 numberdisplaymin = ((!offset && stplyr->kartstuff[k_itemtype] == KITEM_ORBINAUT) ? 5 : 2);
+	INT32 itembar = 0;
+	UINT8 localcolor = SKINCOLOR_NONE;
+	SINT8 colormode = TC_RAINBOW;
+	UINT8 *colmap = NULL;
+	boolean flipamount = false;	// Used for 3P/4P splitscreen to flip item amount stuff
+
+	if (stplyr->kartstuff[k_itemroulette])
+	{
+		if (stplyr->skincolor)
+			localcolor = stplyr->skincolor;
+
+		switch((stplyr->kartstuff[k_itemroulette] % (14*3)) / 3)
+		{
+			// Each case is handled in threes, to give three frames of in-game time to see the item on the roulette
+			case 0: // Sneaker
+				localpatch = kp_sneaker[offset];
+				//localcolor = SKINCOLOR_RASPBERRY;
+				break;
+			case 1: // Banana
+				localpatch = kp_banana[offset];
+				//localcolor = SKINCOLOR_YELLOW;
+				break;
+			case 2: // Orbinaut
+				localpatch = kp_orbinaut[3+offset];
+				//localcolor = SKINCOLOR_STEEL;
+				break;
+			case 3: // Mine
+				localpatch = kp_mine[offset];
+				//localcolor = SKINCOLOR_JET;
+				break;
+			case 4: // Grow
+				localpatch = kp_grow[offset];
+				//localcolor = SKINCOLOR_TEAL;
+				break;
+			case 5: // Hyudoro
+				localpatch = kp_hyudoro[offset];
+				//localcolor = SKINCOLOR_STEEL;
+				break;
+			case 6: // Rocket Sneaker
+				localpatch = kp_rocketsneaker[offset];
+				//localcolor = SKINCOLOR_TANGERINE;
+				break;
+			case 7: // Jawz
+				localpatch = kp_jawz[offset];
+				//localcolor = SKINCOLOR_JAWZ;
+				break;
+			case 8: // Self-Propelled Bomb
+				localpatch = kp_selfpropelledbomb[offset];
+				//localcolor = SKINCOLOR_JET;
+				break;
+			case 9: // Shrink
+				localpatch = kp_shrink[offset];
+				//localcolor = SKINCOLOR_ORANGE;
+				break;
+			case 10: // Invincibility
+				localpatch = localinv;
+				//localcolor = SKINCOLOR_GREY;
+				break;
+			case 11: // Eggman Monitor
+				localpatch = kp_eggman[offset];
+				//localcolor = SKINCOLOR_ROSE;
+				break;
+			case 12: // Ballhog
+				localpatch = kp_ballhog[offset];
+				//localcolor = SKINCOLOR_LILAC;
+				break;
+			case 13: // Thunder Shield
+				localpatch = kp_thundershield[offset];
+				//localcolor = SKINCOLOR_CYAN;
+				break;
+			/*case 14: // Pogo Spring
+				localpatch = kp_pogospring[offset];
+				localcolor = SKINCOLOR_TANGERINE;
+				break;
+			case 15: // Kitchen Sink
+				localpatch = kp_kitchensink[offset];
+				localcolor = SKINCOLOR_STEEL;
+				break;*/
+			default:
+				break;
+		}
+	}
+	else
+	{
+		// I'm doing this a little weird and drawing mostly in reverse order
+		// The only actual reason is to make sneakers line up this way in the code below
+		// This shouldn't have any actual baring over how it functions
+		// Hyudoro is first, because we're drawing it on top of the player's current item
+		if (stplyr->kartstuff[k_stolentimer] > 0)
+		{
+			if (leveltime & 2)
+				localpatch = kp_hyudoro[offset];
+			else
+				localpatch = kp_nodraw;
+		}
+		else if ((stplyr->kartstuff[k_stealingtimer] > 0) && (leveltime & 2))
+		{
+			localpatch = kp_hyudoro[offset];
+		}
+		else if (stplyr->kartstuff[k_eggmanexplode] > 1)
+		{
+			if (leveltime & 1)
+				localpatch = kp_eggman[offset];
+			else
+				localpatch = kp_nodraw;
+		}
+		else if (stplyr->kartstuff[k_rocketsneakertimer] > 1)
+		{
+			itembar = stplyr->kartstuff[k_rocketsneakertimer];
+			if (leveltime & 1)
+				localpatch = kp_rocketsneaker[offset];
+			else
+				localpatch = kp_nodraw;
+		}
+		else if (stplyr->kartstuff[k_growshrinktimer] > 0)
+		{
+			if (leveltime & 1)
+				localpatch = kp_grow[offset];
+			else
+				localpatch = kp_nodraw;
+		}
+		else if (stplyr->kartstuff[k_sadtimer] > 0)
+		{
+			if (leveltime & 2)
+				localpatch = kp_sadface[offset];
+			else
+				localpatch = kp_nodraw;
+		}
+		else
+		{
+			if (stplyr->kartstuff[k_itemamount] <= 0)
+				return;
+
+			switch(stplyr->kartstuff[k_itemtype])
+			{
+				case KITEM_SNEAKER:
+					localpatch = kp_sneaker[offset];
+					break;
+				case KITEM_ROCKETSNEAKER:
+					localpatch = kp_rocketsneaker[offset];
+					break;
+				case KITEM_INVINCIBILITY:
+					localpatch = localinv;
+					localbg = kp_itembg[offset+1];
+					break;
+				case KITEM_BANANA:
+					localpatch = kp_banana[offset];
+					break;
+				case KITEM_EGGMAN:
+					localpatch = kp_eggman[offset];
+					break;
+				case KITEM_ORBINAUT:
+					localpatch = kp_orbinaut[(offset ? 4 : min(stplyr->kartstuff[k_itemamount]-1, 3))];
+					break;
+				case KITEM_JAWZ:
+					localpatch = kp_jawz[offset];
+					break;
+				case KITEM_MINE:
+					localpatch = kp_mine[offset];
+					break;
+				case KITEM_BALLHOG:
+					localpatch = kp_ballhog[offset];
+					break;
+				case KITEM_SPB:
+					localpatch = kp_selfpropelledbomb[offset];
+					localbg = kp_itembg[offset+1];
+					break;
+				case KITEM_GROW:
+					localpatch = kp_grow[offset];
+					break;
+				case KITEM_SHRINK:
+					localpatch = kp_shrink[offset];
+					break;
+				case KITEM_THUNDERSHIELD:
+					localpatch = kp_thundershield[offset];
+					localbg = kp_itembg[offset+1];
+					break;
+				case KITEM_HYUDORO:
+					localpatch = kp_hyudoro[offset];
+					break;
+				case KITEM_POGOSPRING:
+					localpatch = kp_pogospring[offset];
+					break;
+				case KITEM_KITCHENSINK:
+					localpatch = kp_kitchensink[offset];
+					break;
+				case KITEM_SAD:
+					localpatch = kp_sadface[offset];
+					break;
+				default:
+					return;
+			}
+
+			if (stplyr->kartstuff[k_itemheld] && !(leveltime & 1))
+				localpatch = kp_nodraw;
+		}
+
+		if (stplyr->kartstuff[k_itemblink] && (leveltime & 1))
+		{
+			colormode = TC_BLINK;
+
+			switch (stplyr->kartstuff[k_itemblinkmode])
+			{
+				case 2:
+					localcolor = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1)));
+					break;
+				case 1:
+					localcolor = SKINCOLOR_RED;
+					break;
+				default:
+					localcolor = SKINCOLOR_WHITE;
+					break;
+			}
+		}
+	}
+
+	// pain and suffering defined below
+	if (splitscreen < 2)	// don't change shit for THIS splitscreen.
+	{
+		fx = ITEM_X;
+		fy = ITEM_Y;
+		fflags = K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOLEFT);
+	}
+	else				// now we're having a fun game.
+	{
+		if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer])	// If we are P1 or P3...
+		{
+			fx = ITEM_X;
+			fy = ITEM_Y;
+			fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN : V_SNAPTOTOP);	// flip P3 to the bottom.
+		}
+		else // else, that means we're P2 or P4.
+		{
+			fx = ITEM2_X;
+			fy = ITEM2_Y;
+			fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN : V_SNAPTOTOP);	// flip P4 to the bottom
+			flipamount = true;
+		}
+	}
+
+	if (localcolor != SKINCOLOR_NONE)
+		colmap = R_GetTranslationColormap(colormode, localcolor, 0);
+
+	V_DrawScaledPatch(fx, fy, V_HUDTRANS|fflags, localbg);
+
+	// Then, the numbers:
+	if (stplyr->kartstuff[k_itemamount] >= numberdisplaymin && !stplyr->kartstuff[k_itemroulette])
+	{
+		V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]);	// flip this graphic for p2 and p4 in split and shift it.
+		V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, FRACUNIT, V_HUDTRANS|fflags, localpatch, colmap);
+		if (offset)
+			if (flipamount)	// reminder that this is for 3/4p's right end of the screen.
+				V_DrawString(fx+2, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|fflags, va("x%d", stplyr->kartstuff[k_itemamount]));
+			else
+				V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|fflags, va("x%d", stplyr->kartstuff[k_itemamount]));
+		else
+		{
+			V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|fflags, kp_itemx);
+			V_DrawKartString(fx+38, fy+36, V_HUDTRANS|fflags, va("%d", stplyr->kartstuff[k_itemamount]));
+		}
+	}
+	else
+		V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, FRACUNIT, V_HUDTRANS|fflags, localpatch, colmap);
+
+	// Extensible meter, currently only used for rocket sneaker...
+	if (itembar && hudtrans)
+	{
+		const INT32 barlength = (splitscreen > 1 ? 12 : 26);
+		const INT32 maxl = (itemtime*3) - barlength; // timer's normal highest value
+		const INT32 fill = ((itembar*barlength)/maxl);
+		const INT32 length = min(barlength, fill);
+		const INT32 height = (offset ? 1 : 2);
+		const INT32 x = (offset ? 17 : 11), y = (offset ? 27 : 35);
+
+		V_DrawScaledPatch(fx+x, fy+y, V_HUDTRANS|fflags, kp_itemtimer[offset]);
+		// The left dark "AA" edge
+		V_DrawFill(fx+x+1, fy+y+1, (length == 2 ? 2 : 1), height, 12|fflags);
+		// The bar itself
+		if (length > 2)
+		{
+			V_DrawFill(fx+x+length, fy+y+1, 1, height, 12|fflags); // the right one
+			if (height == 2)
+				V_DrawFill(fx+x+2, fy+y+2, length-2, 1, 8|fflags); // the dulled underside
+			V_DrawFill(fx+x+2, fy+y+1, length-2, 1, 120|fflags); // the shine
+		}
+	}
+
+	// Quick Eggman numbers
+	if (stplyr->kartstuff[k_eggmanexplode] > 1 /*&& stplyr->kartstuff[k_eggmanexplode] <= 3*TICRATE*/)
+		V_DrawScaledPatch(fx+17, fy+13-offset, V_HUDTRANS|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->kartstuff[k_eggmanexplode]))]);
+
+}
+
+void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode)
+{
+	// TIME_X = BASEVIDWIDTH-124;	// 196
+	// TIME_Y = 6;					//   6
+
+	tic_t worktime;
+
+	INT32 splitflags = 0;
+	if (!mode)
+	{
+		splitflags = V_HUDTRANS|K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTORIGHT);
+		if (cv_timelimit.value && timelimitintics > 0)
+		{
+			if (drawtime >= timelimitintics)
+				drawtime = 0;
+			else
+				drawtime = timelimitintics - drawtime;
+		}
+	}
+
+	V_DrawScaledPatch(TX, TY, splitflags, ((mode == 2) ? kp_lapstickerwide : kp_timestickerwide));
+
+	TX += 33;
+
+	worktime = drawtime/(60*TICRATE);
+
+	if (mode && !drawtime)
+		V_DrawKartString(TX, TY+3, splitflags, va("--'--\"--"));
+	else if (worktime < 100) // 99:99:99 only
+	{
+		// zero minute
+		if (worktime < 10)
+		{
+			V_DrawKartString(TX, TY+3, splitflags, va("0"));
+			// minutes time       0 __ __
+			V_DrawKartString(TX+12, TY+3, splitflags, va("%d", worktime));
+		}
+		// minutes time       0 __ __
+		else
+			V_DrawKartString(TX, TY+3, splitflags, va("%d", worktime));
+
+		// apostrophe location     _'__ __
+		V_DrawKartString(TX+24, TY+3, splitflags, va("'"));
+
+		worktime = (drawtime/TICRATE % 60);
+
+		// zero second        _ 0_ __
+		if (worktime < 10)
+		{
+			V_DrawKartString(TX+36, TY+3, splitflags, va("0"));
+		// seconds time       _ _0 __
+			V_DrawKartString(TX+48, TY+3, splitflags, va("%d", worktime));
+		}
+		// zero second        _ 00 __
+		else
+			V_DrawKartString(TX+36, TY+3, splitflags, va("%d", worktime));
+
+		// quotation mark location    _ __"__
+		V_DrawKartString(TX+60, TY+3, splitflags, va("\""));
+
+		worktime = G_TicsToCentiseconds(drawtime);
+
+		// zero tick          _ __ 0_
+		if (worktime < 10)
+		{
+			V_DrawKartString(TX+72, TY+3, splitflags, va("0"));
+		// tics               _ __ _0
+			V_DrawKartString(TX+84, TY+3, splitflags, va("%d", worktime));
+		}
+		// zero tick          _ __ 00
+		else
+			V_DrawKartString(TX+72, TY+3, splitflags, va("%d", worktime));
+	}
+	else if ((drawtime/TICRATE) & 1)
+		V_DrawKartString(TX, TY+3, splitflags, va("99'59\"99"));
+
+	if (emblemmap && (modeattacking || (mode == 1)) && !demoplayback) // emblem time!
+	{
+		INT32 workx = TX + 96, worky = TY+18;
+		SINT8 curemb = 0;
+		patch_t *emblempic[3] = {NULL, NULL, NULL};
+		UINT8 *emblemcol[3] = {NULL, NULL, NULL};
+
+		emblem_t *emblem = M_GetLevelEmblems(emblemmap);
+		while (emblem)
+		{
+			char targettext[9];
+
+			switch (emblem->type)
+			{
+				case ET_TIME:
+					{
+						static boolean canplaysound = true;
+						tic_t timetoreach = emblem->var;
+
+						if (emblem->collected)
+						{
+							emblempic[curemb] = W_CachePatchName(M_GetEmblemPatch(emblem), PU_CACHE);
+							emblemcol[curemb] = R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE);
+							if (++curemb == 3)
+								break;
+							goto bademblem;
+						}
+
+						snprintf(targettext, 9, "%i'%02i\"%02i",
+							G_TicsToMinutes(timetoreach, false),
+							G_TicsToSeconds(timetoreach),
+							G_TicsToCentiseconds(timetoreach));
+
+						if (!mode)
+						{
+							if (stplyr->realtime > timetoreach)
+							{
+								splitflags = (splitflags &~ V_HUDTRANS)|V_HUDTRANSHALF;
+								if (canplaysound)
+								{
+									S_StartSound(NULL, sfx_s3k72); //sfx_s26d); -- you STOLE fizzy lifting drinks
+									canplaysound = false;
+								}
+							}
+							else if (!canplaysound)
+								canplaysound = true;
+						}
+
+						targettext[8] = 0;
+					}
+					break;
+				default:
+					goto bademblem;
+			}
+
+			V_DrawRightAlignedString(workx, worky, splitflags, targettext);
+			workx -= 67;
+			V_DrawSmallScaledPatch(workx + 4, worky, splitflags, W_CachePatchName("NEEDIT", PU_CACHE));
+
+			break;
+
+			bademblem:
+			emblem = M_GetLevelEmblems(-1);
+		}
+
+		if (!mode)
+			splitflags = (splitflags &~ V_HUDTRANSHALF)|V_HUDTRANS;
+		while (curemb--)
+		{
+			workx -= 12;
+			V_DrawSmallMappedPatch(workx + 4, worky, splitflags, emblempic[curemb], emblemcol[curemb]);
+		}
+	}
+}
+
+static void K_DrawKartPositionNum(INT32 num)
+{
+	// POSI_X = BASEVIDWIDTH - 51;	// 269
+	// POSI_Y = BASEVIDHEIGHT- 64;	// 136
+
+	boolean win = (stplyr->exiting && num == 1);
+	//INT32 X = POSI_X;
+	INT32 W = SHORT(kp_positionnum[0][0]->width);
+	fixed_t scale = FRACUNIT;
+	patch_t *localpatch = kp_positionnum[0][0];
+	//INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTORIGHT);
+	INT32 fx = 0, fy = 0, fflags = 0;
+	boolean flipdraw = false;	// flip the order we draw it in for MORE splitscreen bs. fun.
+	boolean flipvdraw = false;	// used only for 2p splitscreen so overtaking doesn't make 1P's position fly off the screen.
+	boolean overtake = false;
+
+	if (stplyr->kartstuff[k_positiondelay] || stplyr->exiting)
+	{
+		scale *= 2;
+		overtake = true;	// this is used for splitscreen stuff in conjunction with flipdraw.
+	}
+	if (splitscreen)
+		scale /= 2;
+
+	W = FixedMul(W<<FRACBITS, scale)>>FRACBITS;
+
+	// pain and suffering defined below
+	if (!splitscreen)
+	{
+		fx = POSI_X;
+		fy = BASEVIDHEIGHT - 8;
+		fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+	}
+	else if (splitscreen == 1)	// for this splitscreen, we'll use case by case because it's a bit different.
+	{
+		fx = POSI_X;
+		if (stplyr == &players[displayplayer])	// for player 1: display this at the top right, above the minimap.
+		{
+			fy = 30;
+			fflags = V_SNAPTOTOP|V_SNAPTORIGHT;
+			if (overtake)
+				flipvdraw = true;	// make sure overtaking doesn't explode us
+		}
+		else	// if we're not p1, that means we're p2. display this at the bottom right, below the minimap.
+		{
+			fy = BASEVIDHEIGHT - 8;
+			fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+		}
+	}
+	else
+	{
+		if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer])	// If we are P1 or P3...
+		{
+			fx = POSI_X;
+			fy = POSI_Y;
+			fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P3 to the bottom.
+			flipdraw = true;
+			if (num && num >= 10)
+				fx += W;	// this seems dumb, but we need to do this in order for positions above 10 going off screen.
+		}
+		else // else, that means we're P2 or P4.
+		{
+			fx = POSI2_X;
+			fy = POSI2_Y;
+			fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P4 to the bottom
+		}
+	}
+
+	// Special case for 0
+	if (!num)
+	{
+		V_DrawFixedPatch(fx<<FRACBITS, fy<<FRACBITS, scale, V_HUDTRANSHALF|fflags, kp_positionnum[0][0], NULL);
+		return;
+	}
+
+	I_Assert(num >= 0); // This function does not draw negative numbers
+
+	// Draw the number
+	while (num)
+	{
+		if (win) // 1st place winner? You get rainbows!!
+			localpatch = kp_winnernum[(leveltime % (NUMWINFRAMES*3)) / 3];
+		else if (stplyr->laps+1 >= cv_numlaps.value || stplyr->exiting) // Check for the final lap, or won
+		{
+			// Alternate frame every three frames
+			switch (leveltime % 9)
+			{
+				case 1: case 2: case 3:
+					if (K_IsPlayerLosing(stplyr))
+						localpatch = kp_positionnum[num % 10][4];
+					else
+						localpatch = kp_positionnum[num % 10][1];
+					break;
+				case 4: case 5: case 6:
+					if (K_IsPlayerLosing(stplyr))
+						localpatch = kp_positionnum[num % 10][5];
+					else
+						localpatch = kp_positionnum[num % 10][2];
+					break;
+				case 7: case 8: case 9:
+					if (K_IsPlayerLosing(stplyr))
+						localpatch = kp_positionnum[num % 10][6];
+					else
+						localpatch = kp_positionnum[num % 10][3];
+					break;
+				default:
+					localpatch = kp_positionnum[num % 10][0];
+					break;
+			}
+		}
+		else
+			localpatch = kp_positionnum[num % 10][0];
+
+		V_DrawFixedPatch((fx<<FRACBITS) + ((overtake && flipdraw) ? (SHORT(localpatch->width)*scale/2) : 0), (fy<<FRACBITS) + ((overtake && flipvdraw) ? (SHORT(localpatch->height)*scale/2) : 0), scale, V_HUDTRANSHALF|fflags, localpatch, NULL);
+		// ^ if we overtake as p1 or p3 in splitscren, we shift it so that it doesn't go off screen.
+		// ^ if we overtake as p1 in 2p splits, shift vertically so that this doesn't happen either.
+
+		fx -= W;
+		num /= 10;
+	}
+}
+
+static boolean K_drawKartPositionFaces(void)
+{
+	// FACE_X = 15;				//  15
+	// FACE_Y = 72;				//  72
+
+	INT32 Y = FACE_Y+9; // +9 to offset where it's being drawn if there are more than one
+	INT32 i, j, ranklines, strank = -1;
+	boolean completed[MAXPLAYERS];
+	INT32 rankplayer[MAXPLAYERS];
+	INT32 bumperx, numplayersingame = 0;
+	UINT8 *colormap;
+
+	ranklines = 0;
+	memset(completed, 0, sizeof (completed));
+	memset(rankplayer, 0, sizeof (rankplayer));
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		rankplayer[i] = -1;
+
+		if (!playeringame[i] || players[i].spectator || !players[i].mo)
+			continue;
+
+		numplayersingame++;
+	}
+
+	if (numplayersingame <= 1)
+		return true;
+
+#ifdef HAVE_BLUA
+	if (!LUA_HudEnabled(hud_minirankings))
+		return false;	// Don't proceed but still return true for free play above if HUD is disabled.
+#endif
+
+	for (j = 0; j < numplayersingame; j++)
+	{
+		UINT8 lowestposition = MAXPLAYERS+1;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (completed[i] || !playeringame[i] || players[i].spectator || !players[i].mo)
+				continue;
+
+			if (players[i].kartstuff[k_position] >= lowestposition)
+				continue;
+
+			rankplayer[ranklines] = i;
+			lowestposition = players[i].kartstuff[k_position];
+		}
+
+		i = rankplayer[ranklines];
+
+		completed[i] = true;
+
+		if (players+i == stplyr)
+			strank = ranklines;
+
+		//if (ranklines == 5)
+			//break; // Only draw the top 5 players -- we do this a different way now...
+
+		ranklines++;
+	}
+
+	if (ranklines < 5)
+		Y -= (9*ranklines);
+	else
+		Y -= (9*5);
+
+	if (G_BattleGametype() || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2)
+	{
+		i = 0;
+		if (ranklines > 5) // could be both...
+			ranklines = 5;
+	}
+	else if (strank+3 > ranklines) // too close to the bottom?
+	{
+		i = ranklines - 5;
+		if (i < 0)
+			i = 0;
+	}
+	else
+	{
+		i = strank-2;
+		ranklines = strank+3;
+	}
+
+	for (; i < ranklines; i++)
+	{
+		if (!playeringame[rankplayer[i]]) continue;
+		if (players[rankplayer[i]].spectator) continue;
+		if (!players[rankplayer[i]].mo) continue;
+
+		bumperx = FACE_X+19;
+
+		if (players[rankplayer[i]].mo->color)
+		{
+			colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE);
+			if (players[rankplayer[i]].mo->colorized)
+				colormap = R_GetTranslationColormap(TC_RAINBOW, players[rankplayer[i]].mo->color, GTC_CACHE);
+			else
+				colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE);
+
+			V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, facerankprefix[players[rankplayer[i]].skin], colormap);
+
+#ifdef HAVE_BLUA
+			if (LUA_HudEnabled(hud_battlebumpers))
+			{
+#endif
+				if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] > 0)
+				{
+					V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[0], colormap);
+					for (j = 1; j < players[rankplayer[i]].kartstuff[k_bumper]; j++)
+					{
+						bumperx += 5;
+						V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[1], colormap);
+					}
+				}
+#ifdef HAVE_BLUA
+			}	// A new level of stupidity: checking if lua is enabled to close a bracket. :Fascinating:
+#endif
+		}
+
+		if (i == strank)
+			V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]);
+
+		if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] <= 0)
+			V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SNAPTOLEFT, kp_ranknobumpers);
+		else
+		{
+			INT32 pos = players[rankplayer[i]].kartstuff[k_position];
+			if (pos < 0 || pos > MAXPLAYERS)
+				pos = 0;
+			// Draws the little number over the face
+			V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SNAPTOLEFT, kp_facenum[pos]);
+		}
+
+		Y += 18;
+	}
+
+	return false;
+}
+
+//
+// HU_DrawTabRankings -- moved here to take advantage of kart stuff!
+//
+void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol)
+{
+	INT32 i, rightoffset = 240;
+	const UINT8 *colormap;
+	INT32 dupadjust = (vid.width/vid.dupx), duptweak = (dupadjust - BASEVIDWIDTH)/2;
+
+	//this function is designed for 9 or less score lines only
+	//I_Assert(scorelines <= 9); -- not today bitch, kart fixed it up
+
+	V_DrawFill(1-duptweak, 26, dupadjust-2, 1, 0); // Draw a horizontal line because it looks nice!
+	if (scorelines > 8)
+	{
+		V_DrawFill(160, 26, 1, 147, 0); // Draw a vertical line to separate the two sides.
+		V_DrawFill(1-duptweak, 173, dupadjust-2, 1, 0); // And a horizontal line near the bottom.
+		rightoffset = (BASEVIDWIDTH/2) - 4 - x;
+	}
+
+	for (i = 0; i < scorelines; i++)
+	{
+		char strtime[MAXPLAYERNAME+1];
+
+		if (players[tab[i].num].spectator || !players[tab[i].num].mo)
+			continue; //ignore them.
+
+		if (netgame // don't draw it offline
+		&& tab[i].num != serverplayer)
+			HU_drawPing(x + ((i < 8) ? -19 : rightoffset + 13), y+2, playerpingtable[tab[i].num], false);
+
+		STRBUFCPY(strtime, tab[i].name);
+
+		if (scorelines > 8)
+			V_DrawThinString(x + 20, y, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, strtime);
+		else
+			V_DrawString(x + 20, y, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE, strtime);
+
+		if (players[tab[i].num].mo->color)
+		{
+			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
+			if (players[tab[i].num].mo->colorized)
+				colormap = R_GetTranslationColormap(TC_RAINBOW, players[tab[i].num].mo->color, GTC_CACHE);
+			else
+				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
+
+			V_DrawMappedPatch(x, y-4, 0, facerankprefix[players[tab[i].num].skin], colormap);
+			/*if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] > 0) -- not enough space for this
+			{
+				INT32 bumperx = x+19;
+				V_DrawMappedPatch(bumperx-2, y-4, 0, kp_tinybumper[0], colormap);
+				for (j = 1; j < players[tab[i].num].kartstuff[k_bumper]; j++)
+				{
+					bumperx += 5;
+					V_DrawMappedPatch(bumperx, y-4, 0, kp_tinybumper[1], colormap);
+				}
+			}*/
+		}
+
+		if (tab[i].num == whiteplayer)
+			V_DrawScaledPatch(x, y-4, 0, kp_facehighlight[(leveltime / 4) % 8]);
+
+		if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] <= 0)
+			V_DrawScaledPatch(x-4, y-7, 0, kp_ranknobumpers);
+		else
+		{
+			INT32 pos = players[tab[i].num].kartstuff[k_position];
+			if (pos < 0 || pos > MAXPLAYERS)
+				pos = 0;
+			// Draws the little number over the face
+			V_DrawScaledPatch(x-5, y+6, 0, kp_facenum[pos]);
+		}
+
+		if (G_RaceGametype())
+		{
+#define timestring(time) va("%i'%02i\"%02i", G_TicsToMinutes(time, true), G_TicsToSeconds(time), G_TicsToCentiseconds(time))
+			if (scorelines > 8)
+			{
+				if (players[tab[i].num].exiting)
+					V_DrawRightAlignedThinString(x+rightoffset, y-1, hilicol|V_6WIDTHSPACE, timestring(players[tab[i].num].realtime));
+				else if (players[tab[i].num].pflags & PF_TIMEOVER)
+					V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, "NO CONTEST.");
+				else if (circuitmap)
+					V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, va("Lap %d", tab[i].count));
+			}
+			else
+			{
+				if (players[tab[i].num].exiting)
+					V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime));
+				else if (players[tab[i].num].pflags & PF_TIMEOVER)
+					V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "NO CONTEST.");
+				else if (circuitmap)
+					V_DrawRightAlignedString(x+rightoffset, y, 0, va("Lap %d", tab[i].count));
+			}
+#undef timestring
+		}
+		else
+			V_DrawRightAlignedString(x+rightoffset, y, 0, va("%u", tab[i].count));
+
+		y += 18;
+		if (i == 7)
+		{
+			y = 33;
+			x = (BASEVIDWIDTH/2) + 4;
+		}
+	}
+}
+
+static void K_drawKartLaps(void)
+{
+	INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT);
+	INT32 fx = 0, fy = 0, fflags = 0;	// stuff for 3p / 4p splitscreen.
+	boolean flipstring = false;	// used for 3p or 4p
+	INT32 stringw = 0;	// used with the above
+
+	if (splitscreen > 1)
+	{
+
+		// pain and suffering defined below
+		if (splitscreen < 2)	// don't change shit for THIS splitscreen.
+		{
+			fx = LAPS_X;
+			fy = LAPS_Y;
+			fflags = splitflags;
+		}
+		else
+		{
+			if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer])	// If we are P1 or P3...
+			{
+				fx = LAPS_X;
+				fy = LAPS_Y;
+				fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P3 to the bottom.
+			}
+			else // else, that means we're P2 or P4.
+			{
+				fx = LAPS2_X;
+				fy = LAPS2_Y;
+				fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P4 to the bottom
+				flipstring = true;	// make the string right aligned and other shit
+			}
+		}
+
+
+
+		if (stplyr->exiting)	// draw stuff as god intended.
+		{
+			V_DrawScaledPatch(fx, fy, V_HUDTRANS|fflags, kp_splitlapflag);
+			V_DrawString(fx+13, fy+1, V_HUDTRANS|fflags, "FIN");
+		}
+		else					// take flipstring into account here since we may have more laps than just 10
+			if (flipstring)
+			{
+				stringw = V_StringWidth(va("%d/%d", stplyr->laps+1, cv_numlaps.value), 0);
+
+				V_DrawScaledPatch(BASEVIDWIDTH-stringw-16, fy, V_HUDTRANS|fflags, kp_splitlapflag);
+				V_DrawRightAlignedString(BASEVIDWIDTH-3, fy+1, V_HUDTRANS|fflags, va("%d/%d", stplyr->laps+1, cv_numlaps.value));
+			}
+			else	// draw stuff NORMALLY.
+			{
+				V_DrawScaledPatch(fx, fy, V_HUDTRANS|fflags, kp_splitlapflag);
+				V_DrawString(fx+13, fy+1, V_HUDTRANS|fflags, va("%d/%d", stplyr->laps+1, cv_numlaps.value));
+			}
+	}
+	else
+	{
+		V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_lapsticker);
+
+		if (stplyr->exiting)
+			V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, "FIN");
+		else
+			V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->laps+1, cv_numlaps.value));
+	}
+}
+
+static void K_drawKartSpeedometer(void)
+{
+	fixed_t convSpeed;
+	INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT);
+
+	if (cv_kartspeedometer.value == 1) // Kilometers
+	{
+		convSpeed = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale)/FRACUNIT; // 2.172409058
+		V_DrawKartString(SPDM_X, SPDM_Y, V_HUDTRANS|splitflags, va("%3d km/h", convSpeed));
+	}
+	else if (cv_kartspeedometer.value == 2) // Miles
+	{
+		convSpeed = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale)/FRACUNIT; // 1.349868774
+		V_DrawKartString(SPDM_X, SPDM_Y, V_HUDTRANS|splitflags, va("%3d mph", convSpeed));
+	}
+	else if (cv_kartspeedometer.value == 3) // Fracunits
+	{
+		convSpeed = FixedDiv(stplyr->speed, mapobjectscale)/FRACUNIT;
+		V_DrawKartString(SPDM_X, SPDM_Y, V_HUDTRANS|splitflags, va("%3d fu/t", convSpeed));
+	}
+}
+
+static void K_drawKartBumpersOrKarma(void)
+{
+	UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, 0);
+	INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT);
+	INT32 fx = 0, fy = 0, fflags = 0;
+	boolean flipstring = false;	// same as laps, used for splitscreen
+	INT32 stringw = 0;	// used with the above
+
+	if (splitscreen > 1)
+	{
+
+		// we will reuse lap coords here since it's essentially the same shit.
+
+		if (stplyr == &players[displayplayer] || stplyr == &players[thirddisplayplayer])	// If we are P1 or P3...
+		{
+			fx = LAPS_X;
+			fy = LAPS_Y;
+			fflags = V_SNAPTOLEFT|((stplyr == &players[thirddisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P3 to the bottom.
+		}
+		else // else, that means we're P2 or P4.
+		{
+			fx = LAPS2_X;
+			fy = LAPS2_Y;
+			fflags = V_SNAPTORIGHT|((stplyr == &players[fourthdisplayplayer]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0);	// flip P4 to the bottom
+			flipstring = true;
+		}
+
+		if (stplyr->kartstuff[k_bumper] <= 0)
+		{
+			V_DrawMappedPatch(fx, fy-1, V_HUDTRANS|fflags, kp_splitkarmabomb, colormap);
+			V_DrawString(fx+13, fy+1, V_HUDTRANS|fflags, va("%d/2", stplyr->kartstuff[k_comebackpoints]));
+		}
+		else	// the above doesn't need to account for weird stuff since the max amount of karma necessary is always 2 ^^^^
+		{
+			if (flipstring)	// for p2 and p4, assume we can have more than 10 bumpers. It's retarded but who knows.
+			{
+				stringw = V_StringWidth(va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value), 0);
+
+				V_DrawMappedPatch(BASEVIDWIDTH-stringw-16, fy-1, V_HUDTRANS|fflags, kp_rankbumper, colormap);
+				V_DrawRightAlignedString(BASEVIDWIDTH-3, fy+1, V_HUDTRANS|fflags, va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value));
+			}
+			else	// draw bumpers normally.
+			{
+				V_DrawMappedPatch(fx, fy-1, V_HUDTRANS|fflags, kp_rankbumper, colormap);
+				V_DrawString(fx+13, fy+1, V_HUDTRANS|fflags, va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value));
+			}
+		}
+	}
+	else
+	{
+		if (stplyr->kartstuff[k_bumper] <= 0)
+		{
+			V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_karmasticker, colormap);
+			V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/2", stplyr->kartstuff[k_comebackpoints]));
+		}
+		else
+		{
+			if (stplyr->kartstuff[k_bumper] > 9 && cv_kartbumpers.value > 9)
+				V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_bumperstickerwide, colormap);
+			else
+				V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_bumpersticker, colormap);
+			V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value));
+		}
+	}
+}
+
+static fixed_t K_FindCheckX(fixed_t px, fixed_t py, angle_t ang, fixed_t mx, fixed_t my)
+{
+	fixed_t dist, x;
+	fixed_t range = RING_DIST/3;
+	angle_t diff;
+
+	range *= gamespeed+1;
+
+	dist = abs(R_PointToDist2(px, py, mx, my));
+	if (dist > range)
+		return -320;
+
+	diff = R_PointToAngle2(px, py, mx, my) - ang;
+
+	if (diff < ANGLE_90 || diff > ANGLE_270)
+		return -320;
+	else
+		x = (FixedMul(FINETANGENT(((diff+ANGLE_90)>>ANGLETOFINESHIFT) & 4095), 160<<FRACBITS) + (160<<FRACBITS))>>FRACBITS;
+
+	if (encoremode)
+		x = 320-x;
+
+	if (splitscreen > 1)
+		x /= 2;
+
+	return x;
+}
+
+static void K_drawKartWanted(void)
+{
+	UINT8 i, numwanted = 0;
+	UINT8 *colormap = NULL;
+	INT32 basex = 0, basey = 0;
+
+	if (stplyr != &players[displayplayer])
+		return;
+
+	for (i = 0; i < 4; i++)
+	{
+		if (battlewanted[i] == -1)
+			break;
+		numwanted++;
+	}
+
+	if (numwanted <= 0)
+		return;
+
+	// set X/Y coords depending on splitscreen.
+	if (splitscreen < 3)		// 1P and 2P use the same code.
+	{
+		basex = WANT_X;
+		basey = WANT_Y;
+		if (splitscreen == 2)
+		{
+			basey += 16;	// slight adjust for 3P
+			basex -= 6;
+		}
+	}
+	else if (splitscreen == 3)	// 4P splitscreen...
+	{
+		basex = BASEVIDWIDTH/2 - (SHORT(kp_wantedsplit->width)/2);	// center on screen
+		basey = BASEVIDHEIGHT - 55;
+		//basey2 = 4;
+	}
+
+	if (battlewanted[0] != -1)
+		colormap = R_GetTranslationColormap(0, players[battlewanted[0]].skincolor, GTC_CACHE);
+	V_DrawFixedPatch(basex<<FRACBITS, basey<<FRACBITS, FRACUNIT, V_HUDTRANS|(splitscreen < 3 ? V_SNAPTORIGHT : 0)|V_SNAPTOBOTTOM, (splitscreen > 1 ? kp_wantedsplit : kp_wanted), colormap);
+	/*if (basey2)
+		V_DrawFixedPatch(basex<<FRACBITS, basey2<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SNAPTOTOP, (splitscreen == 3 ? kp_wantedsplit : kp_wanted), colormap);	// < used for 4p splits.*/
+
+	for (i = 0; i < numwanted; i++)
+	{
+		INT32 x = basex+(splitscreen > 1 ? 13 : 8), y = basey+(splitscreen > 1 ? 16 : 21);
+		fixed_t scale = FRACUNIT/2;
+		player_t *p = &players[battlewanted[i]];
+
+		if (battlewanted[i] == -1)
+			break;
+
+		if (numwanted == 1)
+			scale = FRACUNIT;
+		else
+		{
+			if (i & 1)
+				x += 16;
+			if (i > 1)
+				y += 16;
+		}
+
+		if (players[battlewanted[i]].skincolor)
+		{
+			colormap = R_GetTranslationColormap(TC_RAINBOW, p->skincolor, GTC_CACHE);
+			V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT, V_HUDTRANS|(splitscreen < 3 ? V_SNAPTORIGHT : 0)|V_SNAPTOBOTTOM, (scale == FRACUNIT ? facewantprefix[p->skin] : facerankprefix[p->skin]), colormap);
+			/*if (basey2)	// again with 4p stuff
+				V_DrawFixedPatch(x<<FRACBITS, (y - (basey-basey2))<<FRACBITS, FRACUNIT, V_HUDTRANS|V_SNAPTOTOP, (scale == FRACUNIT ? facewantprefix[p->skin] : facerankprefix[p->skin]), colormap);*/
+		}
+	}
+}
+
+static void K_drawKartPlayerCheck(void)
+{
+	INT32 i;
+	UINT8 *colormap;
+	INT32 x;
+
+	INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM);
+
+	if (!stplyr->mo || stplyr->spectator)
+		return;
+
+	if (stplyr->awayviewtics)
+		return;
+
+	if (camspin)
+		return;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		UINT8 pnum = 0;
+
+		if (&players[i] == stplyr)
+			continue;
+		if (!playeringame[i] || players[i].spectator)
+			continue;
+		if (!players[i].mo)
+			continue;
+
+		if ((players[i].kartstuff[k_invincibilitytimer] <= 0) && (leveltime & 2))
+			pnum++; // white frames
+
+		if (players[i].kartstuff[k_itemtype] == KITEM_GROW || players[i].kartstuff[k_growshrinktimer] > 0)
+			pnum += 4;
+		else if (players[i].kartstuff[k_itemtype] == KITEM_INVINCIBILITY || players[i].kartstuff[k_invincibilitytimer])
+			pnum += 2;
+
+		x = K_FindCheckX(stplyr->mo->x, stplyr->mo->y, stplyr->mo->angle, players[i].mo->x, players[i].mo->y);
+		if (x <= 320 && x >= 0)
+		{
+			if (x < 14)
+				x = 14;
+			else if (x > 306)
+				x = 306;
+
+			colormap = R_GetTranslationColormap(TC_DEFAULT, players[i].mo->color, 0);
+			V_DrawMappedPatch(x, CHEK_Y, V_HUDTRANS|splitflags, kp_check[pnum], colormap);
+		}
+	}
+}
+
+static void K_drawKartMinimapHead(mobj_t *mo, INT32 x, INT32 y, INT32 flags, patch_t *AutomapPic)
+{
+	// amnum xpos & ypos are the icon's speed around the HUD.
+	// The number being divided by is for how fast it moves.
+	// The higher the number, the slower it moves.
+
+	// am xpos & ypos are the icon's starting position. Withouht
+	// it, they wouldn't 'spawn' on the top-right side of the HUD.
+
+	UINT8 skin = 0;
+
+	fixed_t amnumxpos, amnumypos;
+	INT32 amxpos, amypos;
+
+	node_t *bsp = &nodes[numnodes-1];
+	fixed_t maxx, minx, maxy, miny;
+
+	fixed_t mapwidth, mapheight;
+	fixed_t xoffset, yoffset;
+	fixed_t xscale, yscale, zoom;
+
+	if (mo->skin)
+		skin = ((skin_t*)mo->skin)-skins;
+
+	maxx = maxy = INT32_MAX;
+	minx = miny = INT32_MIN;
+	minx = bsp->bbox[0][BOXLEFT];
+	maxx = bsp->bbox[0][BOXRIGHT];
+	miny = bsp->bbox[0][BOXBOTTOM];
+	maxy = bsp->bbox[0][BOXTOP];
+
+	if (bsp->bbox[1][BOXLEFT] < minx)
+		minx = bsp->bbox[1][BOXLEFT];
+	if (bsp->bbox[1][BOXRIGHT] > maxx)
+		maxx = bsp->bbox[1][BOXRIGHT];
+	if (bsp->bbox[1][BOXBOTTOM] < miny)
+		miny = bsp->bbox[1][BOXBOTTOM];
+	if (bsp->bbox[1][BOXTOP] > maxy)
+		maxy = bsp->bbox[1][BOXTOP];
+
+	// You might be wondering why these are being bitshift here
+	// it's because mapwidth and height would otherwise overflow for maps larger than half the size possible...
+	// map boundaries and sizes will ALWAYS be whole numbers thankfully
+	// later calculations take into consideration that these are actually not in terms of FRACUNIT though
+	minx >>= FRACBITS;
+	maxx >>= FRACBITS;
+	miny >>= FRACBITS;
+	maxy >>= FRACBITS;
+
+	mapwidth = maxx - minx;
+	mapheight = maxy - miny;
+
+	// These should always be small enough to be bitshift back right now
+	xoffset = (minx + mapwidth/2)<<FRACBITS;
+	yoffset = (miny + mapheight/2)<<FRACBITS;
+
+	xscale = FixedDiv(AutomapPic->width, mapwidth);
+	yscale = FixedDiv(AutomapPic->height, mapheight);
+	zoom = FixedMul(min(xscale, yscale), FRACUNIT-FRACUNIT/20);
+
+	amnumxpos = (FixedMul(mo->x, zoom) - FixedMul(xoffset, zoom));
+	amnumypos = -(FixedMul(mo->y, zoom) - FixedMul(yoffset, zoom));
+
+	if (encoremode)
+		amnumxpos = -amnumxpos;
+
+	amxpos = amnumxpos + ((x + AutomapPic->width/2 - (facemmapprefix[skin]->width/2))<<FRACBITS);
+	amypos = amnumypos + ((y + AutomapPic->height/2 - (facemmapprefix[skin]->height/2))<<FRACBITS);
+
+	// do we want this? it feels unnecessary. easier to just modify the amnumxpos?
+	/*if (encoremode)
+	{
+		flags |= V_FLIP;
+		amxpos = -amnumxpos + ((x + AutomapPic->width/2 + (facemmapprefix[skin]->width/2))<<FRACBITS);
+	}*/
+
+	if (!mo->color) // 'default' color
+		V_DrawSciencePatch(amxpos, amypos, flags, facemmapprefix[skin], FRACUNIT);
+	else
+	{
+		UINT8 *colormap;
+		if (mo->colorized)
+			colormap = R_GetTranslationColormap(TC_RAINBOW, mo->color, GTC_CACHE);
+		else
+			colormap = R_GetTranslationColormap(skin, mo->color, GTC_CACHE);
+		V_DrawFixedPatch(amxpos, amypos, FRACUNIT, flags, facemmapprefix[skin], colormap);
+		if (mo->player && K_IsPlayerWanted(mo->player))
+			V_DrawFixedPatch(amxpos - (4<<FRACBITS), amypos - (4<<FRACBITS), FRACUNIT, flags, kp_wantedreticle, NULL);
+	}
+}
+
+static void K_drawKartMinimap(void)
+{
+	INT32 lumpnum;
+	patch_t *AutomapPic;
+	INT32 i = 0;
+	INT32 x, y;
+	INT32 minimaptrans, splitflags = (splitscreen == 3 ? 0 : V_SNAPTORIGHT);	// flags should only be 0 when it's centered (4p split)
+	SINT8 localplayers[4];
+	SINT8 numlocalplayers = 0;
+
+	// Draw the HUD only when playing in a level.
+	// hu_stuff needs this, unlike st_stuff.
+	if (gamestate != GS_LEVEL)
+		return;
+
+	if (stplyr != &players[displayplayer])
+		return;
+
+	lumpnum = W_CheckNumForName(va("%sR", G_BuildMapName(gamemap)));
+
+	if (lumpnum != -1)
+		AutomapPic = W_CachePatchName(va("%sR", G_BuildMapName(gamemap)), PU_HUDGFX);
+	else
+		return; // no pic, just get outta here
+
+	x = MINI_X - (AutomapPic->width/2);
+	y = MINI_Y - (AutomapPic->height/2);
+
+	if (timeinmap > 105)
+	{
+		minimaptrans = cv_kartminimap.value;
+		if (timeinmap <= 113)
+			minimaptrans = ((((INT32)timeinmap) - 105)*minimaptrans)/(113-105);
+		if (!minimaptrans)
+			return;
+	}
+	else
+		return;
+
+	minimaptrans = ((10-minimaptrans)<<FF_TRANSSHIFT);
+	splitflags |= minimaptrans;
+
+	if (encoremode)
+		V_DrawScaledPatch(x+(AutomapPic->width), y, splitflags|V_FLIP, AutomapPic);
+	else
+		V_DrawScaledPatch(x, y, splitflags, AutomapPic);
+
+	if (!(splitscreen == 2))
+	{
+		splitflags &= ~minimaptrans;
+		splitflags |= V_HUDTRANSHALF;
+	}
+
+	// let offsets transfer to the heads, too!
+	if (encoremode)
+		x += SHORT(AutomapPic->leftoffset);
+	else
+		x -= SHORT(AutomapPic->leftoffset);
+	y -= SHORT(AutomapPic->topoffset);
+
+	// initialize
+	for (i = 0; i < 4; i++)
+		localplayers[i] = -1;
+
+	// Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen)
+	if (ghosts)
+	{
+		demoghost *g = ghosts;
+		while (g)
+		{
+			K_drawKartMinimapHead(g->mo, x, y, splitflags, AutomapPic);
+			g = g->next;
+		}
+
+		if (!stplyr->mo || stplyr->spectator) // do we need the latter..?
+			return;
+
+		localplayers[numlocalplayers] = stplyr-players;
+		numlocalplayers++;
+	}
+	else
+	{
+		for (i = MAXPLAYERS-1; i >= 0; i--)
+		{
+			if (!playeringame[i])
+				continue;
+			if (!players[i].mo || players[i].spectator)
+				continue;
+
+			if (i != displayplayer || splitscreen)
+			{
+				if (G_BattleGametype() && players[i].kartstuff[k_bumper] <= 0)
+					continue;
+
+				if (players[i].kartstuff[k_hyudorotimer] > 0)
+				{
+					if (!((players[i].kartstuff[k_hyudorotimer] < 1*TICRATE/2
+						|| players[i].kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2))
+						&& !(leveltime & 1)))
+						continue;
+				}
+			}
+
+			if (i == displayplayer || i == secondarydisplayplayer || i == thirddisplayplayer || i == fourthdisplayplayer)
+			{
+				// Draw display players on top of everything else
+				localplayers[numlocalplayers] = i;
+				numlocalplayers++;
+				continue;
+			}
+
+			K_drawKartMinimapHead(players[i].mo, x, y, splitflags, AutomapPic);
+		}
+	}
+
+	// draw our local players here, opaque.
+	splitflags &= ~V_HUDTRANSHALF;
+	splitflags |= V_HUDTRANS;
+
+	for (i = 0; i < numlocalplayers; i++)
+	{
+		if (i == -1)
+			continue; // this doesn't interest us
+		K_drawKartMinimapHead(players[localplayers[i]].mo, x, y, splitflags, AutomapPic);
+	}
+}
+
+static void K_drawKartStartCountdown(void)
+{
+	INT32 pnum = 0, splitflags = K_calcSplitFlags(0); // 3
+
+	if (leveltime >= starttime-(2*TICRATE)) // 2
+		pnum++;
+	if (leveltime >= starttime-TICRATE) // 1
+		pnum++;
+	if (leveltime >= starttime) // GO!
+		pnum++;
+	if ((leveltime % (2*5)) / 5) // blink
+		pnum += 4;
+	if (splitscreen) // splitscreen
+		pnum += 8;
+
+	V_DrawScaledPatch(STCD_X - (SHORT(kp_startcountdown[pnum]->width)/2), STCD_Y - (SHORT(kp_startcountdown[pnum]->height)/2), splitflags, kp_startcountdown[pnum]);
+}
+
+static void K_drawKartFinish(void)
+{
+	INT32 pnum = 0, splitflags = K_calcSplitFlags(0);
+
+	if (!stplyr->kartstuff[k_cardanimation] || stplyr->kartstuff[k_cardanimation] >= 2*TICRATE)
+		return;
+
+	if ((stplyr->kartstuff[k_cardanimation] % (2*5)) / 5) // blink
+		pnum = 1;
+
+	if (splitscreen > 1) // 3/4p, stationary FIN
+	{
+		pnum += 2;
+		V_DrawScaledPatch(STCD_X - (SHORT(kp_racefinish[pnum]->width)/2), STCD_Y - (SHORT(kp_racefinish[pnum]->height)/2), splitflags, kp_racefinish[pnum]);
+		return;
+	}
+
+	//else -- 1/2p, scrolling FINISH
+	{
+		INT32 x, xval;
+
+		if (splitscreen) // wide splitscreen
+			pnum += 4;
+
+		x = ((vid.width<<FRACBITS)/vid.dupx);
+		xval = (SHORT(kp_racefinish[pnum]->width)<<FRACBITS);
+		x = ((TICRATE - stplyr->kartstuff[k_cardanimation])*(xval > x ? xval : x))/TICRATE;
+
+		if (splitscreen && stplyr == &players[secondarydisplayplayer])
+			x = -x;
+
+		V_DrawFixedPatch(x + (STCD_X<<FRACBITS) - (xval>>1),
+			(STCD_Y<<FRACBITS) - (SHORT(kp_racefinish[pnum]->height)<<(FRACBITS-1)),
+			FRACUNIT,
+			splitflags, kp_racefinish[pnum], NULL);
+	}
+}
+
+static void K_drawBattleFullscreen(void)
+{
+	INT32 x = BASEVIDWIDTH/2;
+	INT32 y = -64+(stplyr->kartstuff[k_cardanimation]); // card animation goes from 0 to 164, 164 is the middle of the screen
+	INT32 splitflags = V_SNAPTOTOP; // I don't feel like properly supporting non-green resolutions, so you can have a misuse of SNAPTO instead
+	fixed_t scale = FRACUNIT;
+
+	if (splitscreen)
+	{
+		if ((splitscreen == 1 && stplyr == &players[secondarydisplayplayer])
+			|| (splitscreen > 1 && (stplyr == &players[thirddisplayplayer]
+			|| (stplyr == &players[fourthdisplayplayer] && splitscreen > 2))))
+		{
+			y = 232-(stplyr->kartstuff[k_cardanimation]/2);
+			splitflags = V_SNAPTOBOTTOM;
+		}
+		else
+			y = -32+(stplyr->kartstuff[k_cardanimation]/2);
+
+		if (splitscreen > 1)
+		{
+			scale /= 2;
+
+			if (stplyr == &players[secondarydisplayplayer]
+				|| (stplyr == &players[fourthdisplayplayer] && splitscreen > 2))
+				x = 3*BASEVIDWIDTH/4;
+			else
+				x = BASEVIDWIDTH/4;
+		}
+		else
+		{
+			if (stplyr->exiting)
+			{
+				if (stplyr == &players[secondarydisplayplayer])
+					x = BASEVIDWIDTH-96;
+				else
+					x = 96;
+			}
+			else
+				scale /= 2;
+		}
+	}
+
+	if (stplyr->exiting)
+	{
+		if (stplyr == &players[displayplayer])
+			V_DrawFadeScreen(0xFF00, 16);
+		if (stplyr->exiting < 6*TICRATE && !stplyr->spectator)
+		{
+			if (stplyr->kartstuff[k_position] == 1)
+				V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, kp_battlewin, NULL);
+			else
+				V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, (K_IsPlayerLosing(stplyr) ? kp_battlelose : kp_battlecool), NULL);
+		}
+		else
+			K_drawKartFinish();
+	}
+	else if (stplyr->kartstuff[k_bumper] <= 0 && stplyr->kartstuff[k_comebacktimer] && comeback && !stplyr->spectator)
+	{
+		UINT16 t = stplyr->kartstuff[k_comebacktimer]/(10*TICRATE);
+		INT32 txoff, adjust = (splitscreen > 1) ? 4 : 6; // normal string is 8, kart string is 12, half of that for ease
+		INT32 ty = (BASEVIDHEIGHT/2)+66;
+
+		txoff = adjust;
+
+		while (t)
+		{
+			txoff += adjust;
+			t /= 10;
+		}
+
+		if (splitscreen)
+		{
+			if (splitscreen > 1)
+				ty = (BASEVIDHEIGHT/4)+33;
+			if ((splitscreen == 1 && stplyr == &players[secondarydisplayplayer])
+				|| (stplyr == &players[thirddisplayplayer] && splitscreen > 1)
+				|| (stplyr == &players[fourthdisplayplayer] && splitscreen > 2))
+				ty += (BASEVIDHEIGHT/2);
+		}
+		else
+			V_DrawFadeScreen(0xFF00, 16);
+
+		if (!comebackshowninfo)
+			V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, kp_battleinfo, NULL);
+		else
+			V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, kp_battlewait, NULL);
+
+		if (splitscreen > 1)
+			V_DrawString(x-txoff, ty, 0, va("%d", stplyr->kartstuff[k_comebacktimer]/TICRATE));
+		else
+		{
+			V_DrawFixedPatch(x<<FRACBITS, ty<<FRACBITS, scale, 0, kp_timeoutsticker, NULL);
+			V_DrawKartString(x-txoff, ty, 0, va("%d", stplyr->kartstuff[k_comebacktimer]/TICRATE));
+		}
+	}
+
+	if (netgame && !stplyr->spectator && timeinmap > 113) // FREE PLAY?
+	{
+		UINT8 i;
+
+		// check to see if there's anyone else at all
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (i == displayplayer)
+				continue;
+			if (playeringame[i] && !stplyr->spectator)
+				return;
+		}
+
+#ifdef HAVE_BLUA
+		if (LUA_HudEnabled(hud_freeplay))
+#endif
+			K_drawKartFreePlay(leveltime);
+	}
+}
+
+static void K_drawKartFirstPerson(void)
+{
+	static INT32 pnum[4], turn[4], drift[4];
+	INT32 pn = 0, tn = 0, dr = 0;
+	INT32 target = 0, splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM);
+	INT32 x = BASEVIDWIDTH/2, y = BASEVIDHEIGHT;
+	fixed_t scale;
+	UINT8 *colmap = NULL;
+	ticcmd_t *cmd = &stplyr->cmd;
+
+	if (stplyr->spectator || !stplyr->mo || (stplyr->mo->flags2 & MF2_DONTDRAW))
+		return;
+
+	if (stplyr == &players[secondarydisplayplayer] && splitscreen)
+		{ pn = pnum[1]; tn = turn[1]; dr = drift[1]; }
+	else if (stplyr == &players[thirddisplayplayer] && splitscreen > 1)
+		{ pn = pnum[2]; tn = turn[2]; dr = drift[2]; }
+	else if (stplyr == &players[fourthdisplayplayer] && splitscreen > 2)
+		{ pn = pnum[3]; tn = turn[3]; dr = drift[3]; }
+	else
+		{ pn = pnum[0]; tn = turn[0]; dr = drift[0]; }
+
+	if (splitscreen)
+	{
+		y >>= 1;
+		if (splitscreen > 1)
+			x >>= 1;
+	}
+
+	{
+		if (stplyr->speed < FixedMul(stplyr->runspeed, stplyr->mo->scale) && (leveltime & 1) && !splitscreen)
+			y++;
+		// the following isn't EXPLICITLY right, it just gets the result we want, but i'm too lazy to look up the right way to do it
+		if (stplyr->mo->flags2 & MF2_SHADOW)
+			splitflags |= FF_TRANS80;
+		else if (stplyr->mo->frame & FF_TRANSMASK)
+			splitflags |= (stplyr->mo->frame & FF_TRANSMASK);
+	}
+
+	if (cmd->driftturn > 400) // strong left turn
+		target = 2;
+	else if (cmd->driftturn < -400) // strong right turn
+		target = -2;
+	else if (cmd->driftturn > 0) // weak left turn
+		target = 1;
+	else if (cmd->driftturn < 0) // weak right turn
+		target = -1;
+	else // forward
+		target = 0;
+
+	if (encoremode)
+		target = -target;
+
+	if (pn < target)
+		pn++;
+	else if (pn > target)
+		pn--;
+
+	if (pn < 0)
+		splitflags |= V_FLIP; // right turn
+
+	target = abs(pn);
+	if (target > 2)
+		target = 2;
+
+	x <<= FRACBITS;
+	y <<= FRACBITS;
+
+	if (tn != cmd->driftturn/50)
+		tn -= (tn - (cmd->driftturn/50))/8;
+
+	if (dr != stplyr->kartstuff[k_drift]*16)
+		dr -= (dr - (stplyr->kartstuff[k_drift]*16))/8;
+
+	if (splitscreen == 1)
+	{
+		scale = (2*FRACUNIT)/3;
+		y += FRACUNIT/(vid.dupx < vid.dupy ? vid.dupx : vid.dupy); // correct a one-pixel gap on the screen view (not the basevid view)
+	}
+	else if (splitscreen)
+		scale = FRACUNIT/2;
+	else
+		scale = FRACUNIT;
+
+	if (stplyr->mo)
+	{
+		INT32 dsone = K_GetKartDriftSparkValue(stplyr);
+		INT32 dstwo = dsone*2;
+		INT32 dsthree = dstwo*2;
+
+#ifndef DONTLIKETOASTERSFPTWEAKS
+		{
+			const angle_t ang = R_PointToAngle2(0, 0, stplyr->rmomx, stplyr->rmomy) - stplyr->frameangle;
+			// yes, the following is correct. no, you do not need to swap the x and y.
+			fixed_t xoffs = -P_ReturnThrustY(stplyr->mo, ang, (BASEVIDWIDTH<<(FRACBITS-2))/2);
+			fixed_t yoffs = -(P_ReturnThrustX(stplyr->mo, ang, 4*FRACUNIT) - 4*FRACUNIT);
+
+			if (splitscreen)
+				xoffs = FixedMul(xoffs, scale);
+
+			xoffs -= (tn)*scale;
+			xoffs -= (dr)*scale;
+
+			if (stplyr->frameangle == stplyr->mo->angle)
+			{
+				const fixed_t mag = FixedDiv(stplyr->speed, 10*stplyr->mo->scale);
+
+				if (mag < FRACUNIT)
+				{
+					xoffs = FixedMul(xoffs, mag);
+					if (!splitscreen)
+						yoffs = FixedMul(yoffs, mag);
+				}
+			}
+
+			if (stplyr->mo->momz > 0) // TO-DO: Draw more of the kart so we can remove this if!
+				yoffs += stplyr->mo->momz/3;
+
+			if (encoremode)
+				x -= xoffs;
+			else
+				x += xoffs;
+			if (!splitscreen)
+				y += yoffs;
+		}
+
+		// drift sparks!
+		if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsthree))
+			colmap = R_GetTranslationColormap(TC_RAINBOW, (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))), 0);
+		else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dstwo))
+			colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_KETCHUP, 0);
+		else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsone))
+			colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SAPPHIRE, 0);
+		else
+#endif
+		// invincibility/grow/shrink!
+		if (stplyr->mo->colorized && stplyr->mo->color)
+			colmap = R_GetTranslationColormap(TC_RAINBOW, stplyr->mo->color, 0);
+	}
+
+	V_DrawFixedPatch(x, y, scale, splitflags, kp_fpview[target], colmap);
+
+	if (stplyr == &players[secondarydisplayplayer] && splitscreen)
+		{ pnum[1] = pn; turn[1] = tn; drift[1] = dr; }
+	else if (stplyr == &players[thirddisplayplayer] && splitscreen > 1)
+		{ pnum[2] = pn; turn[2] = tn; drift[2] = dr; }
+	else if (stplyr == &players[fourthdisplayplayer] && splitscreen > 2)
+		{ pnum[3] = pn; turn[3] = tn; drift[3] = dr; }
+	else
+		{ pnum[0] = pn; turn[0] = tn; drift[0] = dr; }
+}
+
+// doesn't need to ever support 4p
+static void K_drawInput(void)
+{
+	static INT32 pn = 0;
+	INT32 target = 0, splitflags = (V_SNAPTOBOTTOM|V_SNAPTORIGHT);
+	INT32 x = BASEVIDWIDTH - 32, y = BASEVIDHEIGHT-24, offs, col;
+	const INT32 accent1 = splitflags|colortranslations[stplyr->skincolor][5];
+	const INT32 accent2 = splitflags|colortranslations[stplyr->skincolor][9];
+	ticcmd_t *cmd = &stplyr->cmd;
+
+	if (timeinmap <= 105)
+		return;
+
+	if (timeinmap < 113)
+	{
+		INT32 count = ((INT32)(timeinmap) - 105);
+		offs = (titledemo ? 128 : 64);
+		while (count-- > 0)
+			offs >>= 1;
+		x += offs;
+	}
+
+	if (titledemo)
+	{
+		V_DrawTinyScaledPatch(x-54, 128, splitflags, W_CachePatchName("TTKBANNR", PU_CACHE));
+		V_DrawTinyScaledPatch(x-54, 128+25, splitflags, W_CachePatchName("TTKART", PU_CACHE));
+		return;
+	}
+
+#define BUTTW 8
+#define BUTTH 11
+
+#define drawbutt(xoffs, butt, symb)\
+	if (stplyr->cmd.buttons & butt)\
+	{\
+		offs = 2;\
+		col = accent1;\
+	}\
+	else\
+	{\
+		offs = 0;\
+		col = accent2;\
+		V_DrawFill(x+(xoffs), y+BUTTH, BUTTW-1, 2, splitflags|31);\
+	}\
+	V_DrawFill(x+(xoffs), y+offs, BUTTW-1, BUTTH, col);\
+	V_DrawFixedPatch((x+1+(xoffs))<<FRACBITS, (y+offs+1)<<FRACBITS, FRACUNIT, splitflags, tny_font[symb-HU_FONTSTART], NULL)
+
+	drawbutt(-2*BUTTW, BT_ACCELERATE, 'A');
+	drawbutt(  -BUTTW, BT_BRAKE,      'B');
+	drawbutt(       0, BT_DRIFT,      'D');
+	drawbutt(   BUTTW, BT_ATTACK,     'I');
+
+#undef drawbutt
+
+#undef BUTTW
+#undef BUTTH
+
+	y -= 1;
+
+	if (!cmd->driftturn) // no turn
+		target = 0;
+	else // turning of multiple strengths!
+	{
+		target = ((abs(cmd->driftturn) - 1)/125)+1;
+		if (target > 4)
+			target = 4;
+		if (cmd->driftturn < 0)
+			target = -target;
+	}
+
+	if (pn != target)
+	{
+		if (abs(pn - target) == 1)
+			pn = target;
+		else if (pn < target)
+			pn += 2;
+		else //if (pn > target)
+			pn -= 2;
+	}
+
+	if (pn < 0)
+	{
+		splitflags |= V_FLIP; // right turn
+		x--;
+	}
+
+	target = abs(pn);
+	if (target > 4)
+		target = 4;
+
+	if (!stplyr->skincolor)
+		V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT, splitflags, kp_inputwheel[target], NULL);
+	else
+	{
+		UINT8 *colormap;
+		colormap = R_GetTranslationColormap(0, stplyr->skincolor, 0);
+		V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT, splitflags, kp_inputwheel[target], colormap);
+	}
+}
+
+static void K_drawChallengerScreen(void)
+{
+	// This is an insanely complicated animation.
+	static UINT8 anim[52] = {
+		0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13, // frame 1-14, 2 tics: HERE COMES A NEW slides in
+		14,14,14,14,14,14, // frame 15, 6 tics: pause on the W
+		15,16,17,18, // frame 16-19, 1 tic: CHALLENGER approaches screen
+		19,20,19,20,19,20,19,20,19,20, // frame 20-21, 1 tic, 5 alternating: all text vibrates from impact
+		21,22,23,24 // frame 22-25, 1 tic: CHALLENGER turns gold
+	};
+	const UINT8 offset = min(52-1, (3*TICRATE)-mapreset);
+
+	V_DrawFadeScreen(0xFF00, 16); // Fade out
+	V_DrawScaledPatch(0, 0, 0, kp_challenger[anim[offset]]);
+}
+
+static void K_drawLapStartAnim(void)
+{
+	// This is an EVEN MORE insanely complicated animation.
+	const UINT8 progress = 80-stplyr->kartstuff[k_lapanimation];
+	UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, 0);
+
+	V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->kartstuff[k_lapanimation]-76)))*FRACUNIT,
+		(48 - (32*max(0, progress-76)))*FRACUNIT,
+		FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
+		(modeattacking ? kp_lapanim_emblem[1] : kp_lapanim_emblem[0]), colormap);
+
+	if (stplyr->kartstuff[k_laphand] >= 1 && stplyr->kartstuff[k_laphand] <= 3)
+	{
+		V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->kartstuff[k_lapanimation]-76)))*FRACUNIT,
+			(48 - (32*max(0, progress-76))
+				+ 4 - abs((signed)((leveltime % 8) - 4)))*FRACUNIT,
+			FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
+			kp_lapanim_hand[stplyr->kartstuff[k_laphand]-1], NULL);
+	}
+
+	if (stplyr->laps == (UINT8)(cv_numlaps.value - 1))
+	{
+		V_DrawFixedPatch((62 - (32*max(0, progress-76)))*FRACUNIT, // 27
+			30*FRACUNIT, // 24
+			FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
+			kp_lapanim_final[min(progress/2, 10)], NULL);
+
+		if (progress/2-12 >= 0)
+		{
+			V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194
+				30*FRACUNIT, // 24
+				FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
+				kp_lapanim_lap[min(progress/2-12, 6)], NULL);
+		}
+	}
+	else
+	{
+		V_DrawFixedPatch((82 - (32*max(0, progress-76)))*FRACUNIT, // 61
+			30*FRACUNIT, // 24
+			FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
+			kp_lapanim_lap[min(progress/2, 6)], NULL);
+
+		if (progress/2-8 >= 0)
+		{
+			V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194
+				30*FRACUNIT, // 24
+				FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
+				kp_lapanim_number[(((UINT32)stplyr->laps+1) / 10)][min(progress/2-8, 2)], NULL);
+
+			if (progress/2-10 >= 0)
+			{
+				V_DrawFixedPatch((208 + (32*max(0, progress-76)))*FRACUNIT, // 221
+					30*FRACUNIT, // 24
+					FRACUNIT, V_SNAPTOTOP|V_HUDTRANS,
+					kp_lapanim_number[(((UINT32)stplyr->laps+1) % 10)][min(progress/2-10, 2)], NULL);
+			}
+		}
+	}
+}
+
+void K_drawKartFreePlay(UINT32 flashtime)
+{
+	// no splitscreen support because it's not FREE PLAY if you have more than one player in-game
+
+	if ((flashtime % TICRATE) < TICRATE/2)
+		return;
+
+	V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - (12*9), // mirror the laps thingy
+		LAPS_Y+3, V_SNAPTOBOTTOM|V_SNAPTORIGHT, "FREE PLAY");
+}
+
+static void K_drawDistributionDebugger(void)
+{
+	patch_t *items[NUMKARTRESULTS] = {
+		kp_sadface[1],
+		kp_sneaker[1],
+		kp_rocketsneaker[1],
+		kp_invincibility[7],
+		kp_banana[1],
+		kp_eggman[1],
+		kp_orbinaut[4],
+		kp_jawz[1],
+		kp_mine[1],
+		kp_ballhog[1],
+		kp_selfpropelledbomb[1],
+		kp_grow[1],
+		kp_shrink[1],
+		kp_thundershield[1],
+		kp_hyudoro[1],
+		kp_pogospring[1],
+		kp_kitchensink[1],
+
+		kp_sneaker[1],
+		kp_banana[1],
+		kp_banana[1],
+		kp_orbinaut[4],
+		kp_orbinaut[4],
+		kp_jawz[1]
+	};
+	INT32 useodds = 0;
+	INT32 pingame = 0, bestbumper = 0;
+	INT32 i;
+	INT32 x = -9, y = -9;
+	boolean dontforcespb = false;
+
+	if (stplyr != &players[displayplayer]) // only for p1
+		return;
+
+	// The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i] || players[i].spectator)
+			continue;
+		pingame++;
+		if (players[i].exiting)
+			dontforcespb = true;
+		if (players[i].kartstuff[k_bumper] > bestbumper)
+			bestbumper = players[i].kartstuff[k_bumper];
+	}
+
+	useodds = K_FindUseodds(stplyr, 0, pingame, bestbumper, (spbplace != -1 && stplyr->kartstuff[k_position] == spbplace+1), dontforcespb);
+
+	for (i = 1; i < NUMKARTRESULTS; i++)
+	{
+		const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0);
+		if (itemodds <= 0)
+			continue;
+
+		V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOTOP, items[i]);
+		V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SNAPTOTOP, va("%d", itemodds));
+
+		// Display amount for multi-items
+		if (i >= NUMKARTITEMS)
+		{
+			INT32 amount;
+			switch (i)
+			{
+				case KRITEM_TENFOLDBANANA:
+					amount = 10;
+					break;
+				case KRITEM_QUADORBINAUT:
+					amount = 4;
+					break;
+				case KRITEM_DUALJAWZ:
+					amount = 2;
+					break;
+				default:
+					amount = 3;
+					break;
+			}
+			V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", amount));
+		}
+
+		x += 32;
+		if (x >= 297)
+		{
+			x = -9;
+			y += 32;
+		}
+	}
+
+	V_DrawString(0, 0, V_HUDTRANS|V_SNAPTOTOP, va("USEODDS %d", useodds));
+}
+
+static void K_drawCheckpointDebugger(void)
+{
+	if (stplyr != &players[displayplayer]) // only for p1
+		return;
+
+	if (stplyr->starpostnum >= (numstarposts - (numstarposts/2)))
+		V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Can finish)", stplyr->starpostnum, numstarposts));
+	else
+		V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Skip: %d)", stplyr->starpostnum, numstarposts, ((numstarposts/2) + stplyr->starpostnum)));
+	V_DrawString(8, 192, 0, va("Waypoint dist: Prev %d, Next %d", stplyr->kartstuff[k_prevcheck], stplyr->kartstuff[k_nextcheck]));
+}
+
+void K_drawKartHUD(void)
+{
+	boolean isfreeplay = false;
+	boolean battlefullscreen = false;
+
+	// Define the X and Y for each drawn object
+	// This is handled by console/menu values
+	K_initKartHUD();
+
+	// Draw that fun first person HUD! Drawn ASAP so it looks more "real".
+	if ((stplyr == &players[displayplayer] && !camera.chase)
+		|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase)
+		|| ((splitscreen > 1 && stplyr == &players[thirddisplayplayer]) && !camera3.chase)
+		|| ((splitscreen > 2 && stplyr == &players[fourthdisplayplayer]) && !camera4.chase))
+		K_drawKartFirstPerson();
+
+	// Draw full screen stuff that turns off the rest of the HUD
+	if (mapreset && stplyr == &players[displayplayer])
+	{
+		K_drawChallengerScreen();
+		return;
+	}
+
+	battlefullscreen = ((G_BattleGametype())
+		&& (stplyr->exiting
+		|| (stplyr->kartstuff[k_bumper] <= 0
+		&& stplyr->kartstuff[k_comebacktimer]
+		&& comeback
+		&& stplyr->playerstate == PST_LIVE)));
+
+	if (!battlefullscreen || splitscreen)
+	{
+		// Draw the CHECK indicator before the other items, so it's overlapped by everything else
+		if (cv_kartcheck.value && !splitscreen && !players[displayplayer].exiting)
+			K_drawKartPlayerCheck();
+
+		// Draw WANTED status
+		if (G_BattleGametype())
+		{
+#ifdef HAVE_BLUA
+			if (LUA_HudEnabled(hud_wanted))
+#endif
+				K_drawKartWanted();
+		}
+
+		if (cv_kartminimap.value && !titledemo)
+		{
+#ifdef HAVE_BLUA
+			if (LUA_HudEnabled(hud_minimap))
+#endif
+				K_drawKartMinimap();
+		}
+	}
+
+	if (battlefullscreen)
+	{
+		K_drawBattleFullscreen();
+		return;
+	}
+
+	// Draw the item window
+#ifdef HAVE_BLUA
+	if (LUA_HudEnabled(hud_item))
+#endif
+		K_drawKartItem();
+
+	// If not splitscreen, draw...
+	if (!splitscreen && !titledemo)
+	{
+		// Draw the timestamp
+#ifdef HAVE_BLUA
+		if (LUA_HudEnabled(hud_time))
+#endif
+			K_drawKartTimestamp(stplyr->realtime, TIME_X, TIME_Y, gamemap, 0);
+
+		if (!modeattacking)
+		{
+			// The top-four faces on the left
+			/*#ifdef HAVE_BLUA
+			if (LUA_HudEnabled(hud_minirankings))
+			#endif*/
+				isfreeplay = K_drawKartPositionFaces();
+		}
+	}
+
+	if (!stplyr->spectator) // Bottom of the screen elements, don't need in spectate mode
+	{
+		if (G_RaceGametype()) // Race-only elements
+		{
+			if (!titledemo)
+			{
+				// Draw the lap counter
+#ifdef HAVE_BLUA
+				if (LUA_HudEnabled(hud_gametypeinfo))
+#endif
+					K_drawKartLaps();
+
+				if (!splitscreen)
+				{
+					// Draw the speedometer
+					// TODO: Make a better speedometer.
+#ifdef HAVE_BLUA
+				if (LUA_HudEnabled(hud_speedometer))
+#endif
+					K_drawKartSpeedometer();
+				}
+			}
+
+			if (isfreeplay)
+				;
+			else if (!modeattacking)
+			{
+				// Draw the numerical position
+#ifdef HAVE_BLUA
+				if (LUA_HudEnabled(hud_position))
+#endif
+					K_DrawKartPositionNum(stplyr->kartstuff[k_position]);
+			}
+			else //if (!(demoplayback && hu_showscores))
+			{
+				// Draw the input UI
+#ifdef HAVE_BLUA
+				if (LUA_HudEnabled(hud_position))
+#endif
+					K_drawInput();
+			}
+		}
+		else if (G_BattleGametype()) // Battle-only
+		{
+			// Draw the hits left!
+#ifdef HAVE_BLUA
+			if (LUA_HudEnabled(hud_gametypeinfo))
+#endif
+				K_drawKartBumpersOrKarma();
+		}
+	}
+
+	// Draw the countdowns after everything else.
+	if (leveltime >= starttime-(3*TICRATE)
+		&& leveltime < starttime+TICRATE)
+		K_drawKartStartCountdown();
+	else if (countdown && (!splitscreen || !stplyr->exiting))
+	{
+		char *countstr = va("%d", countdown/TICRATE);
+
+		if (splitscreen > 1)
+			V_DrawCenteredString(BASEVIDWIDTH/4, LAPS_Y+1, K_calcSplitFlags(0), countstr);
+		else
+		{
+			INT32 karlen = strlen(countstr)*6; // half of 12
+			V_DrawKartString((BASEVIDWIDTH/2)-karlen, LAPS_Y+3, K_calcSplitFlags(0), countstr);
+		}
+	}
+
+	// Race overlays
+	if (G_RaceGametype())
+	{
+		if (stplyr->exiting)
+			K_drawKartFinish();
+		else if (stplyr->kartstuff[k_lapanimation] && !splitscreen)
+			K_drawLapStartAnim();
+	}
+
+	if (modeattacking) // everything after here is MP and debug only
+		return;
+
+	if (G_BattleGametype() && !splitscreen && (stplyr->kartstuff[k_yougotem] % 2)) // * YOU GOT EM *
+		V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem);
+
+	// Draw FREE PLAY.
+	if (isfreeplay && !stplyr->spectator && timeinmap > 113)
+	{
+#ifdef HAVE_BLUA
+		if (LUA_HudEnabled(hud_freeplay))
+#endif
+			K_drawKartFreePlay(leveltime);
+	}
+
+	if (cv_kartdebugdistribution.value)
+		K_drawDistributionDebugger();
+
+	if (cv_kartdebugcheckpoint.value)
+		K_drawCheckpointDebugger();
+
+	if (cv_kartdebugnodes.value)
+	{
+		UINT8 p;
+		for (p = 0; p < MAXPLAYERS; p++)
+			V_DrawString(8, 64+(8*p), V_YELLOWMAP, va("%d - %d (%dl)", p, playernode[p], players[p].cmd.latency));
+	}
+}
+
+//}
diff --git a/src/k_kart.h b/src/k_kart.h
index 17652b9d..dc37956a 100644
--- a/src/k_kart.h
+++ b/src/k_kart.h
@@ -1,83 +1,83 @@
-// SONIC ROBO BLAST 2 KART ~ ZarroTsu
-//-----------------------------------------------------------------------------
-/// \file  k_kart.h
-/// \brief SRB2kart stuff.
-
-#ifndef __K_KART__
-#define __K_KART__
-
-#include "doomdef.h"
-#include "d_player.h" // Need for player_t
-
-#define KART_FULLTURN 800
-
-UINT8 colortranslations[MAXSKINCOLORS][16];
-extern const char *KartColor_Names[MAXSKINCOLORS];
-extern const UINT8 KartColor_Opposite[MAXSKINCOLORS*2];
-void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor);
-void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color);
-UINT8 K_GetKartColorByName(const char *name);
-
-void K_RegisterKartStuff(void);
-
-boolean K_IsPlayerLosing(player_t *player);
-boolean K_IsPlayerWanted(player_t *player);
-void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid);
-void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master);
-void K_RespawnChecker(player_t *player);
-void K_KartMoveAnimation(player_t *player);
-void K_KartPlayerHUDUpdate(player_t *player);
-void K_KartPlayerThink(player_t *player, ticcmd_t *cmd);
-void K_KartPlayerAfterThink(player_t *player);
-void K_DoInstashield(player_t *player);
-void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount);
-void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, mobj_t *inflictor, boolean trapitem);
-void K_SquishPlayer(player_t *player, mobj_t *source, mobj_t *inflictor);
-void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor);
-void K_StealBumper(player_t *player, player_t *victim, boolean force);
-void K_SpawnKartExplosion(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle, boolean spawncenter, boolean ghostit, mobj_t *source);
-void K_SpawnMineExplosion(mobj_t *source, UINT8 color);
-void K_SpawnBoostTrail(player_t *player);
-void K_SpawnSparkleTrail(mobj_t *mo);
-void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent);
-void K_DriftDustHandling(mobj_t *spawner);
-void K_DoSneaker(player_t *player, INT32 type);
-void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound);
-void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source);
-void K_UpdateHnextList(player_t *player, boolean clean);
-void K_DropHnextList(player_t *player);
-void K_RepairOrbitChain(mobj_t *orbit);
-player_t *K_FindJawzTarget(mobj_t *actor, player_t *source);
-boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y);
-INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue);
-INT32 K_GetKartDriftSparkValue(player_t *player);
-void K_KartUpdatePosition(player_t *player);
-void K_DropItems(player_t *player);
-void K_StripItems(player_t *player);
-void K_StripOther(player_t *player);
-void K_MomentumToFacing(player_t *player);
-fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower);
-fixed_t K_GetKartAccel(player_t *player);
-UINT16 K_GetKartFlashing(player_t *player);
-fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove);
-void K_MoveKartPlayer(player_t *player, boolean onground);
-void K_CalculateBattleWanted(void);
-void K_CheckBumpers(void);
-void K_CheckSpectateStatus(void);
-
-// sound stuff for lua
-void K_PlayAttackTaunt(mobj_t *source);
-void K_PlayBoostTaunt(mobj_t *source);
-void K_PlayOvertakeSound(mobj_t *source);
-void K_PlayHitEmSound(mobj_t *source);
-void K_PlayPowerGloatSound(mobj_t *source);
-
-const char *K_GetItemPatch(UINT8 item, boolean tiny);
-INT32 K_calcSplitFlags(INT32 snapflags);
-void K_LoadKartHUDGraphics(void);
-void K_drawKartHUD(void);
-void K_drawKartFreePlay(UINT32 flashtime);
-void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode);
-
-// =========================================================================
-#endif  // __K_KART__
+// SONIC ROBO BLAST 2 KART ~ ZarroTsu
+//-----------------------------------------------------------------------------
+/// \file  k_kart.h
+/// \brief SRB2kart stuff.
+
+#ifndef __K_KART__
+#define __K_KART__
+
+#include "doomdef.h"
+#include "d_player.h" // Need for player_t
+
+#define KART_FULLTURN 800
+
+UINT8 colortranslations[MAXSKINCOLORS][16];
+extern const char *KartColor_Names[MAXSKINCOLORS];
+extern const UINT8 KartColor_Opposite[MAXSKINCOLORS*2];
+void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor);
+void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color);
+UINT8 K_GetKartColorByName(const char *name);
+
+void K_RegisterKartStuff(void);
+
+boolean K_IsPlayerLosing(player_t *player);
+boolean K_IsPlayerWanted(player_t *player);
+void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid);
+void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master);
+void K_RespawnChecker(player_t *player);
+void K_KartMoveAnimation(player_t *player);
+void K_KartPlayerHUDUpdate(player_t *player);
+void K_KartPlayerThink(player_t *player, ticcmd_t *cmd);
+void K_KartPlayerAfterThink(player_t *player);
+void K_DoInstashield(player_t *player);
+void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount);
+void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, mobj_t *inflictor, boolean trapitem);
+void K_SquishPlayer(player_t *player, mobj_t *source, mobj_t *inflictor);
+void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor);
+void K_StealBumper(player_t *player, player_t *victim, boolean force);
+void K_SpawnKartExplosion(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle, boolean spawncenter, boolean ghostit, mobj_t *source);
+void K_SpawnMineExplosion(mobj_t *source, UINT8 color);
+void K_SpawnBoostTrail(player_t *player);
+void K_SpawnSparkleTrail(mobj_t *mo);
+void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent);
+void K_DriftDustHandling(mobj_t *spawner);
+void K_DoSneaker(player_t *player, INT32 type);
+void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound);
+void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source);
+void K_UpdateHnextList(player_t *player, boolean clean);
+void K_DropHnextList(player_t *player);
+void K_RepairOrbitChain(mobj_t *orbit);
+player_t *K_FindJawzTarget(mobj_t *actor, player_t *source);
+boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y);
+INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue);
+INT32 K_GetKartDriftSparkValue(player_t *player);
+void K_KartUpdatePosition(player_t *player);
+void K_DropItems(player_t *player);
+void K_StripItems(player_t *player);
+void K_StripOther(player_t *player);
+void K_MomentumToFacing(player_t *player);
+fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower);
+fixed_t K_GetKartAccel(player_t *player);
+UINT16 K_GetKartFlashing(player_t *player);
+fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove);
+void K_MoveKartPlayer(player_t *player, boolean onground);
+void K_CalculateBattleWanted(void);
+void K_CheckBumpers(void);
+void K_CheckSpectateStatus(void);
+
+// sound stuff for lua
+void K_PlayAttackTaunt(mobj_t *source);
+void K_PlayBoostTaunt(mobj_t *source);
+void K_PlayOvertakeSound(mobj_t *source);
+void K_PlayHitEmSound(mobj_t *source);
+void K_PlayPowerGloatSound(mobj_t *source);
+
+const char *K_GetItemPatch(UINT8 item, boolean tiny);
+INT32 K_calcSplitFlags(INT32 snapflags);
+void K_LoadKartHUDGraphics(void);
+void K_drawKartHUD(void);
+void K_drawKartFreePlay(UINT32 flashtime);
+void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode);
+
+// =========================================================================
+#endif  // __K_KART__

From dc5024f94cf0dd8439d2dc8c4ab342b2633b37f2 Mon Sep 17 00:00:00 2001
From: Alam Arias <Alam.GBC@gmail.com>
Date: Tue, 5 Feb 2019 15:42:37 -0500
Subject: [PATCH 72/73] remove whitespaces

---
 assets/HISTORY.txt |  2 +-
 src/f_finale.c     |  2 +-
 src/g_input.c      |  4 ++--
 src/hu_stuff.c     |  4 ++--
 src/info.c         | 30 +++++++++++++++---------------
 src/info.h         |  8 ++++----
 src/p_user.c       |  1 -
 src/sdl/i_video.c  |  2 +-
 src/v_video.c      |  2 +-
 9 files changed, 27 insertions(+), 28 deletions(-)

diff --git a/assets/HISTORY.txt b/assets/HISTORY.txt
index dfa06f2c..5c7fe3b0 100644
--- a/assets/HISTORY.txt
+++ b/assets/HISTORY.txt
@@ -129,7 +129,7 @@ Oni: 	Sev, Sal, and toast were the most unexpected things to ever happen to this
 
 [Oni wiping sweat off his brow] Things only got more drastically revamped… very very rapidly.
 
- 	The Mario aesthetic was entirely tossed out, as Sal was willing to work with me night and day on redoing most of everything about items… and then sounds. My power level for sprites massively jumped during TD development, so I decided to take it upon myself to do almost everything. They’re such friendly and cooperative coders that I can’t help but push a little harder than I used to (I was WAY lazier before they got here) to keep up.
+	The Mario aesthetic was entirely tossed out, as Sal was willing to work with me night and day on redoing most of everything about items… and then sounds. My power level for sprites massively jumped during TD development, so I decided to take it upon myself to do almost everything. They’re such friendly and cooperative coders that I can’t help but push a little harder than I used to (I was WAY lazier before they got here) to keep up.
 
 
 
diff --git a/src/f_finale.c b/src/f_finale.c
index 0fe13a8c..b863ea74 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -181,7 +181,7 @@ static void F_SkyScroll(INT32 scrollspeed)
 		{
 			V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTOLEFT, pat, NULL);
 			x += SHORT(pat->width);
-		} 
+		}
 
 		x = -anim2;
 		y = BASEVIDHEIGHT - SHORT(pat2->height);
diff --git a/src/g_input.c b/src/g_input.c
index 871c1e60..cab35830 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -37,7 +37,7 @@ INT32 mlooky; // like mousey but with a custom sensitivity for mlook
 INT32 mouse2x, mouse2y, mlook2y;
 
 // joystick values are repeated
-INT32 joyxmove[JOYAXISSET], joyymove[JOYAXISSET], joy2xmove[JOYAXISSET], joy2ymove[JOYAXISSET], 
+INT32 joyxmove[JOYAXISSET], joyymove[JOYAXISSET], joy2xmove[JOYAXISSET], joy2ymove[JOYAXISSET],
 joy3xmove[JOYAXISSET], joy3ymove[JOYAXISSET], joy4xmove[JOYAXISSET], joy4ymove[JOYAXISSET];
 
 // current state of the keys: true if pushed
@@ -1308,7 +1308,7 @@ void G_Controldefault(UINT8 player)
 		gamecontrol[gc_brake      ][1] = KEY_JOY1+1; // B
 		gamecontrol[gc_fire       ][1] = KEY_JOY1+4; // LB
 		gamecontrol[gc_drift      ][1] = KEY_JOY1+5; // RB
-		
+
 		// Extra controls
 		gamecontrol[gc_pause      ][0] = KEY_PAUSE;
 		gamecontrol[gc_console    ][0] = KEY_CONSOLE;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index a308dfc2..3d8f2383 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1185,8 +1185,8 @@ boolean HU_Responder(event_t *ev)
 			return true;
 
 		// Ignore non-keyboard keys, except when the talk key is bound
-		if (ev->data1 >= KEY_MOUSE1 
-		&& (ev->data1 != gamecontrol[gc_talkkey][0] 
+		if (ev->data1 >= KEY_MOUSE1
+		&& (ev->data1 != gamecontrol[gc_talkkey][0]
 		&& ev->data1 != gamecontrol[gc_talkkey][1]))
 			return false;
 
diff --git a/src/info.c b/src/info.c
index ccdfa3cf..5701f1c9 100644
--- a/src/info.c
+++ b/src/info.c
@@ -3297,39 +3297,39 @@ state_t states[NUMSTATES] =
 	{SPR_CNDL, 1|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_BLUEFIRE3}, // S_BLUEFIRE2
 	{SPR_CNDL, 2|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_BLUEFIRE4}, // S_BLUEFIRE3
 	{SPR_CNDL, 3|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_BLUEFIRE1}, // S_BLUEFIRE4
- 
+
 	{SPR_CNDL, 4|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_GREENFIRE2}, // S_GREENFIRE1
 	{SPR_CNDL, 5|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_GREENFIRE3}, // S_GREENFIRE2
 	{SPR_CNDL, 6|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_GREENFIRE4}, // S_GREENFIRE3
 	{SPR_CNDL, 7|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_GREENFIRE1}, // S_GREENFIRE4
- 
+
 	{SPR_CHES, 0, -1, {NULL}, 0, 0, S_NULL}, // S_REGALCHEST
 	{SPR_CHIM, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_CHIMERASTATUE
 	{SPR_DRGN, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_DRAGONSTATUE
 	{SPR_LZMN, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LIZARDMANSTATUE
 	{SPR_PGSS, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_PEGASUSSTATUE
- 
+
 	{SPR_ZTCH,   FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_ZELDAFIRE2}, // S_ZELDAFIRE1
 	{SPR_ZTCH, 1|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_ZELDAFIRE3}, // S_ZELDAFIRE2
 	{SPR_ZTCH, 2|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_ZELDAFIRE4}, // S_ZELDAFIRE3
 	{SPR_ZTCH, 3|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_ZELDAFIRE1}, // S_ZELDAFIRE4
- 
+
 	{SPR_DOCH, 0, -1, {NULL}, 0, 0, S_NULL}, // S_GANBARETHING
 	{SPR_DUCK, 0, -1, {NULL}, 0, 0, S_NULL}, // S_GANBAREDUCK
 	{SPR_GTRE, 0, -1, {NULL}, 0, 0, S_NULL}, // S_GANBARETREE
- 
+
 	{SPR_MKMA, 1, 2, {A_Look}, (256<<16)|1, 0, S_MONOIDLE}, // S_MONOIDLE
 	{SPR_MKMA, 0, 3, {A_Chase}, 3, 0, S_MONOCHASE2}, // S_MONOCHASE1
 	{SPR_MKMA, 1, 3, {A_Chase}, 3, 0, S_MONOCHASE3}, // S_MONOCHASE2
 	{SPR_MKMA, 2, 3, {A_Chase}, 3, 0, S_MONOCHASE4}, // S_MONOCHASE3
 	{SPR_MKMA, 3, 3, {A_Chase}, 3, 0, S_MONOCHASE1}, // S_MONOCHASE4
 	{SPR_MKMP, 0, 24, {A_Pain}, 3, 0, S_MONOIDLE}, // S_MONOPAIN
- 
+
 	{SPR_RTCH,   FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_REDZELDAFIRE2}, // S_REDZELDAFIRE1
 	{SPR_RTCH, 1|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_REDZELDAFIRE3}, // S_REDZELDAFIRE2
 	{SPR_RTCH, 2|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_REDZELDAFIRE4}, // S_REDZELDAFIRE3
 	{SPR_RTCH, 3|FF_FULLBRIGHT|FF_TRANS50, 3, {NULL}, 0, 0, S_REDZELDAFIRE1}, // S_REDZELDAFIRE4
- 
+
 	{SPR_BOWL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BOWLINGPIN
 	{SPR_BOWH, 0, 4, {A_BunnyHop}, 5, 20, S_BOWLINGHIT2}, // S_BOWLINGHIT1
 	{SPR_BOWH, 1, 2, {NULL}, 0, 0, S_BOWLINGHIT3}, // S_BOWLINGHIT2
@@ -3341,7 +3341,7 @@ state_t states[NUMSTATES] =
 	{SPR_TOAH, 1, 3, {NULL}, 0, 0, S_TOADHIT3},  // S_TOADHIT2
 	{SPR_TOAH, 2, 3, {NULL}, 0, 0, S_TOADHIT4},  // S_TOADHIT3
 	{SPR_TOAH, 3, 3, {NULL}, 0, 0, S_EBARREL18}, // S_TOADHIT4
- 
+
 	{SPR_BRRL, 0, 1, {A_Look}, (96<<16)|1, 0, S_EBARRELIDLE}, // S_EBARRELIDLE
 	{SPR_BRRR,  0, 4, {NULL}, 0, 0, S_EBARREL2},  // S_EBARREL1
 	{SPR_BRRR,  1, 4, {NULL}, 0, 0, S_EBARREL3},  // S_EBARREL2
@@ -3361,30 +3361,30 @@ state_t states[NUMSTATES] =
 	{SPR_BRRR, 15, 4, {NULL}, 0, 0, S_EBARREL17}, // S_EBARREL16
 	{SPR_BRRR, 16, 4, {NULL}, 0, 0, S_EBARREL18}, // S_EBARREL17
 	{SPR_BRRR, 16, 0, {A_MineExplode}, MT_MINEEXPLOSION, 0, S_NULL}, // S_EBARREL18
- 
+
 	{SPR_HRSE, 0, 230, {A_PlaySeeSound}, 0, 0, S_MERRYHORSE}, // S_MERRYHORSE
- 
+
 	{SPR_BFRT, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BLUEFRUIT
 	{SPR_OFRT, 0, -1, {NULL}, 0, 0, S_NULL}, // S_ORANGEFRUIT
 	{SPR_RFRT, 0, -1, {NULL}, 0, 0, S_NULL}, // S_REDFRUIT
 	{SPR_PFRT, 0, -1, {NULL}, 0, 0, S_NULL}, // S_PINKFRUIT
- 
+
 	{SPR_ASPK, 0, 50, {A_PlayAttackSound}, 0, 0, S_ADVENTURESPIKEA2}, // S_ADVENTURESPIKEA1
 	{SPR_ASPK, 0, 50, {A_BunnyHop},       20, 0, S_ADVENTURESPIKEA1}, // S_ADVENTURESPIKEA2
 	{SPR_ASPK, 0, 50, {A_PlayAttackSound}, 0, 0, S_ADVENTURESPIKEB2}, // S_ADVENTURESPIKEB1
 	{SPR_ASPK, 0, 35, {A_BunnyHop},       15, 0, S_ADVENTURESPIKEB1}, // S_ADVENTURESPIKEB2
 	{SPR_ASPK, 0, 50, {A_PlayAttackSound}, 0, 0, S_ADVENTURESPIKEC2}, // S_ADVENTURESPIKEC1
 	{SPR_ASPK, 0, 65, {A_BunnyHop},       25, 0, S_ADVENTURESPIKEC1}, // S_ADVENTURESPIKEC1
- 
+
 	{SPR_HBST,   FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_BOOSTPROMPT2}, // S_BOOSTPROMPT1
 	{SPR_HBST, 1|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_BOOSTPROMPT1}, // S_BOOSTPROMPT2
- 
+
 	{SPR_HBSF,   FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_BOOSTOFF2}, // S_BOOSTOFF1
 	{SPR_HBSF, 1|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_BOOSTOFF1}, // S_BOOSTOFF2
- 
+
 	{SPR_HBSO,   FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_BOOSTON2}, // S_BOOSTON1
 	{SPR_HBSO, 1|FF_FULLBRIGHT, 5, {NULL}, 0, 0, S_BOOSTON1}, // S_BOOSTON2
- 
+
 	{SPR_WBLZ, 0, -1, {NULL}, 0, 0, S_NULL}, // S_LIZARDMAN
 	{SPR_WBLN, 0, -1, {NULL}, 0, 0, S_NULL}, // S_LIONMAN
 
diff --git a/src/info.h b/src/info.h
index 2ebd11fe..157c72e1 100644
--- a/src/info.h
+++ b/src/info.h
@@ -608,7 +608,7 @@ typedef enum sprite
 	// Kart Items
 	SPR_RSHE, // Rocket sneaker
 	SPR_FITM, // Eggman Monitor
- 	SPR_BANA, // Banana Peel
+	SPR_BANA, // Banana Peel
 	SPR_ORBN, // Orbinaut
 	SPR_JAWZ, // Jawz
 	SPR_SSMN, // SS Mine
@@ -3871,7 +3871,7 @@ typedef enum state
 	S_GARU1,
 	S_GARU2,
 	S_GARU3,
-	S_TGARU0,	
+	S_TGARU0,
 	S_TGARU1,
 	S_TGARU2,
 	S_TGARU3,	// Wind attack used by Roaming Shadows on Players.
@@ -4612,14 +4612,14 @@ typedef enum mobj_type
 	MT_EGGMANITEM_SHIELD,
 
 	MT_BANANA, // Banana Stuff
-	MT_BANANA_SHIELD, 
+	MT_BANANA_SHIELD,
 
 	MT_ORBINAUT, // Orbinaut stuff
 	MT_ORBINAUT_SHIELD,
 
 	MT_JAWZ, // Jawz stuff
 	MT_JAWZ_DUD,
-	MT_JAWZ_SHIELD, 
+	MT_JAWZ_SHIELD,
 
 	MT_PLAYERRETICULE, // Jawz reticule
 
diff --git a/src/p_user.c b/src/p_user.c
index 3ebe343c..62f75900 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -9810,4 +9810,3 @@ void P_PlayerAfterThink(player_t *player)
 
 	K_KartPlayerAfterThink(player);
 }
-
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index f63e9847..2a77604d 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1229,7 +1229,7 @@ void I_GetEvent(void)
 				// update the menu
 				if (currentMenu == &OP_JoystickSetDef)
 					M_SetupJoystickMenu(0);
-			 	break;
+				break;
 			case SDL_QUIT:
 				I_Quit();
 				M_QuitResponse('y');
diff --git a/src/v_video.c b/src/v_video.c
index dd95efe3..115e081e 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -2305,7 +2305,7 @@ INT32 V_ThinStringWidth(const char *string, INT32 option)
 		}
 	}
 
-	
+
 	return w;
 }
 

From 1c3fe3ce83bc0af01265a3950b04394b74a0ea47 Mon Sep 17 00:00:00 2001
From: Sally Cochenour <tehrealsalt@gmail.com>
Date: Wed, 6 Feb 2019 01:31:42 -0500
Subject: [PATCH 73/73] Kill me

---
 src/k_kart.c | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/src/k_kart.c b/src/k_kart.c
index ae200f74..49fd54e6 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -1746,11 +1746,17 @@ fixed_t K_GetKartAccel(player_t *player)
 
 UINT16 K_GetKartFlashing(player_t *player)
 {
-    UINT16 tics = flashingtics;
-    if (G_BattleGametype())
-        tics *= 2;
-    tics += (flashingtics/8) * (player->kartspeed);
-    return tics;
+	UINT16 tics = flashingtics;
+
+	if (!player)
+		return tics;
+
+	if (G_BattleGametype())
+		tics *= 2;
+
+	tics += (flashingtics/8) * (player->kartspeed);
+
+	return tics;
 }
 
 fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove)