diff --git a/src/b_bot.c b/src/b_bot.c
index dc65c9c16b8dd8f8bfa98dca65fbfd022d6d02ea..543bcb183000d42db13f6a534c5bfad0d96460a2 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -271,6 +271,12 @@ void B_RespawnBot(INT32 playernum)
 	player->powers[pw_spacetime] = sonic->player->powers[pw_spacetime];
 	player->powers[pw_gravityboots] = sonic->player->powers[pw_gravityboots];
 	player->powers[pw_nocontrol] = sonic->player->powers[pw_nocontrol];
+	player->acceleration = sonic->player->acceleration;
+	player->accelstart = sonic->player->accelstart;
+	player->thrustfactor = sonic->player->thrustfactor;
+	player->normalspeed = sonic->player->normalspeed;
+	player->pflags |= PF_AUTOBRAKE;
+	player->pflags &= ~PF_DIRECTIONCHAR;
 
 	P_TeleportMove(tails, x, y, z);
 	if (player->charability == CA_FLY)
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 3878d879560d0a0991a0191cfdabd9c6188c8658..9719178bdb4971c3e25c6ed7815ebc4c02c49021 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2253,7 +2253,8 @@ static void Command_connect(void)
 		CONS_Printf(M_GetText(
 			"Connect <serveraddress> (port): connect to a server\n"
 			"Connect ANY: connect to the first lan server found\n"
-			"Connect SELF: connect to your own server.\n"));
+			//"Connect SELF: connect to your own server.\n"
+			));
 		return;
 	}
 
@@ -2267,7 +2268,7 @@ static void Command_connect(void)
 	// we don't request a restart unless the filelist differs
 
 	server = false;
-
+/*
 	if (!stricmp(COM_Argv(1), "self"))
 	{
 		servernode = 0;
@@ -2276,6 +2277,7 @@ static void Command_connect(void)
 		//SV_SpawnServer();
 	}
 	else
+*/
 	{
 		// used in menu to connect to a server in the list
 		if (netgame && !stricmp(COM_Argv(1), "node"))
@@ -2299,10 +2301,13 @@ static void Command_connect(void)
 
 			if (!stricmp(COM_Argv(1), "any"))
 				servernode = BROADCASTADDR;
-			else if (I_NetMakeNodewPort && COM_Argc() >= 3)
-				servernode = I_NetMakeNodewPort(COM_Argv(1), COM_Argv(2));
 			else if (I_NetMakeNodewPort)
-				servernode = I_NetMakeNode(COM_Argv(1));
+			{
+				if (COM_Argc() >= 3) // address AND port
+					servernode = I_NetMakeNodewPort(COM_Argv(1), COM_Argv(2));
+				else // address only, or address:port
+					servernode = I_NetMakeNode(COM_Argv(1));
+			}
 			else
 			{
 				CONS_Alert(CONS_ERROR, M_GetText("There is no server identification with this network driver\n"));
@@ -3097,11 +3102,15 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 			secondarydisplayplayer = newplayernum;
 			DEBFILE("spawning me\n");
 			// Apply player flags as soon as possible!
-			players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
+			players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE);
 			if (cv_flipcam.value)
 				players[newplayernum].pflags |= PF_FLIPCAM;
 			if (cv_analog.value)
 				players[newplayernum].pflags |= PF_ANALOGMODE;
+			if (cv_directionchar.value)
+				players[newplayernum].pflags |= PF_DIRECTIONCHAR;
+			if (cv_autobrake.value)
+				players[newplayernum].pflags |= PF_AUTOBRAKE;
 		}
 		else
 		{
@@ -3110,11 +3119,15 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 			if (botingame)
 				players[newplayernum].bot = 1;
 			// Same goes for player 2 when relevant
-			players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
+			players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE);
 			if (cv_flipcam2.value)
 				players[newplayernum].pflags |= PF_FLIPCAM;
 			if (cv_analog2.value)
 				players[newplayernum].pflags |= PF_ANALOGMODE;
+			if (cv_directionchar2.value)
+				players[newplayernum].pflags |= PF_DIRECTIONCHAR;
+			if (cv_autobrake2.value)
+				players[newplayernum].pflags |= PF_AUTOBRAKE;
 		}
 		D_SendPlayerConfig();
 		addedtogame = true;
diff --git a/src/d_main.c b/src/d_main.c
index ff9dd22c2e870f62ba9cc86659e754523d96b7d6..bbddfe791547b3944c8475cf9b05c3cbbdc2da28 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -301,7 +301,7 @@ static void D_Display(void)
 		if (rendermode != render_none)
 		{
 			// Fade to black first
-			if (gamestate != GS_LEVEL // fades to black on its own timing, always
+			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)) // fades to black on its own timing, always
 			 && wipedefs[wipedefindex] != UINT8_MAX)
 			{
 				F_WipeStartScreen();
@@ -317,6 +317,12 @@ static void D_Display(void)
 	// do buffered drawing
 	switch (gamestate)
 	{
+		case GS_TITLESCREEN:
+			if (!titlemapinaction) {
+				F_TitleScreenDrawer();
+				break;
+			}
+			// Intentional fall-through
 		case GS_LEVEL:
 			if (!gametic)
 				break;
@@ -366,10 +372,6 @@ static void D_Display(void)
 			HU_Drawer();
 			break;
 
-		case GS_TITLESCREEN:
-			F_TitleScreenDrawer();
-			break;
-
 		case GS_WAITINGPLAYERS:
 			// The clientconnect drawer is independent...
 		case GS_DEDICATEDSERVER:
@@ -379,9 +381,10 @@ static void D_Display(void)
 
 	// clean up border stuff
 	// see if the border needs to be initially drawn
-	if (gamestate == GS_LEVEL)
+	if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction))
 	{
 		// draw the view directly
+
 		if (!automapactive && !dedicated && cv_renderview.value)
 		{
 			if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
@@ -439,9 +442,13 @@ static void D_Display(void)
 			lastdraw = false;
 		}
 
-		ST_Drawer();
-
-		HU_Drawer();
+		if (gamestate == GS_LEVEL)
+		{
+			ST_Drawer();
+			HU_Drawer();
+		}
+		else
+			F_TitleScreenDrawer();
 	}
 
 	// change gamma if needed
@@ -681,6 +688,9 @@ void D_AdvanceDemo(void)
 void D_StartTitle(void)
 {
 	INT32 i;
+
+	S_StopMusic();
+
 	if (netgame)
 	{
 		if (gametype == GT_COOP)
@@ -1346,6 +1356,19 @@ void D_SRB2Main(void)
 		ultimatemode = true;
 	}
 
+	// rei/miru: bootmap (Idea: starts the game on a predefined map)
+	if (bootmap && !(M_CheckParm("-warp") && M_IsNextParm()))
+	{
+		pstartmap = bootmap;
+
+		if (pstartmap < 1 || pstartmap > NUMMAPS)
+			I_Error("Cannot warp to map %d (out of range)\n", pstartmap);
+		else
+		{
+			autostart = true;
+		}
+	}
+
 	if (autostart || netgame || M_CheckParm("+connect") || M_CheckParm("-connect"))
 	{
 		gameaction = ga_nothing;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index b6558c58be8bb6365795778dc28c15d36832db5a..f9bd43b48d4ca0476cfd10feb4982326f6b1abc9 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -771,6 +771,12 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_useranalog);
 	CV_RegisterVar(&cv_useranalog2);
 
+	// deez New User eXperiences
+	CV_RegisterVar(&cv_directionchar);
+	CV_RegisterVar(&cv_directionchar2);
+	CV_RegisterVar(&cv_autobrake);
+	CV_RegisterVar(&cv_autobrake2);
+
 	// s_sound.c
 	CV_RegisterVar(&cv_soundvolume);
 	CV_RegisterVar(&cv_closedcaptioning);
@@ -1433,6 +1439,10 @@ void SendWeaponPref(void)
 		buf[0] |= 1;
 	if (players[consoleplayer].pflags & PF_ANALOGMODE)
 		buf[0] |= 2;
+	if (players[consoleplayer].pflags & PF_DIRECTIONCHAR)
+		buf[0] |= 4;
+	if (players[consoleplayer].pflags & PF_AUTOBRAKE)
+		buf[0] |= 8;
 	SendNetXCmd(XD_WEAPONPREF, buf, 1);
 }
 
@@ -1445,6 +1455,10 @@ void SendWeaponPref2(void)
 		buf[0] |= 1;
 	if (players[secondarydisplayplayer].pflags & PF_ANALOGMODE)
 		buf[0] |= 2;
+	if (players[secondarydisplayplayer].pflags & PF_DIRECTIONCHAR)
+		buf[0] |= 4;
+	if (players[secondarydisplayplayer].pflags & PF_AUTOBRAKE)
+		buf[0] |= 8;
 	SendNetXCmd2(XD_WEAPONPREF, buf, 1);
 }
 
