diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 1b0cb523f85f4cf16ee4cfab4d84326aba0678e7..274fe398aa1403b54c62c8dcb218d03ebf954709 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2056,17 +2056,11 @@ static void CL_ConnectToServer(boolean viams)
 
 	if (i != -1)
 	{
-		INT32 j;
+		UINT8 num = serverlist[i].info.gametype;
 		const char *gametypestr = NULL;
 		CONS_Printf(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername);
-		for (j = 0; gametype_cons_t[j].strvalue; j++)
-		{
-			if (gametype_cons_t[j].value == serverlist[i].info.gametype)
-			{
-				gametypestr = gametype_cons_t[j].strvalue;
-				break;
-			}
-		}
+		if (num < NUMGAMETYPES)
+			gametypestr = Gametype_Names[num];
 		if (gametypestr)
 			CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr);
 		CONS_Printf(M_GetText("Version: %d.%d.%u\n"), serverlist[i].info.version/100,
@@ -2598,7 +2592,10 @@ static void Command_Ban(void)
 		else
 		{
 			if (server) // only the server is allowed to do this right now
+			{
 				Ban_Add(COM_Argv(2));
+				D_SaveBan(); // save the ban list
+			}
 
 			if (COM_Argc() == 2)
 			{
@@ -2629,6 +2626,42 @@ static void Command_Ban(void)
 
 }
 
+static void Command_BanIP(void)
+{
+	if (COM_Argc() < 2)
+	{
+		CONS_Printf(M_GetText("banip <ip> <reason>: ban an ip address\n"));
+		return;
+	}
+
+	if (server) // Only the server can use this, otherwise does nothing.
+	{
+		const char *address = (COM_Argv(1));
+		const char *reason;
+
+		if (COM_Argc() == 2)
+			reason = NULL;
+		else
+			reason = COM_Argv(2);
+
+
+		if (I_SetBanAddress && I_SetBanAddress(address, NULL))
+		{
+			if (reason)
+				CONS_Printf("Banned IP address %s for: %s\n", address, reason);
+			else
+				CONS_Printf("Banned IP address %s\n", address);
+
+			Ban_Add(reason);
+			D_SaveBan();
+		}
+		else
+		{
+			return;
+		}
+	}
+}
+
 static void Command_Kick(void)
 {
 	if (COM_Argc() < 2)
@@ -2908,6 +2941,7 @@ void D_ClientServerInit(void)
 	COM_AddCommand("getplayernum", Command_GetPlayerNum);
 	COM_AddCommand("kick", Command_Kick);
 	COM_AddCommand("ban", Command_Ban);
+	COM_AddCommand("banip", Command_BanIP);
 	COM_AddCommand("clearbans", Command_ClearBans);
 	COM_AddCommand("showbanlist", Command_ShowBan);
 	COM_AddCommand("reloadbans", Command_ReloadBan);
diff --git a/src/d_main.c b/src/d_main.c
index 529c8360232742f8ccfe453f9bce0b32ea3129a5..3344732e511629a6a5d5a45b146ee9949ef9c9c0 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1100,6 +1100,10 @@ void D_SRB2Main(void)
 	// Setup default unlockable conditions
 	M_SetupDefaultConditionSets();
 
+	// Setup character tables
+	// Have to be done here before files are loaded
+	M_InitCharacterTables();
+
 	// load wad, including the main wad file
 	CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n");
 	if (!W_InitMultipleFiles(startupwadfiles))
@@ -1328,13 +1332,9 @@ void D_SRB2Main(void)
 			INT16 newgametype = -1;
 			const char *sgametype = M_GetNextParm();
 
-			for (j = 0; gametype_cons_t[j].strvalue; j++)
-				if (!strcasecmp(gametype_cons_t[j].strvalue, sgametype))
-				{
-					newgametype = (INT16)gametype_cons_t[j].value;
-					break;
-				}
-			if (!gametype_cons_t[j].strvalue) // reached end of the list with no match
+			newgametype = G_GetGametypeByName(sgametype);
+
+			if (newgametype == -1) // reached end of the list with no match
 			{
 				j = atoi(sgametype); // assume they gave us a gametype number, which is okay too
 				if (j >= 0 && j < NUMGAMETYPES)
diff --git a/src/d_net.h b/src/d_net.h
index 61c669dbb93cbcd8cafda1fbc7ca132aac5fe6d9..3d1058702101fef5d250d1c210b2756dd47ea408 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -19,7 +19,7 @@
 #define __D_NET__
 
 // Max computers in a game
-#define MAXNETNODES 32
+#define MAXNETNODES (MAXPLAYERS+4)
 #define BROADCASTADDR MAXNETNODES
 #define MAXSPLITSCREENPLAYERS 2 // Max number of players on a single computer
 //#define NETSPLITSCREEN // Kart's splitscreen netgame feature
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index bee6b4091169413a483f66fea840711b03ca7a36..998eef05d137badc41cc5bd3029fc0fba53ef700 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -409,6 +409,16 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
   */
 void D_RegisterServerCommands(void)
 {
+	INT32 i;
+
+	for (i = 0; i < NUMGAMETYPES; i++)
+	{
+		gametype_cons_t[i].value = i;
+		gametype_cons_t[i].strvalue = Gametype_Names[i];
+	}
+	gametype_cons_t[NUMGAMETYPES].value = 0;
+	gametype_cons_t[NUMGAMETYPES].strvalue = NULL;
+
 	RegisterNetXCmd(XD_NAMEANDCOLOR, Got_NameAndColor);
 	RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref);
 	RegisterNetXCmd(XD_MAP, Got_Mapcmd);
@@ -1639,7 +1649,7 @@ static void Command_Map_f(void)
 {
 	const char *mapname;
 	size_t i;
-	INT32 j, newmapnum;
+	INT32 newmapnum;
 	boolean newresetplayers;
 	INT32 newgametype = gametype;
 
@@ -1707,27 +1717,13 @@ static void Command_Map_f(void)
 			return;
 		}
 
-		for (j = 0; gametype_cons_t[j].strvalue; j++)
-			if (!strcasecmp(gametype_cons_t[j].strvalue, COM_Argv(i+1)))
-			{
-				// Don't do any variable setting here. Wait until you get your
-				// map packet first to avoid sending the same info twice!
-				newgametype = gametype_cons_t[j].value;
-
-				break;
-			}
+		newgametype = G_GetGametypeByName(COM_Argv(i+1));
 
-		if (!gametype_cons_t[j].strvalue) // reached end of the list with no match
+		if (newgametype == -1) // reached end of the list with no match
 		{
-			// assume they gave us a gametype number, which is okay too
-			for (j = 0; gametype_cons_t[j].strvalue != NULL; j++)
-			{
-				if (atoi(COM_Argv(i+1)) == gametype_cons_t[j].value)
-				{
-					newgametype = gametype_cons_t[j].value;
-					break;
-				}
-			}
+			INT32 j = atoi(COM_Argv(i+1)); // assume they gave us a gametype number, which is okay too
+			if (j >= 0 && j < NUMGAMETYPES)
+				newgametype = (INT16)j;
 		}
 	}
 
@@ -1742,12 +1738,11 @@ static void Command_Map_f(void)
 		char gametypestring[32] = "Single Player";
 
 		if (multiplayer)
-			for (i = 0; gametype_cons_t[i].strvalue != NULL; i++)
-				if (gametype_cons_t[i].value == newgametype)
-				{
-					strcpy(gametypestring, gametype_cons_t[i].strvalue);
-					break;
-				}
+		{
+			if (newgametype >= 0 && newgametype < NUMGAMETYPES
+			&& Gametype_Names[newgametype])
+				strcpy(gametypestring, Gametype_Names[newgametype]);
+		}
 
 		CONS_Alert(CONS_WARNING, M_GetText("%s doesn't support %s mode!\n(Use -force to override)\n"), mapname, gametypestring);
 		return;
@@ -3483,7 +3478,6 @@ static void Command_ModDetails_f(void)
 //
 static void Command_ShowGametype_f(void)
 {
-	INT32 j;
 	const char *gametypestr = NULL;
 
 	if (!(netgame || multiplayer)) // print "Single player" instead of "Co-op"
@@ -3491,15 +3485,11 @@ static void Command_ShowGametype_f(void)
 		CONS_Printf(M_GetText("Current gametype is %s\n"), M_GetText("Single player"));
 		return;
 	}
-	// find name string for current gametype
-	for (j = 0; gametype_cons_t[j].strvalue; j++)
-	{
-		if (gametype_cons_t[j].value == gametype)
-		{
-			gametypestr = gametype_cons_t[j].strvalue;
-			break;
-		}
-	}
+
+	// get name string for current gametype
+	if (gametype >= 0 && gametype < NUMGAMETYPES)
+		gametypestr = Gametype_Names[gametype];
+
 	if (gametypestr)
 		CONS_Printf(M_GetText("Current gametype is %s\n"), gametypestr);
 	else // string for current gametype was not found above (should never happen)
@@ -3641,15 +3631,13 @@ void D_GameTypeChanged(INT32 lastgametype)
 {
 	if (netgame)
 	{
-		INT32 j;
 		const char *oldgt = NULL, *newgt = NULL;
-		for (j = 0; gametype_cons_t[j].strvalue; j++)
-		{
-			if (gametype_cons_t[j].value == lastgametype)
-				oldgt = gametype_cons_t[j].strvalue;
-			if (gametype_cons_t[j].value == gametype)
-				newgt = gametype_cons_t[j].strvalue;
-		}
+
+		if (lastgametype >= 0 && lastgametype < NUMGAMETYPES)
+			oldgt = Gametype_Names[lastgametype];
+		if (gametype >= 0 && lastgametype < NUMGAMETYPES)
+			newgt = Gametype_Names[gametype];
+
 		if (oldgt && newgt)
 			CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt);
 	}
diff --git a/src/dehacked.c b/src/dehacked.c
index 871fa0a443b976659934e4b16642d26cec648f0c..bda0c38f73a73a7682424eab7ddb284d49386b22 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -435,11 +435,11 @@ static void readAnimTex(MYFILE *f, INT32 num)
 static boolean findFreeSlot(INT32 *num)
 {
 	// Send the character select entry to a free slot.
-	while (*num < 32 && PlayerMenu[*num].status != IT_DISABLED)
+	while (*num < MAXSKINS && PlayerMenu[*num].status != IT_DISABLED)
 		*num = *num+1;
 
 	// No more free slots. :(
-	if (*num >= 32)
+	if (*num >= MAXSKINS)
 		return false;
 
 	// Found one! ^_^
@@ -1198,6 +1198,11 @@ static void readlevelheader(MYFILE *f, INT32 num)
 				mapheaderinfo[num-1]->mustrack = ((UINT16)i - 1);
 			else if (fastcmp(word, "MUSICPOS"))
 				mapheaderinfo[num-1]->muspos = (UINT32)get_number(word2);
+			else if (fastcmp(word, "MUSICINTERFADEOUT"))
+				mapheaderinfo[num-1]->musinterfadeout = (UINT32)get_number(word2);
+			else if (fastcmp(word, "MUSICINTER"))
+				deh_strlcpy(mapheaderinfo[num-1]->musintername, word2,
+					sizeof(mapheaderinfo[num-1]->musintername), va("Level header %d: intermission music", num));
 			else if (fastcmp(word, "FORCECHARACTER"))
 			{
 				strlcpy(mapheaderinfo[num-1]->forcecharacter, word2, SKINNAMESIZE+1);
diff --git a/src/doomstat.h b/src/doomstat.h
index ad989f365b9223bcf3408fc04f163ded829b0f90..8050a1beb2562d50dc63fb1c1e5a753602225486 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -247,6 +247,10 @@ typedef struct
 	UINT8 numGradedMares;   ///< Internal. For grade support.
 	nightsgrades_t *grades; ///< NiGHTS grades. Allocated dynamically for space reasons. Be careful.
 
+	// Music stuff.
+	UINT32 musinterfadeout;  ///< Fade out level music on intermission screen in milliseconds
+	char musintername[7];    ///< Intermission screen music.
+
 	// Lua stuff.
 	// (This is not ifdeffed so the map header structure can stay identical, just in case.)
 	UINT8 numCustomOptions;     ///< Internal. For Lua custom value support.
@@ -314,7 +318,10 @@ enum GameType
 
 	NUMGAMETYPES
 };
-// If you alter this list, update gametype_cons_t in m_menu.c
+// If you alter this list, update dehacked.c, and Gametype_Names in g_game.c
+
+// String names for gametypes
+extern const char *Gametype_Names[NUMGAMETYPES];
 
 extern tic_t totalplaytime;
 
diff --git a/src/f_finale.c b/src/f_finale.c
index 4cdb877465538417bd36560d4063b5c795611e47..63221fc23bc6237d0ab71522aef575297736264f 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -995,6 +995,7 @@ static const char *credits[] = {
 	"Andrew \"orospakr\" Clunis",
 	"Gregor \"Oogaland\" Dick",
 	"Louis-Antoine \"LJSonic\" de Moulins", // for fixing 2.1's netcode (de Rochefort doesn't quite fit on the screen sorry lol)
+	"Victor \"Steel Titanium\" Fuentes",
 	"Julio \"Chaos Zero 64\" Guir",
 	"\"Jimita\"",
 	"\"Kalaron\"", // Coded some of Sryder13's collection of OpenGL fixes, especially fog
@@ -1005,7 +1006,6 @@ static const char *credits[] = {
 	"Colin \"Sonict\" Pfaff",
 	"Sean \"Sryder13\" Ryder",
 	"Tasos \"tatokis\" Sahanidis", // Corrected C FixedMul, making 64-bit builds netplay compatible
-	"\"Steel Titanium\"",
 	"Ben \"Cue\" Woodford",
 	// Git contributors with 5+ approved merges of substantive quality,
 	// or contributors with at least one groundbreaking merge, may be named.
@@ -1054,12 +1054,12 @@ static const char *credits[] = {
 	"Dan \"Blitzzo\" Hagerstrand",
 	"Kepa \"Nev3r\" Iceta",
 	"Thomas \"Shadow Hog\" Igoe",
-	"Erik \"Torgo\" Nielsen",
 	"\"Kaito Sinclaire\"",
 	"Wessel \"sphere\" Smit",
 	"\"Spazzo\"",
 	"\"SSNTails\"",
 	"Rob Tisdell",
+	"\"Torgo\"",
 	"Jarrett \"JEV3\" Voight",
 	"Johnny \"Sonikku\" Wallbank",
 	"Marco \"mazmazz\" Zafra",
diff --git a/src/g_game.c b/src/g_game.c
index 0992d55a7a37b9631c2b25ffb0a70764354647e4..f9477f91e08f39d54e25989c076266c0cb844dab 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2263,6 +2263,22 @@ void G_PlayerReborn(INT32 player)
 	if ((netgame || multiplayer) && !p->spectator)
 		p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent
 
+	if (p-players == consoleplayer)
+	{
+		if (mapmusflags & MUSIC_RELOADRESET)
+		{
+			strncpy(mapmusname, mapheaderinfo[gamemap-1]->musname, 7);
+			mapmusname[6] = 0;
+			mapmusflags = (mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK);
+			mapmusposition = mapheaderinfo[gamemap-1]->muspos;
+		}
+
+		// This is in S_Start, but this was not here previously.
+		// if (cv_resetmusic.value)
+		// 	S_StopMusic();
+		S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
+	}
+
 	if (gametype == GT_COOP)
 		P_FindEmerald(); // scan for emeralds to hunt for
 
@@ -2682,6 +2698,38 @@ void G_ExitLevel(void)
 	}
 }
 
+// See also the enum GameType in doomstat.h
+const char *Gametype_Names[NUMGAMETYPES] =
+{
+	"Co-op", // GT_COOP
+	"Competition", // GT_COMPETITION
+	"Race", // GT_RACE
+
+	"Match", // GT_MATCH
+	"Team Match", // GT_TEAMMATCH
+
+	"Tag", // GT_TAG
+	"Hide and Seek", // GT_HIDEANDSEEK
+
+	"CTF" // GT_CTF
+};
+
+//
+// G_GetGametypeByName
+//
+// Returns the number for the given gametype name string, or -1 if not valid.
+//
+INT32 G_GetGametypeByName(const char *gametypestr)
+{
+	INT32 i;
+
+	for (i = 0; i < NUMGAMETYPES; i++)
+		if (!stricmp(gametypestr, Gametype_Names[i]))
+			return i;
+
+	return -1; // unknown gametype
+}
+
 //
 // G_IsSpecialStage
 //
diff --git a/src/g_game.h b/src/g_game.h
index 5259eacbbf4848ab208e3290b04ffd308fbdaa14..87ddb3103d73f2cc222794bff1e0f993c6610154 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -163,6 +163,7 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(void);
 void G_StopDemo(void);
 boolean G_CheckDemoStatus(void);
 
+INT32 G_GetGametypeByName(const char *gametypestr);
 boolean G_IsSpecialStage(INT32 mapnum);
 boolean G_GametypeUsesLives(void);
 boolean G_GametypeHasTeams(void);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index fdaf36cb2231ac67537fdf23a0618c953e7d814d..a9a2b7504c07287ab40efae2c01d4e23ae88fc5f 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1944,19 +1944,17 @@ static void HU_DrawCEcho(void)
 
 static void HU_drawGametype(void)
 {
-	INT32 i = 0;
+	const char *strvalue = NULL;
 
-	for (i = 0; gametype_cons_t[i].strvalue; i++)
-	{
-		if (gametype_cons_t[i].value == gametype)
-		{
-			if (splitscreen)
-				V_DrawString(4, 184, 0, gametype_cons_t[i].strvalue);
-			else
-				V_DrawString(4, 192, 0, gametype_cons_t[i].strvalue);
-			return;
-		}
-	}
+	if (gametype < 0 || gametype >= NUMGAMETYPES)
+		return; // not a valid gametype???
+
+	strvalue = Gametype_Names[gametype];
+
+	if (splitscreen)
+		V_DrawString(4, 184, 0, strvalue);
+	else
+		V_DrawString(4, 192, 0, strvalue);
 }
 
 //
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index f5f3aeb765e486cf89e08f214c31c5041c8c423c..d3443312a5edd697bd9db5fb6e13fa32194883ad 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1899,42 +1899,6 @@ static int lib_sSpeedMusic(lua_State *L)
 	return 0;
 }
 
-static int lib_sMusicExists(lua_State *L)
-{
-	boolean checkMIDI = lua_opttrueboolean(L, 2);
-	boolean checkDigi = lua_opttrueboolean(L, 3);
-#ifdef MUSICSLOT_COMPATIBILITY
-	const char *music_name;
-	UINT32 music_num;
-	char music_compat_name[7];
-	UINT16 music_flags = 0;
-	NOHUD
-	if (lua_isnumber(L, 1))
-	{
-		music_num = (UINT32)luaL_checkinteger(L, 1);
-		music_flags = (UINT16)(music_num & 0x0000FFFF);
-		if (music_flags && music_flags <= 1035)
-			snprintf(music_compat_name, 7, "%sM", G_BuildMapName((INT32)music_flags));
-		else if (music_flags && music_flags <= 1050)
-			strncpy(music_compat_name, compat_special_music_slots[music_flags - 1036], 7);
-		else
-			music_compat_name[0] = 0; // becomes empty string
-		music_compat_name[6] = 0;
-		music_name = (const char *)&music_compat_name;
-	}
-	else
-	{
-		music_num = 0;
-		music_name = luaL_checkstring(L, 1);
-	}
-#else
-	const char *music_name = luaL_checkstring(L, 1);
-#endif
-	NOHUD
-	lua_pushboolean(L, S_MusicExists(music_name, checkMIDI, checkDigi));
-	return 1;
-}
-
 static int lib_sStopMusic(lua_State *L)
 {
 	player_t *player = NULL;
@@ -2405,7 +2369,6 @@ static luaL_Reg lib[] = {
 	{"S_StopSound",lib_sStopSound},
 	{"S_ChangeMusic",lib_sChangeMusic},
 	{"S_SpeedMusic",lib_sSpeedMusic},
-	{"S_MusicExists",lib_sMusicExists},
 	{"S_StopMusic",lib_sStopMusic},
 	{"S_SetInternalMusicVolume", lib_sSetInternalMusicVolume},
 	{"S_StopFadingMusic",lib_sStopFadingMusic},
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index e63ea34d83ba17e5815e152b4075c5f1730e465c..35542fdceed0c9fc8f973810059d2cb2e6985b2a 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -1470,6 +1470,10 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushinteger(L, header->mustrack);
 	else if (fastcmp(field,"muspos"))
 		lua_pushinteger(L, header->muspos);
+	else if (fastcmp(field,"musinterfadeout"))
+		lua_pushinteger(L, header->musinterfadeout);
+	else if (fastcmp(field,"musintername"))
+		lua_pushstring(L, header->musintername);
 	else if (fastcmp(field,"forcecharacter"))
 		lua_pushstring(L, header->forcecharacter);
 	else if (fastcmp(field,"weather"))
diff --git a/src/m_menu.c b/src/m_menu.c
index cad16aa856ddfa57553e879cb852cdcdad502f5b..4c93f9d3eaee5538f45ceeb79bca51baeec7fd7c 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -112,41 +112,8 @@ typedef enum
 const char *quitmsg[NUM_QUITMESSAGES];
 
 // Stuff for customizing the player select screen Tails 09-22-2003
-description_t description[32] =
-{
-	{"\x82Sonic\x80 is the fastest of the three, but also the hardest to control. Beginners beware, but experts will find Sonic very powerful.\n\n\x82""Ability:\x80 Speed Thok\nDouble jump to zoom forward with a huge burst of speed.\n\n\x82Tip:\x80 Simply letting go of forward does not slow down in SRB2. To slow down, hold the opposite direction.", "", "sonic"},
-	{"\x82Tails\x80 is the most mobile of the three, but has the slowest speed. Because of his mobility, he's well-\nsuited to beginners.\n\n\x82""Ability:\x80 Fly\nDouble jump to start flying for a limited time. Repetitively hit the jump button to ascend.\n\n\x82Tip:\x80 To quickly descend while flying, hit the spin button.", "", "tails"},
-	{"\x82Knuckles\x80 is well-\nrounded and can destroy breakable walls simply by touching them, but he can't jump as high as the other two.\n\n\x82""Ability:\x80 Glide & Climb\nDouble jump to glide in the air as long as jump is held. Glide into a wall to climb it.\n\n\x82Tip:\x80 Press spin while climbing to jump off the wall; press jump instead to jump off\nand face away from\nthe wall.", "", "knuckles"},
-	{"\x82Sonic & Tails\x80 team up to take on Dr. Eggman!\nControl Sonic while Tails desperately struggles to keep up.\n\nPlayer 2 can control Tails directly by setting the controls in the options menu.\nTails's directional controls are relative to Player 1's camera.\n\nTails can pick up Sonic while flying and carry him around.", "CHRS&T", "sonic&tails"},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""},
-	{"???", "", ""}
-};
+description_t description[MAXSKINS];
+
 static char *char_notes = NULL;
 static fixed_t char_scroll = 0;
 
@@ -383,23 +350,9 @@ static CV_PossibleValue_t skins_cons_t[MAXSKINS+1] = {{1, DEFAULTSKIN}};
 consvar_t cv_chooseskin = {"chooseskin", DEFAULTSKIN, CV_HIDEN|CV_CALL, skins_cons_t, Nextmap_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 // This gametype list is integral for many different reasons.
-// When you add gametypes here, don't forget to update them in CV_AddValue!
-CV_PossibleValue_t gametype_cons_t[] =
-{
-	{GT_COOP, "Co-op"},
-
-	{GT_COMPETITION, "Competition"},
-	{GT_RACE, "Race"},
+// When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h!
+CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1];
 
-	{GT_MATCH, "Match"},
-	{GT_TEAMMATCH, "Team Match"},
-
-	{GT_TAG, "Tag"},
-	{GT_HIDEANDSEEK, "Hide and Seek"},
-
-	{GT_CTF, "CTF"},
-	{0, NULL}
-};
 consvar_t cv_newgametype = {"newgametype", "Co-op", CV_HIDEN|CV_CALL, gametype_cons_t, Newgametype_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t serversort_cons_t[] = {
@@ -845,41 +798,7 @@ static menuitem_t SP_LevelStatsMenu[] =
 // A rare case.
 // External files modify this menu, so we can't call it static.
 // And I'm too lazy to go through and rename it everywhere. ARRGH!
-menuitem_t PlayerMenu[32] =
-{
-	{IT_CALL, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_CALL, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_CALL, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_CALL, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0},
-	{IT_DISABLED, NULL, NULL, M_ChoosePlayer, 0}
-};
+menuitem_t PlayerMenu[MAXSKINS];
 
 // -----------------------------------
 // Multiplayer and all of its submenus
@@ -2883,6 +2802,55 @@ void M_Init(void)
 	CV_RegisterVar(&cv_allcaps);
 }
 
+void M_InitCharacterTables(void)
+{
+	UINT8 i;
+
+	// Setup PlayerMenu table
+	for (i = 0; i < MAXSKINS; i++)
+	{
+		PlayerMenu[i].status = (i < 4 ? IT_CALL : IT_DISABLED);
+		PlayerMenu[i].patch = PlayerMenu[i].text = NULL;
+		PlayerMenu[i].itemaction = M_ChoosePlayer;
+		PlayerMenu[i].alphaKey = 0;
+	}
+
+	// Setup description table
+	for (i = 0; i < MAXSKINS; i++)
+	{
+		if (i == 0)
+		{
+			strcpy(description[i].notes, "\x82Sonic\x80 is the fastest of the three, but also the hardest to control. Beginners beware, but experts will find Sonic very powerful.\n\n\x82""Ability:\x80 Speed Thok\nDouble jump to zoom forward with a huge burst of speed.\n\n\x82Tip:\x80 Simply letting go of forward does not slow down in SRB2. To slow down, hold the opposite direction.");
+			strcpy(description[i].picname, "");
+			strcpy(description[i].skinname, "sonic");
+		}
+		else if (i == 1)
+		{
+			strcpy(description[i].notes, "\x82Tails\x80 is the most mobile of the three, but has the slowest speed. Because of his mobility, he's well-\nsuited to beginners.\n\n\x82""Ability:\x80 Fly\nDouble jump to start flying for a limited time. Repetitively hit the jump button to ascend.\n\n\x82Tip:\x80 To quickly descend while flying, hit the spin button.");
+			strcpy(description[i].picname, "");
+			strcpy(description[i].skinname, "tails");
+		}
+		else if (i == 2)
+		{
+			strcpy(description[i].notes, "\x82Knuckles\x80 is well-\nrounded and can destroy breakable walls simply by touching them, but he can't jump as high as the other two.\n\n\x82""Ability:\x80 Glide & Climb\nDouble jump to glide in the air as long as jump is held. Glide into a wall to climb it.\n\n\x82Tip:\x80 Press spin while climbing to jump off the wall; press jump instead to jump off\nand face away from\nthe wall.");
+			strcpy(description[i].picname, "");
+			strcpy(description[i].skinname, "knuckles");
+		}
+		else if (i == 3)
+		{
+			strcpy(description[i].notes, "\x82Sonic & Tails\x80 team up to take on Dr. Eggman!\nControl Sonic while Tails desperately struggles to keep up.\n\nPlayer 2 can control Tails directly by setting the controls in the options menu.\nTails's directional controls are relative to Player 1's camera.\n\nTails can pick up Sonic while flying and carry him around.");
+			strcpy(description[i].picname, "CHRS&T");
+			strcpy(description[i].skinname, "sonic&tails");
+		}
+		else
+		{
+			strcpy(description[i].notes, "???");
+			strcpy(description[i].picname, "");
+			strcpy(description[i].skinname, "");
+		}
+	}
+}
+
 // ==========================================================================
 // SPECIAL MENU OPTION DRAW ROUTINES GO HERE
 // ==========================================================================
@@ -6582,7 +6550,7 @@ static void M_DrawRoomMenu(void)
 
 static void M_DrawConnectMenu(void)
 {
-	UINT16 i, j;
+	UINT16 i;
 	const char *gt = "Unknown";
 	INT32 numPages = (serverlistcount+(SERVERS_PER_PAGE-1))/SERVERS_PER_PAGE;
 
@@ -6628,11 +6596,8 @@ static void M_DrawConnectMenu(void)
 		                     va("Ping: %u", (UINT32)LONG(serverlist[slindex].info.time)));
 
 		gt = "Unknown";
-		for (j = 0; gametype_cons_t[j].strvalue; j++)
-		{
-			if (gametype_cons_t[j].value == serverlist[slindex].info.gametype)
-				gt = gametype_cons_t[j].strvalue;
-		}
+		if (serverlist[slindex].info.gametype < NUMGAMETYPES)
+			gt = Gametype_Names[serverlist[slindex].info.gametype];
 
 		V_DrawSmallString(currentMenu->x+46,S_LINEY(i)+8, globalflags,
 		                         va("Players: %02d/%02d", serverlist[slindex].info.numberofplayer, serverlist[slindex].info.maxplayer));
diff --git a/src/m_menu.h b/src/m_menu.h
index eb770c19425c2b6730bdfbd8479463dd8f567112..fcde5b7dbb9204ec60fa591f2c0a49f23f6f2bd6 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -38,6 +38,9 @@ void M_Drawer(void);
 // Called by D_SRB2Main, loads the config file.
 void M_Init(void);
 
+// Called by D_SRB2Main also, sets up the playermenu and description tables.
+void M_InitCharacterTables(void);
+
 // Called by intro code to force menu up upon a keypress,
 // does nothing if menu is already up.
 void M_StartControlPanel(void);
@@ -151,7 +154,7 @@ typedef struct menuitem_s
 	UINT8 alphaKey;
 } menuitem_t;
 
-extern menuitem_t PlayerMenu[32];
+extern menuitem_t PlayerMenu[MAXSKINS];
 
 typedef struct menu_s
 {
@@ -206,7 +209,7 @@ typedef struct
 	UINT8 netgame;
 } saveinfo_t;
 
-extern description_t description[32];
+extern description_t description[MAXSKINS];
 
 extern consvar_t cv_newgametype, cv_nextmap, cv_chooseskin, cv_serversort;
 extern CV_PossibleValue_t gametype_cons_t[];
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 12ee1345bc567b4bf2e80d3f7a748ac34bd403cc..fce72b9271977db0f8ccc7604642a65eec2ca50c 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -647,8 +647,6 @@ static void P_NetArchiveWorld(void)
 
 	WRITEUINT16(put, 0xffff);
 
-	mld = W_CacheLumpNum(lastloadedmaplumpnum+ML_LINEDEFS, PU_CACHE);
-	msd = W_CacheLumpNum(lastloadedmaplumpnum+ML_SIDEDEFS, PU_CACHE);
 	// do lines
 	for (i = 0; i < numlines; i++, mld++, li++)
 	{
diff --git a/src/p_setup.c b/src/p_setup.c
index db7379fbb3ad9c858d38e3e8464fea0d2a2b696a..033e99f10b64dd4260dd08770dffe0301f010c87 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -189,6 +189,10 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->mustrack = 0;
 	DEH_WriteUndoline("MUSICPOS", va("%d", mapheaderinfo[num]->muspos), UNDO_NONE);
 	mapheaderinfo[num]->muspos = 0;
+	DEH_WriteUndoline("MUSICINTERFADEOUT", va("%d", mapheaderinfo[num]->musinterfadeout), UNDO_NONE);
+	mapheaderinfo[num]->musinterfadeout = 0;
+	DEH_WriteUndoline("MUSICINTER", mapheaderinfo[num]->musintername, UNDO_NONE);
+	mapheaderinfo[num]->musintername[0] = '\0';
 	DEH_WriteUndoline("FORCECHARACTER", va("%d", mapheaderinfo[num]->forcecharacter), UNDO_NONE);
 	mapheaderinfo[num]->forcecharacter[0] = '\0';
 	DEH_WriteUndoline("WEATHER", va("%d", mapheaderinfo[num]->weather), UNDO_NONE);
diff --git a/src/p_slopes.c b/src/p_slopes.c
index 68767b50cb4b6fe880a6ed86c8159e1e5d8a01bf..4e12b104f3f4a0d104b81ea93e8835777dc22442 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -264,7 +264,7 @@ void P_SpawnSlope_Line(int linenum)
 
 	if(!line->frontsector || !line->backsector)
 	{
-		CONS_Printf("P_SpawnSlope_Line used on a line without two sides.\n");
+		CONS_Debug(DBG_SETUP, "P_SpawnSlope_Line used on a line without two sides. (line number %i)\n", linenum);
 		return;
 	}
 
diff --git a/src/p_spec.c b/src/p_spec.c
index e8dc80a75bae7049bded93af3980e4c3e984723f..f3be86ee1c3b9f4a9be69b6d911644cc8614d5e9 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2414,8 +2414,9 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				UINT32 prefadems = (UINT32)max(sides[line->sidenum[0]].textureoffset >> FRACBITS, 0);
 				UINT32 postfadems = (UINT32)max(sides[line->sidenum[0]].rowoffset >> FRACBITS, 0);
 				UINT8 fadetarget = (UINT8)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].textureoffset >> FRACBITS : 0, 0);
-				INT16 fadesource = (INT16)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].rowoffset >> FRACBITS : 0, -1);
+				INT16 fadesource = (INT16)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].rowoffset >> FRACBITS : -1, -1);
 
