diff --git a/src/d_main.c b/src/d_main.c
index 29a916863a9231afead3d6722a623374d7afb21d..5cf95f4b8d6b30f66f65e5a2838704e830799aa7 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 a015b8e73ce92a000c769c4fa1288ab9a8b5a669..6df7718efc354c7e03f51c19aa3ab86a9dde1ef0 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;
@@ -2209,10 +2209,12 @@ static void Command_Map_f(void)
 		return;
 	}
 
-	if (!(netgame || multiplayer) && (!modifiedgame || savemoddata))
+	if (!(netgame || multiplayer) && !majormods)
 	{
 		if (COM_CheckParm("-force"))
-			G_SetGameModified(false);
+		{
+			G_SetGameModified(false, true);
+		}
 		else
 		{
 			CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
@@ -3791,7 +3793,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;
 	}
 
@@ -3845,7 +3847,7 @@ static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum)
 	}
 
 	P_RunSOC(filename);
-	G_SetGameModified(true);
+	G_SetGameModified(true, false);
 }
 
 /** Adds a pwad at runtime.
@@ -3882,7 +3884,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.
@@ -4128,7 +4130,7 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
-	G_SetGameModified(true);
+	G_SetGameModified(true, false);
 }
 
 static void Command_ListWADS_f(void)
@@ -4485,7 +4487,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)
@@ -4506,7 +4508,7 @@ static void Gravity_OnChange(void)
 #endif
 
 	if (!CV_IsSetToDefault(&cv_gravity))
-		G_SetGameModified(multiplayer);
+		G_SetGameModified(multiplayer, true);
 	gravity = cv_gravity.value;
 }
 
@@ -4902,7 +4904,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)
@@ -4918,12 +4920,14 @@ 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"));
+	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(M_GetText("modifiedgame is true, extras will not be unlocked\n"));
+		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("modifiedgame is false, you can 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/d_netfil.c b/src/d_netfil.c
index c7cfdbc11c7b5e4da7925566ef414b90d77cdf95..99a05840389a9ee49cb8760be1aeecde695422ac 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/d_player.h b/src/d_player.h
index 27fdef8dc34ece0357ecc1d8261d207301457c9d..5ce9066b20f8aa2048eb8d9e16e6e2781f5336cf 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 b9e29bc472647e48146413dbc1319219ef4a463f..b03530a45f194ffed49146ecb29861906b854d66 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
 {
@@ -602,6 +601,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 +671,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 +681,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;
 					}
 			}
@@ -3139,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
@@ -3151,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"))
 			{
@@ -3382,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))
@@ -3417,10 +3425,12 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 			if (fastcmp(word, "FREESLOT"))
 			{
 				readfreeslots(f);
+				// This is not a major mod.
 				continue;
 			}
 			else if (fastcmp(word, "MAINCFG"))
 			{
+				G_SetGameModified(multiplayer, true);
 				readmaincfg(f);
 				DEH_WriteUndoline(word, "", UNDO_HEADER);
 				continue;
@@ -3429,6 +3439,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 			{
 				readwipes(f);
 				DEH_WriteUndoline(word, "", UNDO_HEADER);
+				// This is not a major mod.
 				continue;
 			}
 			word2 = strtok(NULL, " ");
@@ -3449,6 +3460,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					ignorelines(f);
 				}
 				DEH_WriteUndoline(word, word2, UNDO_HEADER);
+				// This is not a major mod.
 				continue;
 			}
 			if (word2)
@@ -3462,19 +3474,25 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					// Read texture from spec file.
 					readtexture(f, word2);
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					// 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);
+					// This is not a major mod.
 				}
 				else if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
 				{
 					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]))
+							G_SetGameModified(multiplayer, 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);
@@ -3485,6 +3503,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 /*				else if (fastcmp(word, "ANIMTEX"))
 				{
 					readAnimTex(f, i);
+					// This is not a major mod.
 				}*/
 				else if (fastcmp(word, "LIGHT"))
 				{
@@ -3498,6 +3517,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					// This is not a major mod.
 #endif
 				}
 				else if (fastcmp(word, "SPRITE"))