@@ -1452,11 +1466,15 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum)
 {
 	UINT8 prefs = READUINT8(*cp);
 
-	players[playernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
+	players[playernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE);
 	if (prefs & 1)
 		players[playernum].pflags |= PF_FLIPCAM;
 	if (prefs & 2)
 		players[playernum].pflags |= PF_ANALOGMODE;
+	if (prefs & 4)
+		players[playernum].pflags |= PF_DIRECTIONCHAR;
+	if (prefs & 8)
+		players[playernum].pflags |= PF_AUTOBRAKE;
 }
 
 void D_SendPlayerConfig(void)
@@ -2578,12 +2596,12 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 		{
 			players[playernum].spectator = true;
 			players[playernum].pflags &= ~PF_TAGIT;
-			players[playernum].pflags &= ~PF_TAGGED;
+			players[playernum].pflags &= ~PF_GAMETYPEOVER;
 		}
 		else if (NetPacket.packet.newteam != 3) // .newteam == 1 or 2.
 		{
 			players[playernum].spectator = false;
-			players[playernum].pflags &= ~PF_TAGGED;//Just in case.
+			players[playernum].pflags &= ~PF_GAMETYPEOVER; //Just in case.
 
 			if (NetPacket.packet.newteam == 1) //Make the player IT.
 				players[playernum].pflags |= PF_TAGIT;
diff --git a/src/d_player.h b/src/d_player.h
index d578d15efd71d5cb41fab0ec263c32ad667be299..bf0b303b8ece7552fb3a40c271826a3b40905fd3 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -98,66 +98,58 @@ typedef enum
 //
 typedef enum
 {
-	// Flip camera angle with gravity flip prefrence.
-	PF_FLIPCAM = 1,
+	// Cvars
+	PF_FLIPCAM       = 1, // Flip camera angle with gravity flip prefrence.
+	PF_ANALOGMODE    = 1<<1, // Analog mode?
+	PF_DIRECTIONCHAR = 1<<2, // Directional character sprites?
+	PF_AUTOBRAKE     = 1<<3, // Autobrake?
 
 	// Cheats
-	PF_GODMODE = 1<<1,
-	PF_NOCLIP  = 1<<2,
-	PF_INVIS   = 1<<3,
+	PF_GODMODE = 1<<4,
+	PF_NOCLIP  = 1<<5,
+	PF_INVIS   = 1<<6,
 
 	// True if button down last tic.
-	PF_ATTACKDOWN = 1<<4,
-	PF_USEDOWN    = 1<<5,
-	PF_JUMPDOWN   = 1<<6,
-	PF_WPNDOWN    = 1<<7,
+	PF_ATTACKDOWN = 1<<7,
+	PF_USEDOWN    = 1<<8,
+	PF_JUMPDOWN   = 1<<9,
+	PF_WPNDOWN    = 1<<10,
 
 	// Unmoving states
-	PF_STASIS     = 1<<8, // Player is not allowed to move
-	PF_JUMPSTASIS = 1<<9, // and that includes jumping.
+	PF_STASIS     = 1<<11, // Player is not allowed to move
+	PF_JUMPSTASIS = 1<<12, // and that includes jumping.
 	PF_FULLSTASIS = PF_STASIS|PF_JUMPSTASIS,
 
-	// Did you get a time-over?
-	PF_TIMEOVER = 1<<10,
+	// Applying autobrake?
+	PF_APPLYAUTOBRAKE = 1<<13,
 
 	// Character action status
-	PF_STARTJUMP = 1<<11,
-	PF_JUMPED    = 1<<12,
-	PF_SPINNING  = 1<<13,
-	PF_STARTDASH = 1<<14,
-	PF_THOKKED   = 1<<15,
+	PF_STARTJUMP     = 1<<14,
+	PF_JUMPED        = 1<<15,
+	PF_NOJUMPDAMAGE  = 1<<16,
 
-	// Are you gliding?
-	PF_GLIDING   = 1<<16,
+	PF_SPINNING      = 1<<17,
+	PF_STARTDASH     = 1<<18,
 
-	// Sliding (usually in water) like Labyrinth/Oil Ocean
-	PF_SLIDING   = 1<<17,
+	PF_THOKKED       = 1<<19,
+	PF_SHIELDABILITY = 1<<20,
+	PF_GLIDING       = 1<<21,
+	PF_BOUNCING      = 1<<22,
 
-	// Bouncing
-	PF_BOUNCING  = 1<<18,
+	// Sliding (usually in water) like Labyrinth/Oil Ocean
+	PF_SLIDING       = 1<<23,
 
-	/*** NIGHTS STUFF ***/
-	PF_TRANSFERTOCLOSEST = 1<<19,
-	PF_NIGHTSFALL        = 1<<20,
-	PF_DRILLING          = 1<<21,
-	PF_SKIDDOWN          = 1<<22,
+	// NiGHTS stuff
+	PF_TRANSFERTOCLOSEST = 1<<24,
+	PF_DRILLING          = 1<<25,
 
-	/*** TAG STUFF ***/
-	PF_TAGGED            = 1<<23, // Player has been tagged and awaits the next round in hide and seek.
-	PF_TAGIT             = 1<<24, // The player is it! For Tag Mode
+	// Gametype-specific stuff
+	PF_GAMETYPEOVER = 1<<26, // Race time over, or H&S out-of-game
+	PF_TAGIT        = 1<<27, // The player is it! For Tag Mode
 
 	/*** misc ***/
-	PF_FORCESTRAFE       = 1<<25, // Turning inputs are translated into strafing inputs
-	PF_ANALOGMODE        = 1<<26, // Analog mode?
-
-	// Can carry another player?
-	PF_CANCARRY          = 1<<27,
-
-	// Used shield ability
-	PF_SHIELDABILITY     = 1<<28,
-
-	// Jump damage?
-	PF_NOJUMPDAMAGE   = 1<<29,
+	PF_FORCESTRAFE = 1<<28, // Turning inputs are translated into strafing inputs
+	PF_CANCARRY    = 1<<29, // Can carry another player?
 
 	// up to 1<<31 is free
 } pflags_t;
@@ -234,6 +226,7 @@ typedef enum
 	CR_PLAYER,
 	// NiGHTS mode. Not technically a CARRYING, but doesn't stack with any of the others, so might as well go here.
 	CR_NIGHTSMODE,
+	CR_NIGHTSFALL,
 	// Old Brak sucks hard, but this gimmick could be used for something better, so we might as well continue supporting it.
 	CR_BRAKGOOP,
 	// Specific level gimmicks.
@@ -254,6 +247,7 @@ typedef enum
 	pw_underwater, // underwater timer
 	pw_spacetime, // In space, no one can hear you spin!
 	pw_extralife, // Extra Life timer
+	pw_pushing,
 
 	pw_super, // Are you super?
 	pw_gravityboots, // gravity boots
@@ -326,6 +320,9 @@ typedef struct player_s
 	// It is updated with cmd->aiming.
 	angle_t aiming;
 
+	// fun thing for player sprite
+	angle_t drawangle;
+
 	// player's ring count
 	INT32 rings;
 
diff --git a/src/dehacked.c b/src/dehacked.c
index 719476543d95d99aebfdceb2d81957074a0d917d..d1d21b340f161434967d19a389b63b3c6d612a27 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -64,6 +64,7 @@ memset(used_spr,0,sizeof(UINT8) * ((NUMSPRITEFREESLOTS / 8) + 1));\
 static mobjtype_t get_mobjtype(const char *word);
 static statenum_t get_state(const char *word);
 static spritenum_t get_sprite(const char *word);
+static playersprite_t get_sprite2(const char *word);
 static sfxenum_t get_sfx(const char *word);
 #ifdef MUSICSLOT_COMPATIBILITY
 static UINT16 get_mus(const char *word, UINT8 dehacked_mode);
@@ -77,6 +78,8 @@ boolean deh_loaded = false;
 static int dbg_line;
 
 static boolean gamedataadded = false;
+static boolean titlechanged = false;
+static boolean introchanged = false;
 
 ATTRINLINE static FUNCINLINE char myfget_color(MYFILE *f)
 {
@@ -769,6 +772,49 @@ static void readspritelight(MYFILE *f, INT32 num)
 }
 #endif // HWRENDER
 
+static void readsprite2(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word, *word2;
+	char *tmp;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+				strupr(word2);
+			else
+				break;
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+
+			if (fastcmp(word, "DEFAULT"))
+				spr2defaults[num] = get_number(word2);
+			else
+				deh_warning("Sprite2 %s: unknown word '%s'", spr2names[num], word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
 static const struct {
 	const char *name;
 	const UINT16 flag;
@@ -2667,14 +2713,36 @@ static void readmaincfg(MYFILE *f)
 				// range check, you morons.
 				if (introtoplay > 128)
 					introtoplay = 128;
+				introchanged = true;
 			}
 			else if (fastcmp(word, "LOOPTITLE"))
 			{
 				looptitle = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEMAP"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				titlemap = (INT16)value;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "HIDETITLEPICS"))
+			{
+				hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
 			}
 			else if (fastcmp(word, "TITLESCROLLSPEED"))
 			{
 				titlescrollspeed = get_number(word2);
+				titlechanged = true;
 			}
 			else if (fastcmp(word, "CREDITSCUTSCENE"))
 			{
@@ -2690,14 +2758,17 @@ static void readmaincfg(MYFILE *f)
 			else if (fastcmp(word, "NUMDEMOS"))
 			{
 				numDemos = (UINT8)get_number(word2);
+				titlechanged = true;
 			}
 			else if (fastcmp(word, "DEMODELAYTIME"))
 			{
 				demoDelayTime = get_number(word2);
+				titlechanged = true;
 			}
 			else if (fastcmp(word, "DEMOIDLETIME"))
 			{
 				demoIdleTime = get_number(word2);
+				titlechanged = true;
 			}
 			else if (fastcmp(word, "USE1UPSOUND"))
 			{
@@ -2731,14 +2802,31 @@ static void readmaincfg(MYFILE *f)
 				strlcat(savegamename, "%u.ssg", sizeof(savegamename));
 
 				gamedataadded = true;
+				titlechanged = true;
 			}
 			else if (fastcmp(word, "RESETDATA"))
 			{
 				P_ResetData(value);
+				titlechanged = true;
 			}
 			else if (fastcmp(word, "CUSTOMVERSION"))
 			{
 				strlcpy(customversionstring, word2, sizeof (customversionstring));
+				//titlechanged = true;
+			}
+			else if (fastcmp(word, "BOOTMAP"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				bootmap = (INT16)value;
+				//titlechanged = true;
 			}
 			else
 				deh_warning("Maincfg: unknown word '%s'", word);
@@ -2935,7 +3023,7 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 
 	deh_num_warning = 0;
 
-	gamedataadded = false;
+	gamedataadded = titlechanged = introchanged = 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.
@@ -3020,9 +3108,21 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 						ignorelines(f);
 					}
 				}
-				else if (fastcmp(word, "LIGHT"))
+				else if (fastcmp(word, "SPRITE2"))
 				{
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_sprite2(word2); // find a sprite by name
+					if (i < (INT32)free_spr2 && i >= (INT32)SPR2_FIRSTFREESLOT)
+						readsprite2(f, i);
+					else
+					{
+						deh_warning("Sprite2 number %d out of range (%d - %d)", i, SPR2_FIRSTFREESLOT, free_spr2-1);
+						ignorelines(f);
+					}
+				}
 #ifdef HWRENDER
+				else if (fastcmp(word, "LIGHT"))
+				{
 					// TODO: Read lights by name
 					if (i > 0 && i < NUMLIGHTS)
 						readlight(f, i);
@@ -3031,22 +3131,20 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 						deh_warning("Light number %d out of range (1 - %d)", i, NUMLIGHTS-1);
 						ignorelines(f);
 					}
-#endif
 				}
 				else if (fastcmp(word, "SPRITE"))
 				{
-#ifdef HWRENDER
 					if (i == 0 && word2[0] != '0') // If word2 isn't a number
 						i = get_sprite(word2); // find a sprite by name
-					if (i < NUMSPRITES && i >= 0)
+					if (i < NUMSPRITES && i > 0)
 						readspritelight(f, i);
 					else
 					{
 						deh_warning("Sprite number %d out of range (0 - %d)", i, NUMSPRITES-1);
 						ignorelines(f);
 					}
-#endif
 				}
+#endif
 				else if (fastcmp(word, "LEVEL"))
 				{
 					// Support using the actual map name,
@@ -3236,6 +3334,14 @@ static void DEH_LoadDehackedFile(MYFILE *f)
 	if (gamedataadded)
 		G_LoadGameData();
 
+	if (gamestate == GS_TITLESCREEN)
+	{
+		if (introchanged)
+			COM_BufAddText("playintro");
+		else if (titlechanged)
+			COM_BufAddText("exitgame"); // Command_ExitGame_f() but delayed
+	}
+
 	dbg_line = -1;
 	if (deh_num_warning)
 	{
@@ -3302,6 +3408,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_STND",
 	"S_PLAY_WAIT",
 	"S_PLAY_WALK",
+	"S_PLAY_SKID",
 	"S_PLAY_RUN",
 	"S_PLAY_DASH",
 	"S_PLAY_PAIN",
@@ -6422,8 +6529,12 @@ static const char *const MAPTHINGFLAG_LIST[4] = {
 #endif
 
 static const char *const PLAYERFLAG_LIST[] = {
-	// Flip camera angle with gravity flip prefrence.
-	"FLIPCAM",
+
+	// Cvars
+	"FLIPCAM", // Flip camera angle with gravity flip prefrence.
+	"ANALOGMODE", // Analog mode?
+	"DIRECTIONCHAR", // Directional character sprites?
+	"AUTOBRAKE", // Autobrake?
 
 	// Cheats
 	"GODMODE",
@@ -6441,41 +6552,36 @@ static const char *const PLAYERFLAG_LIST[] = {
 	"JUMPSTASIS", // and that includes jumping.
 	// (we don't include FULLSTASIS here I guess because it's just those two together...?)
 
-	// Did you get a time-over?
-	"TIMEOVER",
+	// Applying autobrake?
+	"APPLYAUTOBRAKE",
 
 	// Character action status
 	"STARTJUMP",
 	"JUMPED",
+	"NOJUMPDAMAGE",
+
 	"SPINNING",
 	"STARTDASH",
-	"THOKKED",
 
-	// Are you gliding?
+	"THOKKED",
+	"SHIELDABILITY",
 	"GLIDING",
+	"BOUNCING",
 
 	// Sliding (usually in water) like Labyrinth/Oil Ocean
 	"SLIDING",
 
-	// Bouncing
-	"BOUNCING",
-
-	/*** NIGHTS STUFF ***/
+	// NiGHTS stuff
 	"TRANSFERTOCLOSEST",
-	"NIGHTSFALL",
 	"DRILLING",
-	"SKIDDOWN",
 
-	/*** TAG STUFF ***/
-	"TAGGED", // Player has been tagged and awaits the next round in hide and seek.
+	// Gametype-specific stuff
+	"GAMETYPEOVER", // Race time over, or H&S out-of-game
 	"TAGIT", // The player is it! For Tag Mode
 
 	/*** misc ***/
 	"FORCESTRAFE", // Translate turn inputs into strafe inputs
-	"ANALOGMODE", // Analog mode?
 	"CANCARRY", // Can carry?
-	"SHIELDABILITY", // Thokked with shield ability
-	"NOJUMPDAMAGE", // No jump damage
 
 	NULL // stop loop here.
 };
@@ -6641,6 +6747,7 @@ static const char *const POWERS_LIST[] = {
 	"UNDERWATER", // underwater timer
 	"SPACETIME", // In space, no one can hear you spin!
 	"EXTRALIFE", // Extra Life timer
+	"PUSHING",
 
 	"SUPER", // Are you super?
 	"GRAVITYBOOTS", // gravity boots
@@ -6891,6 +6998,7 @@ struct {
 	{"CR_GENERIC",CR_GENERIC},
 	{"CR_PLAYER",CR_PLAYER},
 	{"CR_NIGHTSMODE",CR_NIGHTSMODE},
+	{"CR_NIGHTSFALL",CR_NIGHTSFALL},
 	{"CR_BRAKGOOP",CR_BRAKGOOP},
 	{"CR_ZOOMTUBE",CR_ZOOMTUBE},
 	{"CR_ROPEHANG",CR_ROPEHANG},
@@ -7290,6 +7398,20 @@ static spritenum_t get_sprite(const char *word)
 	return SPR_NULL;
 }
 
+static playersprite_t get_sprite2(const char *word)
+{ // Returns the value of SPR2_ enumerations
+	playersprite_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("SPR2_",word,5))
+		word += 5; // take off the SPR2_
+	for (i = 0; i < NUMPLAYERSPRITES; i++)
+		if (!spr2names[i][4] && memcmp(word,spr2names[i],4)==0)
+			return i;
+	deh_warning("Couldn't find sprite named 'SPR2_%s'",word);
+	return SPR2_STND;
+}
+
 static sfxenum_t get_sfx(const char *word)
 { // Returns the value of SFX_ enumerations
 	sfxenum_t i;
@@ -7735,7 +7857,7 @@ static inline int lib_freeslot(lua_State *L)
 		else if (fastcmp(type, "SPR2"))
 		{
 			// Search if we already have an SPR2 by that name...
-			enum playersprite i;
+			playersprite_t i;
 			for (i = SPR2_FIRSTFREESLOT; i < free_spr2; i++)
 				if (memcmp(spr2names[i],word,4) == 0)
 					break;
@@ -7914,7 +8036,7 @@ static inline int lib_getenum(lua_State *L)
 		if (mathlib) return luaL_error(L, "sprite '%s' could not be found.\n", word);
 		return 0;
 	}
-	else if (fastncmp("SPR2_",word,4)) {
+	else if (fastncmp("SPR2_",word,5)) {
 		p = word+5;
 		for (i = 0; i < (fixed_t)free_spr2; i++)
 			if (!spr2names[i][4])
@@ -8119,6 +8241,12 @@ static inline int lib_getenum(lua_State *L)
 	} else if (fastcmp(word,"paused")) {
 		lua_pushboolean(L, paused);
 		return 1;
+	} else if (fastcmp(word,"titlemap")) {
+		lua_pushinteger(L, titlemap);
+		return 1;
+	} else if (fastcmp(word,"titlemapinaction")) {
+		lua_pushboolean(L, (titlemapinaction != TITLEMAP_OFF));
+		return 1;
 	} else if (fastcmp(word,"gametype")) {
 		lua_pushinteger(L, gametype);
 		return 1;
diff --git a/src/doomstat.h b/src/doomstat.h
index 182f39ba4a619f69ba4b7118b10f12dfe74d0f74..d76907c3c7dd44270e65d5bcf72cecd77f7f384f 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -45,6 +45,10 @@ extern INT32 cursaveslot;
 extern INT16 lastmaploaded;
 extern boolean gamecomplete;
 
+#define maxgameovers 13
+extern UINT8 numgameovers;
+extern SINT8 startinglivesbalance[maxgameovers+1];
+
 #define PRECIP_NONE  0
 #define PRECIP_STORM 1
 #define PRECIP_SNOW  2
@@ -125,6 +129,10 @@ extern INT16 spstage_start;
 extern INT16 sstage_start;
 extern INT16 sstage_end;
 
+extern INT16 titlemap;
+extern boolean hidetitlepics;
+extern INT16 bootmap; //bootmap for loading a map on startup
+
 extern boolean looptitle;
 extern boolean useNightsSS;
 
diff --git a/src/f_finale.c b/src/f_finale.c
index db497daf7f26d59057e24c32f27c45fb521c73de..05c3d5fc0cf6edbca1a4768ecd09e40d0c47219b 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -31,11 +31,18 @@
 #include "m_random.h"
 #include "y_inter.h"
 #include "m_cond.h"
+#include "p_local.h"
+#include "p_setup.h"
+
+#ifdef HAVE_BLUA
+#include "lua_hud.h"
+#endif
 
 // Stage of animation:
 // 0 = text, 1 = art screen
 static INT32 finalecount;
 INT32 titlescrollspeed = 80;
+UINT8 titlemapinaction = TITLEMAP_OFF;
 
 static INT32 timetonext; // Delay between screen changes
 static INT32 continuetime; // Short delay when continuing
@@ -217,17 +224,19 @@ static void F_SkyScroll(INT32 scrollspeed)
 {
 	INT32 scrolled, x, mx, fakedwidth;
 	patch_t *pat;
+	INT16 patwidth;
 
 	pat = W_CachePatchName("TITLESKY", PU_CACHE);
 
-	animtimer = ((finalecount*scrollspeed)/16) % SHORT(pat->width);
+	patwidth = SHORT(pat->width);
+	animtimer = ((finalecount*scrollspeed)/16 + patwidth) % patwidth;
 
 	fakedwidth = vid.width / vid.dupx;
 
 	if (rendermode == render_soft)
 	{ // if only hardware rendering could be this elegant and complete
-		scrolled = (SHORT(pat->width) - animtimer) - 1;
-		for (x = 0, mx = scrolled; x < fakedwidth; x++, mx = (mx+1)%SHORT(pat->width))
+		scrolled = (patwidth - animtimer) - 1;
+		for (x = 0, mx = scrolled; x < fakedwidth; x++, mx = (mx+1)%patwidth)
 			F_DrawPatchCol(x, pat, mx);
 	}
 #ifdef HWRENDER
@@ -235,8 +244,8 @@ static void F_SkyScroll(INT32 scrollspeed)
 	{ // if only software rendering could be this simple and retarded
 		scrolled = animtimer;
 		if (scrolled > 0)
-			V_DrawScaledPatch(scrolled - SHORT(pat->width), 0, 0, pat);
-		for (x = 0; x < fakedwidth; x += SHORT(pat->width))
+			V_DrawScaledPatch(scrolled - patwidth, 0, 0, pat);
+		for (x = 0; x < fakedwidth; x += patwidth)
 			V_DrawScaledPatch(x + scrolled, 0, 0, pat);
 	}
 #endif
@@ -278,6 +287,8 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
 
 void F_StartIntro(void)
 {
+	S_StopMusic();
+
 	if (introtoplay)
 	{
 		if (!cutscenes[introtoplay - 1])
@@ -998,7 +1009,7 @@ static const char *credits[] = {
 	"",
 	"\1Sprite Artists",
 	"Odi \"Iceman404\" Atunzu",
-	"Victor \"VAdaPEGA\" Ara\x1Fjo", // Araújo -- sorry for our limited font! D:
+	"Victor \"VAdaPEGA\" Ara\x1Fjo", // Araújo -- sorry for our limited font! D:
 	"Jim \"MotorRoach\" DeMello",
 	"Desmond \"Blade\" DesJardins",
 	"Sherman \"CoatRack\" DesJardins",
@@ -1114,7 +1125,7 @@ void F_StartCredits(void)
 	M_ClearMenus(true);
 
 	// Save the second we enter the credits
-	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot >= 0)
+	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
 		G_SaveGame((UINT32)cursaveslot);
 
 	if (creditscutscene)
@@ -1252,7 +1263,7 @@ static boolean drawemblem = false, drawchaosemblem = false;
 void F_StartGameEvaluation(void)
 {
 	// Credits option in secrets menu
-	if (cursaveslot == -2)
+	if (cursaveslot == -1)
 	{
 		F_StartGameEnd();
 		return;
@@ -1266,7 +1277,7 @@ void F_StartGameEvaluation(void)
 	// Save the second we enter the evaluation
 	// We need to do this again!  Remember, it's possible a mod designed skipped
 	// the credits sequence!
-	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot >= 0)
+	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
 		G_SaveGame((UINT32)cursaveslot);
 
 	gameaction = ga_nothing;
@@ -1415,17 +1426,72 @@ void F_GameEndTicker(void)
 // ==============
 void F_StartTitleScreen(void)
 {
+	S_ChangeMusicInternal("_title", looptitle);
+
 	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
 		finalecount = 0;
 	else
 		wipegamestate = GS_TITLESCREEN;
+
+	if (titlemap)
+	{
+		mapthing_t *startpos;
+
+		gamestate_t prevwipegamestate = wipegamestate;
+		titlemapinaction = TITLEMAP_LOADING;
+		gamemap = titlemap;
+
+		if (!mapheaderinfo[gamemap-1])
+			P_AllocMapHeader(gamemap-1);
+
+		maptol = mapheaderinfo[gamemap-1]->typeoflevel;
+		globalweather = mapheaderinfo[gamemap-1]->weather;
+
+		G_DoLoadLevel(true);
+		if (!titlemap)
+			return;
+
+		players[displayplayer].playerstate = PST_DEAD; // Don't spawn the player in dummy (I'm still a filthy cheater)
+
+		// Set Default Position
+		if (playerstarts[0])
+			startpos = playerstarts[0];
+		else if (deathmatchstarts[0])
+			startpos = deathmatchstarts[0];
+		else
+			startpos = NULL;
+
+		if (startpos)
+		{
+			camera.x = startpos->x << FRACBITS;
+			camera.y = startpos->y << FRACBITS;
+			camera.subsector = R_PointInSubsector(camera.x, camera.y);
+			camera.z = camera.subsector->sector->floorheight + ((startpos->options >> ZSHIFT) << FRACBITS);
+			camera.angle = (startpos->angle % 360)*ANG1;
+			camera.aiming = 0;
+		}
+		else
+		{
+			camera.x = camera.y = camera.z = camera.angle = camera.aiming = 0;
+			camera.subsector = NULL; // toast is filthy too
+		}
+
+		camera.chase = true;
+		camera.height = 0;
+
+		wipegamestate = prevwipegamestate;
+	}
+	else
+	{
+		titlemapinaction = TITLEMAP_OFF;
+		gamemap = 1; // g_game.c
+		CON_ClearHUD();
+	}
+
 	G_SetGamestate(GS_TITLESCREEN);
-	CON_ClearHUD();
 
 	// IWAD dependent stuff.
 
-	S_ChangeMusicInternal("_title", looptitle);
-
 	animtimer = 0;
 
 	demoDelayLeft = demoDelayTime;
@@ -1455,12 +1521,21 @@ void F_TitleScreenDrawer(void)
 		return; // We likely came here from retrying. Don't do a damn thing.
 
 	// Draw that sky!
-	F_SkyScroll(titlescrollspeed);
+	if (!titlemapinaction)
+		F_SkyScroll(titlescrollspeed);
 
 	// Don't draw outside of the title screewn, or if the patch isn't there.
 	if (!ttwing || (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS))
 		return;
 
+	// rei|miru: use title pics?
+	if (hidetitlepics)
+#ifdef HAVE_BLUA
+		goto luahook;
+#else
+		return;
+#endif
+
 	V_DrawScaledPatch(30, 14, 0, ttwing);
 
 	if (finalecount < 57)
@@ -1497,6 +1572,11 @@ void F_TitleScreenDrawer(void)
 	}
 
 	V_DrawScaledPatch(48, 142, 0,ttbanner);
+
+#ifdef HAVE_BLUA
+luahook:
+	LUAh_TitleHUD();
+#endif
 }
 
 // (no longer) De-Demo'd Title Screen
@@ -1509,6 +1589,46 @@ void F_TitleScreenTicker(boolean run)
 	if (gameaction != ga_nothing || gamestate != GS_TITLESCREEN)
 		return;
 
+	// Execute the titlemap camera settings
+	if (titlemapinaction)
+	{
+		thinker_t *th;
+		mobj_t *mo2;
+		mobj_t *cameraref = NULL;
+
+		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		{
+			if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				continue;
+
+			mo2 = (mobj_t *)th;
+
+			 if (!mo2)
+				continue;
+
+			if (mo2->type != MT_ALTVIEWMAN)
+				continue;
+
+			cameraref = mo2;
+			break;
+		}
+
+		if (cameraref)
+		{
+			camera.x = cameraref->x;
+			camera.y = cameraref->y;
+			camera.z = cameraref->z;
+			camera.angle = cameraref->angle;
+			camera.aiming = cameraref->cusval;
+			camera.subsector = cameraref->subsector;
+		}
+		else
+		{
+			// Default behavior: Do a lil' camera spin if a title map is loaded;
+			camera.angle += titlescrollspeed*ANG1/64;
+		}
+	}
+
 	// no demos to play? or, are they disabled?
 	if (!cv_rollingdemos.value || !numDemos)
 		return;
diff --git a/src/f_finale.h b/src/f_finale.h
index 1f23643bec2bd52a91cd3477ded284eb2a842484..aadc64ad08e8abb9547155c8ada4420b49468146 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -62,6 +62,15 @@ void F_ContinueDrawer(void);
 
 extern INT32 titlescrollspeed;
 
+typedef enum
+{
+	TITLEMAP_OFF = 0,
+	TITLEMAP_LOADING,
+	TITLEMAP_RUNNING
+} titlemap_enum;
+
+extern UINT8 titlemapinaction;
+
 //
 // WIPE
 //
diff --git a/src/g_game.c b/src/g_game.c
index 0c42a95878ff6a4555ec494463494c0338d0d4d7..0d5d35ec595b9fdab5b93d93df9f27677707025b 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -76,11 +76,14 @@ INT16 gamemap = 1;
 INT16 maptol;
 UINT8 globalweather = 0;
 INT32 curWeather = PRECIP_NONE;
-INT32 cursaveslot = -1; // Auto-save 1p savegame slot
+INT32 cursaveslot = 0; // Auto-save 1p savegame slot
 //INT16 lastmapsaved = 0; // Last map we auto-saved at
 INT16 lastmaploaded = 0; // Last map the game loaded
 boolean gamecomplete = false;
 
+UINT8 numgameovers = 0; // for startinglives balance
+SINT8 startinglivesbalance[maxgameovers+1] = {3, 5, 7, 9, 12, 15, 20, 25, 30, 40, 50, 75, 99, 0x7F};
+
 UINT16 mainwads = 0;
 boolean modifiedgame; // Set if homebrew PWAD stuff has been added.
 boolean savemoddata = false;
@@ -121,6 +124,10 @@ INT16 spstage_start;
 INT16 sstage_start;
 INT16 sstage_end;
 
+INT16 titlemap = 0;
+boolean hidetitlepics = false;
+INT16 bootmap; //bootmap for loading a map on startup
+
 boolean looptitle = false;
 boolean useNightsSS = false;
 
@@ -286,6 +293,10 @@ static void UserAnalog_OnChange(void);
 static void UserAnalog2_OnChange(void);
 static void Analog_OnChange(void);
 static void Analog2_OnChange(void);
+static void DirectionChar_OnChange(void);
+static void DirectionChar2_OnChange(void);
+static void AutoBrake_OnChange(void);
+static void AutoBrake2_OnChange(void);
 void SendWeaponPref(void);
 void SendWeaponPref2(void);
 
@@ -368,6 +379,14 @@ consvar_t cv_useranalog = {"useranalog", "Off", CV_SAVE|CV_CALL, CV_OnOff, UserA
 consvar_t cv_useranalog2 = {"useranalog2", "Off", CV_SAVE|CV_CALL, CV_OnOff, UserAnalog2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 #endif
 
+static CV_PossibleValue_t directionchar_cons_t[] = {{0, "Camera"}, {1, "Movement"}, {0, NULL}};
+
+// deez New User eXperiences
+consvar_t cv_directionchar = {"directionchar", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_directionchar2 = {"directionchar2", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_autobrake = {"autobrake", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_autobrake2 = {"autobrake2", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
 typedef enum
 {
 	AXISNONE = 0,
@@ -1615,6 +1634,46 @@ static void Analog2_OnChange(void)
 	SendWeaponPref2();
 }
 
+static void DirectionChar_OnChange(void)
+{
+	if (cv_directionchar.value)
+		players[consoleplayer].pflags |= PF_DIRECTIONCHAR;
+	else
+		players[consoleplayer].pflags &= ~PF_DIRECTIONCHAR;
+
+	SendWeaponPref();
+}
+
+static void DirectionChar2_OnChange(void)
+{
+	if (cv_directionchar2.value)
+		players[secondarydisplayplayer].pflags |= PF_DIRECTIONCHAR;
+	else
+		players[secondarydisplayplayer].pflags &= ~PF_DIRECTIONCHAR;
+
+	SendWeaponPref2();
+}
+
+static void AutoBrake_OnChange(void)
+{
+	if (cv_autobrake.value)
+		players[consoleplayer].pflags |= PF_AUTOBRAKE;
+	else
+		players[consoleplayer].pflags &= ~PF_AUTOBRAKE;
+
+	SendWeaponPref();
+}
+
+static void AutoBrake2_OnChange(void)
+{
+	if (cv_autobrake2.value)
+		players[secondarydisplayplayer].pflags |= PF_AUTOBRAKE;
+	else
+		players[secondarydisplayplayer].pflags &= ~PF_AUTOBRAKE;
+
+	SendWeaponPref2();
+}
+
 //
 // G_DoLoadLevel
 //
@@ -1633,6 +1692,21 @@ void G_DoLoadLevel(boolean resetplayer)
 	if (gamestate == GS_INTERMISSION)
 		Y_EndIntermission();
 
+	// cleanup
+	if (titlemapinaction == TITLEMAP_LOADING)
+	{
+		if (W_CheckNumForName(G_BuildMapName(gamemap)) == LUMPERROR)
+		{
+			titlemap = 0; // let's not infinite recursion ok
+			Command_ExitGame_f();
+			return;
+		}
+
+		titlemapinaction = TITLEMAP_RUNNING;
+	}
+	else
+		titlemapinaction = TITLEMAP_OFF;
+
 	G_SetGamestate(GS_LEVEL);
 
 	for (i = 0; i < MAXPLAYERS; i++)
@@ -1642,7 +1716,7 @@ void G_DoLoadLevel(boolean resetplayer)
 	}
 
 	// Setup the level.
-	if (!P_SetupLevel(false))
+	if (!P_SetupLevel(false)) // this never returns false?
 	{
 		// fail so reset game stuff
 		Command_ExitGame_f();
@@ -1989,6 +2063,7 @@ void G_Ticker(boolean run)
 			break;
 
 		case GS_TITLESCREEN:
+			if (titlemapinaction) P_Ticker(run); // then intentionally fall through
 		case GS_WAITINGPLAYERS:
 			F_TitleScreenTicker(run);
 			break;
@@ -2101,7 +2176,7 @@ void G_PlayerReborn(INT32 player)
 	jointime = players[player].jointime;
 	spectator = players[player].spectator;
 	outofcoop = players[player].outofcoop;
-	pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_ANALOGMODE));
+	pflags = (players[player].pflags & (PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE|PF_TAGIT|PF_GAMETYPEOVER));
 
 	// As long as we're not in multiplayer, carry over cheatcodes from map to map
 	if (!(netgame || multiplayer))
@@ -3127,8 +3202,11 @@ static void G_DoContinued(void)
 	tokenlist = 0;
 	token = 0;
 
+	if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && (!modifiedgame || savemoddata) && cursaveslot > 0)
+		G_SaveGameOver((UINT32)cursaveslot, true);
+
 	// Reset # of lives
-	pl->lives = (ultimatemode) ? 1 : 3;
+	pl->lives = (ultimatemode) ? 1 : startinglivesbalance[numgameovers];
 
 	D_MapChange(gamemap, gametype, ultimatemode, false, 0, false, false);
 
@@ -3467,59 +3545,6 @@ void G_SaveGameData(void)
 
 #define VERSIONSIZE 16
 
-#ifdef SAVEGAMES_OTHERVERSIONS
-static INT16 startonmapnum = 0;
-
-//
-// User wants to load a savegame from a different version?
-//
-static void M_ForceLoadGameResponse(INT32 ch)
-{
-	if (ch != 'y' && ch != KEY_ENTER)
-	{
-		//refused
-		Z_Free(savebuffer);
-		save_p = savebuffer = NULL;
-		startonmapnum = 0;
-		M_SetupNextMenu(&SP_LoadDef);
-		return;
-	}
-
-	// pick up where we left off.
-	save_p += VERSIONSIZE;
-	if (!P_LoadGame(startonmapnum))
-	{
-		M_ClearMenus(true); // so ESC backs out to title
-		M_StartMessage(M_GetText("Savegame file corrupted\n\nPress ESC\n"), NULL, MM_NOTHING);
-		Command_ExitGame_f();
-		Z_Free(savebuffer);
-		save_p = savebuffer = NULL;
-		startonmapnum = 0;
-
-		// no cheating!
-		memset(&savedata, 0, sizeof(savedata));
-		return;
-	}
-
-	// done
-	Z_Free(savebuffer);
-	save_p = savebuffer = NULL;
-	startonmapnum = 0;
-
-	//set cursaveslot to -1 so nothing gets saved.
-	cursaveslot = -1;
-
-	displayplayer = consoleplayer;
-	multiplayer = splitscreen = false;
-
-	if (setsizeneeded)
-		R_ExecuteSetViewSize();
-
-	M_ClearMenus(true);
-	CON_ToggleOff();
-}
-#endif
-
 //
 // G_InitFromSavegame
 // Can be called by the startup code or the menu task.
@@ -3612,13 +3637,13 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
 // G_SaveGame
 // Saves your game.
 //
-void G_SaveGame(UINT32 savegameslot)
+void G_SaveGame(UINT32 slot)
 {
 	boolean saved;
 	char savename[256] = "";
 	const char *backup;
 
-	sprintf(savename, savegamename, savegameslot);
+	sprintf(savename, savegamename, slot);
 	backup = va("%s",savename);
 
 	// save during evaluation or credits? game's over, folks!
@@ -3654,9 +3679,91 @@ void G_SaveGame(UINT32 savegameslot)
 	if (cv_debug && saved)
 		CONS_Printf(M_GetText("Game saved.\n"));
 	else if (!saved)
-		CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, savegameslot, savegamename);
+		CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, savegamename);
 }
 
+#define BADSAVE goto cleanup;
+#define CHECKPOS if (save_p >= end_p) BADSAVE
+void G_SaveGameOver(UINT32 slot, boolean modifylives)
+{
+	boolean saved = false;
+	size_t length;
+	char vcheck[VERSIONSIZE];
+	char savename[255];
+	const char *backup;
+
+	sprintf(savename, savegamename, slot);
+	backup = va("%s",savename);
+
+	length = FIL_ReadFile(savename, &savebuffer);
+	if (!length)
+	{
+		CONS_Printf(M_GetText("Couldn't read file %s\n"), savename);
+		return;
+	}
+
+	{
+		char temp[sizeof(timeattackfolder)];
+		UINT8 *end_p = savebuffer + length;
+		UINT8 *lives_p;
+		SINT8 pllives;
+
+		save_p = savebuffer;
+		// Version check
+		memset(vcheck, 0, sizeof (vcheck));
+		sprintf(vcheck, "version %d", VERSION);
+		if (strcmp((const char *)save_p, (const char *)vcheck)) BADSAVE
+		save_p += VERSIONSIZE;
+
+		// P_UnArchiveMisc()
+		(void)READINT16(save_p);
+		CHECKPOS
+		(void)READUINT16(save_p); // emeralds
+		CHECKPOS
+		READSTRINGN(save_p, temp, sizeof(temp)); // mod it belongs to
+		if (strcmp(temp, timeattackfolder)) BADSAVE
+
+		// P_UnArchivePlayer()
+		CHECKPOS
+		(void)READUINT16(save_p);
+		CHECKPOS
+
+		WRITEUINT8(save_p, numgameovers);
+		CHECKPOS
+
+		lives_p = save_p;
+		pllives = READSINT8(save_p); // lives
+		CHECKPOS
+		if (modifylives && pllives < startinglivesbalance[numgameovers])
+		{
+			pllives = startinglivesbalance[numgameovers];
+			WRITESINT8(lives_p, pllives);
+		}
+
+		(void)READINT32(save_p); // Score
+		CHECKPOS
+		(void)READINT32(save_p); // continues
+
+		// File end marker check
+		CHECKPOS
+		if (READUINT8(save_p) != 0x1d) BADSAVE;
+
+		// done
+		saved = FIL_WriteFile(backup, savebuffer, length);
+	}
+
+cleanup:
+	if (cv_debug && saved)
+		CONS_Printf(M_GetText("Game saved.\n"));
+	else if (!saved)
+		CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, savegamename);
+	Z_Free(savebuffer);
+	save_p = savebuffer = NULL;
+
+}
+#undef CHECKPOS
+#undef BADSAVE
+
 //
 // G_DeferedInitNew
 // Can be called by the startup code or the menu task,
@@ -3664,7 +3771,7 @@ void G_SaveGame(UINT32 savegameslot)
 //
 void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, boolean SSSG, boolean FLS)
 {
-	UINT8 color = 0;
+	UINT8 color = skins[pickedchar].prefcolor;
 	paused = false;
 
 	if (demoplayback)
@@ -3676,10 +3783,8 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, b
 
 	if (savedata.lives > 0)
 	{
-		color = savedata.skincolor;
-		botskin = savedata.botskin;
-		botcolor = savedata.botcolor;
-		botingame = (botskin != 0);
+		if ((botingame = ((botskin = savedata.botskin) != 0)))
+			botcolor = skins[botskin-1].prefcolor;
 	}
 	else if (splitscreen != SSSG)
 	{
@@ -3687,8 +3792,7 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, b
 		SplitScreen_OnChange();
 	}
 
-	if (!color)
-		color = skins[pickedchar].prefcolor;
+	color = skins[pickedchar].prefcolor;
 	SetPlayerSkinByNum(consoleplayer, pickedchar);
 	CV_StealthSet(&cv_skin, skins[pickedchar].name);
 	CV_StealthSetValue(&cv_playercolor, color);
@@ -3720,7 +3824,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	if (resetplayer)
 	{
 		// Clear a bunch of variables
-		tokenlist = token = sstimer = redscore = bluescore = lastmap = 0;
+		numgameovers = tokenlist = token = sstimer = redscore = bluescore = lastmap = 0;
 		countdown = countdown2 = 0;
 
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -3735,22 +3839,17 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 					players[i].lives = cv_startinglives.value;
 				players[i].continues = 0;
 			}
-			else if (pultmode)
-			{
-				players[i].lives = 1;
-				players[i].continues = 0;
-			}
 			else
 			{
-				players[i].lives = 3;
-				players[i].continues = 1;
+				players[i].lives = (pultmode) ? 1 : startinglivesbalance[0];
+				players[i].continues = (pultmode) ? 0 : 1;
 			}
 
 			if (!((netgame || multiplayer) && (FLS)))
 				players[i].score = 0;
 
 			// The latter two should clear by themselves, but just in case
-			players[i].pflags &= ~(PF_TAGIT|PF_TAGGED|PF_FULLSTASIS);
+			players[i].pflags &= ~(PF_TAGIT|PF_GAMETYPEOVER|PF_FULLSTASIS);
 
 			// Clear cheatcodes too, just in case.
 			players[i].pflags &= ~(PF_GODMODE|PF_NOCLIP|PF_INVIS);
diff --git a/src/g_game.h b/src/g_game.h
index 72a6f3d6e56e2e73f909134bbdfe39c76068fdc2..31eea061a917a7eb85c001a3c0b872b4faa66733 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -59,6 +59,8 @@ extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_mousemove;
 extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_mousemove2;
 extern consvar_t cv_useranalog, cv_useranalog2;
 extern consvar_t cv_analog, cv_analog2;
+extern consvar_t cv_directionchar, cv_directionchar2;
+extern consvar_t cv_autobrake, cv_autobrake2;
 extern consvar_t cv_sideaxis,cv_turnaxis,cv_moveaxis,cv_lookaxis,cv_fireaxis,cv_firenaxis;
 extern consvar_t cv_sideaxis2,cv_turnaxis2,cv_moveaxis2,cv_lookaxis2,cv_fireaxis2,cv_firenaxis2;
 extern consvar_t cv_ghost_bestscore, cv_ghost_besttime, cv_ghost_bestrings, cv_ghost_last, cv_ghost_guest;
@@ -116,6 +118,8 @@ void G_SaveGameData(void);
 
 void G_SaveGame(UINT32 slot);
 
+void G_SaveGameOver(UINT32 slot, boolean modifylives);
+
 // Only called by startup code.
 void G_RecordDemo(const char *name);
 void G_RecordMetal(void);
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index d74cd0587778e9181e63d75d6de7ce01ce0eafb9..cc3f404028a4c440acfc7fb02765fd14441e0c9e 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -4753,8 +4753,6 @@ static polyplaneinfo_t *polyplaneinfo = NULL;
 #ifndef SORTING
 size_t numfloors = 0;
 #else
-//static floorinfo_t *floorinfo = NULL;
-//static size_t numfloors = 0;
 //Hurdler: 3D water sutffs
 typedef struct gr_drawnode_s
 {
@@ -5241,7 +5239,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		// Note: DO NOT do this in software mode version, it actually
 		// makes papersprites look WORSE there (I know, I've tried)
 		// Monster Iestyn - 13/05/17
-		ang = dup_viewangle - thing->angle;
+		ang = dup_viewangle - (thing->player ? thing->player->drawangle : thing->angle);
 		ang_scale = FIXED_TO_FLOAT(FINESINE(ang>>ANGLETOFINESHIFT));
 		ang_scalez = FIXED_TO_FLOAT(FINECOSINE(ang>>ANGLETOFINESHIFT));
 
@@ -5252,7 +5250,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		}
 	}
 	else if (sprframe->rotate != SRF_SINGLE)
-		ang = R_PointToAngle (thing->x, thing->y) - thing->angle;
+		ang = R_PointToAngle (thing->x, thing->y) - (thing->player ? thing->player->drawangle : thing->angle);
 
 	if (sprframe->rotate == SRF_SINGLE)
 	{
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index b0a14d3b58640b3d43ec10e91cb5de75b1f70a93..cb49f817ce1cf1f3e294755eebde0c19e9d08b74 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -46,8 +46,7 @@ void HWR_SetViewSize(void);
 void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option);
 void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t scale, INT32 option, const UINT8 *colormap);
 void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t scale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
-void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, INT32 option, fixed_t scale, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
-void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipmap, boolean makebitmap);
+void HWR_MakePatch(const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipmap, boolean makebitmap);
 void HWR_CreatePlanePolygons(INT32 bspnum);
 void HWR_CreateStaticLightmaps(INT32 bspnum);
 void HWR_PrepLevelCache(size_t pnumtextures);
@@ -104,13 +103,4 @@ extern float gr_viewwindowx, gr_basewindowcentery;
 extern fixed_t *hwbbox;
 extern FTransform atransform;
 
-typedef struct
-{
-	wallVert3D    floorVerts[4];
-	FSurfaceInfo  Surf;
-	INT32           texnum;
-	INT32           blend;
-	INT32           drawcount;
-} floorinfo_t;
-
 #endif
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index df2c9f59a60b675f5aacfdce1be23a30064af3bd..b34ddfc01d5e55501d55271fc5a07f5d9e3902a2 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -28,6 +28,7 @@
 
 #include "../doomdef.h"
 #include "../doomstat.h"
+#include "../fastcmp.h"
 
 #ifdef HWRENDER
 #include "hw_drv.h"
@@ -265,6 +266,9 @@ static void md2_freeModel (md2_model_t *model)
 			free(model->frames);
 		}
 
+		if (model->spr2frames)
+			free(model->spr2frames);
+
 		if (model->glCommandBuffer)
 			free(model->glCommandBuffer);
 
@@ -395,6 +399,39 @@ static md2_model_t *md2_readModel(const char *filename)
 			}
 
 			strcpy(model->frames[i].name, frame->name);
+			if (frame->name[0] == 'S')
+			{
+				boolean super;
+				if ((super = (fastncmp("UPER", frame->name+1, 4))) // SUPER
+					|| fastncmp("PR2_", frame->name+1, 4)) // SPR2_
+				{
+					UINT8 spr2;
+					for (spr2 = 0; spr2 < free_spr2; spr2++)
+						if (fastncmp(frame->name+5,spr2names[spr2],3)
+						&& ((frame->name[8] == spr2names[spr2][3])
+							|| (frame->name[8] == '.' && spr2names[spr2][3] == '_')))
+							break;
+
+					if (spr2 < free_spr2)
+					{
+						if (!model->spr2frames)
+						{
+							model->spr2frames = calloc(sizeof (size_t), 2*NUMPLAYERSPRITES*2);
+							if (!model->spr2frames)
+							{
+								md2_freeModel (model);
+								fclose(file);
+								return 0;
+							}
+						}
+						if (super)
+							spr2 |= FF_SPR2SUPER;
+						if (model->spr2frames[spr2*2 + 1]++ == 0) // numspr2frames
+							model->spr2frames[spr2*2] = i; // starting frame
+						CONS_Debug(DBG_RENDER, "frame %s, sprite2 %s - starting frame %d, number of frames %d\n", frame->name, spr2names[spr2 & ~FF_SPR2SUPER], model->spr2frames[spr2*2], model->spr2frames[spr2*2 + 1]);
+					}
+				}
+			}
 			for (j = 0; j < model->header.numVertices; j++)
 			{
 				model->frames[i].vertices[j].vertex[0] = (float) ((INT32) frame->alias_vertices[j].vertex[0]) * frame->scale[0] + frame->translate[0];
@@ -1078,6 +1115,51 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, con
 	res?
 	run?
 	*/
+
+static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *player)
+{
+	UINT8 super = 0, i = 0;
+
+	if (!md2 || !skin)
+		return 0;
+
+	while (!(md2->model->spr2frames[spr2*2 + 1])
+		&& spr2 != SPR2_STND
+		&& ++i != 32) // recursion limiter
+	{
+		if (spr2 & FF_SPR2SUPER)
+		{
+			super = FF_SPR2SUPER;
+			spr2 &= ~FF_SPR2SUPER;
+			continue;
+		}
+
+		switch(spr2)
+		{
+
+		// Normal special cases.
+		case SPR2_JUMP:
+			spr2 = ((player
+					? player->charflags
+					: skin->flags)
+					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
+			break;
+		case SPR2_TIRE:
+			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
+			break;
+
+		// Use the handy list, that's what it's there for!
+		default:
+			spr2 = spr2defaults[spr2];
+			break;
+		}
+
+		spr2 |= super;
+	}
+
+	return spr2;
+}
+
 #define NORMALFOG 0x00000000
 #define FADEFOG 0x19000000
 void HWR_DrawMD2(gr_vissprite_t *spr)
@@ -1225,32 +1307,71 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			tics = spr->mobj->anim_duration;
 		}
 
-		//FIXME: this is not yet correct
-		frame = (spr->mobj->frame & FF_FRAMEMASK) % md2->model->header.numFrames;
-		buff = md2->model->glCommandBuffer;
-		curr = &md2->model->frames[frame];
-		if (cv_grmd2.value == 1)
+#define INTERPOLERATION_LIMIT TICRATE/4
+
+		if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY && md2->model->spr2frames)
 		{
-			// frames are handled differently for states with FF_ANIMATE, so get the next frame differently for the interpolation
-			if (spr->mobj->frame & FF_ANIMATE)
+			UINT8 spr2 = P_GetModelSprite2(md2, spr->mobj->skin, spr->mobj->sprite2, spr->mobj->player);
+			UINT8 mod = md2->model->spr2frames[spr2*2 + 1] ? md2->model->spr2frames[spr2*2 + 1] : md2->model->header.numFrames;
+			if (mod > ((skin_t *)spr->mobj->skin)->sprites[spr2].numframes)
+				mod = ((skin_t *)spr->mobj->skin)->sprites[spr2].numframes;
+			//FIXME: this is not yet correct
+			frame = (spr->mobj->frame & FF_FRAMEMASK);
+			if (frame >= mod)
+				frame = 0;
+			buff = md2->model->glCommandBuffer;
+			curr = &md2->model->frames[md2->model->spr2frames[spr2*2] + frame];
+			if (cv_grmd2.value == 1 && tics <= INTERPOLERATION_LIMIT)
 			{
-				UINT32 nextframe = (spr->mobj->frame & FF_FRAMEMASK) + 1;
-				if (nextframe >= (UINT32)spr->mobj->state->var1)
-					nextframe = (spr->mobj->state->frame & FF_FRAMEMASK);
-				nextframe %= md2->model->header.numFrames;
-				next = &md2->model->frames[nextframe];
+				if (durs > INTERPOLERATION_LIMIT)
+					durs = INTERPOLERATION_LIMIT;
+
+				if (spr->mobj->frame & FF_ANIMATE
+					|| (spr->mobj->state->nextstate != S_NULL
+					&& states[spr->mobj->state->nextstate].sprite == spr->mobj->sprite
+					&& (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) == spr->mobj->sprite2))
+				{
+					if (++frame >= mod)
+						frame = 0;
+					if (frame || !(spr->mobj->state->frame & FF_SPR2ENDSTATE))
+						next = &md2->model->frames[md2->model->spr2frames[spr2*2] + frame];
+				}
 			}
-			else
+		}
+		else
+		{
+			//FIXME: this is not yet correct
+			frame = (spr->mobj->frame & FF_FRAMEMASK) % md2->model->header.numFrames;
+			buff = md2->model->glCommandBuffer;
+			curr = &md2->model->frames[frame];
+			if (cv_grmd2.value == 1 && tics <= INTERPOLERATION_LIMIT)
 			{
-				if (spr->mobj->state->nextstate != S_NULL && states[spr->mobj->state->nextstate].sprite != SPR_NULL
-					&& !(spr->mobj->player && spr->mobj->state->nextstate == S_PLAY_WAIT && spr->mobj->state == &states[S_PLAY_STND]))
+				if (durs > INTERPOLERATION_LIMIT)
+					durs = INTERPOLERATION_LIMIT;
+
+				// frames are handled differently for states with FF_ANIMATE, so get the next frame differently for the interpolation
+				if (spr->mobj->frame & FF_ANIMATE)
 				{
-					const UINT32 nextframe = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % md2->model->header.numFrames;
+					UINT32 nextframe = (spr->mobj->frame & FF_FRAMEMASK) + 1;
+					if (nextframe >= (UINT32)spr->mobj->state->var1)
+						nextframe = (spr->mobj->state->frame & FF_FRAMEMASK);
+					nextframe %= md2->model->header.numFrames;
 					next = &md2->model->frames[nextframe];
 				}
+				else
+				{
+					if (spr->mobj->state->nextstate != S_NULL
+					&& states[spr->mobj->state->nextstate].sprite == spr->mobj->sprite)
+					{
+						const UINT32 nextframe = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % md2->model->header.numFrames;
+						next = &md2->model->frames[nextframe];
+					}
+				}
 			}
 		}
 
+#undef INTERPOLERATION_LIMIT
+
 		//Hurdler: it seems there is still a small problem with mobj angle
 		p.x = FIXED_TO_FLOAT(spr->mobj->x);
 		p.y = FIXED_TO_FLOAT(spr->mobj->y)+md2->offset;
@@ -1269,7 +1390,7 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 
 		if (sprframe->rotate)
 		{
-			const fixed_t anglef = AngleFixed(spr->mobj->angle);
+			const fixed_t anglef = AngleFixed((spr->mobj->player ? spr->mobj->player->drawangle : spr->mobj->angle));
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
 		else
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 299d1240005daba28769c1e8b5e12adfd1b67401..c7cda35af6f308c1f8fa28e0fb8358fd55542996 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -22,6 +22,7 @@
 #define _HW_MD2_H_
 
 #include "hw_glob.h"
+#include "../info.h"
 
 // magic number "IDP2" or 844121161
 #define MD2_IDENT                       (INT32)(('2' << 24) + ('P' << 16) + ('D' << 8) + 'I')
@@ -111,7 +112,8 @@ typedef struct
 	md2_textureCoordinate_t *texCoords;
 	md2_triangle_t          *triangles;
 	md2_frame_t             *frames;
-	INT32                     *glCommandBuffer;
+	size_t                  *spr2frames; // size_t spr2frames[2*NUMPLAYERSPRITES][2];
+	INT32                   *glCommandBuffer;
 } ATTRPACK md2_model_t;
 
 #if defined(_MSC_VER)
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index e92b969957d1b109290d4fca2fce70563c7d7ba4..b49d3eb96c62bcf87b2e16948463c916b23bd7e8 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -83,6 +83,7 @@ patch_t *rmatcico;
 patch_t *bmatcico;
 patch_t *tagico;
 patch_t *tallminus;
+patch_t *tallinfin;
 
 //-------------------------------------------
 //              coop hud
@@ -235,6 +236,7 @@ void HU_LoadGraphics(void)
 
 	// minus for negative tallnums
 	tallminus = (patch_t *)W_CachePatchName("STTMINUS", PU_HUDGFX);
+	tallinfin = (patch_t *)W_CachePatchName("STTINFIN", PU_HUDGFX);
 
 	// cache the crosshairs, don't bother to know which one is being used,
 	// just cache all 3, they're so small anyway.
@@ -1250,7 +1252,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 			}
 		}
 
-		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3))) //show lives
+		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != 0x7f)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE|(greycheck ? V_60TRANS : 0), va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 		{
@@ -1388,7 +1390,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		             | (greycheck ? V_TRANSLUCENT : 0)
 		             | V_ALLOWLOWERCASE, name);
 
-		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3))) //show lives
+		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != 0x7f)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE, va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 			V_DrawSmallScaledPatch(x-28, y-4, 0, tagico);
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index e757db85a97e43c6607b104d89a6c5c35fb4bb72..2dbeb556d7056e65b62f38b02785e3ef38dee942 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -71,6 +71,7 @@ extern patch_t *rmatcico;
 extern patch_t *bmatcico;
 extern patch_t *tagico;
 extern patch_t *tallminus;