+				// Seek offset from current song position
 				if (line->flags & ML_EFFECT1)
 				{
 					// adjust for loop point if subtracting
@@ -2427,8 +2428,14 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						position = max(S_GetMusicPosition() + position, 0);
 				}
 
+				// Fade current music to target volume (if music won't be changed)
 				if ((line->flags & ML_EFFECT2) && fadetarget && musicsame)
 				{
+					// 0 fadesource means fade from current volume.
+					// meaning that we can't specify volume 0 as the source volume -- this starts at 1.
+					if (!fadesource)
+						fadesource = -1;
+
 					if (!postfadems)
 						S_SetInternalMusicVolume(fadetarget);
 					else
@@ -2437,6 +2444,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					if (position)
 						S_SetMusicPosition(position);
 				}
+				// Change the music and apply position/fade operations
 				else
 				{
 					strncpy(mapmusname, sides[line->sidenum[0]].text, 7);
diff --git a/src/p_user.c b/src/p_user.c
index ada14997411de33d227190b54656e18df52fd07c..f04386fec64bb35fd5ef6bd14b80e100f5bf63c2 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8688,8 +8688,11 @@ void P_PlayerThink(player_t *player)
 
 	if (player->bot)
 	{
-		if (player->playerstate == PST_LIVE && B_CheckRespawn(player))
-			player->playerstate = PST_REBORN;
+		if (player->playerstate == PST_LIVE || player->playerstate == PST_DEAD)
+		{
+			if (B_CheckRespawn(player))
+				player->playerstate = PST_REBORN;
+		}
 		if (player->playerstate == PST_REBORN)
 			return;
 	}
diff --git a/src/r_things.c b/src/r_things.c
index 67a45a76e4681be7d94e04e63737ae3d1e97b51a..0382596f16bc5f1c17b84d5d2e6ae8ad9f259f3b 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2306,7 +2306,7 @@ void R_DrawMasked(void)
 // ==========================================================================
 
 INT32 numskins = 0;
-skin_t skins[MAXSKINS+1];
+skin_t skins[MAXSKINS];
 // FIXTHIS: don't work because it must be inistilised before the config load
 //#define SKINVALUES
 #ifdef SKINVALUES
@@ -2559,7 +2559,7 @@ void R_AddSkins(UINT16 wadnum)
 		// advance by default
 		lastlump = lump + 1;
 
-		if (numskins > MAXSKINS)
+		if (numskins >= MAXSKINS)
 		{
 			CONS_Debug(DBG_RENDER, "ignored skin (%d skins maximum)\n", MAXSKINS);
 			continue; // so we know how many skins couldn't be added
diff --git a/src/r_things.h b/src/r_things.h
index 6614e0aa4b8c3a1d91d01dd38d2e17b58dd75bb6..2823c3894e1b40b98b9c4310b8eb8a074e40036d 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -180,7 +180,7 @@ typedef struct drawnode_s
 } drawnode_t;
 
 extern INT32 numskins;
-extern skin_t skins[MAXSKINS + 1];
+extern skin_t skins[MAXSKINS];
 
 void SetPlayerSkin(INT32 playernum,const char *skinname);
 void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
diff --git a/src/s_sound.c b/src/s_sound.c
index 329c9de558d68a3c29fdc212946d5e792b5c5676..445312788bdf563e3f1be7cec6287cd229b13d34 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1532,6 +1532,11 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32
 		I_SetSongPosition(position);
 		I_FadeSong(100, fadeinms, NULL);
  	}