@@ -3513,6 +3533,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					// This is not a major mod.
 #endif
 				}
 				else if (fastcmp(word, "LEVEL"))
@@ -3525,7 +3546,11 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						i = M_MapNumber(word2[0], word2[1]);
 
 					if (i > 0 && i <= NUMMAPS)
+					{
+						if (mapheaderinfo[i])
+							G_SetGameModified(multiplayer, 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,13 +3568,18 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					//G_SetGameModified(multiplayer, true); -- might have to reconsider in a future update
 				}
 				else if (fastcmp(word, "FRAME") || fastcmp(word, "STATE"))
 				{
 					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]))
+							G_SetGameModified(multiplayer, 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);
@@ -3578,6 +3608,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					else
 						deh_warning("pointer (Frame %d) : missing ')'", i);
+					G_SetGameModified(multiplayer, true);
 				}*/
 				else if (fastcmp(word, "SOUND"))
 				{
@@ -3591,6 +3622,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					// This is not a major mod.
 				}
 /*				else if (fastcmp(word, "SPRITE"))
 				{
@@ -3611,6 +3643,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					else
 						deh_warning("Sprite %d doesn't exist",i);
+					// This is not a major mod.
 				}*/
 				else if (fastcmp(word, "HUDITEM"))
 				{
@@ -3624,10 +3657,11 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						ignorelines(f);
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
+					// This is not a major mod.
 				}
 				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);
@@ -3647,7 +3681,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);
@@ -3667,7 +3701,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);
@@ -3683,7 +3717,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);
@@ -3718,7 +3752,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;
@@ -3755,8 +3789,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)
@@ -8299,7 +8333,8 @@ static const char *const KARTSTUFF_LIST[] = {
 
 	"ITEMBLINK",
 	"ITEMBLINKMODE",
-	"GETSPARKS"
+	"GETSPARKS",
+	"JAWZTARGETDELAY"
 };
 
 static const char *const HUDITEMS_LIST[] = {
@@ -9356,6 +9391,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;
@@ -9371,6 +9407,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;
@@ -9740,6 +9777,9 @@ static inline int lib_getenum(lua_State *L)
 	} else if (fastcmp(word,"modifiedgame")) {
 		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);
 		return 1;
diff --git a/src/dehacked.h b/src/dehacked.h
index 683fe7d943178beee76938622c8b61c9c9fb3b5a..0d6cc9022db25eff91d20840b856068cee0e8bb9 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/doomstat.h b/src/doomstat.h
index 6d710e28cbf39b1b86838fd9451be4fe12fa5781..9ae2726d7e4e7c173d7d5538e287ca8413611513 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/filesrch.h b/src/filesrch.h
index 4186271b038d23aac0ad9a1f77a0a3b368914ada..01a52848219db85f3189f52742ea48da0a5e3516 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 ee004b64b14a0a6f3f17858e070580b08aa857ea..f0d221ff67bb5896a1b7430e6645e225a6089c6a 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"
@@ -86,7 +87,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;
@@ -752,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) || !mainwads || (refreshdirmenu & REFRESHDIR_GAMEDATA)) // new gamedata amnesty?
 		return;
 
 	modifiedgame = true;
-	savemoddata = false;
+
+	if (!major)
+		return;
+
+	//savemoddata = false; -- there is literally no reason to do this anymore.
+	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)
@@ -3942,7 +3949,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;
@@ -3959,9 +3965,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;
@@ -3974,7 +3978,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...
@@ -4060,9 +4064,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
@@ -5150,22 +5151,20 @@ 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;
 				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);
 					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;
@@ -5921,6 +5920,32 @@ 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 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;
+	}
+
+	// ...*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));
@@ -5952,9 +5977,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
@@ -6004,6 +6026,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';
@@ -6149,6 +6172,21 @@ void G_AddGhost(char *defdemoname)
 		return;
 	}
 