+extern patch_t *tallinfin;
 extern patch_t *tokenicon;
 
 // set true when entering a chat message
diff --git a/src/info.c b/src/info.c
index 2d6c9a3d12bb9ad518506531b6ad8acd89bfae61..d62111640efa8660cc0b094dc826e76538d7872f 100644
--- a/src/info.c
+++ b/src/info.c
@@ -405,6 +405,7 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"STND",
 	"WAIT",
 	"WALK",
+	"SKID",
 	"RUN_",
 	"DASH",
 	"PAIN",
@@ -481,7 +482,89 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"SIGN",
 	"LIFE"
 };
-enum playersprite free_spr2 = SPR2_FIRSTFREESLOT;
+playersprite_t free_spr2 = SPR2_FIRSTFREESLOT;
+
+playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
+	0, // SPR2_STND,
+	0, // SPR2_WAIT,
+	0, // SPR2_WALK,
+	SPR2_WALK, // SPR2_SKID,
+	SPR2_WALK, // SPR2_RUN ,
+	SPR2_FRUN, // SPR2_DASH,
+	0, // SPR2_PAIN,
+	SPR2_PAIN, // SPR2_STUN,
+	SPR2_PAIN, // SPR2_DEAD,
+	SPR2_DEAD, // SPR2_DRWN,
+	0, // SPR2_ROLL,
+	SPR2_SPNG, // SPR2_GASP,
+	0, // SPR2_JUMP, (conditional)
+	SPR2_FALL, // SPR2_SPNG,
+	SPR2_WALK, // SPR2_FALL,
+	0, // SPR2_EDGE,
+	SPR2_FALL, // SPR2_RIDE,
+
+	SPR2_ROLL, // SPR2_SPIN,
+
+	SPR2_SPNG, // SPR2_FLY ,
+	SPR2_FLY , // SPR2_SWIM,
+	0, // SPR2_TIRE, (conditional)
+
+	SPR2_FLY , // SPR2_GLID,
+	SPR2_CLMB, // SPR2_CLNG,
+	SPR2_ROLL, // SPR2_CLMB,
+
+	SPR2_WALK, // SPR2_FLT ,
+	SPR2_RUN , // SPR2_FRUN,
+
+	SPR2_FALL, // SPR2_BNCE,
+	SPR2_ROLL, // SPR2_BLND,
+
+	0, // SPR2_FIRE,
+
+	SPR2_ROLL, // SPR2_TWIN,
+
+	SPR2_TWIN, // SPR2_MLEE,
+	0, // SPR2_MLEL,
+
+	0, // SPR2_TRNS,
+
+	FF_SPR2SUPER|SPR2_STND, // SPR2_NSTD,
+	FF_SPR2SUPER|SPR2_FLT , // SPR2_NFLT,
+	FF_SPR2SUPER|SPR2_STUN, // SPR2_NSTN,
+	SPR2_NSTN, // SPR2_NPUL,
+	FF_SPR2SUPER|SPR2_ROLL, // SPR2_NATK,
+
+	0, // SPR2_NGT0, (should never be referenced)
+	SPR2_NGT0, // SPR2_NGT1,
+	SPR2_NGT1, // SPR2_NGT2,
+	SPR2_NGT2, // SPR2_NGT3,
+	SPR2_NGT3, // SPR2_NGT4,
+	SPR2_NGT4, // SPR2_NGT5,
+	SPR2_NGT5, // SPR2_NGT6,
+	SPR2_NGT0, // SPR2_NGT7,
+	SPR2_NGT7, // SPR2_NGT8,
+	SPR2_NGT8, // SPR2_NGT9,
+	SPR2_NGT9, // SPR2_NGTA,
+	SPR2_NGTA, // SPR2_NGTB,
+	SPR2_NGTB, // SPR2_NGTC,
+
+	SPR2_NGT0, // SPR2_DRL0,
+	SPR2_NGT1, // SPR2_DRL1,
+	SPR2_NGT2, // SPR2_DRL2,
+	SPR2_NGT3, // SPR2_DRL3,
+	SPR2_NGT4, // SPR2_DRL4,
+	SPR2_NGT5, // SPR2_DRL5,
+	SPR2_NGT6, // SPR2_DRL6,
+	SPR2_NGT7, // SPR2_DRL7,
+	SPR2_NGT8, // SPR2_DRL8,
+	SPR2_NGT9, // SPR2_DRL9,
+	SPR2_NGTA, // SPR2_DRLA,
+	SPR2_NGTB, // SPR2_DRLB,
+	SPR2_NGTC, // SPR2_DRLC,
+
+	0, // SPR2_SIGN,
+	0, // SPR2_LIFE
+};
 
 // Doesn't work with g++, needs actionf_p1 (don't modify this comment)
 state_t states[NUMSTATES] =
@@ -512,6 +595,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_STND|FF_ANIMATE,    105, {NULL}, 0,  7, S_PLAY_WAIT}, // S_PLAY_STND
 	{SPR_PLAY, SPR2_WAIT|FF_ANIMATE,     -1, {NULL}, 0, 16, S_NULL},      // S_PLAY_WAIT
 	{SPR_PLAY, SPR2_WALK,                 4, {NULL}, 0,  0, S_PLAY_WALK}, // S_PLAY_WALK
+	{SPR_PLAY, SPR2_SKID,                 1, {NULL}, 0,  0, S_PLAY_WALK}, // S_PLAY_SKID
 	{SPR_PLAY, SPR2_RUN ,                 2, {NULL}, 0,  0, S_PLAY_RUN},  // S_PLAY_RUN
 	{SPR_PLAY, SPR2_DASH,                 2, {NULL}, 0,  0, S_PLAY_DASH}, // S_PLAY_DASH
 	{SPR_PLAY, SPR2_PAIN|FF_ANIMATE,    350, {NULL}, 0,  4, S_PLAY_FALL}, // S_PLAY_PAIN
diff --git a/src/info.h b/src/info.h
index cd79b12a98951a6116cbe9a0d7d88d6b9e8263d9..7bc7e673a6cf8e3b381dd20df5d18b1c4fe6639d 100644
--- a/src/info.h
+++ b/src/info.h
@@ -604,11 +604,12 @@ typedef enum sprite
 // Make sure to be conscious of FF_FRAMEMASK and the fact sprite2 is stored as a UINT8 whenever you change this table.
 // Currently, FF_FRAMEMASK is 0xff, or 255 - but the second half is used by FF_SPR2SUPER, so the limitation is 0x7f.
 // Since this is zero-based, there can be at most 128 different SPR2_'s without changing that.