+	else // reset volume to 100 with same music
+	{
+		I_StopFadingSong();
+		I_FadeSong(100, 500, NULL);
+	}
 }
 
 void S_StopMusic(void)
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 71d80605005782a7b0c1d8485bb2f3b8fb4caef4..77bf414ccc8265e4eeb602aee0ad8edb5ac65884 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -996,6 +996,7 @@ void I_UpdateNoBlit(void)
 // from PrBoom's src/SDL/i_video.c
 static inline boolean I_SkipFrame(void)
 {
+#if 0
 	static boolean skip = false;
 
 	if (rendermode != render_soft)
@@ -1015,6 +1016,8 @@ static inline boolean I_SkipFrame(void)
 		default:
 			return false;
 	}
+#endif
+	return false;
 }
 
 //
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index 3c4a5c7b55833b3c9d7a2555441a817de6b066d4..3932d23d3374b0c3e323e48ab623ac175bfc3c34 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -996,21 +996,10 @@ boolean I_LoadSong(char *data, size_t len)
 	const char *key1 = "LOOP";
 	const char *key2 = "POINT=";
 	const char *key3 = "MS=";
-	const char *key4 = "LENGTHMS=";
 	const size_t key1len = strlen(key1);
 	const size_t key2len = strlen(key2);
 	const size_t key3len = strlen(key3);