+	for (i = 0; i < numskins; i++)
+		if (!stricmp(skins[i].name,skin))
+		{
+			ghskin = &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;
@@ -6194,14 +6232,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 = ghskin;
 
 	// Set color
 	gh->mo->color = ((skin_t*)gh->mo->skin)->prefcolor;
diff --git a/src/g_game.h b/src/g_game.h
index 6d11a4a02fbdc81dcd80ec0f04f05d39759dec59..fc7a4a4f580f5cbcfa762c470bb262d77edbf1d3 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -226,7 +226,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/info.c b/src/info.c
index ea9eac9d54943d6536402e6f9d2d38b3f18b3f0b..ccdfa3cf9957285301ab05736153165c84c663ea 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 f01f0d0b21ecb0e6b66440aa0216b20b15b99386..37c70681dbed45bee89a3be6310d5afabf0e0f39 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
@@ -616,6 +616,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];
@@ -626,21 +652,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;
+			}
 		}
 	}
 
@@ -650,120 +679,61 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed)
 													players[first].mo->y - players[second].mo->y),
 													players[first].mo->z - players[second].mo->z) / mapobjectscale;
 		if (franticitems)
-			secondist = (15*secondist/14);
-		if (pingame < 8 && !G_BattleGametype())
-			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.
 	// 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 8 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)
+	// 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.
-	// 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; \
+	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)
 	{
-		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 >= max(1, (pingame+2) / 4))
+				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:
-			POWERITEMODDS(newodds);
-			if (!cv_ballhog.value) newodds = 0;
-			break;
-		case KITEM_SPB:
-			//POWERITEMODDS(newodds);
-			if (((!cv_selfpropelledbomb.value)
-				|| (indirectitemcooldown > 0)
-				|| (pexiting > 0)
-				|| (secondist/distvar < 3))
-				&& (pos != 9)) // Force SPB
-				newodds = 0;
-			newodds *= min((secondist/distvar)-4, 3);
-			break;
-		case KITEM_GROW:
-			POWERITEMODDS(newodds);
-			if ((!cv_grow.value) || (pinvin >= 2)) newodds = 0;
-			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:
+		case KRITEM_DUALJAWZ:
 			POWERITEMODDS(newodds);
-			if (!cv_quadorbinaut.value) newodds = 0;
 			break;
-		case KRITEM_DUALJAWZ:
+		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 (!cv_dualjawz.value) newodds = 0;
+			if ((indirectitemcooldown > 0) || (pingame-1 <= pexiting))
+				newodds = 0;
 			break;
 		default:
 			break;
 	}
+
 #undef POWERITEMODDS
 
 	return newodds;
@@ -851,11 +821,12 @@ 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 = (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;
-		if (pingame < 8)
-			pdis = ((28+(8-pingame))*pdis)/28;
+			pdis = (3 * pdis) / 2;
+
+		pdis = ((28 + (8-pingame)) * pdis) / 28;
 
 		if (pingame == 1 && oddsvalid[0])					// Record Attack, or just alone
 			useodds = 0;
@@ -1097,6 +1068,26 @@ 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
+		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])
 	{
@@ -1211,8 +1202,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 +1214,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]);
 		}
 	}
 }
@@ -1248,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;
@@ -1264,33 +1254,20 @@ 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;
+	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)
-			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 = (offroadstrength << FRACBITS) / (TICRATE/2);
 
 			//if (player->kartstuff[k_growshrinktimer] > 1) // grow slows down half as fast
 			//	offroad /= 2;
@@ -1298,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;
@@ -1649,10 +1626,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
@@ -2534,7 +2509,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 */
@@ -3658,6 +3643,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
 
@@ -3675,10 +3661,10 @@ 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 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12);
+						cur->extravalue1 += P_AproxDistance(cur->extravalue1, radius) / 12;
 					if (cur->extravalue1 > radius)
 						cur->extravalue1 = radius;
 
@@ -3938,13 +3924,14 @@ 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
-			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))
 			{
@@ -3957,6 +3944,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;
 
@@ -4470,12 +4462,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;
 		}
 
@@ -4485,7 +4487,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);
@@ -4493,11 +4495,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;
 	}
 }
 