-enum playersprite
+typedef enum playersprite
 {
 	SPR2_STND = 0,
 	SPR2_WAIT,
 	SPR2_WALK,
+	SPR2_SKID,
 	SPR2_RUN ,
 	SPR2_DASH,
 	SPR2_PAIN,
@@ -690,7 +691,7 @@ enum playersprite
 	SPR2_FIRSTFREESLOT,
 	SPR2_LASTFREESLOT = 0x7f,
 	NUMPLAYERSPRITES
-};
+} playersprite_t;
 
 typedef enum state
 {
@@ -713,6 +714,7 @@ typedef enum state
 	S_PLAY_STND,
 	S_PLAY_WAIT,
 	S_PLAY_WALK,
+	S_PLAY_SKID,
 	S_PLAY_RUN,
 	S_PLAY_DASH,
 	S_PLAY_PAIN,
@@ -3193,8 +3195,9 @@ typedef struct
 extern state_t states[NUMSTATES];
 extern char sprnames[NUMSPRITES + 1][5];
 extern char spr2names[NUMPLAYERSPRITES][5];
+extern playersprite_t spr2defaults[NUMPLAYERSPRITES];
 extern state_t *astate;
-extern enum playersprite free_spr2;
+extern playersprite_t free_spr2;
 
 typedef enum mobj_type
 {
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index be14554153962e68d0aeec8c84043b2d2e156b39..b88a9712e1a0c06e137e98a2d7bcdbffa36db70f 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -462,6 +462,8 @@ static int lib_pSpawnLockOn(lua_State *L)
 		return LUA_ErrInvalid(L, "mobj_t");
 	if (!player)
 		return LUA_ErrInvalid(L, "player_t");
+	if (state >= NUMSTATES)
+		return luaL_error(L, "state %d out of range (0 - %d)", state, NUMSTATES-1);
 	if (P_IsLocalPlayer(player)) // Only display it on your own view.
 	{
 		mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
diff --git a/src/lua_hud.h b/src/lua_hud.h
index ba0a1d8941deff4c5b19e40cae1368c19ad86b18..beaca7883e4425818539cbd9d0866e4e6cf3b8cc 100644
--- a/src/lua_hud.h
+++ b/src/lua_hud.h
@@ -42,3 +42,4 @@ boolean LUA_HudEnabled(enum hud option);
 
 void LUAh_GameHUD(player_t *stplyr);
 void LUAh_ScoresHUD(void);
+void LUAh_TitleHUD(void);
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 8175f1b9beacb2ac9e6920a66922c125d474500d..68a69cd1d2934e3af83f24c804aad7368b6f7359 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -88,11 +88,13 @@ static const char *const patch_opt[] = {
 
 enum hudhook {
 	hudhook_game = 0,
-	hudhook_scores
+	hudhook_scores,
+	hudhook_title
 };
 static const char *const hudhook_opt[] = {
 	"game",
 	"scores",
+	"title",
 	NULL};
 
 // alignment types for v.drawString
@@ -808,6 +810,9 @@ int LUA_HudLib(lua_State *L)
 
 		lua_newtable(L);
 		lua_rawseti(L, -2, 3); // HUD[2] = scores rendering functions array
+
+		lua_newtable(L);
+		lua_rawseti(L, -2, 4); // HUD[3] = title rendering functions array
 	lua_setfield(L, LUA_REGISTRYINDEX, "HUD");
 
 	luaL_newmetatable(L, META_HUDINFO);
@@ -920,4 +925,29 @@ void LUAh_ScoresHUD(void)
 	hud_running = false;
 }
 
+void LUAh_TitleHUD(void)
+{
+	if (!gL || !(hudAvailable & (1<<hudhook_title)))
+		return;
+
+	hud_running = true;
+	lua_pop(gL, -1);
+
+	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
+	I_Assert(lua_istable(gL, -1));
+	lua_rawgeti(gL, -1, 4); // HUD[4] = rendering funcs
+	I_Assert(lua_istable(gL, -1));
+
+	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
+	I_Assert(lua_istable(gL, -1));
+	lua_remove(gL, -3); // pop HUD
+	lua_pushnil(gL);
+	while (lua_next(gL, -3) != 0) {
+		lua_pushvalue(gL, -3); // graphics library (HUD[1])
+		LUA_Call(gL, 1);
+	}
+	lua_pop(gL, -1);
+	hud_running = false;
+}
+
 #endif
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 9361abe94e9ec06cf622a7999820e169c2270316..c3803f7e246f395bd761d953b7a091b94aa28eae 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -127,6 +127,74 @@ static int lib_getSpr2name(lua_State *L)
 	return 0;
 }
 
+static int lib_getSpr2default(lua_State *L)
+{
+	UINT32 i;
+
+	lua_remove(L, 1); // don't care about spr2defaults[] dummy userdata.
+
+	if (lua_isnumber(L, 1))
+		i = lua_tonumber(L, 1);
+	else if (lua_isstring(L, 1))
+	{
+		const char *name = lua_tostring(L, 1);
+		for (i = 0; i < free_spr2; i++)
+			if (fastcmp(name, spr2names[i]))
+				break;
+	}
+	else
+		return luaL_error(L, "spr2defaults[] invalid index");
+
+	if (i >= free_spr2)
+		return 0;
+
+	lua_pushinteger(L, spr2defaults[i]);
+	return 1;
+}
+
+static int lib_setSpr2default(lua_State *L)
+{
+	UINT32 i;
+	UINT8 j = 0;
+
+	lua_remove(L, 1); // don't care about spr2defaults[] dummy userdata.
+
+	if (lua_isnumber(L, 1))
+		i = lua_tonumber(L, 1);
+	else if (lua_isstring(L, 1))
+	{
+		const char *name = lua_tostring(L, 1);
+		for (i = 0; i < free_spr2; i++)
+			if (fastcmp(name, spr2names[i]))
+				break;
+			if (i == free_spr2)
+				return luaL_error(L, "spr2defaults[] invalid index");
+	}
+	else
+		return luaL_error(L, "spr2defaults[] invalid index");
+
+	if (i < SPR2_FIRSTFREESLOT || i >= free_spr2)
+		return luaL_error(L, "spr2defaults[] index %d out of range (%d - %d)", i, SPR2_FIRSTFREESLOT, free_spr2-1);
+
+	if (lua_isnumber(L, 2))
+		j = lua_tonumber(L, 2);
+	else if (lua_isstring(L, 2))
+	{
+		const char *name = lua_tostring(L, 2);
+		for (j = 0; j < free_spr2; j++)
+			if (fastcmp(name, spr2names[j]))
+				break;
+			if (j == free_spr2)
+				return luaL_error(L, "spr2defaults[] invalid index");
+	}
+
+	if (j >= free_spr2)
+		j = 0; // return luaL_error(L, "spr2defaults[] set %d out of range (%d - %d)", j, 0, free_spr2-1);
+
+	spr2defaults[i] = j;
+	return 0;
+}
+
 static int lib_spr2namelen(lua_State *L)
 {
 	lua_pushinteger(L, free_spr2);
@@ -984,6 +1052,19 @@ int LUA_InfoLib(lua_State *L)
 		lua_setmetatable(L, -2);
 	lua_setglobal(L, "spr2names");
 
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_pushcfunction(L, lib_getSpr2default);
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, lib_setSpr2default);
+			lua_setfield(L, -2, "__newindex");
+
+			lua_pushcfunction(L, lib_spr2namelen);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, "spr2defaults");
+
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
 			lua_pushcfunction(L, lib_getState);
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index b8a6ea4fd5938090c85ba6f8ea085a43d2e51f3f..7c55012d22c1e8bfe2472eb808482c39617906ef 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -126,6 +126,8 @@ static int player_get(lua_State *L)
 		lua_pushfixed(L, plr->bob);
 	else if (fastcmp(field,"aiming"))
 		lua_pushangle(L, plr->aiming);
+	else if (fastcmp(field,"drawangle"))
+		lua_pushangle(L, plr->drawangle);
 	else if (fastcmp(field,"rings"))
 		lua_pushinteger(L, plr->rings);
 	else if (fastcmp(field,"pity"))
@@ -386,6 +388,8 @@ static int player_set(lua_State *L)
 		else if (plr == &players[secondarydisplayplayer])
 			localaiming2 = plr->aiming;
 	}
+	else if (fastcmp(field,"drawangle"))
+		plr->drawangle = luaL_checkangle(L, 3);
 	else if (fastcmp(field,"rings"))
 		plr->rings = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"pity"))
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 2c17203bcbd79361f33f4e46e4fdea55ccb4ba22..0d68241253e6095a6586e302346d389cd470e7b0 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -61,7 +61,7 @@ static UINT8 cheatf_ultimate(void)
 	if (menuactive && (currentMenu != &MainDef && currentMenu != &SP_LoadDef))
 		return 0; // Only on the main menu, or the save select!
 
-	S_StartSound(0, sfx_itemup);
+	BwehHehHe();
 	ultimate_selectable = (!ultimate_selectable);
 
 	// If on the save select, move to what is now Ultimate Mode!
@@ -1097,7 +1097,7 @@ void OP_ObjectplaceMovement(player_t *player)
 	ticcmd_t *cmd = &player->cmd;
 
 	if (!player->climbing && (netgame || !cv_analog.value || (player->pflags & PF_SPINNING)))
-		player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
+		player->drawangle = player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
 
 	ticruned++;
 	if (!(cmd->angleturn & TICCMD_RECEIVED))
diff --git a/src/m_menu.c b/src/m_menu.c
index c1b1479fbbc9c4078402714ae3f60f21d48da845..3a6a3156d53ec03475d775481ddb3accc7c590d6 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -172,7 +172,9 @@ static char joystickInfo[8][25];
 static UINT32 serverlistpage;
 #endif
 
-static saveinfo_t savegameinfo[MAXSAVEGAMES]; // Extra info about the save games.
+static UINT8 numsaves = 0;
+static saveinfo_t* savegameinfo = NULL; // Extra info about the save games.
+static patch_t *savselp[7];
 
 INT16 startmap; // Mario, NiGHTS, or just a plain old normal game?
 
@@ -222,7 +224,7 @@ static INT32 lsoffs[2];
 #define lshli levelselectselect[2]
 
 #define lshseperation 101
-#define lsbasevseperation 62
+#define lsbasevseperation (62*vid.height)/(BASEVIDHEIGHT*vid.dupy) //62
 #define lsheadingheight 16
 #define getheadingoffset(row) (levelselect.rows[row].header[0] ? lsheadingheight : 0)
 #define lsvseperation(row) lsbasevseperation + getheadingoffset(row)
@@ -1047,7 +1049,9 @@ static menuitem_t OP_P1ControlsMenu[] =
 	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam , 60},
 	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 70},
 
-	{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog,  90},
+	//{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog,  90},
+	{IT_STRING  | IT_CVAR, NULL, "Character angle", &cv_directionchar,  90},
+	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake,  100},
 };
 
 static menuitem_t OP_P2ControlsMenu[] =
@@ -1060,7 +1064,9 @@ static menuitem_t OP_P2ControlsMenu[] =
 	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam2 , 60},
 	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 70},
 
-	{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog2,  90},
+	//{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog2,  90},
+	{IT_STRING  | IT_CVAR, NULL, "Character angle", &cv_directionchar2,  90},
+	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake2,  100},
 };
 
 static menuitem_t OP_ChangeControlsMenu[] =
@@ -2545,7 +2551,7 @@ boolean M_Responder(event_t *ev)
 				{
 					if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && modifiedgame && !savemoddata)
 					{
-						S_StartSound(NULL, sfx_menu1);
+						S_StartSound(NULL, sfx_skid);
 						M_StartMessage(M_GetText("This cannot be done in a modified game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
 						return true;
 					}
@@ -4047,10 +4053,10 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 					if (actnum)
 						sprintf(mapname, "%s %d", mapheaderinfo[mapnum]->lvlttl, actnum);
 					else
-						sprintf(mapname, "%s", mapheaderinfo[mapnum]->lvlttl);
+						strcpy(mapname, mapheaderinfo[mapnum]->lvlttl);
 
 					if (strlen(mapname) >= 17)
-						sprintf(mapname+17-3, "...");
+						strcpy(mapname+17-3, "...");
 
 					strcpy(levelselect.rows[row].mapnames[col], (const char *)mapname);
 				}
@@ -4079,6 +4085,24 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 		mapnum++;
 	}
 
+#ifdef SYMMETRICAL_PLATTER
+	// horizontally space out rows with missing right sides
+	for (; row >= 0; row--)
+	{
+		if (!levelselect.rows[row].maplist[2] // no right side
+		&& levelselect.rows[row].maplist[0] && levelselect.rows[row].maplist[1]) // all the left filled in
+		{
+			levelselect.rows[row].maplist[2] = levelselect.rows[row].maplist[1];
+			STRBUFCPY(levelselect.rows[row].mapnames[2], levelselect.rows[row].mapnames[1]);
+			levelselect.rows[row].mapavailable[2] = levelselect.rows[row].mapavailable[1];
+
+			levelselect.rows[row].maplist[1] = -1; // diamond
+			levelselect.rows[row].mapnames[1][0] = '\0';
+			levelselect.rows[row].mapavailable[1] = false;
+		}
+	}
+#endif
+
 	if (levselp[0][0]) // never going to have some provided but not all, saves individually checking
 	{
 		W_UnlockCachedPatch(levselp[0][0]);
@@ -4276,14 +4300,10 @@ void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean headerhighlig
 	y += lsheadingheight - 12;
 	V_DrawString(19, y, (headerhighlight ? V_YELLOWMAP : 0)|(allowlowercase ? V_ALLOWLOWERCASE : 0), header);
 	y += 9;
-	if ((y >= 0) && (y < 200))
-	{
-		V_DrawFill(19, y, 281, 1, (headerhighlight ? yellowmap[3] : 3));
-		V_DrawFill(300, y, 1, 1, 26);
-	}
+	V_DrawFill(19, y, 281, 1, (headerhighlight ? yellowmap[3] : 3));
+	V_DrawFill(300, y, 1, 1, 26);
 	y++;
-	if ((y >= 0) && (y < 200))
-		V_DrawFill(19, y, 282, 1, 26);
+	V_DrawFill(19, y, 282, 1, 26);
 }
 
 static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolean highlight)
@@ -4291,7 +4311,7 @@ static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, bo
 	patch_t *patch;
 
 	INT32 map = levelselect.rows[row].maplist[col];
-	if (!map)
+	if (map <= 0)
 		return;
 
 	//  A 564x100 image of the level as entry MAPxxW
@@ -4307,22 +4327,9 @@ static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, bo
 		V_DrawSmallScaledPatch(x, y, 0, patch);
 	}
 
-	if ((y+50) < 200)
-	{
-		INT32 topy = (y+50), h = 8;
-
-		if (topy < 0)
-		{
-			h += topy;
-			topy = 0;
-		}
-		else if (topy + h >= 200)
-			h = 200 - y;
-		if (h > 0)
-			V_DrawFill(x, topy, 282, h,
-			((mapheaderinfo[map-1]->unlockrequired < 0)
-			? 159 : 63));
-	}
+	V_DrawFill(x, y+50, 282, 8,
+		((mapheaderinfo[map-1]->unlockrequired < 0)
+		? 159 : 63));
 
 	V_DrawString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
 }
@@ -4332,7 +4339,7 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 	patch_t *patch;
 
 	INT32 map = levelselect.rows[row].maplist[col];
-	if (!map)
+	if (map <= 0)
 		return;
 
 	//  A 160x100 image of the level as entry MAPxxP
@@ -4348,22 +4355,9 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 		V_DrawSmallScaledPatch(x, y, 0, patch);
 	}
 
-	if ((y+50) < 200)
-	{
-		INT32 topy = (y+50), h = 8;
-
-		if (topy < 0)
-		{
-			h += topy;
-			topy = 0;
-		}
-		else if (topy + h >= 200)
-			h = 200 - y;
-		if (h > 0)
-			V_DrawFill(x, topy, 80, h,
-			((mapheaderinfo[map-1]->unlockrequired < 0)
-			? 159 : 63));
-	}
+	V_DrawFill(x, y+50, 80, 8,
+		((mapheaderinfo[map-1]->unlockrequired < 0)
+		? 159 : 63));
 
 	if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "AERIAL GARDEN" vs "ACT 18" - "THE ACT" intentionally compressed
 		V_DrawThinString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
@@ -4440,17 +4434,17 @@ static void M_DrawLevelPlatterMenu(void)
 		V_DrawSmallScaledPatch(lsbasex + cursorx + lsoffs[1], lsbasey, 0, (levselp[sizeselect][((skullAnimCounter/4) ? 1 : 0)]));
 
 #if 0
-	if (levelselect.rows[lsrow].maplist[lscol])
+	if (levelselect.rows[lsrow].maplist[lscol] > 0)
 		V_DrawScaledPatch(lsbasex + cursorx-17, lsbasey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_CACHE));
 #endif
 
 	// handle movement of cursor box
-	if (abs(lsoffs[0]) > 1)
+	if (lsoffs[0] > 1 || lsoffs[0] < -1)
 		lsoffs[0] = 2*lsoffs[0]/3;
 	else
 		lsoffs[0] = 0;
 
-	if (abs(lsoffs[1]) > 1)
+	if (lsoffs[1] > 1 || lsoffs[1] < -1)
 		lsoffs[1] = 2*lsoffs[1]/3;
 	else
 		lsoffs[1] = 0;
@@ -5452,7 +5446,7 @@ static void M_LevelSelectWarp(INT32 choice)
 		G_LoadGame((UINT32)cursaveslot, startmap);
 	else
 	{
-		cursaveslot = -1;
+		cursaveslot = 0;
 		M_SetupChoosePlayer(0);
 	}
 }
@@ -6027,7 +6021,7 @@ static void M_CustomWarp(INT32 choice)
 static void M_Credits(INT32 choice)
 {
 	(void)choice;
-	cursaveslot = -2;
+	cursaveslot = -1;
 	M_ClearMenus(true);
 	F_StartCredits();
 }
@@ -6085,149 +6079,285 @@ static void M_LoadGameLevelSelect(INT32 choice)
 // LOAD GAME MENU
 // ==============
 
-static INT32 saveSlotSelected = 0;
-static short menumovedir = 0;
+static INT32 saveSlotSelected = 1;
+static INT32 loadgamescroll = 0;
+static UINT8 loadgameoffset = 0;
 
 static void M_DrawLoadGameData(void)
 {
-	INT32 ecks;
-	INT32 i;
+	INT32 i, savetodraw, x, y, hsep = 90;
+	skin_t *charskin = NULL;
 
-	ecks = SP_LoadDef.x + 24;
-	M_DrawTextBox(SP_LoadDef.x-12,144, 24, 4);
+	if (vid.width != BASEVIDWIDTH*vid.dupx)
+		hsep = (hsep*vid.width)/(BASEVIDWIDTH*vid.dupx);
 
-	if (saveSlotSelected == NOSAVESLOT) // last slot is play without saving
+	for (i = -2; i <= 2; i++)
 	{
-		if (ultimate_selectable)
+		savetodraw = (saveSlotSelected + i + numsaves)%numsaves;
+		x = (BASEVIDWIDTH/2 - 42 + loadgamescroll) + (i*hsep);
+		y = 33 + 9;
+
 		{
-			V_DrawCenteredString(ecks + 68, 144, V_ORANGEMAP, "ULTIMATE MODE");
-			V_DrawCenteredString(ecks + 68, 156, 0, "NO RINGS, NO ONE-UPS,");
-			V_DrawCenteredString(ecks + 68, 164, 0, "NO CONTINUES, ONE LIFE,");
-			V_DrawCenteredString(ecks + 68, 172, 0, "FINAL DESTINATION.");
+			INT32 diff = x - (BASEVIDWIDTH/2 - 42);
+			if (diff < 0)
+				diff = -diff;
+			diff = (42 - diff)/3 - loadgameoffset;
+			if (diff < 0)
+				diff = 0;
+			y -= diff;
 		}
-		else
+
+		if (savetodraw == 0)
 		{
-			V_DrawCenteredString(ecks + 68, 144, V_ORANGEMAP, "PLAY WITHOUT SAVING");
-			V_DrawCenteredString(ecks + 68, 156, 0, "THIS GAME WILL NOT BE");
-			V_DrawCenteredString(ecks + 68, 164, 0, "SAVED, BUT YOU CAN STILL");
-			V_DrawCenteredString(ecks + 68, 172, 0, "GET EMBLEMS AND SECRETS.");
+			V_DrawSmallScaledPatch(x, y, 0,
+				savselp[((ultimate_selectable) ? 2 : 1)]);
+			x += 2;
+			y += 1;
+			V_DrawString(x, y,
+				((savetodraw == saveSlotSelected) ? V_YELLOWMAP : 0),
+				"NO FILE");
+			if (savetodraw == saveSlotSelected)
+				V_DrawFill(x, y+9, 80, 1, yellowmap[3]);
+			y += 11;
+			V_DrawSmallScaledPatch(x, y, V_STATIC, savselp[4]);
+			y += 41;
+			if (ultimate_selectable)
+				V_DrawRightAlignedThinString(x + 79, y, V_REDMAP, "ULTIMATE.");
+			else
+				V_DrawRightAlignedThinString(x + 79, y, V_GRAYMAP, "DON'T SAVE!");
+			continue;
 		}
-		return;
-	}
 
-	if (savegameinfo[saveSlotSelected].lives == -42) // Empty
-	{
-		V_DrawCenteredString(ecks + 68, 160, 0, "NO DATA");
-		return;
-	}
+		savetodraw--;
 
-	if (savegameinfo[saveSlotSelected].lives == -666) // savegame is bad
-	{
-		V_DrawCenteredString(ecks + 68, 144, V_REDMAP, "CORRUPT SAVE FILE");
-		V_DrawCenteredString(ecks + 68, 156, 0, "THIS SAVE FILE");
-		V_DrawCenteredString(ecks + 68, 164, 0, "CAN NOT BE LOADED.");
-		V_DrawCenteredString(ecks + 68, 172, 0, "DELETE USING BACKSPACE.");
-		return;
-	}
+		if (savegameinfo[savetodraw].lives > 0)
+			charskin = &skins[savegameinfo[savetodraw].skinnum];
 
-	// Draw the back sprite, it looks ugly if we don't
-	V_DrawScaledPatch(SP_LoadDef.x, 144+8, 0, livesback);
-	if (savegameinfo[saveSlotSelected].skincolor == 0)
-		V_DrawScaledPatch(SP_LoadDef.x,144+8,0,W_CachePatchName(skins[savegameinfo[saveSlotSelected].skinnum].face, PU_CACHE));
-	else
-	{
-		UINT8 *colormap = R_GetTranslationColormap(savegameinfo[saveSlotSelected].skinnum, savegameinfo[saveSlotSelected].skincolor, 0);
-		V_DrawMappedPatch(SP_LoadDef.x,144+8,0,W_CachePatchName(skins[savegameinfo[saveSlotSelected].skinnum].face, PU_CACHE), colormap);
-	}
+		// signpost background
+		{
+			UINT8 col;
+			if (savegameinfo[savetodraw].lives == -666)
+			{
+				V_DrawSmallScaledPatch(x+2, y+64, 0, savselp[5]);
+			}
+#ifndef PERFECTSAVE // disabled, don't touch
+			else if ((savegameinfo[savetodraw].skinnum == 1)
+			&& (savegameinfo[savetodraw].lives == 99)
+			&& (savegameinfo[savetodraw].gamemap & 8192)
+			&& (savegameinfo[savetodraw].numgameovers == 0)
+			&& (savegameinfo[savetodraw].numemeralds == ((1<<7) - 1))) // perfect save
+			{
+				V_DrawFill(x+6, y+64, 72, 50, 134);
+				V_DrawFill(x+6, y+74, 72, 30, 201);
+				V_DrawFill(x+6, y+84, 72, 10, 1);
+			}
+#endif
+			else
+			{
+				if (savegameinfo[savetodraw].lives == -42)
+					col = 26;
+				else if (savegameinfo[savetodraw].botskin == 3) // & knuckles
+					col = 105;
+				else if (savegameinfo[savetodraw].botskin) // tailsbot or custom
+					col = 134;
+				else
+				{
+					col = (charskin->prefcolor - 1)*2;
+					col = Color_Index[Color_Opposite[col]-1][Color_Opposite[col+1]];
+				}
 
-	V_DrawString(ecks + 12, 152, 0, savegameinfo[saveSlotSelected].playername);
+				V_DrawFill(x+6, y+64, 72, 50, col);
+			}
+		}
+			
+		V_DrawSmallScaledPatch(x, y, 0, savselp[0]);
+		x += 2;
+		y += 1;
+		V_DrawString(x, y,
+			((savetodraw == saveSlotSelected-1) ? V_YELLOWMAP : 0),
+			va("FILE %d", savetodraw+1));
+		if (savetodraw == saveSlotSelected-1)
+				V_DrawFill(x, y+9, 80, 1, yellowmap[3]);
+		y += 11;
+
+		// level image area
+		{
+			patch_t *patch;
+			INT32 flags = 0;
 
-#ifdef SAVEGAMES_OTHERVERSIONS
-	if (savegameinfo[saveSlotSelected].gamemap & 16384)
-		V_DrawCenteredString(ecks + 68, 144, V_REDMAP, "OUTDATED SAVE FILE!");
-#endif
+			if ((savegameinfo[savetodraw].lives == -42)
+			|| (savegameinfo[savetodraw].lives == -666))
+			{
+				patch = savselp[3];
+				flags = V_STATIC;
+			}
+			else if (savegameinfo[savetodraw].gamemap & 8192)
+				patch = savselp[6];
+			else
+			{
+				lumpnum_t lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName((savegameinfo[savetodraw].gamemap) & 8191)));
+				if (lumpnum != LUMPERROR)
+					patch = W_CachePatchNum(lumpnum, PU_CACHE);
+				else
+					patch = savselp[5];
+			}
+				
+			V_DrawSmallScaledPatch(x, y, flags, patch);
 
-	if (savegameinfo[saveSlotSelected].gamemap & 8192)
-		V_DrawString(ecks + 12, 160, V_GREENMAP, "CLEAR!");
-	else
-		V_DrawString(ecks + 12, 160, 0, va("%s", savegameinfo[saveSlotSelected].levelname));
+			y += 41;
 
-	// Use the big face pic for lives, duh. :3
-	V_DrawScaledPatch(ecks + 12, 175, 0, W_CachePatchName("STLIVEX", PU_HUDGFX));
-	V_DrawTallNum(ecks + 40, 172, 0, savegameinfo[saveSlotSelected].lives);
+			if (savegameinfo[savetodraw].lives == -42)
+				V_DrawRightAlignedThinString(x + 79, y, V_GRAYMAP, "NEW GAME");
+			else if (savegameinfo[savetodraw].lives == -666)
+				V_DrawRightAlignedThinString(x + 79, y, V_REDMAP, "CAN'T LOAD!");
+			else if (savegameinfo[savetodraw].gamemap & 8192)
+				V_DrawRightAlignedThinString(x + 79, y, V_GREENMAP, "CLEAR!");
+			else
+				V_DrawRightAlignedThinString(x + 79, y, V_YELLOWMAP, savegameinfo[savetodraw].levelname);
+		}
 
-	// Absolute ridiculousness, condensed into another function.
-	V_DrawContinueIcon(ecks + 58, 182, 0, savegameinfo[saveSlotSelected].skinnum, savegameinfo[saveSlotSelected].skincolor);
-	V_DrawScaledPatch(ecks + 68, 175, 0, W_CachePatchName("STLIVEX", PU_HUDGFX));
-	V_DrawTallNum(ecks + 96, 172, 0, savegameinfo[saveSlotSelected].continues);
+		if ((savegameinfo[savetodraw].lives == -42)
+		|| (savegameinfo[savetodraw].lives == -666))
+			continue;
 
-	for (i = 0; i < 7; ++i)
-	{
-		if (savegameinfo[saveSlotSelected].numemeralds & (1 << i))
-			V_DrawScaledPatch(ecks + 104 + (i * 8), 172, 0, tinyemeraldpics[i]);
-	}
-}
+		y += 64;
 