-	const size_t key4len = strlen(key4);
 
-	// for mp3 wide chars
-	const char *key1w = "L\0O\0O\0P\0";
-	const char *key2w = "P\0O\0I\0N\0T\0\0\0\xFF\xFE";
-	const char *key3w = "M\0S\0\0\0\xFF\xFE";
-	const char *key4w = "L\0E\0N\0G\0T\0H\0M\0S\0\0\0\xFF\xFE";
-	const char *wterm = "\0\0";
-	char wval[10];
-
-	size_t wstart, wp;
 	char *p = data;
 	SDL_RWops *rw;
 
@@ -1134,12 +1123,6 @@ boolean I_LoadSong(char *data, size_t len)
 		return false;
 	}
 
-	if (I_SongType() == MU_MP3)
-	{
-		CONS_Debug(DBG_BASIC, "MP3 songs are unsupported and may crash! Use OGG instead.\n");
-		CONS_Debug(DBG_DETAILED, "MP3 songs are unsupported and may crash! Use OGG instead.\n");
-	}
-
 #ifdef HAVE_OPENMPT
 	switch(Mix_GetMusicType(music))
 	{
@@ -1199,61 +1182,8 @@ boolean I_LoadSong(char *data, size_t len)
 				// Everything that uses LOOPMS will work perfectly with SDL_Mixer.
 			}
 		}
