From 198685582f8fb463b85578420877733c9dce01ff Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Tue, 28 Nov 2017 01:13:23 -0500
Subject: [PATCH] Previous EXE: The Better Edition

- Improve balloon-based items. Much more distinguished differences
between the different ranks
- Reverted Feather strafing. You can now just... properly move in the
air, if you're using a Feather or a Bounce Pad.
- Bounce pads are now stronger while using speed items.
- Fixed the long standing bug about offroad & ziplines working when
you're on FOFs above them. This is a tricky subject, so please report
any issues you may come across related to this.
- Shells should no longer travel to Mars, nor will they  when using
bounce pads. They should now do a gentle hop.
- :WIP: new MP Player Setup screen, now shows character stats. Will
later feature the list of characters in a row of icons, and a backported
2.2 color selector with toast's permission
- Renamed "Match" to "Battle" :p
- Yet MORE minor cases where kartspeed & kartweight were being read as
fixed_t when they're freakin' UINT8's, people!
---
 src/d_clisrv.c      |   8 ++--
 src/k_kart.c        | 109 +++++++++++++++++++++-----------------------
 src/lua_playerlib.c |   4 +-
 src/m_menu.c        |  78 +++++++++++++++++++++----------
 src/p_floor.c       |  27 -----------
 src/p_local.h       |   1 +
 src/p_mobj.c        |  12 +++--
 src/p_spec.c        |  43 ++++++++---------
 src/p_user.c        |  34 ++++++++++++++
 9 files changed, 176 insertions(+), 140 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 7e1cfc38..dc844451 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -534,8 +534,8 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 	// Just in case Lua does something like
 	// modify these at runtime
 	// SRB2kart
-	rsp->kartspeed = (fixed_t)LONG(players[i].kartspeed);
-	rsp->kartweight = (fixed_t)LONG(players[i].kartweight);
+	rsp->kartspeed = (UINT8)LONG(players[i].kartspeed);
+	rsp->kartweight = (UINT8)LONG(players[i].kartweight);
 	//
 	rsp->normalspeed = (fixed_t)LONG(players[i].normalspeed);
 	rsp->runspeed = (fixed_t)LONG(players[i].runspeed);
@@ -666,8 +666,8 @@ static void resynch_read_player(resynch_pak *rsp)
 	players[i].skin = LONG(rsp->skin);
 	// Just in case Lua does something like
 	// modify these at runtime
-	players[i].kartspeed = (fixed_t)LONG(rsp->kartspeed);
-	players[i].kartweight = (fixed_t)LONG(rsp->kartweight);
+	players[i].kartspeed = (UINT8)LONG(rsp->kartspeed);
+	players[i].kartweight = (UINT8)LONG(rsp->kartweight);
 
 	players[i].normalspeed = (fixed_t)LONG(rsp->normalspeed);
 	players[i].runspeed = (fixed_t)LONG(rsp->runspeed);