-#define LOADBARHEIGHT SP_LoadDef.y + (LINEHEIGHT * (j+1)) + ymod
-#define CURSORHEIGHT  SP_LoadDef.y + (LINEHEIGHT*3) - 1
-static void M_DrawLoad(void)
-{
-	INT32 i, j;
-	INT32 ymod = 0, offset = 0;
+		// tiny emeralds
+		{
+			INT32 j, workx = x + 6;
+			for (j = 0; j < 7; ++j)
+			{
+				if (savegameinfo[savetodraw].numemeralds & (1 << j))
+					V_DrawScaledPatch(workx, y, 0, tinyemeraldpics[j]);
+				workx += 10;
+			}
+		}
 
-	M_DrawMenuTitle();
+		y -= 13;
 
-	if (menumovedir != 0) //movement illusion
-	{
-		ymod = (-(LINEHEIGHT/4))*menumovedir;
-		offset = ((menumovedir > 0) ? -1 : 1);
-	}
+		// character heads, lives, and continues
+		{
+			spritedef_t *sprdef;
+			spriteframe_t *sprframe;
+			patch_t *patch;
+			UINT8 *colormap = NULL;
 
-	V_DrawCenteredString(BASEVIDWIDTH/2, 40, 0, "Press backspace to delete a save.");
+			INT32 tempx = (x+40)<<FRACBITS, tempy = y<<FRACBITS, flip = 0, calc;
 
-	for (i = MAXSAVEGAMES + saveSlotSelected - 2 + offset, j = 0;i <= MAXSAVEGAMES + saveSlotSelected + 2 + offset; i++, j++)
-	{
-		if ((menumovedir < 0 && j == 4) || (menumovedir > 0 && j == 0))
-			continue; //this helps give the illusion of movement
+			// botskin first
+			if (savegameinfo[savetodraw].botskin)
+			{
+				skin_t *charbotskin = &skins[savegameinfo[savetodraw].botskin-1];
+				sprdef = &charbotskin->sprites[SPR2_SIGN];
+				if (!sprdef->numframes)
+					goto skipbot;
+				colormap = R_GetTranslationColormap(savegameinfo[savetodraw].botskin, charbotskin->prefcolor, 0);
+				sprframe = &sprdef->spriteframes[0];
+				patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+
+				V_DrawFixedPatch(
+					tempx + (18<<FRACBITS),
+					tempy -  (4<<FRACBITS),
+					charbotskin->highresscale,
+					0, patch, colormap);
+
+				Z_Free(colormap);
+
+				tempx -= (15<<FRACBITS);
+				flip = V_FLIP;
+			}
+skipbot:
+			// signpost image
+			if (!charskin) // shut up compiler
+				goto skipsign;
+			sprdef = &charskin->sprites[SPR2_SIGN];
+			colormap = R_GetTranslationColormap(savegameinfo[savetodraw].skinnum, charskin->prefcolor, 0);
+			if (!sprdef->numframes)
+				goto skipsign;
+			sprframe = &sprdef->spriteframes[0];
+			patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+			if ((calc = SHORT(patch->topoffset) - 42) > 0)
+				tempy += ((4+calc)<<FRACBITS);
+
+			V_DrawFixedPatch(
+				tempx,
+				tempy,
+				charskin->highresscale,
+				flip, patch, colormap);
+
+skipsign:
+			y += 25;
+
+			tempx = x + 10;
+			if (savegameinfo[savetodraw].lives != 0x7f
+			&& savegameinfo[savetodraw].lives > 9)
+				tempx -= 4;
+
+			if (!charskin) // shut up compiler
+				goto skiplife;
+
+			// lives
+			sprdef = &charskin->sprites[SPR2_LIFE];
+			if (!sprdef->numframes)
+				goto skiplife;
+			sprframe = &sprdef->spriteframes[0];
+			patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+
+			V_DrawFixedPatch(
+				(tempx + 4)<<FRACBITS,
+				(y + 6)<<FRACBITS,
+				charskin->highresscale/2,
+				0, patch, colormap);
+skiplife:
+			if (colormap)
+				Z_Free(colormap);
+
+			patch = W_CachePatchName("STLIVEX", PU_CACHE);
+
+			V_DrawScaledPatch(tempx + 9, y + 2, 0, patch);
+			tempx += 16;
+			if (savegameinfo[savetodraw].lives == 0x7f)
+				V_DrawCharacter(tempx, y + 1, '\x16', false);
+			else
+				V_DrawString(tempx, y, 0, va("%d", savegameinfo[savetodraw].lives));
 
-		M_DrawSaveLoadBorder(SP_LoadDef.x, LOADBARHEIGHT);
+			tempx = x + 47;
+			if (savegameinfo[savetodraw].continues > 9)
+				tempx -= 4;
 
-		if ((i%MAXSAVEGAMES) == NOSAVESLOT) // play without saving
-		{
-			if (ultimate_selectable)
-				V_DrawCenteredString(SP_LoadDef.x+92, LOADBARHEIGHT - 1, V_ORANGEMAP, "ULTIMATE MODE");
+			// continues
+			if (savegameinfo[savetodraw].continues > 0)
+			{
+				V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTSAVE", PU_CACHE));
+				V_DrawScaledPatch(tempx + 9, y + 2, 0, patch);
+				V_DrawString(tempx + 16, y, 0, va("%d", savegameinfo[savetodraw].continues));
+			}
 			else
-				V_DrawCenteredString(SP_LoadDef.x+92, LOADBARHEIGHT - 1, V_ORANGEMAP, "PLAY WITHOUT SAVING");
-			continue;
+			{
+				V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTNONE", PU_CACHE));
+				V_DrawScaledPatch(tempx + 9, y + 2, 0, W_CachePatchName("STNONEX", PU_CACHE));
+				V_DrawString(tempx + 16, y, V_GRAYMAP, "0");
+			}
 		}
+	}
+}
 
-		if (savegameinfo[i%MAXSAVEGAMES].lives == -42)
-			V_DrawString(SP_LoadDef.x-6, LOADBARHEIGHT - 1, V_TRANSLUCENT, "NO DATA");
-		else if (savegameinfo[i%MAXSAVEGAMES].lives == -666)
-			V_DrawString(SP_LoadDef.x-6, LOADBARHEIGHT - 1, V_REDMAP, "CORRUPT SAVE FILE");
-		else if (savegameinfo[i%MAXSAVEGAMES].gamemap & 8192)
-			V_DrawString(SP_LoadDef.x-6, LOADBARHEIGHT - 1, V_GREENMAP, "CLEAR!");
-		else
-			V_DrawString(SP_LoadDef.x-6, LOADBARHEIGHT - 1, 0, va("%s", savegameinfo[i%MAXSAVEGAMES].levelname));
+static void M_DrawLoad(void)
+{
+	M_DrawMenuTitle();
 
-		//Draw the save slot number on the right side
-		V_DrawRightAlignedString(SP_LoadDef.x+192, LOADBARHEIGHT - 1, 0, va("%d",(i%MAXSAVEGAMES) + 1));
-	}
+	if (loadgamescroll > 1 || loadgamescroll < -1)
+		loadgamescroll = 2*loadgamescroll/3;
+	else
+		loadgamescroll = 0;
 
-	//Draw cursors on both sides.
-	V_DrawScaledPatch( 32, CURSORHEIGHT, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
-	V_DrawScaledPatch(274, CURSORHEIGHT, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+	if (loadgameoffset > 1)
+		loadgameoffset = 2*loadgameoffset/3;
+	else
+		loadgameoffset = 0;
 
 	M_DrawLoadGameData();
-
-	//finishing the movement illusion
-	if (menumovedir)
-		menumovedir += ((menumovedir > 0) ? 1 : -1);
-	if (abs(menumovedir) > 3)
-		menumovedir = 0;
 }
-#undef LOADBARHEIGHT
-#undef CURSORHEIGHT
 
 //
 // User wants to load this game
@@ -6239,7 +6369,7 @@ static void M_LoadSelect(INT32 choice)
 	if (saveSlotSelected == NOSAVESLOT) //last slot is play without saving
 	{
 		M_NewGame();
-		cursaveslot = -1;
+		cursaveslot = 0;
 		return;
 	}
 
@@ -6248,8 +6378,8 @@ static void M_LoadSelect(INT32 choice)
 		// This slot is empty, so start a new game here.
 		M_NewGame();
 	}
-	else if (savegameinfo[saveSlotSelected].gamemap & 8192) // Completed
-		M_LoadGameLevelSelect(saveSlotSelected + 1);
+	else if (savegameinfo[saveSlotSelected-1].gamemap & 8192) // Completed
+		M_LoadGameLevelSelect(0);
 	else
 		G_LoadGame((UINT32)saveSlotSelected, 0);
 
@@ -6271,12 +6401,11 @@ static void M_ReadSavegameInfo(UINT32 slot)
 	INT32 fake; // Dummy variable
 	char temp[sizeof(timeattackfolder)];
 	char vcheck[VERSIONSIZE];
-#ifdef SAVEGAMES_OTHERVERSIONS
-	boolean oldversion = false;
-#endif
 
 	sprintf(savename, savegamename, slot);
 
+	slot--;
+
 	length = FIL_ReadFile(savename, &savebuffer);
 	if (length == 0)
 	{
@@ -6292,14 +6421,7 @@ static void M_ReadSavegameInfo(UINT32 slot)
 	// Version check
 	memset(vcheck, 0, sizeof (vcheck));
 	sprintf(vcheck, "version %d", VERSION);
-	if (strcmp((const char *)save_p, (const char *)vcheck))
-	{
-#ifdef SAVEGAMES_OTHERVERSIONS
-		oldversion = true;
-#else
-		BADSAVE // Incompatible versions?
-#endif
-	}
+	if (strcmp((const char *)save_p, (const char *)vcheck)) BADSAVE
 	save_p += VERSIONSIZE;
 
 	// dearchive all the modifications
@@ -6311,30 +6433,19 @@ static void M_ReadSavegameInfo(UINT32 slot)
 	if (((fake-1) & 8191) >= NUMMAPS) BADSAVE
 
 	if(!mapheaderinfo[(fake-1) & 8191])
-	{
 		savegameinfo[slot].levelname[0] = '\0';
-		savegameinfo[slot].actnum = 0;
-	}
 	else
 	{
-		strcpy(savegameinfo[slot].levelname, mapheaderinfo[(fake-1) & 8191]->lvlttl);
-		savegameinfo[slot].actnum = mapheaderinfo[(fake-1) & 8191]->actnum;
-	}
+		strlcpy(savegameinfo[slot].levelname, mapheaderinfo[(fake-1) & 8191]->lvlttl, 17+1);
 
-#ifdef SAVEGAMES_OTHERVERSIONS
-	if (oldversion)
-	{
-		if (fake == 24) //meh, let's count old Clear! saves too
-			fake |= 8192;
-		fake |= 16384; // marker for outdated version
+		if (strlen(mapheaderinfo[(fake-1) & 8191]->lvlttl) >= 17)
+			strcpy(savegameinfo[slot].levelname+17-3, "...");
 	}
-#endif
+
 	savegameinfo[slot].gamemap = fake;
 
 	CHECKPOS
-	fake = READUINT16(save_p)-357; // emeralds
-
-	savegameinfo[slot].numemeralds = (UINT8)fake;
+	savegameinfo[slot].numemeralds = READUINT16(save_p)-357; // emeralds
 
 	CHECKPOS
 	READSTRINGN(save_p, temp, sizeof(temp)); // mod it belongs to
@@ -6343,39 +6454,25 @@ static void M_ReadSavegameInfo(UINT32 slot)
 
 	// P_UnArchivePlayer()
 	CHECKPOS
-	savegameinfo[slot].skincolor = READUINT8(save_p);
-	CHECKPOS
-	savegameinfo[slot].skinnum = READUINT8(save_p);
+	fake = READUINT16(save_p);
+	savegameinfo[slot].skinnum = fake & ((1<<5) - 1);
+	if (savegameinfo[slot].skinnum >= numskins
+	|| !R_SkinUsable(-1, savegameinfo[slot].skinnum))
+		BADSAVE
+	savegameinfo[slot].botskin = fake >> 5;
+	if (savegameinfo[slot].botskin-1 >= numskins
+	|| !R_SkinUsable(-1, savegameinfo[slot].botskin-1))
+		BADSAVE
 
 	CHECKPOS
-	(void)READINT32(save_p); // Score
-
+	savegameinfo[slot].numgameovers = READUINT8(save_p); // numgameovers
+	CHECKPOS
+	savegameinfo[slot].lives = READSINT8(save_p); // lives
 	CHECKPOS
-	savegameinfo[slot].lives = READINT32(save_p); // lives
+	(void)READINT32(save_p); // Score
 	CHECKPOS
 	savegameinfo[slot].continues = READINT32(save_p); // continues
 
-	if (fake & (1<<10))
-	{
-		CHECKPOS
-		savegameinfo[slot].botskin = READUINT8(save_p);
-		if (savegameinfo[slot].botskin-1 >= numskins)
-			savegameinfo[slot].botskin = 0;
-		CHECKPOS
-		savegameinfo[slot].botcolor = READUINT8(save_p); // because why not.
-	}
-	else
-		savegameinfo[slot].botskin = 0;
-
-	if (savegameinfo[slot].botskin)
-		snprintf(savegameinfo[slot].playername, 32, "%s & %s",
-			skins[savegameinfo[slot].skinnum].realname,
-			skins[savegameinfo[slot].botskin-1].realname);
-	else
-		strcpy(savegameinfo[slot].playername, skins[savegameinfo[slot].skinnum].realname);
-
-	savegameinfo[slot].playername[31] = 0;
-
 	// File end marker check
 	CHECKPOS
 	if (READUINT8(save_p) != 0x1d) BADSAVE;
@@ -6394,23 +6491,80 @@ static void M_ReadSavegameInfo(UINT32 slot)
 static void M_ReadSaveStrings(void)
 {
 	FILE *handle;
-	UINT32 i;
+	SINT8 i;
 	char name[256];
+	boolean nofile[MAXSAVEGAMES-1];
+	SINT8 tolerance = 3; // empty slots at any time
+	UINT8 lastseen = 0;
 
-	for (i = 0; i < MAXSAVEGAMES; i++)
+	loadgamescroll = 0;
+	loadgameoffset = 14;
+
+	for (i = 1; (i < MAXSAVEGAMES); i++) // slot 0 is no save
 	{
 		snprintf(name, sizeof name, savegamename, i);
 		name[sizeof name - 1] = '\0';
 
 		handle = fopen(name, "rb");
-		if (handle == NULL)
+		if ((nofile[i-1] = (handle == NULL)))
+			continue;
+		fclose(handle);
+		lastseen = i;
+	}
+
+	if (savegameinfo)
+		Z_Free(savegameinfo);
+	savegameinfo = NULL;
+
+	if (lastseen < saveSlotSelected)
+		lastseen = saveSlotSelected;
+
+	i = lastseen;
+
+	for (; (lastseen > 0 && tolerance); lastseen--)
+	{
+		if (nofile[lastseen-1])
+			tolerance--;
+	}
+
+	if ((i += tolerance+1) > MAXSAVEGAMES) // show 3 empty slots at minimum
+		i = MAXSAVEGAMES;
+
+	numsaves = i;
+	savegameinfo = Z_Realloc(savegameinfo, numsaves*sizeof(saveinfo_t), PU_STATIC, NULL);
+	if (!savegameinfo)
+		I_Error("Insufficient memory to prepare save platter");
+
+	for (; i > 0; i--)
+	{
+		if (nofile[i-1] == true)
 		{
-			savegameinfo[i].lives = -42;
+			savegameinfo[i-1].lives = -42;
 			continue;
 		}
-		fclose(handle);
 		M_ReadSavegameInfo(i);
 	}
+
+	if (savselp[0]) // never going to have some provided but not all, saves individually checking
+	{
+		W_UnlockCachedPatch(savselp[0]);
+		W_UnlockCachedPatch(savselp[1]);
+		W_UnlockCachedPatch(savselp[2]);
+
+		W_UnlockCachedPatch(savselp[3]);
+		W_UnlockCachedPatch(savselp[4]);
+		W_UnlockCachedPatch(savselp[5]);
+		W_UnlockCachedPatch(savselp[6]);
+	}
+
+	savselp[0] = W_CachePatchName("SAVEBACK", PU_STATIC);
+	savselp[1] = W_CachePatchName("SAVENONE", PU_STATIC);
+	savselp[2] = W_CachePatchName("ULTIMATE", PU_STATIC);
+
+	savselp[3] = W_CachePatchName("BLACKLVL", PU_STATIC);
+	savselp[4] = W_CachePatchName("BLACXLVL", PU_STATIC);
+	savselp[5] = W_CachePatchName("BLANKLVL", PU_STATIC);
+	savselp[6] = W_CachePatchName("GAMEDONE", PU_STATIC);
 }
 
 //
@@ -6428,8 +6582,19 @@ static void M_SaveGameDeleteResponse(INT32 ch)
 	name[sizeof name - 1] = '\0';
 	remove(name);
 
-	// Refresh savegame menu info
-	M_ReadSaveStrings();
+	BwehHehHe();
+	M_ReadSaveStrings(); // reload the menu
+}
+
+static void M_SaveGameUltimateResponse(INT32 ch)
+{
+	if (ch != 'y' && ch != KEY_ENTER)
+		return;
+
+	S_StartSound(NULL, sfx_menu1);
+	M_LoadSelect(saveSlotSelected);
+	SP_PlayerDef.prevMenu = MessageDef.prevMenu;
+	MessageDef.prevMenu = &SP_PlayerDef;
 }
 
 static void M_HandleLoadSave(INT32 choice)
@@ -6438,26 +6603,46 @@ static void M_HandleLoadSave(INT32 choice)
 
 	switch (choice)
 	{
-		case KEY_DOWNARROW:
-			S_StartSound(NULL, sfx_menu1);
+		case KEY_RIGHTARROW:
+			S_StartSound(NULL, sfx_s3kb7);
 			++saveSlotSelected;
-			if (saveSlotSelected >= MAXSAVEGAMES)
-				saveSlotSelected -= MAXSAVEGAMES;
-			menumovedir = 1;
+			if (saveSlotSelected >= numsaves)
+				saveSlotSelected -= numsaves;
+			loadgamescroll = 90;
 			break;
 
-		case KEY_UPARROW:
-			S_StartSound(NULL, sfx_menu1);
+		case KEY_LEFTARROW:
+			S_StartSound(NULL, sfx_s3kb7);
 			--saveSlotSelected;
 			if (saveSlotSelected < 0)
-				saveSlotSelected += MAXSAVEGAMES;
-			menumovedir = -1;
+				saveSlotSelected += numsaves;
+			loadgamescroll = -90;
 			break;
 
 		case KEY_ENTER:
-			S_StartSound(NULL, sfx_menu1);
-			if (savegameinfo[saveSlotSelected].lives != -666) // don't allow loading of "bad saves"
+			if (ultimate_selectable && saveSlotSelected == NOSAVESLOT)
+			{
+				loadgamescroll = 0;
+				S_StartSound(NULL, sfx_skid);
+				M_StartMessage("Are you sure you want to play\n\x85ultimate mode\x80? It isn't remotely fair,\nand you don't even get an emblem for it.\n\n(Press 'Y' to confirm)\n",M_SaveGameUltimateResponse,MM_YESNO);
+			}
+			else if (saveSlotSelected != NOSAVESLOT && savegameinfo[saveSlotSelected-1].lives == -42 && !(!modifiedgame || savemoddata))
+			{
+				loadgamescroll = 0;
+				S_StartSound(NULL, sfx_skid);
+				M_StartMessage(M_GetText("This cannot be done in a modified game.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+			}
+			else if (saveSlotSelected == NOSAVESLOT || savegameinfo[saveSlotSelected-1].lives != -666) // don't allow loading of "bad saves"
+			{
+				loadgamescroll = 0;
+				S_StartSound(NULL, sfx_menu1);
 				M_LoadSelect(saveSlotSelected);
+			}
+			else if (!loadgameoffset)
+			{
+				S_StartSound(NULL, sfx_lose);
+				loadgameoffset = 14;
+			}
 			break;
 
 		case KEY_ESCAPE:
@@ -6465,11 +6650,25 @@ static void M_HandleLoadSave(INT32 choice)
 			break;
 
 		case KEY_BACKSPACE:
-			S_StartSound(NULL, sfx_menu1);
 			// Don't allow people to 'delete' "Play without Saving."
 			// Nor allow people to 'delete' slots with no saves in them.
-			if (saveSlotSelected != NOSAVESLOT && savegameinfo[saveSlotSelected].lives != -42)
-				M_StartMessage(M_GetText("Are you sure you want to delete\nthis save game?\n\n(Press 'Y' to confirm)\n"),M_SaveGameDeleteResponse,MM_YESNO);
+			if (saveSlotSelected != NOSAVESLOT && savegameinfo[saveSlotSelected-1].lives != -42)
+			{
+				loadgamescroll = 0;
+				S_StartSound(NULL, sfx_skid);
+				M_StartMessage(va("Are you sure you want to delete\nsave file %d?\n\n(Press 'Y' to confirm)\n", saveSlotSelected),M_SaveGameDeleteResponse,MM_YESNO);
+			}
+			else if (!loadgameoffset)
+			{
+				if (saveSlotSelected == NOSAVESLOT && ultimate_selectable)
+				{
+					ultimate_selectable = false;
+					S_StartSound(NULL, sfx_strpst);
+				}
+				else
+					S_StartSound(NULL, sfx_lose);
+				loadgameoffset = 14;
+			}
 			break;
 	}
 	if (exitmenu)
@@ -6478,6 +6677,8 @@ static void M_HandleLoadSave(INT32 choice)
 			M_SetupNextMenu(currentMenu->prevMenu);
 		else
 			M_ClearMenus(true);
+		Z_Free(savegameinfo);
+		savegameinfo = NULL;
 	}
 }
 
@@ -6497,14 +6698,15 @@ static void M_LoadGame(INT32 choice)
 //
 void M_ForceSaveSlotSelected(INT32 sslot)
 {
-	// Already there? Out of bounds? Whatever, then!
-	if (sslot == saveSlotSelected || sslot >= MAXSAVEGAMES)
+	loadgameoffset = 14;
+
+	// Already there? Whatever, then!
+	if (sslot == saveSlotSelected)
 		return;
 
-	// Figure out whether to display up movement or down movement
-	menumovedir = (saveSlotSelected - sslot) > 0 ? -1 : 1;
-	if (abs(saveSlotSelected - sslot) > (MAXSAVEGAMES>>1))
-		menumovedir *= -1;
+	loadgamescroll = 90;
+	if (saveSlotSelected <= numsaves/2)
+		loadgamescroll = -loadgamescroll;
 
 	saveSlotSelected = sslot;
 }
@@ -6775,7 +6977,7 @@ static void M_ChoosePlayer(INT32 choice)
 	}
 
 	if (startmap != spstage_start)
-		cursaveslot = -1;
+		cursaveslot = 0;
 
 	//lastmapsaved = 0;
 	gamecomplete = false;
@@ -6786,6 +6988,10 @@ static void M_ChoosePlayer(INT32 choice)
 	if (levelselect.rows)
 		Z_Free(levelselect.rows);
 	levelselect.rows = NULL;
+
+	if (savegameinfo)
+		Z_Free(savegameinfo);
+	savegameinfo = NULL;
 }
 
 // ===============
@@ -8719,7 +8925,7 @@ static void M_EraseDataResponse(INT32 ch)
 		totalplaytime = 0;
 		F_StartIntro();
 	}
-	S_StartSound(NULL, sfx_bewar1+M_RandomKey(4)); // Bweh heh he
+	BwehHehHe();
 	M_ClearMenus(true);
 }
 
diff --git a/src/m_menu.h b/src/m_menu.h
index 5d15dde191cb0900d2bb1bd000d83725cfbd47b3..813687a91744ab0af918234208ba4188fc7e47ef 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -216,18 +216,14 @@ typedef struct
 // savegame struct for save game menu
 typedef struct
 {
-	char playername[32];
 	char levelname[32];
-	UINT8 actnum;
-	UINT8 skincolor;
 	UINT8 skinnum;
 	UINT8 botskin;
-	UINT8 botcolor;
 	UINT8 numemeralds;
+	UINT8 numgameovers;
 	INT32 lives;
 	INT32 continues;
 	INT32 gamemap;
-	UINT8 netgame;
 } saveinfo_t;
 
 extern description_t description[32];
@@ -239,7 +235,9 @@ extern INT16 startmap;
 extern INT32 ultimate_selectable;
 
 #define MAXSAVEGAMES 31 //note: last save game is "no save"
-#define NOSAVESLOT MAXSAVEGAMES-1 //slot where Play Without Saving appears
+#define NOSAVESLOT 0 //slot where Play Without Saving appears
+
+#define BwehHehHe() S_StartSound(NULL, sfx_bewar1+M_RandomKey(4)) // Bweh heh he
 
 void M_ForceSaveSlotSelected(INT32 sslot);
 
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 0cf86b1ec8131609c7ab75b96a20acba1fedc574..1f5b902d42796827cc5160a8b8fadfe7f2ff8fd4 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -4644,6 +4644,7 @@ void A_CapeChase(mobj_t *actor)
 	fixed_t foffsetx, foffsety, boffsetx, boffsety;
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
+	angle_t angle;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_CapeChase", actor))
 		return;