-		else if (fpclassify(song_length) == FP_ZERO && !strncmp(p, key4, key4len)) // is it LENGTHMS=?
-		{
-			p += key4len; // skip LENGTHMS
-			song_length = (float)(atoi(p) / 1000.0L);
-		}
-		// below: search MP3 or other tags that use wide char encoding
-		else if (fpclassify(loop_point) == FP_ZERO && !memcmp(p, key1w, key1len*2)) // LOOP wide char
-		{
-			p += key1len*2;
-			if (!memcmp(p, key2w, (key2len+1)*2)) // POINT= wide char
-			{
-				p += (key2len+1)*2;
-				wstart = (size_t)p;
-				wp = 0;
-				while (wp < 9 && memcmp(p, wterm, 2))
-				{
-					wval[wp] = *p;
-					p += 2;
-					wp = ((size_t)(p-wstart))/2;
-				}
-				wval[min(wp, 9)] = 0;
-				loop_point = (float)((44.1L+atoi(wval) / 44100.0L));
-			}
-			else if (!memcmp(p, key3w, (key3len+1)*2)) // MS= wide char
-			{
-				p += (key3len+1)*2;
-				wstart = (size_t)p;
-				wp = 0;
-				while (wp < 9 && memcmp(p, wterm, 2))
-				{
-					wval[wp] = *p;
-					p += 2;
-					wp = ((size_t)(p-wstart))/2;
-				}
-				wval[min(wp, 9)] = 0;
-				loop_point = (float)(atoi(wval) / 1000.0L);
-			}
-		}
-		else if (fpclassify(song_length) == FP_ZERO && !memcmp(p, key4w, (key4len+1)*2)) // LENGTHMS= wide char
-		{
-			p += (key4len+1)*2;
-			wstart = (size_t)p;
-			wp = 0;
-			while (wp < 9 && memcmp(p, wterm, 2))
-			{
-				wval[wp] = *p;
-				p += 2;
-				wp = ((size_t)(p-wstart))/2;
-			}
-			wval[min(wp, 9)] = 0;
-			song_length = (float)(atoi(wval) / 1000.0L);
-		}
 