diff --git a/src/k_kart.c b/src/k_kart.c
index 5e07e103..57e38bf8 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -721,26 +721,26 @@ static INT32 K_KartItemOddsDistance_Battle[NUMKARTITEMS][5] =
 {
 				//P-Odds	 0  1  2  3  4
 				/*Magnet*/ { 0, 0, 0, 0, 0 }, // Magnet
-				   /*Boo*/ { 2, 2, 2, 1, 0 }, // Boo
-			  /*Mushroom*/ { 2, 2, 2, 1, 0 }, // Mushroom
+				   /*Boo*/ { 1, 2, 3, 1, 0 }, // Boo
+			  /*Mushroom*/ { 3, 2, 3, 1, 0 }, // Mushroom
 	   /*Triple Mushroom*/ { 0, 0, 0, 0, 0 }, // Triple Mushroom
-		 /*Mega Mushroom*/ { 2, 1, 0, 0, 0 }, // Mega Mushroom
+		 /*Mega Mushroom*/ { 3, 1, 0, 0, 0 }, // Mega Mushroom
 		 /*Gold Mushroom*/ { 0, 0, 0, 0, 0 }, // Gold Mushroom
-				  /*Star*/ { 2, 1, 0, 0, 0 }, // Star
+				  /*Star*/ { 3, 1, 0, 0, 0 }, // Star
 
-		 /*Triple Banana*/ { 1, 1, 1, 1, 0 }, // Triple Banana
-			 /*Fake Item*/ { 0, 1, 2, 4, 6 }, // Fake Item
-				/*Banana*/ { 0, 1, 3, 4, 6 }, // Banana
-		   /*Green Shell*/ { 0, 1, 3, 4, 6 }, // Green Shell
-			 /*Red Shell*/ { 1, 2, 2, 1, 0 }, // Red Shell
-	/*Triple Green Shell*/ { 1, 2, 2, 1, 0 }, // Triple Green Shell
-			   /*Bob-omb*/ { 3, 2, 1, 1, 0 }, // Bob-omb
+		 /*Triple Banana*/ { 0, 2, 1, 1, 0 }, // Triple Banana
+			 /*Fake Item*/ { 0, 0, 2, 5, 8 }, // Fake Item
+				/*Banana*/ { 0, 0, 3, 4, 5 }, // Banana
+		   /*Green Shell*/ { 0, 0, 3, 4, 5 }, // Green Shell
+			 /*Red Shell*/ { 0, 3, 1, 1, 0 }, // Red Shell
+	/*Triple Green Shell*/ { 0, 3, 1, 1, 0 }, // Triple Green Shell
+			   /*Bob-omb*/ { 3, 2, 1, 0, 0 }, // Bob-omb
 			/*Blue Shell*/ { 0, 0, 0, 0, 0 }, // Blue Shell
-		   /*Fire Flower*/ { 3, 2, 1, 1, 0 }, // Fire Flower
-	  /*Triple Red Shell*/ { 2, 1, 0, 0, 0 }, // Triple Red Shell
+		   /*Fire Flower*/ { 3, 2, 1, 0, 0 }, // Fire Flower
+	  /*Triple Red Shell*/ { 4, 1, 0, 0, 0 }, // Triple Red Shell
 			 /*Lightning*/ { 0, 0, 0, 0, 0 }, // Lightning
 
-			   /*Feather*/ { 0, 1, 1, 1, 2 }  // Feather
+			   /*Feather*/ { 0, 1, 2, 2, 2 }  // Feather
 };
 
 /**	\brief	Item Roulette for Kart
@@ -965,11 +965,11 @@ static void K_KartItemRouletteByPosition(player_t *player, ticcmd_t *cmd)
 
 //}
 
-static INT32 K_KartGetItemOdds(INT32 pos, INT32 itemnum, boolean battle)
+static INT32 K_KartGetItemOdds(INT32 pos, INT32 itemnum)
 {
 	INT32 newodds;
 
-	if (battle)
+	if (gametype == GT_MATCH)
 		newodds = K_KartItemOddsDistance_Battle[itemnum-1][pos];
 	else
 		newodds = K_KartItemOddsDistance_Retro[itemnum-1][pos];
@@ -995,10 +995,6 @@ static void K_KartItemRouletteByDistance(player_t *player, ticcmd_t *cmd)
 	INT32 chance = 0, numchoices = 0;
 	INT32 distvar = (64*14);
 	INT32 avgballoon = 0;
-	boolean battle = false;
-
-	if (gametype == GT_MATCH)
-		battle = true;
 
 	// This makes the roulette cycle through items - if this is 0, you shouldn't be here.
 	if (player->kartstuff[k_itemroulette])
@@ -1030,10 +1026,7 @@ static void K_KartItemRouletteByDistance(player_t *player, ticcmd_t *cmd)
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		if (playeringame[i] && !players[i].spectator)
-		{
 			pingame++;
-			continue;
-		}
 		if (players[i].exiting)
 			pexiting++;
 		if (players[i].kartstuff[k_balloon] > 0)
@@ -1055,12 +1048,6 @@ static void K_KartItemRouletteByDistance(player_t *player, ticcmd_t *cmd)
 
 	player->kartstuff[k_itemclose] = 0;	// Reset the item window closer.
 
-	if (cv_kartfrantic.value) // Stupid items
-	{
-		pdis = (13*pdis/12); // multiply...
-		pdis += distvar; // set everyone back another place...
-	}
-
 	if (gametype == GT_MATCH || gametype == GT_TEAMMATCH || gametype == GT_CTF) // Battle Mode
 	{
 		useodds = (player->kartstuff[k_balloon]-avgballoon)+2; // 0 is two balloons below average, 2 is average, 4 is two balloons above average
@@ -1071,6 +1058,12 @@ static void K_KartItemRouletteByDistance(player_t *player, ticcmd_t *cmd)
 	}
 	else
 	{
+		if (cv_kartfrantic.value) // Frantic items
+		{
+			pdis = (13*pdis/12); // make the distances between everyone artifically higher...
+			pdis += distvar; // and set everyone back another place!
+		}
+
 		if (pingame == 1)				useodds = 0; // Record Attack, or just alone
 		else if (pdis <= distvar *  0)	useodds = 1; // (64*14) *  0 =     0
 		else if (pdis <= distvar *  1)	useodds = 2; // (64*14) *  1 =   896
@@ -1083,7 +1076,7 @@ static void K_KartItemRouletteByDistance(player_t *player, ticcmd_t *cmd)
 	}
 
 #define SETITEMRESULT(pos, itemnum) \
-	for (chance = 0; chance < K_KartGetItemOdds(pos, itemnum, battle); chance++) \
+	for (chance = 0; chance < K_KartGetItemOdds(pos, itemnum); chance++) \
 		spawnchance[numchoices++] = itemnum
 
 	// Check the game type to differentiate odds.
@@ -1239,18 +1232,25 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce)
 
 	\return	boolean
 */