@@ -4665,11 +4666,13 @@ void A_CapeChase(mobj_t *actor)
 		return;
 	}
 
-	foffsetx = P_ReturnThrustX(chaser, chaser->angle, FixedMul((locvar2 >> 16)*FRACUNIT, actor->scale));
-	foffsety = P_ReturnThrustY(chaser, chaser->angle, FixedMul((locvar2 >> 16)*FRACUNIT, actor->scale));
+	angle = (chaser->player ? chaser->player->drawangle : chaser->angle);
+
+	foffsetx = P_ReturnThrustX(chaser, angle, FixedMul((locvar2 >> 16)*FRACUNIT, actor->scale));
+	foffsety = P_ReturnThrustY(chaser, angle, FixedMul((locvar2 >> 16)*FRACUNIT, actor->scale));
 
-	boffsetx = P_ReturnThrustX(chaser, chaser->angle-ANGLE_90, FixedMul((locvar2 & 65535)*FRACUNIT, actor->scale));
-	boffsety = P_ReturnThrustY(chaser, chaser->angle-ANGLE_90, FixedMul((locvar2 & 65535)*FRACUNIT, actor->scale));
+	boffsetx = P_ReturnThrustX(chaser, angle-ANGLE_90, FixedMul((locvar2 & 65535)*FRACUNIT, actor->scale));
+	boffsety = P_ReturnThrustY(chaser, angle-ANGLE_90, FixedMul((locvar2 & 65535)*FRACUNIT, actor->scale));
 
 	P_UnsetThingPosition(actor);
 	actor->x = chaser->x + foffsetx + boffsetx;
@@ -4686,7 +4689,7 @@ void A_CapeChase(mobj_t *actor)
 		actor->flags2 &= ~MF2_OBJECTFLIP;
 		actor->z = chaser->z + FixedMul((locvar1 >> 16)*FRACUNIT, actor->scale);
 	}
-	actor->angle = chaser->angle;
+	actor->angle = angle;
 	P_SetThingPosition(actor);
 }
 
diff --git a/src/p_inter.c b/src/p_inter.c
index d2101ca575ae358648a1ed863f642dce57b2d0d2..380483009c414f47ab16f3c3a1fa7c0f77129742 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1532,30 +1532,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			P_SetMobjState(special, special->info->deathstate);
 			return;
 		case MT_SPECIALSPIKEBALL:
-			if (!(!useNightsSS && G_IsSpecialStage(gamemap))) // Only for old special stages
-			{
-				P_DamageMobj(toucher, special, special, 1, 0);
-				return;
-			}
-
-			if (player->powers[pw_invulnerability] || player->powers[pw_flashing]
-			|| player->powers[pw_super])
-				return;
-			if (player->powers[pw_shield] || player->bot)  //If One-Hit Shield
-			{
-				P_RemoveShield(player);
-				S_StartSound(toucher, sfx_shldls); // Ba-Dum! Shield loss.
-			}
+			if (!useNightsSS && G_IsSpecialStage(gamemap)) // Only for old special stages
+				P_SpecialStageDamage(player, special, NULL);
 			else
-			{
-				P_PlayRinglossSound(toucher);
-				if (player->rings >= 10)
-					player->rings -= 10;
-				else
-					player->rings = 0;
-			}
-
-			P_DoPlayerPain(player, special, NULL);
+				P_DamageMobj(toucher, special, special, 1, 0);
 			return;
 		case MT_EGGMOBILE2_POGO:
 			// sanity checks
@@ -1871,7 +1851,7 @@ void P_CheckTimeLimit(void)
 			for (i = 0; i < MAXPLAYERS; i++)
 			{
 				if (!playeringame[i] || players[i].spectator
-				 || (players[i].pflags & PF_TAGGED) || (players[i].pflags & PF_TAGIT))
+				 || (players[i].pflags & PF_GAMETYPEOVER) || (players[i].pflags & PF_TAGIT))
 					continue;
 
 				CONS_Printf(M_GetText("%s received double points for surviving the round.\n"), player_names[i]);
@@ -2018,7 +1998,7 @@ void P_CheckSurvivors(void)
 				spectators++;
 			else if (players[i].pflags & PF_TAGIT)
 				taggers++;
-			else if (!(players[i].pflags & PF_TAGGED))
+			else if (!(players[i].pflags & PF_GAMETYPEOVER))
 			{
 				survivorarray[survivors] = i;
 				survivors++;
@@ -2259,7 +2239,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 
 		if ((target->player->lives <= 1) && (netgame || multiplayer) && (gametype == GT_COOP) && (cv_cooplives.value == 0))
 			;
-		else if (!target->player->bot && !target->player->spectator && !G_IsSpecialStage(gamemap)
+		else if (!target->player->bot && !target->player->spectator && !G_IsSpecialStage(gamemap) && (target->player->lives != 0x7f)
 		 && G_GametypeUsesLives())
 		{
 			target->player->lives -= 1; // Lose a life Tails 03-11-2000
@@ -2289,6 +2269,13 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 					S_StopMusic(); // Stop the Music! Tails 03-14-2000
 					S_ChangeMusicInternal("_gover", false); // Yousa dead now, Okieday? Tails 03-14-2000
 				}
+
+				if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && numgameovers < maxgameovers)
+				{
+					numgameovers++;
+					if ((!modifiedgame || savemoddata) && cursaveslot > 0)
+						G_SaveGameOver((UINT32)cursaveslot, (target->player->continues <= 0));
+				}
 			}
 		}
 		target->player->playerstate = PST_DEAD;
@@ -2325,7 +2312,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 				}
 				else
 				{
-					if (!(target->player->pflags & PF_TAGGED))
+					if (!(target->player->pflags & PF_GAMETYPEOVER))
 					{
 						//otherwise, increment the tagger's score.
 						//in hide and seek, suiciding players are counted as found.
@@ -2337,7 +2324,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 								P_AddPlayerScore(&players[w], 100);
 						}
 
-						target->player->pflags |= PF_TAGGED;
+						target->player->pflags |= PF_GAMETYPEOVER;
 						CONS_Printf(M_GetText("%s was found!\n"), player_names[target->player-players]);
 						P_CheckSurvivors();
 					}
@@ -2793,7 +2780,7 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 		}
 		else
 		{
-			player->pflags |= PF_TAGGED; //in hide and seek, the player is tagged and stays stationary.
+			player->pflags |= PF_GAMETYPEOVER; //in hide and seek, the player is tagged and stays stationary.
 			CONS_Printf(M_GetText("%s was found!\n"), player_names[player-players]); // Tell everyone who is it!
 		}
 
@@ -2979,7 +2966,17 @@ void P_RemoveShield(player_t *player)
 		else
 			player->powers[pw_shield] &= SH_STACK;
 	}
-	else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_NONE)
+	else if (player->powers[pw_shield] & SH_NOSTACK)
+	{ // First layer shields
+		if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ARMAGEDDON) // Give them what's coming to them!
+		{
+			P_BlackOw(player); // BAM!
+			player->pflags |= PF_JUMPDOWN;
+		}
+		else
+			player->powers[pw_shield] &= SH_STACK;
+	}
+	else
 	{ // Second layer shields
 		if (((player->powers[pw_shield] & SH_STACK) == SH_FIREFLOWER) && !(player->powers[pw_super] || (mariomode && player->powers[pw_invulnerability])))
 		{
@@ -2988,13 +2985,6 @@ void P_RemoveShield(player_t *player)
 		}
 		player->powers[pw_shield] = SH_NONE;
 	}
-	else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ARMAGEDDON) // Give them what's coming to them!
-	{
-		P_BlackOw(player); // BAM!
-		player->pflags |= PF_JUMPDOWN;
-	}
-	else
-		player->powers[pw_shield] = player->powers[pw_shield] & SH_STACK;
 }
 
 static void P_ShieldDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
@@ -3064,6 +3054,38 @@ static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, IN
 		player->rings = 0;
 }
 
+//
+// P_SpecialStageDamage
+//
+// Do old special stage-style damaging
+// Removes 10 rings from the player, or knocks off their shield if they have one.
+// If they don't have anything, just knock the player back anyway (this doesn't kill them).
+//
+void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
+{
+	if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super])
+		return;
+
+	if (player->powers[pw_shield] || player->bot)  //If One-Hit Shield
+	{
+		P_RemoveShield(player);
+		S_StartSound(player->mo, sfx_shldls); // Ba-Dum! Shield loss.
+	}
+	else
+	{
+		P_PlayRinglossSound(player->mo);
+		if (player->rings >= 10)
+			player->rings -= 10;
+		else
+			player->rings = 0;
+	}
+
+	P_DoPlayerPain(player, inflictor, source);
+
+	if (gametype == GT_CTF && player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))
+		P_PlayerFlagBurst(player, false);
+}
+
 /** Damages an object, which may or may not be a player.
   * For melee attacks, source and inflictor are the same.
   *
@@ -3208,7 +3230,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			if (player->pflags & PF_GODMODE)
 				return false;
 
-			if (!(target->player->powers[pw_carry] == CR_NIGHTSMODE || target->player->pflags & PF_NIGHTSFALL) && (maptol & TOL_NIGHTS))
+			if ((maptol & TOL_NIGHTS) && target->player->powers[pw_carry] != CR_NIGHTSMODE && target->player->powers[pw_carry] != CR_NIGHTSFALL)
 				return false;
 
 			switch (damagetype)
@@ -3409,7 +3431,7 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 	if (player->rings <= 0)
 		num_rings = 0;
 
-	if (num_rings > 32 && !(player->pflags & PF_NIGHTSFALL))
+	if (num_rings > 32 && player->powers[pw_carry] != CR_NIGHTSFALL)
 		num_rings = 32;
 
 	if (player->powers[pw_emeralds])
@@ -3441,7 +3463,7 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 
 		// Make rings spill out around the player in 16 directions like SA, but spill like Sonic 2.
 		// Technically a non-SA way of spilling rings. They just so happen to be a little similar.
-		if (player->pflags & PF_NIGHTSFALL)
+		if (player->powers[pw_carry] == CR_NIGHTSFALL)
 		{
 			ns = FixedMul(((i*FRACUNIT)/16)+2*FRACUNIT, mo->scale);
 			mo->momx = FixedMul(FINECOSINE(fa),ns);
@@ -3481,13 +3503,13 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 		}
 		if (player->mo->eflags & MFE_VERTICALFLIP)
 			mo->momz *= -1;
+
+		if (P_IsObjectOnGround(player->mo))
+			player->powers[pw_carry] = CR_NONE;
 	}
 
 	player->losstime += 10*TICRATE;
 
-	if (P_IsObjectOnGround(player->mo))
-		player->pflags &= ~PF_NIGHTSFALL;
-
 	return;
 }
 
diff --git a/src/p_local.h b/src/p_local.h
index b1bfc645641b39c04728d2bc45828a01214a6228..91ee0c496224197fa328c0f139af5835c7327b13 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -140,6 +140,7 @@ boolean P_IsObjectOnGround(mobj_t *mo);
 boolean P_IsObjectOnGroundIn(mobj_t *mo, sector_t *sec);
 boolean P_InSpaceSector(mobj_t *mo);
 boolean P_InQuicksand(mobj_t *mo);
+boolean P_PlayerHitFloor(player_t *player);
 
 void P_SetObjectMomZ(mobj_t *mo, fixed_t value, boolean relative);
 void P_RestoreMusic(player_t *player);
@@ -417,6 +418,7 @@ void P_ForceFeed(const player_t *player, INT32 attack, INT32 fade, tic_t duratio
 void P_ForceConstant(const BasicFF_t *FFInfo);
 void P_RampConstant(const BasicFF_t *FFInfo, INT32 Start, INT32 End);
 void P_RemoveShield(player_t *player);
+void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source);
 boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype);
 void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype);
 void P_PlayerRingBurst(player_t *player, INT32 num_rings); /// \todo better fit in p_user.c
diff --git a/src/p_map.c b/src/p_map.c
index 38c310d0bf6318bd0d787c6545880a10bfec43d3..6d1760596520f13043d8e6048800fd264d127ffe 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -195,16 +195,20 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		if (spring->flags & MF_ENEMY) // Spring shells
 			P_SetTarget(&spring->target, object);
 
-		if (horizspeed && object->player->cmd.forwardmove == 0 && object->player->cmd.sidemove == 0)
+		if (horizspeed)
 		{
-			object->angle = spring->angle;
-
-			if (!demoplayback || P_AnalogMove(object->player))
+			object->player->drawangle = spring->angle;
+			if (object->player->cmd.forwardmove == 0 && object->player->cmd.sidemove == 0)
 			{
-				if (object->player == &players[consoleplayer])
-					localangle = spring->angle;
-				else if (object->player == &players[secondarydisplayplayer])
-					localangle2 = spring->angle;
+				object->angle = spring->angle;
+
+				if (!demoplayback || P_AnalogMove(object->player))
+				{
+					if (object->player == &players[consoleplayer])
+						localangle = spring->angle;
+					else if (object->player == &players[secondarydisplayplayer])
+						localangle2 = spring->angle;
+				}
 			}
 		}
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 8f9c44fdbf5789995e83f582f155173dd48b5740..be7ce53f2c69af9b1fdb51c7c60504466148a84f 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -34,6 +34,7 @@
 #ifdef ESLOPE
 #include "p_slopes.h"
 #endif
+#include "f_finale.h"
 
 // protos.
 static CV_PossibleValue_t viewheight_cons_t[] = {{16, "MIN"}, {56, "MAX"}, {0, NULL}};
@@ -250,6 +251,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		player->panim = PA_EDGE;
 		break;
 	case S_PLAY_WALK:
+	case S_PLAY_SKID:
 	case S_PLAY_FLOAT:
 		player->panim = PA_WALK;
 		break;
@@ -1964,6 +1966,8 @@ void P_XYMovement(mobj_t *mo)
 #endif
 
 			P_SlideMove(mo);
+			if (player)
+				player->powers[pw_pushing] = 3;
 			xmove = ymove = 0;
 
 #ifdef ESLOPE
@@ -3040,110 +3044,9 @@ static void P_PlayerZMovement(mobj_t *mo)
 					}
 				}
 
-				if (mo->health && !mo->player->spectator && !P_CheckDeathPitCollide(mo))
-				{
-					if ((mo->player->charability2 == CA2_SPINDASH) && !(mo->player->pflags & PF_THOKKED) && (mo->player->cmd.buttons & BT_USE) && (FixedHypot(mo->momx, mo->momy) > (5*mo->scale)))
-					{
-						mo->player->pflags |= PF_SPINNING;
-						P_SetPlayerMobjState(mo, S_PLAY_ROLL);
-						S_StartSound(mo, sfx_spin);
-					}
-					else
-						mo->player->pflags &= ~PF_SPINNING;
-
-					if (mo->player->pflags & PF_GLIDING) // ground gliding
-					{
-						mo->player->skidtime = TICRATE;
-						mo->tics = -1;
-					}
-					else if (mo->player->charability2 == CA2_MELEE && (mo->player->panim == PA_ABILITY2 && mo->state-states != S_PLAY_MELEE_LANDING))
-					{
-						P_SetPlayerMobjState(mo, S_PLAY_MELEE_LANDING);
-						mo->tics = (mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(mo->movefactor)))>>FRACBITS;
-						S_StartSound(mo, sfx_s3k8b);
-						mo->player->pflags |= PF_FULLSTASIS;
-					}
-					else if (mo->player->pflags & PF_JUMPED || !(mo->player->pflags & PF_SPINNING)
-					|| mo->player->powers[pw_tailsfly] || mo->state-states == S_PLAY_FLY_TIRED)
-					{
-						if (mo->player->cmomx || mo->player->cmomy)
-						{
-							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_DASH)
-								P_SetPlayerMobjState(mo, S_PLAY_DASH);
-							else if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale)
-							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN))
-								P_SetPlayerMobjState(mo, S_PLAY_RUN);
-							else if ((mo->player->rmomx || mo->player->rmomy)
-							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT))
-								P_SetPlayerMobjState(mo, S_PLAY_WALK);
-							else if (!mo->player->rmomx && !mo->player->rmomy && mo->player->panim != PA_IDLE)
-								P_SetPlayerMobjState(mo, S_PLAY_STND);
-						}
-						else
-						{
-							if (mo->player->charflags & SF_DASHMODE && mo->player->dashmode >= 3*TICRATE && mo->player->panim != PA_DASH)
-								P_SetPlayerMobjState(mo, S_PLAY_DASH);
-							else if (mo->player->speed >= FixedMul(mo->player->runspeed, mo->scale)
-							&& (mo->player->panim != PA_RUN || mo->state-states == S_PLAY_FLOAT_RUN))
-								P_SetPlayerMobjState(mo, S_PLAY_RUN);
-							else if ((mo->momx || mo->momy)
-							&& (mo->player->panim != PA_WALK || mo->state-states == S_PLAY_FLOAT))
-								P_SetPlayerMobjState(mo, S_PLAY_WALK);
-							else if (!mo->momx && !mo->momy && mo->player->panim != PA_IDLE)
-								P_SetPlayerMobjState(mo, S_PLAY_STND);
-						}
-					}
-
-					if (!(mo->player->pflags & PF_GLIDING))
-						mo->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
-
-					mo->player->pflags &= ~(PF_STARTJUMP|PF_THOKKED|PF_CANCARRY/*|PF_GLIDING*/);
-					mo->player->secondjump = 0;
-					mo->player->glidetime = 0;
-					mo->player->climbing = 0;
-					mo->player->powers[pw_tailsfly] = 0;
-
-					if (mo->player->pflags & PF_SHIELDABILITY)
-					{
-						mo->player->pflags &= ~PF_SHIELDABILITY;
-
-						if ((mo->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) // Elemental shield's stomp attack.
-						{
-							if (mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)) // play a blunt sound
-								S_StartSound(mo, sfx_s3k4c);
-							else // create a fire pattern on the ground
-							{
-								S_StartSound(mo, sfx_s3k47);
-								P_ElementalFire(mo->player, true);
-							}
-							P_SetObjectMomZ(mo,
-							(mo->eflags & MFE_UNDERWATER)
-							? 6*FRACUNIT/5
-							: 5*FRACUNIT/2,
-							false);
-							P_SetPlayerMobjState(mo, S_PLAY_FALL);
-							mo->momx = mo->momy = 0;
-							clipmomz = false;
-						}
-						else if ((mo->player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) // Bubble shield's bounce attack.
-						{
-							P_DoBubbleBounce(mo->player);
-							clipmomz = false;
-						}
-					}
-
-					if (mo->player->pflags & PF_BOUNCING)
-					{
-						P_MobjCheckWater(mo);
-						mo->momz *= -1;
-						P_DoAbilityBounce(mo->player, true);
-						if (mo->player->scoreadd)
-							mo->player->scoreadd--;
-						clipmomz = false;
-					}
-				}
+				clipmomz = P_PlayerHitFloor(mo->player);
 			}
-			if (!(mo->player->pflags & PF_SPINNING))
+			if (!(mo->player->pflags & PF_SPINNING) && mo->player->powers[pw_carry] != CR_NIGHTSMODE)
 				mo->player->pflags &= ~PF_STARTDASH;
 
 			if (clipmomz)
@@ -8415,6 +8318,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 #endif
 	switch (mobj->type)
 	{
+		case MT_ALTVIEWMAN:
+			if (titlemapinaction) mobj->flags &= ~MF_NOTHINK;
+			break;
 		case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
 			mobj->fuse = mobj->info->mass;
 			break;
@@ -9199,6 +9105,7 @@ void P_AfterPlayerSpawn(INT32 playernum)
 	}
 
 	SV_SpawnPlayer(playernum, mobj->x, mobj->y, mobj->angle);
+	p->drawangle = mobj->angle;
 
 	if (camera.chase)
 	{
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 2d3412e65736115c07122edb3b0dddb80328ed8f..497017f10108185cd4e0f8ba101339449cf30606 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -64,22 +64,16 @@ typedef enum
 static inline void P_ArchivePlayer(void)
 {
 	const player_t *player = &players[consoleplayer];
-	INT32 pllives = player->lives;
-	if (pllives < 3) // Bump up to 3 lives if the player
-		pllives = 3; // has less than that.
-
-	WRITEUINT8(save_p, player->skincolor);
-	WRITEUINT8(save_p, player->skin);
-
+	INT16 skininfo = player->skin + (botskin<<5);
+	SINT8 pllives = player->lives;
+	if (pllives < startinglivesbalance[numgameovers]) // Bump up to 3 lives if the player
+		pllives = startinglivesbalance[numgameovers]; // has less than that.
+
+	WRITEUINT16(save_p, skininfo);
+	WRITEUINT8(save_p, numgameovers);
+	WRITESINT8(save_p, pllives);
 	WRITEUINT32(save_p, player->score);
-	WRITEINT32(save_p, pllives);
 	WRITEINT32(save_p, player->continues);
-
-	if (botskin)
-	{
-		WRITEUINT8(save_p, botskin);
-		WRITEUINT8(save_p, botcolor);
-	}
 }
 
 //
@@ -87,22 +81,14 @@ static inline void P_ArchivePlayer(void)
 //
 static inline void P_UnArchivePlayer(void)
 {
-	savedata.skincolor = READUINT8(save_p);
-	savedata.skin = READUINT8(save_p);
+	INT16 skininfo = READUINT16(save_p);
+	savedata.skin = skininfo & ((1<<5) - 1);
+	savedata.botskin = skininfo >> 5;
 
-	savedata.score = READINT32(save_p);
-	savedata.lives = READINT32(save_p);
+	savedata.numgameovers = READUINT8(save_p);
+	savedata.lives = READSINT8(save_p);
+	savedata.score = READUINT32(save_p);
 	savedata.continues = READINT32(save_p);
-
-	if (savedata.botcolor)
-	{
-		savedata.botskin = READUINT8(save_p);
-		if (savedata.botskin-1 >= numskins)
-			savedata.botskin = 0;
-		savedata.botcolor = READUINT8(save_p);
-	}
-	else
-		savedata.botskin = 0;
 }
 
 //
@@ -126,6 +112,7 @@ static void P_NetArchivePlayers(void)
 		// no longer send ticcmds, player name, skin, or color
 
 		WRITEANGLE(save_p, players[i].aiming);
+		WRITEANGLE(save_p, players[i].drawangle);
 		WRITEANGLE(save_p, players[i].awayviewaiming);
 		WRITEINT32(save_p, players[i].awayviewtics);
 		WRITEINT32(save_p, players[i].rings);
@@ -305,6 +292,7 @@ static void P_NetUnArchivePlayers(void)
 		// (that data is handled in the server config now)
 
 		players[i].aiming = READANGLE(save_p);
+		players[i].drawangle = READANGLE(save_p);
 		players[i].awayviewaiming = READANGLE(save_p);
 		players[i].awayviewtics = READINT32(save_p);
 		players[i].rings = READINT32(save_p);
@@ -3162,7 +3150,7 @@ static inline void P_ArchiveMisc(void)
 	//lastmapsaved = gamemap;
 	lastmaploaded = gamemap;
 
-	WRITEUINT16(save_p, (botskin ? (emeralds|(1<<10)) : emeralds)+357);
+	WRITEUINT16(save_p, emeralds+357);
 	WRITESTRINGN(save_p, timeattackfolder, sizeof(timeattackfolder));
 }
 
@@ -3192,9 +3180,6 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
 	token = 0;
 
 	savedata.emeralds = READUINT16(save_p)-357;
-	if (savedata.emeralds & (1<<10))
-		savedata.botcolor = 0xFF;
-	savedata.emeralds &= 0xff;
 
 	READSTRINGN(save_p, testname, sizeof(testname));
 
diff --git a/src/p_saveg.h b/src/p_saveg.h
index 3670d3503c006066dc1adbfc5342a43ae8faa82d..5960660abf2778169a434d08356f724ddd338ada 100644
--- a/src/p_saveg.h
+++ b/src/p_saveg.h
@@ -30,14 +30,13 @@ mobj_t *P_FindNewPosition(UINT32 oldposition);
 
 typedef struct
 {
-	UINT8 skincolor;
 	UINT8 skin;
 	UINT8 botskin;
-	UINT8 botcolor;
 	INT32 score;
 	INT32 lives;
 	INT32 continues;
 	UINT16 emeralds;
+	UINT8 numgameovers;
 } savedata_t;
 
 extern savedata_t savedata;
diff --git a/src/p_setup.c b/src/p_setup.c
index a5666904eb1d947862c33ad922a185d429e03dec..6f52ed6004a0ef919991d3710de7c42183696bdf 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2244,7 +2244,7 @@ static void P_LevelInitStuff(void)
 		players[i].xtralife = players[i].deadtimer = players[i].numboxes = players[i].totalring = players[i].laps = 0;
 		players[i].rings = 0;
 		players[i].aiming = 0;
-		players[i].pflags &= ~PF_TIMEOVER;
+		players[i].pflags &= ~PF_GAMETYPEOVER;
 
 		players[i].losstime = 0;
 		players[i].timeshit = 0;
@@ -2667,7 +2667,9 @@ boolean P_SetupLevel(boolean skipprecip)
 
 	// As oddly named as this is, this handles music only.
 	// We should be fine starting it here.