-		if (fpclassify(loop_point) != FP_ZERO && fpclassify(song_length) != FP_ZERO && song_length > loop_point) // Got what we needed
-			// the last case is a sanity check, in case the wide char searches were false matches.
+		if (fpclassify(loop_point) != FP_ZERO) // Got what we needed
 			break;
 		else // continue searching
 			p++;
diff --git a/src/w_wad.c b/src/w_wad.c
index c4f9ceca8fbc4d2656aa818e5cc3b327c077709d..5bf7a36d34b8709525818e427a3e0b74bf9911e9 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1717,15 +1717,27 @@ int W_VerifyNMUSlumps(const char *filename)
 	// ENDOOM text and palette lumps
 	lumpchecklist_t NMUSlist[] =
 	{
-		{"D_", 2},
-		{"O_", 2},
-		{"DS", 2},
-		{"ENDOOM", 6},
-		{"PLAYPAL", 7},
-		{"COLORMAP", 8},
-		{"PAL", 3},
-		{"CLM", 3},
-		{"TRANS", 5},
+		{"D_", 2}, // MIDI music
+		{"O_", 2}, // Digital music
+		{"DS", 2}, // Sound effects
+
+		{"ENDOOM", 6}, // ENDOOM text lump
+
+		{"PLAYPAL", 7}, // Palette changes
+		{"PAL", 3}, // Palette changes
+		{"COLORMAP", 8}, // Colormap changes
+		{"CLM", 3}, // Colormap changes
+		{"TRANS", 5}, // Translucency map changes
+
+		{"LTFNT", 5}, // Level title font changes
+		{"TTL", 3}, // Act number changes
+		{"STCFN", 5}, // Console font changes
+		{"TNYFN", 5}, // Tiny console font changes
+		{"SBO", 3}, // Acceptable HUD changes (Score Time Rings)
+		{"RRINGS", 6}, // Rings HUD (not named as SBO)
+		{"YB_", 3}, // Intermission graphics, goes with the above
+		{"M_", 2}, // As does menu stuff
+
 		{NULL, 0},
 	};
 	return W_VerifyFile(filename, NMUSlist, false);