-static boolean K_CheckOffroadCollide(mobj_t *mo)
+static UINT8 K_CheckOffroadCollide(mobj_t *mo, sector_t *sec)
 {
+	UINT8 i;
+	sector_t *sec2;
+
 	I_Assert(mo != NULL);
 	I_Assert(!P_MobjWasRemoved(mo));
 
-	if (P_IsObjectOnGround(mo)
-	&& (GETSECSPECIAL(mo->subsector->sector->special, 1) == 2
-	|| GETSECSPECIAL(mo->subsector->sector->special, 1) == 3
-	|| GETSECSPECIAL(mo->subsector->sector->special, 1) == 4))
-		return true;
+	sec2 = P_ThingOnSpecial3DFloor(mo);
 
-	return false;
+	for (i = 2; i < 5; i++)
+	{
+		if ((sec2 && GETSECSPECIAL(sec2->special, 1) == i)
+			|| (P_IsObjectOnRealGround(mo, sec)
+			&& GETSECSPECIAL(sec->special, 1) == i))
+			return i;
+	}
+
+	return 0;
 }
 
 /**	\brief	Updates the Player's offroad value once per frame
@@ -1267,12 +1267,12 @@ static void K_UpdateOffroad(player_t *player)
 		player->mo->x + player->mo->momx*2, player->mo->y + player->mo->momy*2)->sector;
 
 	fixed_t offroadstrength = 0;
-	
-	if (GETSECSPECIAL(nextsector->special, 1) == 2)		// Weak Offroad
+
+	if (K_CheckOffroadCollide(player->mo, nextsector) == 2)	// Weak Offroad
 		offroadstrength = 1;
-	else if (GETSECSPECIAL(nextsector->special, 1) == 3)	// Mid Offroad
+	else if (K_CheckOffroadCollide(player->mo, nextsector) == 3)	// Mid Offroad
 		offroadstrength = 2;
-	else if (GETSECSPECIAL(nextsector->special, 1) == 4)	// Strong Offroad
+	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.
@@ -1280,8 +1280,9 @@ static void K_UpdateOffroad(player_t *player)
 	//	&& nextsector->special != 1024 && nextsector->special != 4864)
 	if (offroadstrength)
 	{
-		if (K_CheckOffroadCollide(player->mo) && player->kartstuff[k_offroad] == 0)
+		if (K_CheckOffroadCollide(player->mo, player->mo->subsector->sector) && player->kartstuff[k_offroad] == 0)
 			player->kartstuff[k_offroad] = 16;
+
 		if (player->kartstuff[k_offroad] > 0)
 		{
 			if (kartweight < 1) { kartweight = 1; } if (kartweight > 9) { kartweight = 9; } // Safety Net
@@ -2611,16 +2612,20 @@ void K_DoBouncePad(mobj_t *mo, fixed_t vertispeed)
 				thrust = 48<<FRACBITS;
 			if (thrust > 72<<FRACBITS)
 				thrust = 72<<FRACBITS;
+			if (mo->player->kartstuff[k_mushroomtimer])
+				thrust = FixedMul(thrust, 5*FRACUNIT/4);
+			else if (mo->player->kartstuff[k_startimer])
+				thrust = FixedMul(thrust, 9*FRACUNIT/8);
 			mo->momz = FixedMul(FINESINE(ANGLE_22h>>ANGLETOFINESHIFT), thrust);
 		}
 		else
 		{
 			thrust = P_AproxDistance(mo->momx,mo->momy);
-			if (thrust < 8<<FRACBITS)
+			if (thrust < 4<<FRACBITS)
+				thrust = 4<<FRACBITS;
+			if (thrust > 8<<FRACBITS)
 				thrust = 8<<FRACBITS;
-			if (thrust > 32<<FRACBITS)
-				thrust = 32<<FRACBITS;
-			mo->momz = FixedMul(FINESINE(ANGLE_11hh>>ANGLETOFINESHIFT), thrust);
+			mo->momz = FixedMul(FINESINE(ANGLE_22h>>ANGLETOFINESHIFT), thrust);
 		}
 	}
 	else
@@ -3587,18 +3592,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 
 	K_KartDrift(player, onground);
 
-	if (player->kartstuff[k_feather] & 2)
-	{
-		fixed_t strafe = 0;
-		fixed_t strength = FRACUNIT/4;
-		if (cmd->buttons & BT_DRIFTLEFT)
-			strafe -= 1;
-		if (cmd->buttons & BT_DRIFTRIGHT)
-			strafe += 1;
-		strength += FixedDiv(player->speed, K_GetKartSpeed(player, true));
-		P_Thrust(player->mo, player->mo->angle-ANGLE_90, strafe*strength);
-	}
-
 	// Quick Turning
 	// You can't turn your kart when you're not moving.
 	// So now it's time to burn some rubber!
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index a58892d3..4679c0c5 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -150,9 +150,9 @@ static int player_get(lua_State *L)
 		lua_pushinteger(L, plr->dashtime);
 	// SRB2kart
 	else if (fastcmp(field,"kartspeed"))
-		lua_pushfixed(L, plr->kartspeed);
+		lua_pushinteger(L, plr->kartspeed);
 	else if (fastcmp(field,"kartweight"))
-		lua_pushfixed(L, plr->kartweight);
+		lua_pushinteger(L, plr->kartweight);
 	//
 	else if (fastcmp(field,"normalspeed"))
 		lua_pushfixed(L, plr->normalspeed);
diff --git a/src/m_menu.c b/src/m_menu.c
index 14110f2b..74d7f912 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -375,7 +375,7 @@ consvar_t cv_chooseskin = {"chooseskin", DEFAULTSKIN, CV_HIDEN|CV_CALL, skins_co
 // When you add gametypes here, don't forget to update them in CV_AddValue!
 CV_PossibleValue_t gametype_cons_t[] =
 {
-	{GT_RACE, "Race"}, {GT_MATCH, "Match"},
+	{GT_RACE, "Race"}, {GT_MATCH, "Battle"},
 
 	/*						// SRB2kart
 	{GT_COOP, "Co-op"},
@@ -977,9 +977,9 @@ static menuitem_t MP_SplitServerMenu[] =
 
 static menuitem_t MP_PlayerSetupMenu[] =
 {
-	{IT_KEYHANDLER | IT_STRING,   NULL, "Your name",   M_HandleSetupMultiPlayer,   0},
-	{IT_KEYHANDLER | IT_STRING,   NULL, "Your color",  M_HandleSetupMultiPlayer,  16},
-	{IT_KEYHANDLER | IT_STRING,   NULL, "Your player", M_HandleSetupMultiPlayer,  96}, // Tails 01-18-2001
+	{IT_KEYHANDLER | IT_STRING,   NULL, "Name",      M_HandleSetupMultiPlayer,   0},
+	{IT_KEYHANDLER | IT_STRING,   NULL, "Character", M_HandleSetupMultiPlayer,  16}, // Tails 01-18-2001
+	{IT_KEYHANDLER | IT_STRING,   NULL, "Color",     M_HandleSetupMultiPlayer, 152},
 };
 
 // ------------------------------------
@@ -1657,12 +1657,12 @@ menu_t MP_RoomDef =
 menu_t MP_SplitServerDef = MAPICONMENUSTYLE("M_MULTI", MP_SplitServerMenu, &MP_MainDef);
 menu_t MP_PlayerSetupDef =
 {
-	"M_SPLAYR",
+	NULL, //"M_SPLAYR"
 	sizeof (MP_PlayerSetupMenu)/sizeof (menuitem_t),
 	&MP_MainDef,
 	MP_PlayerSetupMenu,
 	M_DrawSetupMultiPlayerMenu,
-	27, 40,
+	32, 16,
 	0,
 	M_QuitMultiPlayerMenu
 };
@@ -5637,11 +5637,11 @@ static void M_HandleStaffReplay(INT32 choice)
 	{
 		case KEY_DOWNARROW:
 			M_NextOpt();
-			S_StartSound(NULL, sfx_bewar1);
+			S_StartSound(NULL, sfx_menu1);
 			break;
 		case KEY_UPARROW:
 			M_PrevOpt();
-			S_StartSound(NULL, sfx_bewar1);
+			S_StartSound(NULL, sfx_menu1);
 			break;
 		case KEY_BACKSPACE:
 		case KEY_ESCAPE:
@@ -6483,8 +6483,13 @@ static void M_DrawSetupMultiPlayerMenu(void)
 	INT32 mx, my, st, flags = 0;
 	spritedef_t *sprdef;
 	spriteframe_t *sprframe;
+	patch_t *statbg = W_CachePatchName("K_STATBG", PU_CACHE);
+	patch_t *statdot = W_CachePatchName("K_SDOT0", PU_CACHE);
 	patch_t *patch;
 	UINT8 frame;
+	UINT8 speed;
+	UINT8 weight;
+	UINT8 i;
 
 	mx = MP_PlayerSetupDef.x;
 	my = MP_PlayerSetupDef.y;
@@ -6493,21 +6498,34 @@ static void M_DrawSetupMultiPlayerMenu(void)
 	M_DrawGenericMenu();
 
 	// draw name string
-	M_DrawTextBox(mx + 90, my - 8, MAXPLAYERNAME, 1);
-	V_DrawString(mx + 98, my, V_ALLOWLOWERCASE, setupm_name);
+	M_DrawTextBox(mx + 40, my - 8, MAXPLAYERNAME, 1);
+	V_DrawString(mx + 56, my, V_ALLOWLOWERCASE, setupm_name);
 
 	// draw skin string
-	V_DrawString(mx + 90, my + 96,
+	V_DrawString(mx + 88, my + 16,
 	             ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|V_YELLOWMAP|V_ALLOWLOWERCASE,
 	             skins[setupm_fakeskin].realname);
 
 	// draw the name of the color you have chosen
 	// Just so people don't go thinking that "Default" is Green.
-	V_DrawString(208, 72, V_YELLOWMAP|V_ALLOWLOWERCASE, KartColor_Names[setupm_fakecolor]);				// SRB2kart
+	V_DrawString(mx + 56, my + 152, V_YELLOWMAP|V_ALLOWLOWERCASE, KartColor_Names[setupm_fakecolor]);	// SRB2kart
 
 	// draw text cursor for name
 	if (!itemOn && skullAnimCounter < 4) // blink cursor
-		V_DrawCharacter(mx + 98 + V_StringWidth(setupm_name, 0), my, '_',false);
+		V_DrawCharacter(mx + 56 + V_StringWidth(setupm_name, 0), my, '_',false);
+
+	// SRB2Kart: draw the stat backer
+	V_DrawFixedPatch((mx+142)<<FRACBITS, (my+62)<<FRACBITS, FRACUNIT, 0, statbg, NULL);
+
+	for (i = 0; i < numskins; i++)
+	{
+		if (i != setupm_fakeskin && R_SkinAvailable(skins[i].name) != -1)
+		{
+			speed = skins[i].kartspeed;
+			weight = skins[i].kartweight;
+			V_DrawFixedPatch(((mx+178) + ((speed-1)*8))<<FRACBITS, ((my+76) + ((weight-1)*8))<<FRACBITS, FRACUNIT, 0, statdot, NULL);
+		}
+	}
 
 	// anim the player in the box
 	if (--multi_tics <= 0)
@@ -6534,25 +6552,34 @@ static void M_DrawSetupMultiPlayerMenu(void)
 		frame = 0; // Try to use standing frame
 
 	sprframe = &sprdef->spriteframes[frame];
-	patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+	patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE);
 	if (sprframe->flip & 1) // Only for first sprite
 		flags |= V_FLIP; // This sprite is left/right flipped!
 
 	// draw box around guy
-	M_DrawTextBox(mx + 90, my + 8, PLBOXW, PLBOXH);
+	M_DrawTextBox(mx + 42, my + 66, PLBOXW, PLBOXH);
+
+	if (skullAnimCounter < 4) // SRB2Kart: we draw this dot later so that it's not covered if there's multiple skins with the same stats
+		statdot = W_CachePatchName("K_SDOT2", PU_CACHE);
+	else
+		statdot = W_CachePatchName("K_SDOT1", PU_CACHE);
+
+	speed = skins[setupm_fakeskin].kartspeed;
+	weight = skins[setupm_fakeskin].kartweight;
 
 	// draw player sprite
 	if (!setupm_fakecolor) // should never happen but hey, who knows
 	{
 		if (skins[setupm_fakeskin].flags & SF_HIRES)
 		{
-			V_DrawSciencePatch((mx+98+(PLBOXW*8/2))<<FRACBITS,
-						(my+16+(PLBOXH*8)-12)<<FRACBITS,
+			V_DrawSciencePatch((mx+50+(PLBOXW*8/2))<<FRACBITS,
+						(my+74+(PLBOXH*8)-12)<<FRACBITS,
 						flags, patch,
 						skins[setupm_fakeskin].highresscale);
 		}
 		else
-			V_DrawScaledPatch(mx + 98 + (PLBOXW*8/2), my + 16 + (PLBOXH*8) - 12, flags, patch);
+			V_DrawScaledPatch(mx + 50 + (PLBOXW*8/2), my + 74 + (PLBOXH*8) - 12, flags, patch);
+		V_DrawFixedPatch(((mx+178) + ((speed-1)*8))<<FRACBITS, ((my+76) + ((weight-1)*8))<<FRACBITS, FRACUNIT, 0, statdot, NULL);
 	}
 	else
 	{
@@ -6560,14 +6587,15 @@ static void M_DrawSetupMultiPlayerMenu(void)
 
 		if (skins[setupm_fakeskin].flags & SF_HIRES)
 		{
-			V_DrawFixedPatch((mx+98+(PLBOXW*8/2))<<FRACBITS,
-						(my+16+(PLBOXH*8)-12)<<FRACBITS,
+			V_DrawFixedPatch((mx+50+(PLBOXW*8/2))<<FRACBITS,
+						(my+74+(PLBOXH*8)-12)<<FRACBITS,
 						skins[setupm_fakeskin].highresscale,
 						flags, patch, colormap);
 		}
 		else
-			V_DrawMappedPatch(mx + 98 + (PLBOXW*8/2), my + 16 + (PLBOXH*8) - 12, flags, patch, colormap);
+			V_DrawMappedPatch(mx + 50 + (PLBOXW*8/2), my + 74 + (PLBOXH*8) - 12, flags, patch, colormap);
 
+		V_DrawFixedPatch(((mx+178) + ((speed-1)*8))<<FRACBITS, ((my+76) + ((weight-1)*8))<<FRACBITS, FRACUNIT, 0, statdot, colormap);
 		Z_Free(colormap);
 	}
 }
@@ -6591,12 +6619,12 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 			break;
 
 		case KEY_LEFTARROW:
-			if (itemOn == 2)       //player skin
+			if (itemOn == 1)       //player skin
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_fakeskin--;
 			}
-			else if (itemOn == 1) // player color
+			else if (itemOn == 2) // player color
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_fakecolor--;
@@ -6604,12 +6632,12 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 			break;
 
 		case KEY_RIGHTARROW:
-			if (itemOn == 2)       //player skin
+			if (itemOn == 1)       //player skin
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_fakeskin++;
 			}
-			else if (itemOn == 1) // player color
+			else if (itemOn == 2) // player color
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_fakecolor++;
diff --git a/src/p_floor.c b/src/p_floor.c
index 3695072d..bc9bde63 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -2037,33 +2037,6 @@ foundenemy:
 	P_RemoveThinker(&nobaddies->thinker);
 }
 
-//
-// P_IsObjectOnRealGround
-//
-// Helper function for T_EachTimeThinker
-// Like P_IsObjectOnGroundIn, except ONLY THE REAL GROUND IS CONSIDERED, NOT FOFS
-// I'll consider whether to make this a more globally accessible function or whatever in future
-// -- Monster Iestyn
-//
-static boolean P_IsObjectOnRealGround(mobj_t *mo, sector_t *sec)
-{
-	// Is the object in reverse gravity?
-	if (mo->eflags & MFE_VERTICALFLIP)
-	{
-		// Detect if the player is on the ceiling.
-		if (mo->z+mo->height >= P_GetSpecialTopZ(mo, sec, sec))
-			return true;
-	}
-	// Nope!
-	else
-	{
-		// Detect if the player is on the floor.
-		if (mo->z <= P_GetSpecialBottomZ(mo, sec, sec))
-			return true;
-	}
-	return false;
-}
-
 //
 // P_HavePlayersEnteredArea
 //
diff --git a/src/p_local.h b/src/p_local.h
index 8d27d69e..673aa4fc 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -135,6 +135,7 @@ boolean P_IsLocalPlayer(player_t *player);
 boolean P_IsObjectInGoop(mobj_t *mo);
 boolean P_IsObjectOnGround(mobj_t *mo);
 boolean P_IsObjectOnGroundIn(mobj_t *mo, sector_t *sec);
+boolean P_IsObjectOnRealGround(mobj_t *mo, sector_t *sec); // SRB2Kart
 boolean P_InSpaceSector(mobj_t *mo);
 boolean P_InQuicksand(mobj_t *mo);
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 6a9890f9..4349bdcc 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -7710,7 +7710,9 @@ void P_MobjThinker(mobj_t *mobj)
 			}
 
 			sec2 = P_ThingOnSpecial3DFloor(mobj);
-			if ((sec2 && GETSECSPECIAL(sec2->special, 3) == 1) || (P_IsObjectOnGround(mobj) && GETSECSPECIAL(mobj->subsector->sector->special, 3) == 1))
+			if ((sec2 && GETSECSPECIAL(sec2->special, 3) == 1)
+				|| (P_IsObjectOnRealGround(mobj, mobj->subsector->sector)
+				&& GETSECSPECIAL(mobj->subsector->sector->special, 3) == 1))
 				K_DoBouncePad(mobj, 0);
 
 			if (mobj->threshold > 0)
@@ -7761,7 +7763,9 @@ void P_MobjThinker(mobj_t *mobj)
 			P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), topspeed);
 
 			sec2 = P_ThingOnSpecial3DFloor(mobj);
-			if ((sec2 && GETSECSPECIAL(sec2->special, 3) == 1) || (P_IsObjectOnGround(mobj) && GETSECSPECIAL(mobj->subsector->sector->special, 3) == 1))
+			if ((sec2 && GETSECSPECIAL(sec2->special, 3) == 1)
+				|| (P_IsObjectOnRealGround(mobj, mobj->subsector->sector)
+				&& GETSECSPECIAL(mobj->subsector->sector->special, 3) == 1))
 				K_DoBouncePad(mobj, 0);
 
 			break;
@@ -7775,7 +7779,9 @@ void P_MobjThinker(mobj_t *mobj)
 			P_InstaThrust(mobj, mobj->angle, mobj->info->speed);
 
 			sec2 = P_ThingOnSpecial3DFloor(mobj);
-			if ((sec2 && GETSECSPECIAL(sec2->special, 3) == 1) || (P_IsObjectOnGround(mobj) && GETSECSPECIAL(mobj->subsector->sector->special, 3) == 1))
+			if ((sec2 && GETSECSPECIAL(sec2->special, 3) == 1)
+				|| (P_IsObjectOnRealGround(mobj, mobj->subsector->sector)
+				&& GETSECSPECIAL(mobj->subsector->sector->special, 3) == 1))
 				K_DoBouncePad(mobj, 0);
 
 			if (mobj->threshold > 0)
diff --git a/src/p_spec.c b/src/p_spec.c
index 7d787eb9..0bc84765 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3729,18 +3729,17 @@ DoneSection2:
 	switch (special)
 	{
 		case 1: // SRB2kart: bounce pad
-			if (!P_IsObjectOnGround(player->mo))
-				break;
+			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
+			{
+				if (player->mo->eflags & MFE_SPRUNG)
+					break;
 
-			if (player->mo->eflags & MFE_SPRUNG)
-				break;
-
-			if (player->speed < K_GetKartSpeed(player, true)/4) // Push forward to prevent getting stuck
-				P_InstaThrust(player->mo, player->mo->angle, FixedMul(K_GetKartSpeed(player, true)/4, player->mo->scale));
-
-			player->kartstuff[k_feather] |= 2;
-			K_DoBouncePad(player->mo, 0);
+				if (player->speed < K_GetKartSpeed(player, true)/4) // Push forward to prevent getting stuck
+					P_InstaThrust(player->mo, player->mo->angle, FixedMul(K_GetKartSpeed(player, true)/4, player->mo->scale));
 
+				player->kartstuff[k_feather] |= 2;
+				K_DoBouncePad(player->mo, 0);
+			}
 			break;
 
 		case 2: // Wind/Current
@@ -3942,20 +3941,22 @@ DoneSection2:
 			break;
 
 		case 6: // SRB2kart 190117 - Mushroom Boost Panel
-			if (!P_IsObjectOnGround(player->mo))
-				break;
-			if (!player->kartstuff[k_floorboost])
-				player->kartstuff[k_floorboost] = 3;
-			else
-				player->kartstuff[k_floorboost] = 2;
-			K_DoMushroom(player, false, false);
+			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
+			{
+				if (!player->kartstuff[k_floorboost])
+					player->kartstuff[k_floorboost] = 3;
+				else
+					player->kartstuff[k_floorboost] = 2;
+				K_DoMushroom(player, false, false);
+			}
 			break;
 
 		case 7: // SRB2kart 190117 - Oil Slick
-			if (!P_IsObjectOnGround(player->mo))
-				break;
-			player->kartstuff[k_spinouttype] = -1;
-			K_SpinPlayer(player, NULL);
+			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
+			{
+				player->kartstuff[k_spinouttype] = -1;
+				K_SpinPlayer(player, NULL);
+			}
 			break;
 
 		case 8: // Zoom Tube Start
diff --git a/src/p_user.c b/src/p_user.c
index 583a0547..f1383646 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1312,6 +1312,36 @@ boolean P_IsObjectOnGroundIn(mobj_t *mo, sector_t *sec)
 	return false;
 }
 
+//
+// P_IsObjectOnRealGround
+//
+// Helper function for T_EachTimeThinker
+// Like P_IsObjectOnGroundIn, except ONLY THE REAL GROUND IS CONSIDERED, NOT FOFS
+// I'll consider whether to make this a more globally accessible function or whatever in future
+// -- Monster Iestyn
+//
+// Really simple, but personally I think it's also incredibly helpful. I think this is fine in p_user.c
+// -- Sal
+
+boolean P_IsObjectOnRealGround(mobj_t *mo, sector_t *sec)
+{
+	// Is the object in reverse gravity?
+	if (mo->eflags & MFE_VERTICALFLIP)
+	{
+		// Detect if the player is on the ceiling.
+		if (mo->z+mo->height >= P_GetSpecialTopZ(mo, sec, sec))
+			return true;
+	}
+	// Nope!
+	else
+	{
+		// Detect if the player is on the floor.
+		if (mo->z <= P_GetSpecialBottomZ(mo, sec, sec))
+			return true;
+	}
+	return false;
+}
+
 //
 // P_SetObjectMomZ
 //
@@ -4706,6 +4736,10 @@ static void P_3dMovement(player_t *player)
 	// Do not let the player control movement if not onground.
 	onground = P_IsObjectOnGround(player->mo);
 
+	// SRB2Kart: shhhhhhh don't question me, feather and speed bumps are supposed to control like you're on the ground :p
+	if (player->kartstuff[k_feather] & 2)
+		onground = true;
+
 	player->aiming = cmd->aiming<<FRACBITS;
 
 	// Set the player speeds.