-	S_Start();
+	/// ... as long as this isn't a titlemap transition, that is
+	if (!titlemapinaction)
+		S_Start();
 
 	// Let's fade to black here
 	// But only if we didn't do the special stage wipe
@@ -2681,7 +2683,7 @@ boolean P_SetupLevel(boolean skipprecip)
 	}
 
 	// Print "SPEEDING OFF TO [ZONE] [ACT 1]..."
-	if (rendermode != render_none)
+	if (!titlemapinaction && rendermode != render_none)
 	{
 		// Don't include these in the fade!
 		char tx[64];
@@ -3021,19 +3023,19 @@ boolean P_SetupLevel(boolean skipprecip)
 	P_RunCachedActions();
 
 	if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking || players[consoleplayer].lives <= 0)
-		&& (!modifiedgame || savemoddata) && cursaveslot >= 0 && CanSaveLevel(gamemap))
+		&& (!modifiedgame || savemoddata) && cursaveslot > 0 && CanSaveLevel(gamemap))
 		G_SaveGame((UINT32)cursaveslot);
 
 	lastmaploaded = gamemap; // HAS to be set after saving!!
 
 	if (savedata.lives > 0)
 	{
+		numgameovers = savedata.numgameovers;
 		players[consoleplayer].continues = savedata.continues;
 		players[consoleplayer].lives = savedata.lives;
 		players[consoleplayer].score = savedata.score;
-		botskin = savedata.botskin;
-		botcolor = savedata.botcolor;
-		botingame = (savedata.botskin != 0);
+		if ((botingame = ((botskin = savedata.botskin) != 0)))
+			botcolor = skins[botskin-1].prefcolor;
 		emeralds = savedata.emeralds;
 		savedata.lives = 0;
 	}
@@ -3202,8 +3204,8 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 		ST_Start();
 
 	// Prevent savefile cheating
-	if (cursaveslot >= 0)
-		cursaveslot = -1;
+	if (cursaveslot > 0)
+		cursaveslot = 0;
 
 	if (replacedcurrentmap && gamestate == GS_LEVEL && (netgame || multiplayer))
 	{
diff --git a/src/p_spec.c b/src/p_spec.c
index 2cad4fc904dd8a8cae32edbabd8f2e08c957656d..b0aa27fbd4e3f6a55348ccd5754851bd07ee3e0c 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3525,31 +3525,14 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 				S_StartSound(player->mo, sfx_itemup);
 			}
 			break;
-		case 11: // Special Stage Damage - Kind of like a mini-P_DamageMobj()
-			if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super] || player->exiting || player->bot)
+		case 11: // Special Stage Damage
+			if (player->exiting || player->bot) // Don't do anything for bots or players who have just finished
 				break;
 
 			if (!(player->powers[pw_shield] || player->rings > 0)) // Don't do anything if no shield or rings anyway
 				break;
 
-			if (player->powers[pw_shield])
-			{
-				P_RemoveShield(player);
-				S_StartSound(player->mo, sfx_shldls); // Ba-Dum! Shield loss.
-			}
-			else if (player->rings > 0)
-			{
-				P_PlayRinglossSound(player->mo);
-				if (player->rings >= 10)
-					player->rings -= 10;
-				else
-					player->rings = 0;
-			}
-
-			P_DoPlayerPain(player, NULL, NULL); // this does basically everything that was here before
-
-			if (gametype == GT_CTF && player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))
-				P_PlayerFlagBurst(player, false);
+			P_SpecialStageDamage(player, NULL, NULL);
 			break;
 		case 12: // Space Countdown
 			if (!(player->powers[pw_shield] & SH_PROTECTWATER) && !player->powers[pw_spacetime])
@@ -3985,7 +3968,7 @@ DoneSection2:
 				player->powers[pw_carry] = CR_ZOOMTUBE;
 				player->speed = speed;
 				player->pflags |= PF_SPINNING;
-				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
+				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
 				player->climbing = 0;
 
 				if (player->mo->state-states != S_PLAY_ROLL)
@@ -4065,7 +4048,7 @@ DoneSection2:
 				player->powers[pw_carry] = CR_ZOOMTUBE;
 				player->speed = speed;
 				player->pflags |= PF_SPINNING;
-				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
+				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
 				player->climbing = 0;
 
 				if (player->mo->state-states != S_PLAY_ROLL)
@@ -4373,7 +4356,7 @@ DoneSection2:
 
 				S_StartSound(player->mo, sfx_s3k4a);
 
-				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_SLIDING|PF_CANCARRY);
+				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
 				player->climbing = 0;
 				P_SetThingPosition(player->mo);
 				P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
diff --git a/src/p_tick.c b/src/p_tick.c
index a79d71ef445333ae6585087d1336657fc1a9c9d3..658b1e4ea82255519c1574bf0098a88ddeef8a15 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -533,7 +533,7 @@ static inline void P_DoTagStuff(void)
 		for (i=0; i < MAXPLAYERS; i++)
 		{
 			if (playeringame[i] && !players[i].spectator && players[i].playerstate == PST_LIVE
-			&& !(players[i].pflags & (PF_TAGIT|PF_TAGGED)))
+			&& !(players[i].pflags & (PF_TAGIT|PF_GAMETYPEOVER)))
 				//points given is the number of participating players divided by two.
 				P_AddPlayerScore(&players[i], participants/2);
 		}
diff --git a/src/p_user.c b/src/p_user.c
index 09cafa0b34ad2d914a6b74416e82edc48379d529..60d4cd5b7117da8afe0170f0b31b5d528d6a465e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -165,7 +165,7 @@ fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move)
 boolean P_AutoPause(void)
 {
 	// Don't pause even on menu-up or focus-lost in netgames or record attack
-	if (netgame || modeattacking)
+	if (netgame || modeattacking || gamestate == GS_TITLESCREEN)
 		return false;
 
 	return (menuactive || window_notinfocus);
@@ -576,7 +576,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	thinker_t *th;
 	mobj_t *mo2;
 
-	player->powers[pw_carry] = CR_NONE;
+	player->powers[pw_carry] = CR_NIGHTSFALL;
 
 	player->powers[pw_underwater] = 0;
 	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
@@ -603,7 +603,6 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->marescore = 0;
 
 	P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
-	player->pflags |= PF_NIGHTSFALL;
 
 	// If in a special stage, add some preliminary exit time.
 	if (G_IsSpecialStage(gamemap))
@@ -857,12 +856,13 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 			fallbackspeed = FixedMul(4*FRACUNIT, player->mo->scale);
 		}
 
+		player->drawangle = ang + ANGLE_180;
 		P_InstaThrust(player->mo, ang, fallbackspeed);
 	}
 
 	// Point penalty for hitting a hazard during tag.
 	// Discourages players from intentionally hurting themselves to avoid being tagged.
-	if (gametype == GT_TAG && (!(player->pflags & PF_TAGGED) && !(player->pflags & PF_TAGIT)))
+	if (gametype == GT_TAG && (!(player->pflags & PF_GAMETYPEOVER) && !(player->pflags & PF_TAGIT)))
 	{
 		if (player->score >= 50)
 			player->score -= 50;
@@ -884,7 +884,7 @@ void P_ResetPlayer(player_t *player)
 {
 	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
 
-	if (!(player->powers[pw_carry] == CR_NIGHTSMODE || player->powers[pw_carry] == CR_BRAKGOOP))
+	if (!(player->powers[pw_carry] == CR_NIGHTSMODE || player->powers[pw_carry] == CR_NIGHTSFALL || player->powers[pw_carry] == CR_BRAKGOOP))
 		player->powers[pw_carry] = CR_NONE;
 
 	player->secondjump = 0;
@@ -950,6 +950,8 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 //
 void P_GivePlayerLives(player_t *player, INT32 numlives)
 {
+	if (player->lives == 0x7f) return;
+
 	player->lives += numlives;
 
 	if (player->lives > 99)
@@ -1153,7 +1155,9 @@ void P_PlayLivesJingle(player_t *player)
 	if (player && !P_IsLocalPlayer(player))
 		return;
 
-	if (gametype == GT_COOP && (netgame || multiplayer) && cv_cooplives.value == 0)
+	if ((player && player->lives == 0x7f)
+	|| (!player && &players[consoleplayer] && players[consoleplayer].lives == 0x7f)
+	|| (gametype == GT_COOP && (netgame || multiplayer) && cv_cooplives.value == 0))
 		S_StartSound(NULL, sfx_lose);
 	else if (use1upSound)
 		S_StartSound(NULL, sfx_oneup);
@@ -1562,7 +1566,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 
 	ghost->color = mobj->color;
 
-	ghost->angle = mobj->angle;
+	ghost->angle = (mobj->player ? mobj->player->drawangle : mobj->angle);
 	ghost->sprite = mobj->sprite;
 	ghost->sprite2 = mobj->sprite2;
 	ghost->frame = mobj->frame;
@@ -1612,7 +1616,7 @@ void P_SpawnThokMobj(player_t *player)
 		mobj = P_SpawnMobj(player->mo->x, player->mo->y, zheight, type);
 
 		// set to player's angle, just in case
-		mobj->angle = player->mo->angle;
+		mobj->angle = player->drawangle;
 
 		// color and skin
 		mobj->color = player->mo->color;
@@ -1672,7 +1676,7 @@ void P_SpawnSpinMobj(player_t *player, mobjtype_t type)
 		mobj = P_SpawnMobj(player->mo->x, player->mo->y, zheight, type);
 
 		// set to player's angle, just in case
-		mobj->angle = player->mo->angle;
+		mobj->angle = player->drawangle;
 
 		// color and skin
 		mobj->color = player->mo->color;
@@ -1777,6 +1781,122 @@ boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 	return false; // No vacuum here, Captain!
 }
 
+//
+// P_PlayerHitFloor
+//
+// Handles player hitting floor surface.
+// Returns whether to clip momz.
+boolean P_PlayerHitFloor(player_t *player)
+{
+	boolean clipmomz;
+
+	I_Assert(player->mo != NULL);
+
+	if ((clipmomz = !(P_CheckDeathPitCollide(player->mo))) && player->mo->health && !player->spectator)
+	{
+		if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_THOKKED) && (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
+		{
+			player->pflags |= PF_SPINNING;
+			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+			S_StartSound(player->mo, sfx_spin);
+		}
+		else
+			player->pflags &= ~PF_SPINNING;
+
+		if (player->pflags & PF_GLIDING) // ground gliding
+		{
+			player->skidtime = TICRATE;
+			player->mo->tics = -1;
+		}
+		else if (player->charability2 == CA2_MELEE && (player->panim == PA_ABILITY2 && player->mo->state-states != S_PLAY_MELEE_LANDING))
+		{
+			P_SetPlayerMobjState(player->mo, S_PLAY_MELEE_LANDING);
+			player->mo->tics = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
+			S_StartSound(player->mo, sfx_s3k8b);
+			player->pflags |= PF_FULLSTASIS;
+		}
+		else if (player->pflags & PF_JUMPED || !(player->pflags & PF_SPINNING)
+		|| player->powers[pw_tailsfly] || player->mo->state-states == S_PLAY_FLY_TIRED)
+		{
+			if (player->cmomx || player->cmomy)
+			{
+				if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
+					P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+				else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
+				&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
+					P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+				else if ((player->rmomx || player->rmomy)
+				&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
+					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				else if (!player->rmomx && !player->rmomy && player->panim != PA_IDLE)
+					P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+			}
+			else
+			{
+				if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
+					P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+				else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
+				&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
+					P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+				else if ((player->mo->momx || player->mo->momy)
+				&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
+					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				else if (!player->mo->momx && !player->mo->momy && player->panim != PA_IDLE)
+					P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+			}
+		}
+
+		if (!(player->pflags & PF_GLIDING))
+			player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
+		player->pflags &= ~(PF_STARTJUMP|PF_THOKKED|PF_CANCARRY/*|PF_GLIDING*/);
+		player->secondjump = 0;
+		player->glidetime = 0;
+		player->climbing = 0;
+		player->powers[pw_tailsfly] = 0;
+
+		if (player->pflags & PF_SHIELDABILITY)
+		{
+			player->pflags &= ~PF_SHIELDABILITY;
+
+			if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) // Elemental shield's stomp attack.
+			{
+				if (player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)) // play a blunt sound
+					S_StartSound(player->mo, sfx_s3k4c);
+				else // create a fire pattern on the ground
+				{
+					S_StartSound(player->mo, sfx_s3k47);
+					P_ElementalFire(player, true);
+				}
+				P_SetObjectMomZ(player->mo,
+				(player->mo->eflags & MFE_UNDERWATER)
+				? 6*FRACUNIT/5
+				: 5*FRACUNIT/2,
+				false);
+				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+				player->mo->momx = player->mo->momy = 0;
+				clipmomz = false;
+			}
+			else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) // Bubble shield's bounce attack.
+			{
+				P_DoBubbleBounce(player);
+				clipmomz = false;
+			}
+		}
+
+		if (player->pflags & PF_BOUNCING)
+		{
+			P_MobjCheckWater(player->mo);
+			player->mo->momz *= -1;
+			P_DoAbilityBounce(player, true);
+			if (player->scoreadd)
+				player->scoreadd--;
+			clipmomz = false;
+		}
+	}
+
+	return clipmomz;
+}
+
 boolean P_InQuicksand(mobj_t *mo) // Returns true if you are in quicksand
 {
 	sector_t *sector = mo->subsector->sector;
@@ -2152,6 +2272,9 @@ static void P_CheckQuicksand(player_t *player)
 
 				if (player->mo->z + player->mo->height >= ceilingheight)
 					player->mo->z = ceilingheight - player->mo->height;
+
+				if (player->mo->momz <= 0)
+					P_PlayerHitFloor(player);
 			}
 			else
 			{
@@ -2161,6 +2284,9 @@ static void P_CheckQuicksand(player_t *player)
 
 				if (player->mo->z <= floorheight)
 					player->mo->z = floorheight;
+
+				if (player->mo->momz >= 0)
+					P_PlayerHitFloor(player);
 			}
 
 			friction = abs(rover->master->v1->y - rover->master->v2->y)>>6;
@@ -3667,7 +3793,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		else
 			player->mo->momz = 15*(FRACUNIT/4);
 
-		player->mo->angle = player->mo->angle - ANGLE_180; // Turn around from the wall you were climbing.
+		player->drawangle = player->mo->angle = player->mo->angle - ANGLE_180; // Turn around from the wall you were climbing.
 
 		if (!demoplayback || P_AnalogMove(player))
 		{
@@ -3825,7 +3951,7 @@ static void P_DoSpinDashDust(player_t *player)
 		prandom[2] = P_RandomFixed()<<3; // P_RandomByte()<<11
 		P_SetObjectMomZ(particle, player->dashspeed/50 + prandom[0], false);
 		P_InstaThrust(particle,
-				player->mo->angle + (prandom[1]*ANG1),
+				player->drawangle + (prandom[1]*ANG1),
 				-FixedMul(player->dashspeed/12 + FRACUNIT + prandom[2], player->mo->scale));
 		P_TryMove(particle, particle->x+particle->momx, particle->y+particle->momy, true);
 	}
@@ -3980,6 +4106,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 									bullet->momy >>= 1;
 								}
 							}
+							player->drawangle = player->mo->angle;
 #undef zpos
 
 							P_SetTarget(&player->mo->tracer, NULL);
@@ -4015,7 +4142,10 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						if (player->mo->eflags & MFE_UNDERWATER)
 							player->mo->momz >>= 1;
 						if (FixedMul(player->speed, FINECOSINE(((player->mo->angle - R_PointToAngle2(0, 0, player->rmomx, player->rmomy)) >> ANGLETOFINESHIFT) & FINEMASK)) < FixedMul(player->maxdash, player->mo->scale))
+						{
+							player->drawangle = player->mo->angle;
 							P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->maxdash, player->mo->scale));
+						}
 						player->mo->momx += player->cmomx;
 						player->mo->momy += player->cmomy;
 						P_SetPlayerMobjState(player->mo, S_PLAY_MELEE);
@@ -4280,6 +4410,8 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->normalspeed, player->mo->scale)*(80-player->flyangle - (player->actionspd>>FRACBITS)/2)/80);
 				else
 					P_InstaThrust(player->mo, player->mo->angle, ((FixedMul(player->normalspeed - player->actionspd/4, player->mo->scale))*2)/3);
+
+				player->drawangle = player->mo->angle;
 			}
 		}
 	}
@@ -4353,6 +4485,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						}
 
 						P_InstaThrust(player->mo, player->mo->angle, FixedMul(actionspd, player->mo->scale));
+						player->drawangle = player->mo->angle;
 
 						if (maptol & TOL_2D)
 						{
@@ -4370,6 +4503,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
 							if (lockon)
 							{
+								P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
 								player->homing = 3*TICRATE;
 							}
@@ -6274,7 +6408,7 @@ static void P_NiGHTSMovement(player_t *player)
 	 && ((cmd->buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)
 	  || (cmd->buttons & BT_USE)))
 	{
-		if (!(player->pflags & PF_SKIDDOWN))
+		if (!(player->pflags & PF_STARTDASH))
 			S_StartSound(player->mo, sfx_ngskid);
 
 		// You can tap the button to only slow down a bit,
@@ -6292,10 +6426,10 @@ static void P_NiGHTSMovement(player_t *player)
 			}
 		}
 
-		player->pflags |= PF_SKIDDOWN;
+		player->pflags |= PF_STARTDASH;
 	}
 	else
-		player->pflags &= ~PF_SKIDDOWN;
+		player->pflags &= ~PF_STARTDASH;
 
 	{
 		const angle_t fa = (FixedAngle(player->flyangle*FRACUNIT)>>ANGLETOFINESHIFT) & FINEMASK;
@@ -6674,8 +6808,8 @@ static void P_SkidStuff(player_t *player)
 			// If your push angle is more than this close to a full 180 degrees, trigger a skid.
 			if (dang > ANGLE_157h)
 			{
-				if (player->panim != PA_WALK)
-					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				if (player->mo->state-states != S_PLAY_SKID)
+					P_SetPlayerMobjState(player->mo, S_PLAY_SKID);
 				player->mo->tics = player->skidtime = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
 				S_StartSound(player->mo, sfx_skid);
 			}
@@ -6741,7 +6875,7 @@ static void P_MovePlayer(player_t *player)
 			if (!(player->pflags & PF_TAGIT))
 			{
 				forcestasis = true;
-				if (player->pflags & PF_TAGGED) // Already hit.
+				if (player->pflags & PF_GAMETYPEOVER) // Already hit.
 					player->powers[pw_flashing] = 5;
 			}
 		}
@@ -6811,8 +6945,7 @@ static void P_MovePlayer(player_t *player)
 				P_CheckQuicksand(player);
 			return;
 		}
-
-		if (player->pflags & PF_NIGHTSFALL && P_IsObjectOnGround(player->mo))
+		else if (player->powers[pw_carry] == CR_NIGHTSFALL && P_IsObjectOnGround(player->mo))
 		{
 			if (G_IsSpecialStage(gamemap))
 			{
@@ -6824,7 +6957,7 @@ static void P_MovePlayer(player_t *player)
 			}
 			else if (player->rings > 0)
 				P_DamageMobj(player->mo, NULL, NULL, 1, 0);
-			player->pflags &= ~PF_NIGHTSFALL;
+			player->powers[pw_carry] = CR_NONE;
 		}
 	}
 
@@ -6917,8 +7050,6 @@ static void P_MovePlayer(player_t *player)
 	if (!player->mo->momx && !player->mo->momy && !player->mo->momz && player->panim == PA_WALK)
 		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
 
-	player->mo->movefactor = FRACUNIT; // We're not going to do any more with this, so let's change it back for the next frame.
-
 //////////////////
 //GAMEPLAY STUFF//
 //////////////////
@@ -7186,7 +7317,7 @@ static void P_MovePlayer(player_t *player)
 	////////////////////////////
 
 	// If the player isn't on the ground, make sure they aren't in a "starting dash" position.
-	if (!onground)
+	if (!onground && player->powers[pw_carry] != CR_NIGHTSMODE)
 	{
 		player->pflags &= ~PF_STARTDASH;
 		player->dashspeed = 0;
@@ -7203,7 +7334,7 @@ static void P_MovePlayer(player_t *player)
 	P_DoJumpStuff(player, cmd);
 
 	// If you're not spinning, you'd better not be spindashing!
-	if (!(player->pflags & PF_SPINNING))
+	if (!(player->pflags & PF_SPINNING) && player->powers[pw_carry] != CR_NIGHTSMODE)
 		player->pflags &= ~PF_STARTDASH;
 
 	//////////////////
@@ -7365,6 +7496,7 @@ static void P_MovePlayer(player_t *player)
 							case SH_FLAMEAURA:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								P_Thrust(player->mo, player->mo->angle, FixedMul(30*FRACUNIT - FixedSqrt(FixedDiv(player->speed, player->mo->scale)), player->mo->scale));
+								player->drawangle = player->mo->angle;
 								S_StartSound(player->mo, sfx_s3k43);
 							default:
 								break;
@@ -7418,6 +7550,8 @@ static void P_MovePlayer(player_t *player)
 
 				if (!(player->mo->tracer->flags & MF_BOSS))
 					player->pflags &= ~PF_THOKKED;
+
+				// P_SetPlayerMobjState(player->mo, S_PLAY_SPRING); -- Speed didn't like it, RIP
 			}
 		}
 
@@ -8084,12 +8218,16 @@ void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 
 	// change angle
 	source->angle = R_PointToAngle2(source->x, source->y, enemy->x, enemy->y);
-	if (source->player && (!demoplayback || P_AnalogMove(source->player)))
+	if (source->player)
 	{
-		if (source->player == &players[consoleplayer])
-			localangle = source->angle;
-		else if (source->player == &players[secondarydisplayplayer])
-			localangle2 = source->angle;
+		source->player->drawangle = source->angle;
+		if (!demoplayback || P_AnalogMove(source->player))
+		{
+			if (source->player == &players[consoleplayer])
+				localangle = source->angle;
+			else if (source->player == &players[secondarydisplayplayer])
+				localangle2 = source->angle;
+		}
 	}
 
 	// change slope
@@ -8160,7 +8298,8 @@ boolean P_GetLives(player_t *player)
 	INT32 i, maxlivesplayer = -1, livescheck = 1;
 	if (!(netgame || multiplayer)
 	|| (gametype != GT_COOP)
-	|| (cv_cooplives.value == 1))
+	|| (cv_cooplives.value == 1)
+	|| (player->lives == 0x7f))
 		return true;
 
 	if ((cv_cooplives.value == 2 || cv_cooplives.value == 0) && player->lives > 0)