diff --git a/src/win32/win_snd.c b/src/win32/win_snd.c
index 4507c27ff3dad2dd6ed04a0060e1993252cfed3b..f3e3bbed487132ef0421474f3dfc90ee70f0e84a 100644
--- a/src/win32/win_snd.c
+++ b/src/win32/win_snd.c
@@ -815,11 +815,11 @@ void I_SetMusicVolume(UINT8 volume)
 	FMR_MUSIC(FMOD_Channel_SetVolume(music_channel, music_volume / 31.0));
 }
 
-UINT32 I_GetSongLength()
+UINT32 I_GetSongLength(void)
 {
+	UINT32 length;
 	if (I_SongType() == MU_MID)
 		return 0;
-	UINT32 length;
 	FMR_MUSIC(FMOD_Sound_GetLength(music_stream, &length, FMOD_TIMEUNIT_MS));
 	return length;
 }
@@ -837,11 +837,11 @@ UINT32 I_GetSongLoopPoint(void)
 
 boolean I_SetSongPosition(UINT32 position)
 {
+	FMOD_RESULT e;
 	if(I_SongType() == MU_MID)
 		// Dummy out; this works for some MIDI, but not others.
 		// SDL does not support this for any MIDI.
 		return false;
-	FMOD_RESULT e;
 	e = FMOD_Channel_SetPosition(music_channel, position, FMOD_TIMEUNIT_MS);
 	if (e == FMOD_OK)
 		return true;
@@ -857,11 +857,11 @@ boolean I_SetSongPosition(UINT32 position)
 
 UINT32 I_GetSongPosition(void)
 {
+	FMOD_RESULT e;
+	unsigned int fmposition = 0;
 	if(I_SongType() == MU_MID)
 		// Dummy out because unsupported, even though FMOD does this correctly.
 		return 0;
-	FMOD_RESULT e;
-	unsigned int fmposition = 0;
 	e = FMOD_Channel_GetPosition(music_channel, &fmposition, FMOD_TIMEUNIT_MS);
 	if (e == FMOD_OK)
 		return (UINT32)fmposition;
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index 77a21f7f38fd6d4c525391be3beaaaf57781885b..8b7adf7c62fc9fe5e243c3f8feba3b2d29d3a8c2 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -639,9 +639,6 @@ void I_Error(const char *error, ...)
 	if (!errorcount)
 	{
 		M_SaveConfig(NULL); // save game config, cvars..
-#ifndef NONET
-		D_SaveBan(); // save the ban list
-#endif
 		G_SaveGameData();
 	}
 
diff --git a/src/y_inter.c b/src/y_inter.c
index 4b340cabd0cb4794b6725b8eaea2dadb49e3961c..ed4972d2e6ae9fd21d7627f9db4650be58ced0f7 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -696,7 +696,19 @@ void Y_Ticker(void)
 		boolean anybonuses = false;
 
 		if (!intertic) // first time only
-			S_ChangeMusicInternal("lclear", false); // don't loop it
+		{
+			if (mapheaderinfo[gamemap-1]->musinterfadeout
+#ifdef _WIN32
+				// can't fade midi due to win32 volume hack
+				&& S_MusicType() != MU_MID
+#endif
+			)
+				S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
+			else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
+				S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
+			else
+				S_ChangeMusicInternal("lclear", false); // don't loop it
+		}
 
 		if (intertic < TICRATE) // one second pause before tally begins
 			return;
@@ -757,7 +769,17 @@ void Y_Ticker(void)
 
 		if (!intertic) // first time only
 		{
-			S_ChangeMusicInternal("lclear", false); // don't loop it
+			if (mapheaderinfo[gamemap-1]->musinterfadeout
+#ifdef _WIN32
+				// can't fade midi due to win32 volume hack
+				&& S_MusicType() != MU_MID
+#endif
+			)
+				S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
+			else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
+				S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
+			else
+				S_ChangeMusicInternal("lclear", false); // don't loop it
 			tallydonetic = 0;
 		}