@@ -4670,8 +4674,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)
 	{
@@ -4701,7 +4703,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)
+		if (player->kartstuff[k_getsparks] == 0 || player->kartstuff[k_offroad])
 			driftadditive = 0;
 		if (player->speed > minspeed*2)
 			player->kartstuff[k_getsparks] = 1;
@@ -5143,7 +5145,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)
 							{
@@ -5184,7 +5186,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)
 							{
@@ -5428,10 +5430,15 @@ 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)
 	{
 		player->mo->friction += 1228;
@@ -5451,11 +5458,14 @@ 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]);
-		if (player->kartstuff[k_wipeoutslow] == 1)
+		if (player->kartstuff[k_offroad])
 			player->mo->friction -= 4912;
+		if (player->kartstuff[k_wipeoutslow] == 1)
+			player->mo->friction -= 9824;
 	}
 
 	K_KartDrift(player, onground);
@@ -7105,19 +7115,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);
 
+		STRBUFCPY(strtime, tab[i].name);
+
 		if (scorelines > 8)
-			strlcpy(strtime, tab[i].name, 6);
+			V_DrawThinString(x + 20, y, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, strtime);
 		else
-			STRBUFCPY(strtime, tab[i].name);
-
-		V_DrawString(x + 20, y,
-			((tab[i].num == whiteplayer)
-				? hilicol|V_ALLOWLOWERCASE
-				: V_ALLOWLOWERCASE),
-			strtime);
+			V_DrawString(x + 20, y, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE, strtime);
 
 		if (players[tab[i].num].mo->color)
 		{
@@ -7157,12 +7163,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/lua_hudlib.c b/src/lua_hudlib.c
index ca952a00960660ce88cd8c45a2dd18ac93978c10..cd8e0392378ca3f26d9aa8f7f4f1cb8f40557d52 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) {
diff --git a/src/lua_script.c b/src/lua_script.c
index 34a260527cd1a0e8ddf011d44f8b9553a0314b7f..1f87d33ee175cec599dfb329095b10aacc06f690 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.
+	G_SetGameModified(multiplayer, true);
+
 	free(name);
 	Z_Free(f.data);
 }
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 38d8cc7a999711c3b8b9820f6c6487c867c55793..2fc2e85c91f211c14723a7617f7a201a12cccf91 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,7 +1267,7 @@ void Command_ObjectPlace_f(void)
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_NOULTIMATE;
 
-	G_SetGameModified(multiplayer);
+	G_SetGameModified(multiplayer, true);
 
 	// Entering objectplace?
 	if (!objectplacing)
diff --git a/src/m_cond.c b/src/m_cond.c
index 35eccd1c4f4efa65c639c19e82e8fa10e9fc8738..b777e7d221590f3f2dcc557bb928d7d0fe1f3ffb 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 && !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 f0831a172d83f823feec147dd2797f887bb1a9f0..d8b1c2d7bcff455aa6e088deaf806808a0f4beb0 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);
@@ -443,7 +442,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};
@@ -935,11 +934,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},
@@ -2739,10 +2738,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) && majormods)
 					{
 						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;
 					}
 				}
@@ -4527,9 +4526,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)
+	{
+		CLEARNAME;
+	}
 	M_StopMessage(choice);
 }
 
@@ -4539,10 +4543,17 @@ static boolean M_AddonsRefresh(void)
 	if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true))
 	{
 		UNEXIST;
+		if (refreshdirname)
+		{
+			CLEARNAME;
+		}
 		return true;
 	}
 
-	if (refreshdirmenu & REFRESHDIR_ADDFILE)
+	if (!majormods && prevmajormods)
+		prevmajormods = false;
+
+	if ((refreshdirmenu & REFRESHDIR_ADDFILE) || (majormods && !prevmajormods))
 	{
 		char *message = NULL;
 
@@ -4550,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 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);
 		}