@@ -8187,7 +8326,8 @@ boolean P_GetLives(player_t *player)
 	{
 		if (cv_cooplives.value == 2 && (P_IsLocalPlayer(player) || P_IsLocalPlayer(&players[maxlivesplayer])))
 			S_StartSound(NULL, sfx_jshard); // placeholder
-		players[maxlivesplayer].lives--;
+		if (players[maxlivesplayer].lives != 0x7f)
+			players[maxlivesplayer].lives--;
 		player->lives++;
 		if (player->lives < 1)
 			player->lives = 1;
@@ -8379,7 +8519,7 @@ static void P_DeathThink(player_t *player)
 	if (gametype == GT_RACE || gametype == GT_COMPETITION || (gametype == GT_COOP && (multiplayer || netgame)))
 	{
 		// Keep time rolling in race mode
-		if (!(countdown2 && !countdown) && !player->exiting && !(player->pflags & PF_TIMEOVER))
+		if (!(countdown2 && !countdown) && !player->exiting && !(player->pflags & PF_GAMETYPEOVER))
 		{
 			if (gametype == GT_RACE || gametype == GT_COMPETITION)
 			{
@@ -9404,7 +9544,7 @@ void P_PlayerThink(player_t *player)
 			if (netgame && player->mo->health > 0)
 				CONS_Printf(M_GetText("%s ran out of time.\n"), player_names[player-players]);
 
-			player->pflags |= PF_TIMEOVER;
+			player->pflags |= PF_GAMETYPEOVER;
 
 			if (player->powers[pw_carry] == CR_NIGHTSMODE)
 			{
@@ -9603,6 +9743,135 @@ void P_PlayerThink(player_t *player)
 	if (!player->mo)
 		return; // P_MovePlayer removed player->mo.
 
+	// deez New User eXperiences.
+	{
+		// Directionchar!
+		// Camera angle stuff.
+		if (player->exiting) // no control, no modification
+			;
+		else if (!(player->pflags & PF_DIRECTIONCHAR)
+		|| (player->climbing // stuff where the direction is forced at all times
+		|| (player->pflags & PF_GLIDING))
+		|| (player->powers[pw_carry] == CR_NIGHTSMODE)
+		|| (P_AnalogMove(player) || twodlevel || player->mo->flags2 & MF2_TWOD) // keep things synchronised up there, since the camera IS seperate from player motion when that happens
+		|| G_RingSlingerGametype()) // no firing rings in directions your player isn't aiming
+			player->drawangle = player->mo->angle;
+		else if (P_PlayerInPain(player))
+			;
+		else if (player->powers[pw_carry] && player->mo->tracer) // carry
+		{
+			switch (player->powers[pw_carry])
+			{
+				case CR_PLAYER:
+					player->drawangle = (player->mo->tracer->player ? player->mo->tracer->player->drawangle : player->mo->tracer->angle);
+					break;
+				/* -- in case we wanted to have the camera freely movable during zoom tubes
+				case CR_ZOOMTUBE:*/
+				case CR_ROPEHANG:
+					if (player->mo->momx || player->mo->momy)
+					{
+						player->drawangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
+						break;
+					}
+				default:
+					player->drawangle = player->mo->angle;
+					break;
+			}
+		}
+		else if ((player->skidtime > (TICRATE/2 - 2) || ((player->pflags & (PF_SPINNING|PF_STARTDASH)) == PF_SPINNING)) && (abs(player->rmomx) > 5*player->mo->scale || abs(player->rmomy) > 5*player->mo->scale)) // spin/skid force
+			player->drawangle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy);
+		else if (((player->charability2 == CA2_GUNSLINGER || player->charability2 == CA2_MELEE) && player->panim == PA_ABILITY2) || player->pflags & PF_STASIS || player->skidtime)
+			;
+		else
+		{
+			angle_t diff;
+			UINT8 factor;
+
+			if (player->pflags & PF_SLIDING)
+			{
+#if 0 // fun hydrocity style horizontal spin
+				if (player->mo->eflags & MFE_TOUCHWATER || player->powers[pw_flashing] > (flashingtics/4)*3)
+				{
+					diff = (player->mo->angle - player->drawangle);
+					factor = 4;
+				}
+				else
+				{
+					diff = factor = 0;
+					player->drawangle += ANGLE_22h;
+				}
+#else
+				diff = (player->mo->angle - player->drawangle);
+				factor = 4;
+#endif
+			}
+			else if (player->pflags & PF_STARTDASH)
+			{
+				diff = (player->mo->angle - player->drawangle);
+				factor = 4;
+			}
+			else if (cmd->forwardmove || cmd->sidemove) // only when you're pressing movement keys
+			{
+				diff = ((player->mo->angle + R_PointToAngle2(0, 0, cmd->forwardmove<<FRACBITS, -cmd->sidemove<<FRACBITS)) - player->drawangle);
+				factor = 4;
+			}
+			else if (player->rmomx || player->rmomy)
+				diff = factor = 0;
+			else
+			{
+				diff = (player->mo->angle - player->drawangle);
+				factor = 8;
+			}
+
+			if (diff)
+			{
+				if (diff > ANGLE_180)
+					diff = InvAngle(InvAngle(diff)/factor);
+				else
+					diff /= factor;
+				player->drawangle += diff;
+			}
+		}
+
+		// Autobrake!
+		{
+			boolean currentlyonground = P_IsObjectOnGround(player->mo);
+
+			if (!player->powers[pw_carry]
+			&& ((player->pflags & (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE)) == (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE))
+			&& !(cmd->forwardmove || cmd->sidemove)
+			&& (player->rmomx || player->rmomy))
+			{
+				fixed_t acceleration = (player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration) * player->thrustfactor * 20;
+				angle_t moveAngle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy);
+
+				if (!currentlyonground)
+					acceleration /= 2;
+
+				if (player->mo->movefactor != FRACUNIT) // Friction-scaled acceleration...
+					acceleration = FixedMul(acceleration<<FRACBITS, player->mo->movefactor)>>FRACBITS;
+
+				P_Thrust(player->mo, moveAngle, -acceleration);
+			}
+
+			if (!(player->pflags & PF_AUTOBRAKE)
+			|| player->powers[pw_carry]
+			|| player->panim == PA_SPRING
+			|| player->panim == PA_PAIN
+			|| !player->mo->health
+			|| player->climbing
+			|| player->pflags & (PF_SPINNING|PF_SLIDING))
+				player->pflags &= ~PF_APPLYAUTOBRAKE;
+			else if (currentlyonground)
+				player->pflags |= PF_APPLYAUTOBRAKE;
+		}
+	}
+
+	if (player->powers[pw_pushing])
+		player->powers[pw_pushing]--;
+
+	player->mo->movefactor = FRACUNIT; // We're not going to do any more with this, so let's change it back for the next frame.
+
 	// Unset statis flags after moving.
 	// In other words, if you manually set stasis via code,
 	// it lasts for one tic.
diff --git a/src/r_main.c b/src/r_main.c
index c998a7d93e3eef39addb34b9e39b5a5f70880c75..cabefed14e7d3714bccd4a57c03925e549b01979 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -914,7 +914,7 @@ void R_SetupFrame(player_t *player, boolean skybox)
 		chasecam = (cv_chasecam.value != 0);
 	}
 
-	if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD)
+	if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN)
 		chasecam = true; // force chasecam on
 	else if (player->spectator) // no spectator chasecam
 		chasecam = false; // force chasecam off
diff --git a/src/r_sky.c b/src/r_sky.c
index 5162518cba3f4de3d4461700817b916cd1bc9bf9..898424a9925d1f1fadb89f208eaf4c4a6d9d88f8 100644
--- a/src/r_sky.c
+++ b/src/r_sky.c
@@ -64,10 +64,6 @@ void R_SetupSkyDraw(void)
 	// the horizon line in a 256x128 sky texture
 	skytexturemid = (textures[skytexture]->height/2)<<FRACBITS;
 
-	// get the right drawer, it was set by screen.c, depending on the
-	// current video mode bytes per pixel (quick fix)
-	wallcolfunc = walldrawerfunc;
-
 	R_SetSkyScale();
 }
 
diff --git a/src/r_things.c b/src/r_things.c
index b2437d4acd19e4c773a1f0ac1b92fca1e9154bc6..116782c3c9c5b5c0b3336cf62eaac25ca89c4276 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1104,7 +1104,7 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	if (sprframe->rotate != SRF_SINGLE || papersprite)
 	{
-		ang = R_PointToAngle (thing->x, thing->y) - thing->angle;
+		ang = R_PointToAngle (thing->x, thing->y) - (thing->player ? thing->player->drawangle : thing->angle);
 		if (papersprite)
 			ang_scale = abs(FINESINE(ang>>ANGLETOFINESHIFT));
 	}
@@ -2431,176 +2431,39 @@ CV_PossibleValue_t skin_cons_t[MAXSKINS+1];
 
 UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 {
-	UINT8 super = (spr2 & FF_SPR2SUPER);
+	UINT8 super = 0, i = 0;
 
 	if (!skin)
 		return 0;
 
 	while (!(skin->sprites[spr2].numframes)
-		&& spr2 != SPR2_STND)
+		&& spr2 != SPR2_STND
+		&& ++i != 32) // recursion limiter
 	{
 		if (spr2 & FF_SPR2SUPER)
 		{
+			super = FF_SPR2SUPER;
 			spr2 &= ~FF_SPR2SUPER;
 			continue;
 		}
 
 		switch(spr2)
 		{
-		case SPR2_RUN:
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_STUN:
-			spr2 = SPR2_PAIN;
-			break;
-		case SPR2_DRWN:
-			spr2 = SPR2_DEAD;
-			break;
-		case SPR2_SPIN:
-			spr2 = SPR2_ROLL;
-			break;
-		case SPR2_GASP:
-			spr2 = SPR2_SPNG;
-			break;
+
+		// Normal special cases.
 		case SPR2_JUMP:
 			spr2 = ((player
 					? player->charflags
 					: skin->flags)
 					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
 			break;
-		case SPR2_SPNG: // spring
-			spr2 = SPR2_FALL;
-			break;
-		case SPR2_FALL:
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_RIDE:
-			spr2 = SPR2_FALL;
-			break;
-
-		case SPR2_FLY :
-			spr2 = SPR2_SPNG;
-			break;
-		case SPR2_SWIM:
-			spr2 = SPR2_FLY ;
-			break;
 		case SPR2_TIRE:
 			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
 			break;
 
-		case SPR2_GLID:
-			spr2 = SPR2_FLY;
-			break;
-		case SPR2_CLMB:
-			spr2 = SPR2_ROLL;
-			break;
-		case SPR2_CLNG:
-			spr2 = SPR2_CLMB;
-			break;
-
-		case SPR2_FLT :
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_FRUN:
-			spr2 = SPR2_RUN ;
-			break;
-
-		case SPR2_DASH:
-			spr2 = SPR2_FRUN;
-			break;
-
-		case SPR2_BNCE:
-			spr2 = SPR2_FALL;
-			break;
-		case SPR2_BLND:
-			spr2 = SPR2_ROLL;
-			break;
-
-		case SPR2_TWIN:
-			spr2 = SPR2_ROLL;
-			break;
-
-		case SPR2_MLEE:
-			spr2 = SPR2_TWIN;
-			break;
-
-		// NiGHTS sprites.
-		case SPR2_NSTD:
-			spr2 = SPR2_STND;
-			super = FF_SPR2SUPER;
-			break;
-		case SPR2_NFLT:
-			spr2 = SPR2_FLT ;
-			super = FF_SPR2SUPER;
-			break;
-		case SPR2_NSTN:
-			spr2 = SPR2_STUN;
-			break;
-		case SPR2_NPUL:
-			spr2 = SPR2_NSTN;
-			break;
-		case SPR2_NATK:
-			spr2 = SPR2_ROLL;
-			super = FF_SPR2SUPER;
-			break;
-		/*case SPR2_NGT0:
-			spr2 = SPR2_NFLT;
-			break;*/
-		case SPR2_NGT1:
-		case SPR2_NGT7:
-		case SPR2_DRL0:
-			spr2 = SPR2_NGT0;
-			break;
-		case SPR2_NGT2:
-		case SPR2_DRL1:
-			spr2 = SPR2_NGT1;
-			break;
-		case SPR2_NGT3:
-		case SPR2_DRL2:
-			spr2 = SPR2_NGT2;
-			break;
-		case SPR2_NGT4:
-		case SPR2_DRL3:
-			spr2 = SPR2_NGT3;
-			break;
-		case SPR2_NGT5:
-		case SPR2_DRL4:
-			spr2 = SPR2_NGT4;
-			break;
-		case SPR2_NGT6:
-		case SPR2_DRL5:
-			spr2 = SPR2_NGT5;
-			break;
-		case SPR2_DRL6:
-			spr2 = SPR2_NGT6;
-			break;
-		case SPR2_NGT8:
-		case SPR2_DRL7:
-			spr2 = SPR2_NGT7;
-			break;
-		case SPR2_NGT9:
-		case SPR2_DRL8:
-			spr2 = SPR2_NGT8;
-			break;
-		case SPR2_NGTA:
-		case SPR2_DRL9:
-			spr2 = SPR2_NGT9;
-			break;
-		case SPR2_NGTB:
-		case SPR2_DRLA:
-			spr2 = SPR2_NGTA;
-			break;
-		case SPR2_NGTC:
-		case SPR2_DRLB:
-			spr2 = SPR2_NGTB;
-			break;
-		case SPR2_DRLC:
-			spr2 = SPR2_NGTC;
-			break;
-
-		// Dunno? Just go to standing then.
+		// Use the handy list, that's what it's there for!
 		default:
-			spr2 = SPR2_STND;
+			spr2 = spr2defaults[spr2];
 			break;
 		}
 
diff --git a/src/screen.c b/src/screen.c
index 2e3d2e0f419545492d1660e4479b3bf335edfa92..8c1811d5d9d0d1122441b41583587ed879ad54eb 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -173,6 +173,9 @@ void SCR_SetMode(void)
 	if (SCR_IsAspectCorrect(vid.width, vid.height))
 		CONS_Alert(CONS_WARNING, M_GetText("Resolution is not aspect-correct!\nUse a multiple of %dx%d\n"), BASEVIDWIDTH, BASEVIDHEIGHT);
 #endif*/
+
+	wallcolfunc = walldrawerfunc;
+
 	// set the apprpriate drawer for the sky (tall or INT16)
 	setmodeneeded = 0;
 }
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 4515495af84b621d31d915514ffc112fc279fc2e..ceef586a40b0fceabc0033c82b2d1a296ddd3908 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -684,6 +684,8 @@ static inline void ST_drawRings(void)
 static void ST_drawLives(void)
 {
 	const INT32 v_splitflag = (splitscreen && stplyr == &players[displayplayer] ? V_SPLITSCREEN : 0);
+	INT32 livescount;
+	boolean notgreyedout;
 
 	if (!stplyr->skincolor)
 		return; // Just joined a server, skin isn't loaded yet!
@@ -723,66 +725,47 @@ static void ST_drawLives(void)
 		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, stlivex);
 
 	// lives number
-	if ((netgame || multiplayer) && gametype == GT_COOP)
+	if ((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 3)
 	{
-		switch (cv_cooplives.value)
+		INT32 i;
+		livescount = 0;
+		notgreyedout = (stplyr->lives > 0);
+		for (i = 0; i < MAXPLAYERS; i++)
 		{
-			case 0:
-				V_DrawCharacter(hudinfo[HUD_LIVESNUM].x - 8, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0), '\x16' | 0x80 | V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, false);
-				return;
-			case 3:
-				{
-					INT32 i, sum = 0;
-					boolean canrespawn = (stplyr->lives > 0);
-					for (i = 0; i < MAXPLAYERS; i++)
-					{
-						if (!playeringame[i])
-							continue;
-
-						if (players[i].lives < 1)
-							continue;
-
-						if (players[i].lives > 1)
-							canrespawn = true;
-
-						sum += (players[i].lives);
-					}
-					V_DrawRightAlignedString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
-						V_SNAPTOLEFT|V_SNAPTOBOTTOM|(canrespawn ? V_HUDTRANS : V_HUDTRANSHALF)|v_splitflag,
-						va("%d",sum));
-					return;
-				}
-#if 0 // render the number of lives you COULD steal
-			case 2:
-				{
-					INT32 i, sum = 0;
-					for (i = 0; i < MAXPLAYERS; i++)
-					{
-						if (!playeringame[i])
-							continue;
+			if (!playeringame[i])
+				continue;
 
-						if (&players[i] == stplyr)
-							continue;
+			if (players[i].lives < 1)
+				continue;
 
-						if (players[i].lives < 2)
-							continue;
+			if (players[i].lives > 1)
+				notgreyedout = true;
 
-						sum += (players[i].lives - 1);
-					}
-					V_DrawString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
-						V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANSHALF|v_splitflag, va("/%d",sum));
-				}
-				// intentional fallthrough
-#endif
-			default:
-				// don't return so the SP one can be drawn below
+			if (players[i].lives == 0x7f)
+			{
+				livescount = 0x7f;
 				break;
+			}
+			else if (livescount < 99)
+				livescount += (players[i].lives);
 		}
 	}
+	else
+	{
+		livescount = stplyr->lives;
+		notgreyedout = true;
+	}
 
-	V_DrawRightAlignedString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
-		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,
-		va("%d",stplyr->lives));
+	if (livescount == 0x7f)
+		V_DrawCharacter(hudinfo[HUD_LIVESNUM].x - 8, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0), '\x16' | 0x80 | V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, false);
+	else
+	{
+		if (livescount > 99)
+			livescount = 99;
+		V_DrawRightAlignedString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
+			V_SNAPTOLEFT|V_SNAPTOBOTTOM|(notgreyedout ? V_HUDTRANS : V_HUDTRANSHALF)|v_splitflag,
+			((livescount > 99) ? "!!" : va("%d",livescount)));
+	}
 }
 
 static void ST_drawLevelTitle(void)
diff --git a/src/v_video.c b/src/v_video.c
index 6ac101f2d6474f6464c787bbafe0b00caa546292..b0d8fc52b0e9f09adaccf151d5dcea7478963804 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -524,12 +524,21 @@ static inline UINT8 transmappedpdraw(const UINT8 *dest, const UINT8 *source, fix
 {
 	return *(v_translevel + (((*(v_colormap + source[ofs>>FRACBITS]))<<8)&0xff00) + (*dest&0xff));
 }
+
+static UINT8 staticstep = 0;
+static fixed_t staticval = 0;
+
 static inline UINT8 staticpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
 {
 	UINT8 val = source[ofs>>FRACBITS];
 	(void)dest;
+	if ((++staticstep) >= 4)
+	{
+		staticstep = 0;
+		staticval = M_RandomFixed();
+	}
 	if (val < 7) return val;
-	return M_RandomKey(7+1)+(val-7);//M_RandomByte();
+	return ((staticval>>staticstep)&7)+(val-7);
 }
 
 // Draws a patch scaled to arbitrary size.
@@ -660,38 +669,40 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 		y = FixedMul(y,dupy<<FRACBITS);
 		x >>= FRACBITS;
 		y >>= FRACBITS;
-		desttop += (y*vid.width) + x;
 
 		// Center it if necessary
 		if (!(scrn & V_SCALEPATCHMASK))
 		{
+			// if it's meant to cover the whole screen, black out the rest
+			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
+			{
+				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
+				source = (const UINT8 *)(column) + 3;
+				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+			}
+
 			if (vid.width != BASEVIDWIDTH * dupx)
 			{
 				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
 				// so center this imaginary screen
 				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
+					x += (vid.width - (BASEVIDWIDTH * dupx));
 				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+					x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
 			}
 			if (vid.height != BASEVIDHEIGHT * dupy)
 			{
 				// same thing here
 				if ((scrn & (V_SPLITSCREEN|V_SNAPTOBOTTOM)) == (V_SPLITSCREEN|V_SNAPTOBOTTOM))
-					desttop += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy)) * vid.width;
+					y += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy));
 				else if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
+					y += (vid.height - (BASEVIDHEIGHT * dupy));
 				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
 			}
 		}
+
+		desttop += (y*vid.width) + x;
 	}
 
 	if (pscale != FRACUNIT) // scale width properly
@@ -797,36 +808,40 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 		y = FixedMul(y,dupy<<FRACBITS);
 		x >>= FRACBITS;
 		y >>= FRACBITS;
-		desttop += (y*vid.width) + x;
 
 		// Center it if necessary
 		if (!(scrn & V_SCALEPATCHMASK))
 		{
+			// if it's meant to cover the whole screen, black out the rest
+			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
+			{
+				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
+				source = (const UINT8 *)(column) + 3;
+				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+			}
+
 			if (vid.width != BASEVIDWIDTH * dupx)
 			{
 				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
 				// so center this imaginary screen
 				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
+					x += (vid.width - (BASEVIDWIDTH * dupx));
 				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+					x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
 			}
 			if (vid.height != BASEVIDHEIGHT * dupy)
 			{
 				// same thing here
-				if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
+				if ((scrn & (V_SPLITSCREEN|V_SNAPTOBOTTOM)) == (V_SPLITSCREEN|V_SNAPTOBOTTOM))
+					y += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy));
+				else if (scrn & V_SNAPTOBOTTOM)
+					y += (vid.height - (BASEVIDHEIGHT * dupy));
 				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
 			}
 		}
+
+		desttop += (y*vid.width) + x;
 	}
 
 	for (col = sx<<FRACBITS; (col>>FRACBITS) < SHORT(patch->width) && (col>>FRACBITS) < w; col += colfrac, ++x, desttop++)
@@ -1346,7 +1361,7 @@ char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
 //
 void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 {
-	INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth = BASEVIDWIDTH, center = 0;
+	INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, center = 0, left = 0;
 	const char *ch = string;
 	INT32 charflags = 0;
 	const UINT8 *colormap = NULL;
@@ -1362,7 +1377,11 @@ void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 		scrwidth = vid.width;
 	}
 	else
+	{
 		dupx = dupy = 1;
+		scrwidth = vid.width/vid.dupx;
+		left = (scrwidth - BASEVIDWIDTH)/2;
+	}
 
 	charflags = (option & V_CHARCOLORMASK);
 
@@ -1422,9 +1441,9 @@ void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 		else
 			w = SHORT(hu_font[c]->width) * dupx;
 
-		if (cx + w > scrwidth)
+		if (cx+left > scrwidth)
 			break;
-		if (cx < 0) //left boundary check
+		if (cx+left + w < 0) //left boundary check
 		{
 			cx += w;
 			continue;
@@ -1455,7 +1474,7 @@ void V_DrawRightAlignedString(INT32 x, INT32 y, INT32 option, const char *string
 //
 void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string)
 {
-	INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth = BASEVIDWIDTH, center = 0;
+	INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, center = 0, left = 0;
 	const char *ch = string;
 	INT32 charflags = 0;
 	const UINT8 *colormap = NULL;
@@ -1471,7 +1490,11 @@ void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string)
 		scrwidth = vid.width;
 	}
 	else
+	{
 		dupx = dupy = 1;
+		scrwidth = vid.width/vid.dupx;
+		left = (scrwidth - BASEVIDWIDTH)/2;
+	}
 
 	charflags = (option & V_CHARCOLORMASK);
 
@@ -1529,9 +1552,9 @@ void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string)
 		}
 		else
 			w = SHORT(hu_font[c]->width) * dupx / 2;
-		if (cx + w > scrwidth)
+		if (cx+left > scrwidth)
 			break;
-		if (cx < 0) //left boundary check
+		if (cx+left + w < 0) //left boundary check
 		{
 			cx += w;
 			continue;
@@ -1556,7 +1579,7 @@ void V_DrawRightAlignedSmallString(INT32 x, INT32 y, INT32 option, const char *s
 //
 void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string)
 {
-	INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth = BASEVIDWIDTH;
+	INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, left = 0;
 	const char *ch = string;
 	INT32 charflags = 0;
 	const UINT8 *colormap = NULL;
@@ -1572,7 +1595,11 @@ void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string)
 		scrwidth = vid.width;
 	}
 	else
+	{
 		dupx = dupy = 1;
+		scrwidth = vid.width/vid.dupx;
+		left = (scrwidth - BASEVIDWIDTH)/2;
+	}
 
 	charflags = (option & V_CHARCOLORMASK);
 
@@ -1628,9 +1655,9 @@ void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string)
 		else
 			w = (SHORT(tny_font[c]->width) * dupx);
 
-		if (cx + w > scrwidth)
+		if (cx+left > scrwidth)
 			break;
-		if (cx < 0) //left boundary check
+		if (cx+left + w < 0) //left boundary check
 		{
 			cx += w;
 			continue;
@@ -1653,7 +1680,7 @@ void V_DrawRightAlignedThinString(INT32 x, INT32 y, INT32 option, const char *st
 void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string)
 {
 	fixed_t cx = x, cy = y;
-	INT32 w, c, dupx, dupy, scrwidth = BASEVIDWIDTH, center = 0;
+	INT32 w, c, dupx, dupy, scrwidth, center = 0, left = 0;
 	const char *ch = string;
 	INT32 spacewidth = 4, charwidth = 0;
 
@@ -1667,7 +1694,11 @@ void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string)
 		scrwidth = vid.width;
 	}
 	else
+	{
 		dupx = dupy = 1;
+		scrwidth = vid.width/vid.dupx;
+		left = (scrwidth - BASEVIDWIDTH)/2;
+	}
 
 	switch (option & V_SPACINGMASK)
 	{
@@ -1720,9 +1751,9 @@ void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string)
 		else
 			w = SHORT(hu_font[c]->width) * dupx;
 
-		if ((cx>>FRACBITS) + w > scrwidth)
+		if ((cx>>FRACBITS)+left > scrwidth)
 			break;
-		if (cx < 0) //left boundary check
+		if (cx+left + w < 0) //left boundary check
 		{
 			cx += w<<FRACBITS;
 			continue;
diff --git a/src/y_inter.c b/src/y_inter.c
index a102aa99f6b62daa6b4112cabd1e6d2ef909b614..3b7f082f9cb2b1f4a1afcb8358bfaa5dd9888553 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -475,7 +475,7 @@ void Y_IntermissionDrawer(void)
 						V_DrawRightAlignedString(x+152, y, 0, va("%i", data.match.scores[i]));
 					else if (intertype == int_race)
 					{
-						if (players[data.match.num[i]].pflags & PF_TIMEOVER)
+						if (players[data.match.num[i]].pflags & PF_GAMETYPEOVER)
 							snprintf(strtime, sizeof strtime, "DNF");
 						else
 							snprintf(strtime, sizeof strtime,
@@ -493,7 +493,7 @@ void Y_IntermissionDrawer(void)
 						V_DrawRightAlignedString(x+152+BASEVIDWIDTH/2, y, 0, va("%u", data.match.scores[i]));
 					else if (intertype == int_race)
 					{
-						if (players[data.match.num[i]].pflags & PF_TIMEOVER)
+						if (players[data.match.num[i]].pflags & PF_GAMETYPEOVER)
 							snprintf(strtime, sizeof strtime, "DNF");
 						else
 							snprintf(strtime, sizeof strtime, "%i:%02i.%02i", G_TicsToMinutes(data.match.scores[i], true),
@@ -643,7 +643,7 @@ void Y_IntermissionDrawer(void)
 				// already constrained to 8 characters
 				V_DrawString(x+36, y, V_ALLOWLOWERCASE, data.competition.name[i]);
 
-				if (players[data.competition.num[i]].pflags & PF_TIMEOVER)
+				if (players[data.competition.num[i]].pflags & PF_GAMETYPEOVER)
 					snprintf(sstrtime, sizeof sstrtime, "Time Over");
 				else if (players[data.competition.num[i]].lives <= 0)
 					snprintf(sstrtime, sizeof sstrtime, "Game Over");