@@ -4559,6 +4570,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)
+		{
+			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);
+			prevmajormods = majormods;
+		}
 
 		if (message)
 		{
@@ -4709,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]);
@@ -5106,7 +5123,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)
@@ -5117,7 +5134,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)
@@ -7384,6 +7401,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.\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;
+	}
+
+	M_ConnectMenu(-1);
+}
+
 static UINT32 roomIds[NUM_LIST_ROOMS];
 
 static void M_RoomMenu(INT32 choice)
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 8a6d7597f98eea19175b2c99c28c3d8c93ca3718..9d3aa95191820747d4e3c124db89c92552d6071d 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)
 		{
diff --git a/src/p_map.c b/src/p_map.c
index c307e57216e0d8ef30b58f0e078dd955d6dea2ee..41e5a455d99e340041d0a6a158c259dd8d432e64 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)
@@ -3231,129 +3227,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 +3280,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 f71ff2092c963c1d9edc9995d5bae5fd24ad8acd..746fc1afd6e354787ac74a05e36d64dea5f596cc 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,12 @@ 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);
-					break;
 				case MT_SINK:
-					gravityadd = FixedMul(gravityadd, 5*FRACUNIT); // Double gravity
+					gravityadd = (5*gravityadd)/2;
 					break;
 				case MT_SIGN:
 					gravityadd /= 8;
@@ -3233,8 +3234,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_setup.c b/src/p_setup.c
index 49b22184e589b7574ad0c3d0b71553afaf944b58..912791cf986477417227213aaa42a2432e4914b1 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 || majormods) // 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) && !majormods)
 		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)
+					G_SetGameModified(multiplayer, 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;
@@ -3481,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/p_spec.c b/src/p_spec.c
index ca4967ce33fc7398fd885c1254c86c1f5bd167a8..24f56c432c605724d62fe307a4ae9d4f7fe8408c 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 d7423d8032e8c706a9ea3b52708e6cd649229dfc..ce411d2d13fa5246ff6ace225406ccd6f6247ee9 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 d6234d4b39636d44005ace01065757108af45801..135ae6a294fe6629556200cf43879a9025550645 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 #.
@@ -2894,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)) \
@@ -2933,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
@@ -3044,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;
diff --git a/src/r_things.h b/src/r_things.h
index 53784415fc6b9ce6b37b0709c93aa56be39f5cff..01d8fc07109a5bf0a95bfd81d7d472c738189ed6 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];
 
-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/sdl/i_system.c b/src/sdl/i_system.c
index 2154a070b9b9e337578491b5bf7e22b1080417ab..f360072a0a6521cf5e79aff7d309cadb5df89915 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 0e5adbb4068e70724e7e4271c21f036656493465..d055a4ca5274b66382b2f8d3dbff7b3a94a6486e 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/w_wad.c b/src/w_wad.c
index 63bee97ded616fdf31b92e4b64c42e4757efd22e..efa09ce43a738b5177faac40a7c87b04ee3bc95f 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,10 @@ UINT16 W_InitFile(const char *filename)
 		break;
 	}
 
+	if (refreshdirmenu & REFRESHDIR_GAMEDATA)
+		G_LoadGameData();
+	DEH_UpdateMaxFreeslots();
+
 	W_InvalidateLumpnumCache();
 	return wadfile->numlumps;
 }
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index 75aca68dfc0e444734a549c2697fd821d0dde4b0..fa9d6d644be7a53d0b917e3cc0306965d75fcabc 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 3b6a472587cc161f214736005c48df406dde9925..091171b5299017b8e70fb2acafa40ccfa513ffb6 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);
 }
 
diff --git a/src/y_inter.c b/src/y_inter.c
index 021519e3bac77e661cd190e3a16a4dc46a365768..095b4ad36b4841f261f5b8a7bff8d42a98cf15f5 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);
@@ -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 (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);
 
 				if (data.match.rankingsmode)
 				{
@@ -494,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)
@@ -513,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]));
+						}
 					}
 				}
 
@@ -786,7 +796,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 (!majormods && !multiplayer && !demoplayback) // remove this once we have a proper time attack screen
 			{
 				// Update visitation flags
 				mapvisited[gamemap-1] |= MV_BEATEN;