diff --git a/src/am_map.c b/src/am_map.c
index 9c2aee2c3daf2b910c8e45adec6b61a2404f8cef..70714facb62d07b4dff1edc281d531e03b4bf284 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -25,36 +25,64 @@
 #endif
 
 // For use if I do walls with outsides/insides
-static const UINT8 GREENS     = (10*16);
-static const UINT8 GREENRANGE = 16;
-static const UINT8 GRAYS      = (0*16);
-static const UINT8 GRAYSRANGE = 16;
-static const UINT8 DBLACK     = 31;
-static const UINT8 DWHITE     = 0;
+static const UINT8 REDS        = (8*16);
+static const UINT8 REDRANGE    = 16;
+static const UINT8 GRAYS       = (1*16);
+static const UINT8 GRAYSRANGE  = 16;
+static const UINT8 BROWNS      = (3*16);
+static const UINT8 BROWNRANGE  = 16;
+static const UINT8 YELLOWS     = (7*16);
+static const UINT8 YELLOWRANGE = 8;
+static const UINT8 GREENS      = (10*16);
+static const UINT8 GREENRANGE  = 16;
+static const UINT8 DBLACK      = 31;
+static const UINT8 DWHITE      = 0;
+
+static const UINT8 NOCLIMBREDS        = 248;
+static const UINT8 NOCLIMBREDRANGE    = 8;
+static const UINT8 NOCLIMBGRAYS       = 204;
+static const UINT8 NOCLIMBGRAYSRANGE  = 4;
+static const UINT8 NOCLIMBBROWNS      = (2*16);
+static const UINT8 NOCLIMBBROWNRANGE  = 16;
+static const UINT8 NOCLIMBYELLOWS     = (11*16);
+static const UINT8 NOCLIMBYELLOWRANGE = 8;
+
 
 #ifdef _NDS
 #undef BACKGROUND
 #endif
 
 // Automap colors
-#define BACKGROUND       DBLACK
-#define YOURCOLORS       DWHITE
-#define YOURRANGE        0
-#define WALLCOLORS       REDS
-#define WALLRANGE        REDRANGE
-#define TSWALLCOLORS     GRAYS
-#define TSWALLRANGE      GRAYSRANGE
-#define FDWALLCOLORS     BROWNS
-#define FDWALLRANGE      BROWNRANGE
-#define CDWALLCOLORS     YELLOWS
-#define CDWALLRANGE      YELLOWRANGE
-#define THINGCOLORS      GREENS
-#define THINGRANGE       GREENRANGE
-#define SECRETWALLCOLORS WALLCOLORS
-#define SECRETWALLRANGE  WALLRANGE
-#define GRIDCOLORS       (GRAYS + GRAYSRANGE/2)
-#define GRIDRANGE        0
-#define XHAIRCOLORS      GRAYS
+#define BACKGROUND            DBLACK
+#define YOURCOLORS            DWHITE
+#define YOURRANGE             0
+#define WALLCOLORS            (REDS + REDRANGE/2)
+#define WALLRANGE             (REDRANGE/2)
+#define NOCLIMBWALLCOLORS     (NOCLIMBREDS + NOCLIMBREDRANGE/2)
+#define NOCLIMBWALLRANGE      (NOCLIMBREDRANGE/2)
+#define THOKWALLCOLORS        REDS
+#define THOKWALLRANGE         REDRANGE
+#define NOCLIMBTHOKWALLCOLORS NOCLIMBREDS
+#define NOCLIMBTHOKWALLRANGE  NOCLIMBREDRANGE
+#define TSWALLCOLORS          GRAYS
+#define TSWALLRANGE           GRAYSRANGE
+#define NOCLIMBTSWALLCOLORS   NOCLIMBGRAYS
+#define NOCLIMBTSWALLRANGE    NOCLIMBGRAYSRANGE
+#define FDWALLCOLORS          BROWNS
+#define FDWALLRANGE           BROWNRANGE
+#define NOCLIMBFDWALLCOLORS   NOCLIMBBROWNS
+#define NOCLIMBFDWALLRANGE    NOCLIMBBROWNRANGE
+#define CDWALLCOLORS          YELLOWS
+#define CDWALLRANGE           YELLOWRANGE
+#define NOCLIMBCDWALLCOLORS   NOCLIMBYELLOWS
+#define NOCLIMBCDWALLRANGE    NOCLIMBYELLOWRANGE
+#define THINGCOLORS           GREENS
+#define THINGRANGE            GREENRANGE
+#define SECRETWALLCOLORS      WALLCOLORS
+#define SECRETWALLRANGE       WALLRANGE
+#define GRIDCOLORS            (GRAYS + GRAYSRANGE/2)
+#define GRIDRANGE             0
+#define XHAIRCOLORS           GRAYS
 
 // drawing stuff
 #define FB 0
@@ -1009,7 +1037,75 @@ static inline void AM_drawWalls(void)
 		l.b.x = lines[i].v2->x;
 		l.b.y = lines[i].v2->y;
 
-		AM_drawMline(&l, GRAYS + 3);
+//		AM_drawMline(&l, GRAYS + 3); // Old, everything-is-gray automap
+		if (!lines[i].backsector) // 1-sided
+		{
+			if (lines[i].flags & ML_NOCLIMB)
+			{
+				AM_drawMline(&l, NOCLIMBWALLCOLORS+lightlev);
+			}
+			else
+			{
+				AM_drawMline(&l, WALLCOLORS+lightlev);
+			}
+		}
+		else if (lines[i].backsector->floorheight == lines[i].backsector->ceilingheight // Back is thok barrier
+				 || lines[i].frontsector->floorheight == lines[i].frontsector->ceilingheight) // Front is thok barrier
+		{
+			if (lines[i].backsector->floorheight == lines[i].backsector->ceilingheight
+				&& lines[i].frontsector->floorheight == lines[i].frontsector->ceilingheight) // BOTH are thok barriers
+			{
+				if (lines[i].flags & ML_NOCLIMB)
+				{
+					AM_drawMline(&l, NOCLIMBTSWALLCOLORS+lightlev);
+				}
+				else
+				{
+					AM_drawMline(&l, TSWALLCOLORS+lightlev);
+				}
+			}
+			else
+			{
+				if (lines[i].flags & ML_NOCLIMB)
+				{
+					AM_drawMline(&l, NOCLIMBTHOKWALLCOLORS+lightlev);
+				}
+				else
+				{
+					AM_drawMline(&l, THOKWALLCOLORS+lightlev);
+				}
+			}
+		}
+		else
+		{
+			if (lines[i].flags & ML_NOCLIMB) {
+				if (lines[i].backsector->floorheight
+						!= lines[i].frontsector->floorheight) {
+					AM_drawMline(&l, NOCLIMBFDWALLCOLORS + lightlev); // floor level change
+				}
+				else if (lines[i].backsector->ceilingheight
+						!= lines[i].frontsector->ceilingheight) {
+					AM_drawMline(&l, NOCLIMBCDWALLCOLORS+lightlev); // ceiling level change
+				}
+				else {
+					AM_drawMline(&l, NOCLIMBTSWALLCOLORS+lightlev);
+				}
+			}
+			else
+			{
+				if (lines[i].backsector->floorheight
+						!= lines[i].frontsector->floorheight) {
+					AM_drawMline(&l, FDWALLCOLORS + lightlev); // floor level change
+				}
+				else if (lines[i].backsector->ceilingheight
+						!= lines[i].frontsector->ceilingheight) {
+					AM_drawMline(&l, CDWALLCOLORS+lightlev); // ceiling level change
+				}
+				else {
+					AM_drawMline(&l, TSWALLCOLORS+lightlev);
+				}
+			}
+		}
 	}
 }
 
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 46a711aac7ffc51b1d878c832a8b23083736a8ca..c6008e86dc3fab7c108fd34944b10179baafed33 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -759,7 +759,10 @@ static inline void resynch_write_ctf(resynchend_pak *rst)
 			// Should be held by a player
 			for (j = 0; j < MAXPLAYERS; ++j)
 			{
-				if (!playeringame[j] || players[j].gotflag != i)
+				// GF_REDFLAG is 1, GF_BLUEFLAG is 2
+				// redflag handling is i=0, blueflag is i=1
+				// so check for gotflag == (i+1)
+				if (!playeringame[j] || players[j].gotflag != (i+1))
 					continue;
 				rst->flagplayer[i] = (SINT8)j;
 				break;
@@ -2229,7 +2232,11 @@ static void CL_RemovePlayer(INT32 playernum)
 
 void CL_Reset(void)
 {
-	if (demorecording || metalrecording)
+	if (metalrecording)
+		G_StopMetalRecording();
+	if (metalplayback)
+		G_StopMetalDemo();
+	if (demorecording)
 		G_CheckDemoStatus();
 
 	// reset client/server code
@@ -2954,8 +2961,10 @@ boolean Playing(void)
 
 boolean SV_SpawnServer(void)
 {
-	if (demoplayback || metalplayback)
+	if (demoplayback)
 		G_StopDemo(); // reset engine parameter
+	if (metalplayback)
+		G_StopMetalDemo();
 
 	if (!serverrunning)
 	{
diff --git a/src/d_main.c b/src/d_main.c
index 025903904555c4dc19351c34a2d1340070b7ccb3..bd8e12f2385186708d378c701404ce73bdc88b04 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1090,14 +1090,14 @@ void D_SRB2Main(void)
 #endif
 	D_CleanFile();
 
-#if 1 // md5s last updated 3/18/14
+#if 1 // md5s last updated 3/22/14
 
 	// Check MD5s of autoloaded files
 	W_VerifyFileMD5(0, "ac309fb3c7d4b5b685e2cd26beccf0e8"); // srb2.srb/srb2.wad
 	W_VerifyFileMD5(1, "a894044b555dfcc71865cee16a996e88"); // zones.dta
 	W_VerifyFileMD5(2, "4c410c1de6e0440cc5b2858dcca80c3e"); // player.dta
 	W_VerifyFileMD5(3, "85901ad4bf94637e5753d2ac2c03ea26"); // rings.dta
-	W_VerifyFileMD5(4, "e868046d2d2da1d8c706c900edfb03f8"); // patch.dta
+	W_VerifyFileMD5(4, "4d56695e194a6fd3bc5c87610a215186"); // patch.dta
 
 	// don't check music.dta because people like to modify it, and it doesn't matter if they do
 	// ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for.
diff --git a/src/d_net.c b/src/d_net.c
index d93b80c95b4ee07f99a450a1877899c5b5a02a98..906c5389016950007bc62a318674fd54a2e31d24 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -129,10 +129,10 @@ boolean Net_GetNetStat(void)
 // -----------------------------------------------------------------
 // Some structs and functions for acknowledgement of packets
 // -----------------------------------------------------------------
-#define MAXACKPACKETS 64 // minimum number of nodes
-#define MAXACKTOSEND 64
-#define URGENTFREESLOTENUM 6
-#define ACKTOSENDTIMEOUT (TICRATE/17)
+#define MAXACKPACKETS 96 // minimum number of nodes
+#define MAXACKTOSEND 96
+#define URGENTFREESLOTENUM 10
+#define ACKTOSENDTIMEOUT (TICRATE/11)
 
 #ifndef NONET
 typedef struct
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 06c6fcb0ea5da45a10931b2b7a1749845697ccc6..6b74e60241a75fde52c961eb0bf087ebc6b13770 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1575,15 +1575,18 @@ static void Command_Playdemo_f(void)
 		return;
 	}
 
-	// disconnect from server here?
-	if (demoplayback || metalplayback)
-		G_StopDemo();
 	if (netgame)
 	{
 		CONS_Printf(M_GetText("You can't play a demo while in a netgame.\n"));
 		return;
 	}
 
+	// disconnect from server here?
+	if (demoplayback)
+		G_StopDemo();
+	if (metalplayback)
+		G_StopMetalDemo();
+
 	// open the demo file
 	strcpy(name, COM_Argv(1));
 	// dont add .lmp so internal game demos can be played
@@ -1603,15 +1606,18 @@ static void Command_Timedemo_f(void)
 		return;
 	}
 
-	// disconnect from server here?
-	if (demoplayback || metalplayback)
-		G_StopDemo();
 	if (netgame)
 	{
 		CONS_Printf(M_GetText("You can't play a demo while in a netgame.\n"));
 		return;
 	}
 
+	// disconnect from server here?
+	if (demoplayback)
+		G_StopDemo();
+	if (metalplayback)
+		G_StopMetalDemo();
+
 	// open the demo file
 	strcpy (name, COM_Argv(1));
 	// dont add .lmp so internal game demos can be played
@@ -1970,6 +1976,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	if (demorecording) // Okay, level loaded, character spawned and skinned,
 		G_BeginRecording(); // I AM NOW READY TO RECORD.
 	demo_start = true;
+	metal_start = true;
 }
 
 static void Command_Pause(void)
diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h
index 41d25f2e9e6430b1b4e5160b1e6b82924966330d..edbba552cb58705be6d12b6ee33addb38e207e5c 100644
--- a/src/d_ticcmd.h
+++ b/src/d_ticcmd.h
@@ -36,8 +36,11 @@ typedef enum
 	BT_CAMRIGHT   = 1<<9, // turn camera right
 	BT_TOSSFLAG   = 1<<10,
 	BT_JUMP       = 1<<11,
-	BT_FIRENORMAL = 1<<12 // Fire a normal ring no matter what
-	// free: up to and including 1<<15
+	BT_FIRENORMAL = 1<<12, // Fire a normal ring no matter what
+
+	BT_CUSTOM1    = 1<<13,
+	BT_CUSTOM2    = 1<<14,
+	BT_CUSTOM3    = 1<<15,
 } buttoncode_t;
 
 // The data sampled per tick (single player)
diff --git a/src/dehacked.c b/src/dehacked.c
index 6eae1ce23ee6e51be273cf443a23b25284ee1449..35e672eb17f08bbf709bc872b069546ea7cae179 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -7101,6 +7101,7 @@ static const char *const MOBJFLAG_LIST[] = {
 	"STICKY",
 	"NIGHTSITEM",
 	"NOCLIPTHING",
+	"GRENADEBOUNCE",
 	"RUNSPAWNFUNC",
 	NULL
 };
@@ -7648,6 +7649,9 @@ struct {
 	{"BT_TOSSFLAG",BT_TOSSFLAG},
 	{"BT_JUMP",BT_JUMP},
 	{"BT_FIRENORMAL",BT_FIRENORMAL}, // Fire a normal ring no matter what
+	{"BT_CUSTOM1",BT_CUSTOM1}, // Lua customizable
+	{"BT_CUSTOM2",BT_CUSTOM2}, // Lua customizable
+	{"BT_CUSTOM3",BT_CUSTOM3}, // Lua customizable
 
 	// cvflags_t
 	{"CV_SAVE",CV_SAVE},
diff --git a/src/doomdef.h b/src/doomdef.h
index a5454851fdb7b4f46196b0eabbe17d855d95806f..6c9d7ebe40beec416a33beb6584774f7e6e9e65d 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -144,8 +144,8 @@ extern FILE *logstream;
 #define VERSIONSTRING "Trunk"
 #else
 #define VERSION    201 // Game version
-#define SUBVERSION 4  // more precise version number
-#define VERSIONSTRING "v2.1.4"
+#define SUBVERSION 5  // more precise version number
+#define VERSIONSTRING "v2.1.5"
 #endif
 
 // Modification options
@@ -201,7 +201,7 @@ extern FILE *logstream;
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.1.0 is not version "1".
-#define MODVERSION 9
+#define MODVERSION 10
 
 
 
diff --git a/src/g_game.c b/src/g_game.c
index dfd9aacd0889946d6324d946eff9fec03d2d16a1..8f0d5b910994ff8b90e75e442fa51473b6b1e143 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -231,14 +231,17 @@ boolean demorecording;
 boolean demoplayback;
 boolean titledemo; // Title Screen demo can be cancelled by any key
 static UINT8 *demobuffer = NULL;
-static UINT8 *metalbuffer = NULL;
-static UINT8 *demo_p, *metal_p, *demotime_p;
+static UINT8 *demo_p, *demotime_p;
 static UINT8 *demoend;
 static UINT8 demoflags;
 boolean singledemo; // quit after playing a demo from cmdline
+boolean demo_start; // don't start playing demo right away
+
 boolean metalrecording; // recording as metal sonic
 mobj_t *metalplayback;
-boolean demo_start; // don't start playing demo right away
+static UINT8 *metalbuffer = NULL;
+static UINT8 *metal_p;
+boolean metal_start;
 
 // extra data stuff (events registered this frame while recording)
 static struct {
@@ -1046,8 +1049,16 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	if (PLAYER1INPUTDOWN(gc_tossflag))
 		cmd->buttons |= BT_TOSSFLAG;
 
+	// Lua scriptable buttons
+	if (PLAYER1INPUTDOWN(gc_custom1))
+		cmd->buttons |= BT_CUSTOM1;
+	if (PLAYER1INPUTDOWN(gc_custom2))
+		cmd->buttons |= BT_CUSTOM2;
+	if (PLAYER1INPUTDOWN(gc_custom3))
+		cmd->buttons |= BT_CUSTOM3;
+
 	// use with any button/key
-	if (PLAYER1INPUTDOWN(gc_use) && !(player->pflags & PF_MACESPIN))
+	if (PLAYER1INPUTDOWN(gc_use))
 		cmd->buttons |= BT_USE;
 
 	// Camera Controls
@@ -1315,6 +1326,14 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	if (PLAYER2INPUTDOWN(gc_tossflag))
 		cmd->buttons |= BT_TOSSFLAG;
 
+	// Lua scriptable buttons
+	if (PLAYER2INPUTDOWN(gc_custom1))
+		cmd->buttons |= BT_CUSTOM1;
+	if (PLAYER2INPUTDOWN(gc_custom2))
+		cmd->buttons |= BT_CUSTOM2;
+	if (PLAYER2INPUTDOWN(gc_custom3))
+		cmd->buttons |= BT_CUSTOM3;
+
 	// use with any button/key
 	if (PLAYER2INPUTDOWN(gc_use))
 		cmd->buttons |= BT_USE;
@@ -2645,8 +2664,10 @@ static void G_DoCompleted(void)
 
 	gameaction = ga_nothing;
 
-	if (metalrecording || metalplayback)
-		G_CheckDemoStatus();
+	if (metalplayback)
+		G_StopMetalDemo();
+	if (metalrecording)
+		G_StopMetalRecording();
 
 	for (i = 0; i < MAXPLAYERS; i++)
 		if (playeringame[i])
@@ -4200,7 +4221,7 @@ void G_ReadMetalTic(mobj_t *metal)
 	UINT16 speed;
 	UINT8 statetype;
 
-	if (!metal_p || !demo_start)
+	if (!metal_p || !metal_start)
 		return;
 	ziptic = READUINT8(metal_p);
 
@@ -4289,7 +4310,7 @@ void G_ReadMetalTic(mobj_t *metal)
 	if (*metal_p == DEMOMARKER)
 	{
 		// end of demo data stream
-		G_CheckDemoStatus();
+		G_StopMetalDemo();
 		return;
 	}
 }
@@ -4396,7 +4417,7 @@ void G_WriteMetalTic(mobj_t *metal)
 	// latest demos with mouse aiming byte in ticcmd
 	if (demo_p >= demoend - 32)
 	{
-		G_CheckDemoStatus(); // no more space
+		G_StopMetalRecording(); // no more space
 		return;
 	}
 }
@@ -4871,6 +4892,7 @@ void G_DoPlayDemo(char *defdemoname)
 
 	// didn't start recording right away.
 	demo_start = false;
+	metal_start = false;
 
 #ifdef HAVE_BLUA
 	LUAh_MapChange();
@@ -4916,6 +4938,7 @@ void G_DoPlayDemo(char *defdemoname)
 	players[0].jumpfactor = jumpfactor;
 
 	demo_start = true;
+	metal_start = true;
 }
 
 void G_AddGhost(char *defdemoname)
@@ -5168,6 +5191,7 @@ void G_DoPlayMetal(void)
 	if (!mo)
 	{
 		CONS_Alert(CONS_ERROR, M_GetText("Failed to find bot entity.\n"));
+		Z_Free(metalbuffer);
 		return;
 	}
 
@@ -5215,18 +5239,46 @@ void G_DoneLevelLoad(void)
 ===================
 */
 
+// Stops metal sonic's demo. Separate from other functions because metal + replays can coexist
+void G_StopMetalDemo(void)
+{
+
+	// Metal Sonic finishing doesn't end the game, dammit.
+	Z_Free(metalbuffer);
+	metalbuffer = NULL;
+	metalplayback = NULL;
+	metal_p = NULL;
+}
+
+// Stops metal sonic recording.
+ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(void)
+{
+	boolean saved = false;
+	if (demo_p)
+	{
+		UINT8 *p = demobuffer+16; // checksum position
+#ifdef NOMD5
+		UINT8 i;
+		WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
+		for (i = 0; i < 16; i++, p++)
+			*p = P_Random(); // This MD5 was chosen by fair dice roll and most likely < 50% correct.
+#else
+		WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
+		md5_buffer((char *)p+16, demo_p - (p+16), (void *)p); // make a checksum of everything after the checksum in the file.
+#endif
+		saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuffer, demo_p - demobuffer); // finally output the file.
+	}
+	free(demobuffer);
+	metalrecording = false;
+	if (saved)
+		I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap));
+	I_Error("Failed to save demo!");
+}
+
 // reset engine variable set for the demos
 // called from stopdemo command, map command, and g_checkdemoStatus.
 void G_StopDemo(void)
 {
-	if (metalplayback)
-	{ // Metal Sonic finishing doesn't end the game, dammit.
-		Z_Free(metalbuffer);
-		metalbuffer = NULL;
-		metalplayback = NULL;
-		if (!demoplayback)
-			return;
-	}
 	Z_Free(demobuffer);
 	demobuffer = NULL;
 	demoplayback = false;
@@ -5250,31 +5302,7 @@ boolean G_CheckDemoStatus(void)
 	if(ghosts) // ... ... ...
 		ghosts = NULL; // :)
 
-	if (metalrecording)
-	{
-		saved = false;
-		if (demo_p)
-		{
-			UINT8 *p = demobuffer+16; // checksum position
-#ifdef NOMD5
-			UINT8 i;
-			WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
-			for (i = 0; i < 16; i++, p++)
-				*p = P_Random(); // This MD5 was chosen by fair dice roll and most likely < 50% correct.
-#else
-			WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
-			md5_buffer((char *)p+16, demo_p - (p+16), (void *)p); // make a checksum of everything after the checksum in the file.
-#endif
-			saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuffer, demo_p - demobuffer); // finally output the file.
-		}
-		free(demobuffer);
-		metalrecording = false;
-		if (saved)
-			I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap));
-		else
-			I_Error("Failed to save demo!");
-		return true;
-	}
+	// DO NOT end metal sonic demos here
 
 	if (timingdemo)
 	{
@@ -5334,12 +5362,6 @@ boolean G_CheckDemoStatus(void)
 		return true;
 	}
 
-	if (metalplayback)
-	{
-		G_StopDemo();
-		return false;
-	}
-
 	return false;
 }
 
diff --git a/src/g_game.h b/src/g_game.h
index d57b6ebdd3112d2121c48dff567f2f71c76e651c..af37fc8a5ea9e5246dcdbb4cff43699d77d9639e 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -37,12 +37,14 @@ extern boolean playeringame[MAXPLAYERS];
 
 // demoplaying back and demo recording
 extern boolean demoplayback, titledemo, demorecording, timingdemo;
-extern mobj_t *metalplayback;
 
 // Quit after playing a demo from cmdline.
 extern boolean singledemo;
 extern boolean demo_start;
 
+extern mobj_t *metalplayback;
+extern boolean metal_start;
+
 // gametic at level start
 extern tic_t levelstarttic;
 
@@ -150,6 +152,8 @@ void G_TimeDemo(const char *name);
 void G_AddGhost(char *defdemoname);
 void G_DoPlayMetal(void);
 void G_DoneLevelLoad(void);
+void G_StopMetalDemo(void);
+ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(void);
 void G_StopDemo(void);
 boolean G_CheckDemoStatus(void);
 
diff --git a/src/g_input.c b/src/g_input.c
index 29d13dce97e946749c76ad61b5c4b3271ba55426..f8170dd3afae9b44463916af8109188d7df253c6 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -989,6 +989,9 @@ static const char *gamecontrolname[num_gamecontrols] =
 	"jump",
 	"console",
 	"pause",
+	"custom1",
+	"custom2",
+	"custom3",
 };
 
 #define NUMKEYNAMES (sizeof (keynames)/sizeof (keyname_t))
diff --git a/src/g_input.h b/src/g_input.h
index 334b7ad9d5d86617f71bbb800400f8868e170245..699542a8a4cd08f664ad8dda51c2d0eba64db4e4 100644
--- a/src/g_input.h
+++ b/src/g_input.h
@@ -118,6 +118,9 @@ typedef enum
 	gc_jump,
 	gc_console,
 	gc_pause,
+	gc_custom1, // Lua scriptable
+	gc_custom2, // Lua scriptable
+	gc_custom3, // Lua scriptable
 	num_gamecontrols
 } gamecontrols_e;
 
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 14f6d54adcc8951920eee04196028abd8c99e9c9..96902c732c732b64fb9591b7cb5603bf6fc750ad 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1714,19 +1714,19 @@ static void HU_DrawCoopOverlay(void)
 #endif
 
 	if (emeralds & EMERALD1)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)-32, V_TRANSLUCENT, emeraldpics[0]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)-32, 0, emeraldpics[0]);
 	if (emeralds & EMERALD2)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)-16, V_TRANSLUCENT, emeraldpics[1]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[1]);
 	if (emeralds & EMERALD3)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)+16, V_TRANSLUCENT, emeraldpics[2]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[2]);
 	if (emeralds & EMERALD4)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)+32, V_TRANSLUCENT, emeraldpics[3]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)+32, 0, emeraldpics[3]);
 	if (emeralds & EMERALD5)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)+16, V_TRANSLUCENT, emeraldpics[4]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[4]);
 	if (emeralds & EMERALD6)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)-16, V_TRANSLUCENT, emeraldpics[5]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[5]);
 	if (emeralds & EMERALD7)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)   , V_TRANSLUCENT, emeraldpics[6]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)   , 0, emeraldpics[6]);
 }
 
 static void HU_DrawNetplayCoopOverlay(void)
@@ -1741,7 +1741,7 @@ static void HU_DrawNetplayCoopOverlay(void)
 	for (i = 0; i < 7; ++i)
 	{
 		if (emeralds & (1 << i))
-			V_DrawScaledPatch(20 + (i * 20), 6, V_TRANSLUCENT, emeraldpics[i]);
+			V_DrawScaledPatch(20 + (i * 20), 6, 0, emeraldpics[i]);
 	}
 }
 
diff --git a/src/info.c b/src/info.c
index e69d84f0997ccd8ee0a1cdd08e107d6d5e52e074..d0ba598b12e62b24ec7d26d5d3731491f03fa879 100644
--- a/src/info.c
+++ b/src/info.c
@@ -4817,10 +4817,10 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		20*TICRATE,     // mass
 		48*FRACUNIT,    // damage
 		sfx_s3k5d,      // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_BOUNCE, // flags
+		MF_NOBLOCKMAP|MF_MISSILE|MF_BOUNCE|MF_GRENADEBOUNCE, // flags
 		S_NULL          // raisestate
 	},
 
@@ -11936,7 +11936,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		6*TICRATE,      // mass (<-- Looking for the Grenade Ring's fuse? It's right here!)
 		1,              // damage
 		sfx_s3k5d,      // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_BOUNCE, // flags
+		MF_NOBLOCKMAP|MF_MISSILE|MF_BOUNCE|MF_GRENADEBOUNCE, // flags
 		S_NULL          // raisestate
 	},
 
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 47bd4370fde64ce71a6a445ea051e8783d7d9f81..38b069610eb40ba152c7a2a250cd55f73ff77212 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -195,6 +195,15 @@ static int patch_set(lua_State *L)
 // lib_draw
 //
 
+static int libd_patchExists(lua_State *L)
+{
+	if (!hud_running)
+		return luaL_error(L, "HUD rendering code should not be called outside of rendering hooks!");
+
+	lua_pushboolean(L, W_CheckNumForName(luaL_checkstring(L, 1)) != LUMPERROR);
+	return 1;
+}
+
 static int libd_cachePatch(lua_State *L)
 {
 	if (!hud_running)
@@ -342,6 +351,7 @@ static int libd_stringWidth(lua_State *L)
 }
 
 static luaL_Reg lib_draw[] = {
+	{"patchExists", libd_patchExists},
 	{"cachePatch", libd_cachePatch},
 	{"draw", libd_draw},
 	{"drawScaled", libd_drawScaled},
diff --git a/src/m_menu.c b/src/m_menu.c
index 0676bb5220ae39e634bafdf4ac96866911a576ec..30876cbcbd2d5f8e50eb3db076f118d1d3f8d899 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1037,6 +1037,10 @@ static menuitem_t OP_CameraControlsMenu[] =
 
 static menuitem_t OP_MiscControlsMenu[] =
 {
+	{IT_CALL | IT_STRING2, NULL, "Custom Action 1",  M_ChangeControl, gc_custom1      },
+	{IT_CALL | IT_STRING2, NULL, "Custom Action 2",  M_ChangeControl, gc_custom2      },
+	{IT_CALL | IT_STRING2, NULL, "Custom Action 3",  M_ChangeControl, gc_custom3      },
+
 	{IT_CALL | IT_STRING2, NULL, "Pause",            M_ChangeControl, gc_pause        },
 	{IT_CALL | IT_STRING2, NULL, "Console",          M_ChangeControl, gc_console      },
 };
@@ -3764,8 +3768,8 @@ static void M_Options(INT32 choice)
 	// if the player is not admin or server, disable server options
 	OP_MainMenu[5].status = (Playing() && !(server || adminplayer == consoleplayer)) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
 
-	// if the player is playing _at all_, disable data options
-	OP_MainMenu[3].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
+	// if the player is playing _at all_, disable the erase data options
+	OP_DataOptionsMenu[1].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
 
 	OP_MainDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&OP_MainDef);
@@ -5709,7 +5713,7 @@ static void M_DrawConnectMenu(void)
 	// Room name
 	if (ms_RoomId < 0)
 		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_room].alphaKey,
-		                         V_YELLOWMAP, "<Offline Mode>");
+		                         V_YELLOWMAP, (itemOn == mp_connect_room) ? "<Select to change>" : "<Offline Mode>");
 	else
 		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_room].alphaKey,
 		                         V_YELLOWMAP, room_list[menuRoomIndex].name);
@@ -5963,8 +5967,10 @@ static void M_StartServer(INT32 choice)
 	// Still need to reset devmode
 	cv_debug = 0;
 
-	if (demoplayback || metalplayback)
+	if (demoplayback)
 		G_StopDemo();
+	if (metalrecording)
+		G_StopMetalDemo();
 
 	if (!StartSplitScreenGame)
 	{
@@ -5999,7 +6005,7 @@ static void M_DrawServerMenu(void)
 	{
 		if (ms_RoomId < 0)
 			V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey,
-			                         V_YELLOWMAP, "<Offline Mode>");
+			                         V_YELLOWMAP, (itemOn == mp_server_room) ? "<Select to change>" : "<Offline Mode>");
 		else
 			V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey,
 			                         V_YELLOWMAP, room_list[menuRoomIndex].name);
@@ -6560,8 +6566,9 @@ static void M_Setup1PControlsMenu(INT32 choice)
 	OP_MPControlsMenu[0].status = IT_CALL|IT_STRING2;
 	OP_MPControlsMenu[1].status = IT_CALL|IT_STRING2;
 	OP_MPControlsMenu[2].status = IT_CALL|IT_STRING2;
-	// Unhide the entire misc menu
-	OP_ControlListMenu[3].status = IT_SUBMENU | IT_STRING;
+	// Unide the pause/console controls too
+	OP_MiscControlsMenu[3].status = IT_CALL|IT_STRING2;
+	OP_MiscControlsMenu[4].status = IT_CALL|IT_STRING2;
 
 	OP_ControlListDef.prevMenu = &OP_P1ControlsDef;
 	M_SetupNextMenu(&OP_ControlListDef);
@@ -6578,8 +6585,9 @@ static void M_Setup2PControlsMenu(INT32 choice)
 	OP_MPControlsMenu[0].status = IT_GRAYEDOUT2;
 	OP_MPControlsMenu[1].status = IT_GRAYEDOUT2;
 	OP_MPControlsMenu[2].status = IT_GRAYEDOUT2;
-	// Hide the entire misc menu
-	OP_ControlListMenu[3].status = IT_GRAYEDOUT;
+	// Hide the pause/console controls too
+	OP_MiscControlsMenu[3].status = IT_GRAYEDOUT2;
+	OP_MiscControlsMenu[4].status = IT_GRAYEDOUT2;
 
 	OP_ControlListDef.prevMenu = &OP_P2ControlsDef;
 	M_SetupNextMenu(&OP_ControlListDef);
diff --git a/src/p_inter.c b/src/p_inter.c
index 03ad7c46ecb9048c2f77d87e5d42ee04df1329b2..398ad493acd02df5dbe9d06fc6a0c7515228700c 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1861,7 +1861,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source)
 	if (target->player && !target->player->spectator)
 	{
 		if (metalrecording) // Ack! Metal Sonic shouldn't die! Cut the tape, end recording!
-			G_CheckDemoStatus();
+			G_StopMetalRecording();
 #ifdef CHAOSISNOTDEADYET
 		if (gametype == GT_CHAOS)
 			target->player->score /= 2; // Halve the player's score in Chaos Mode
diff --git a/src/p_mobj.c b/src/p_mobj.c
index b973c6cc39ae8c5323e0694bb8e6d6aad968f9ec..a7dc363fde21ac6d1e3bdbd2841f6eb6356e25b7 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1669,16 +1669,21 @@ static boolean P_ZMovement(mobj_t *mo)
 				// another to prevent them from turning into hockey pucks.
 				// I'm sorry in advance. -SH
 				// PS: Oh, and Brak's napalm bombs too, now.
-				if (mo->type == MT_THROWNGRENADE || mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE)
+				if (mo->flags & MF_GRENADEBOUNCE)
 				{
-					if (mo->momz <= 0)
+					// Going down? (Or up in reverse gravity?)
+					if (mo->momz*P_MobjFlip(mo) < 0)
 					{
-						if (mo->momz >= -FixedMul(FRACUNIT, mo->scale))
+						// If going slower than a fracunit, just stop.
+						if (abs(mo->momz) < FixedMul(FRACUNIT, mo->scale))
 						{
 							mo->momx = mo->momy = mo->momz = 0;
-							if (mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE)
-								P_SetMobjState(mo, mo->info->deathstate);
+
+							// Napalm hack
+							if (mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE && mo->fuse)
+								mo->fuse = 1;
 						}
+						// Otherwise bounce up at half speed.
 						else
 							mo->momz = -FixedMul(mo->momz, FRACUNIT/2);
 						S_StartSound(mo, mo->info->activesound);
@@ -4595,7 +4600,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 		// This is below threshold because we don't want to bob while zipping around
 
 		// Ohh you're in for it now..
-		if (mobj->flags2 & MF2_FRET && mobj->health <= 3)
+		if (mobj->flags2 & MF2_FRET && mobj->health <= mobj->info->damage)
 			mobj->fuse = 0;
 
 		// reactiontime is used for delays.
@@ -4657,11 +4662,13 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 			case 1: {
 				// Okay, we're up? Good, time to gather energy...
-				if (mobj->health > mobj->info->damage) { // No more bubble if we're broken (pinch phase)
+				if (mobj->health > mobj->info->damage)
+				{ // No more bubble if we're broken (pinch phase)
 					mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
 					P_SetTarget(&mobj->tracer, shield);
 					P_SetTarget(&shield->target, mobj);
-				} else
+				}
+				else
 					P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
 				mobj->fuse = 4*TICRATE;
 				mobj->flags |= MF_PAIN;
@@ -4673,8 +4680,12 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 			case 2:
 				// We're all charged and ready now! Unleash the fury!!
-				P_RemoveMobj(mobj->tracer);
-				P_SetTarget(&mobj->tracer, mobj->hnext);
+				if (mobj->health > mobj->info->damage)
+				{
+					mobj_t *removemobj = mobj->tracer;
+					P_SetTarget(&mobj->tracer, mobj->hnext);
+					P_RemoveMobj(removemobj);
+				}
 				if (mobj->health <= mobj->info->damage) {
 					// Attack 1: Pinball dash!
 					if (mobj->health == 1)
@@ -6483,6 +6494,7 @@ void P_MobjThinker(mobj_t *mobj)
 			{
 				// gargoyle and snowman handled in P_PushableThinker, not here
 				case MT_THROWNGRENADE:
+				case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
 					P_SetMobjState(mobj, mobj->info->deathstate);
 					break;
 				case MT_BLUEFLAG:
@@ -7231,6 +7243,8 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 #endif
 	switch (mobj->type)
 	{
+		case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
+			mobj->fuse = mobj->info->mass;
 		case MT_BLACKEGGMAN:
 			{
 				mobj_t *spawn = P_SpawnMobj(mobj->x, mobj->z, mobj->z+mobj->height-16*FRACUNIT, MT_BLACKEGGMAN_HELPER);
@@ -9445,7 +9459,7 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing)
 boolean P_CheckMissileSpawn(mobj_t *th)
 {
 	// move a little forward so an angle can be computed if it immediately explodes
-	if (th->type != MT_THROWNGRENADE) // hack: bad! should be a flag.
+	if (th->flags & MF_GRENADEBOUNCE) // hack: bad! should be a flag.
 	{
 		th->x += th->momx>>1;
 		th->y += th->momy>>1;
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 84d2230195ce7d47895363b0f8e513f849b2fce9..6d120c4733b1f54ff25cbeab05b691891722c108 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -156,9 +156,11 @@ typedef enum
 	// for chase camera, don't be blocked by things (partial clipping)
 	// (need comma at end of this for SOC editor)
 	MF_NOCLIPTHING      = 1<<27,
+	// Missile bounces like a grenade.
+	MF_GRENADEBOUNCE    = 1<<28,
 	// Run the action thinker on spawn.
-	MF_RUNSPAWNFUNC     = 1<<28,
-	// free: to and including 1<<31
+	MF_RUNSPAWNFUNC     = 1<<29,
+	// free: 1<<30 and 1<<31
 } mobjflag_t;
 
 typedef enum
diff --git a/src/p_setup.c b/src/p_setup.c
index 25bcd0d57e6ec092ab1bd1897adfe022ec62726a..9b32d4a03545fbe277ca4d24b07168e8e0bdecea 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2315,7 +2315,7 @@ boolean P_SetupLevel(boolean skipprecip)
 		cv_debug = botskin = 0;
 
 	if (metalplayback)
-		G_StopDemo();
+		G_StopMetalDemo();
 
 	// Clear CECHO messages
 	HU_ClearCEcho();
diff --git a/src/p_user.c b/src/p_user.c
index ac026a11afaedf017b81a1df468a78121d7fe755..d5c17e0080a5f8166971185e78e64624d6a03670 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3178,12 +3178,26 @@ static void P_DoSuperStuff(player_t *player)
 			P_SetPlayerMobjState(player->mo, S_PLAY_STND);
 			P_RestoreMusic(player);
 			P_SpawnShieldOrb(player);
+
+			// Restore color
+			if (player->powers[pw_shield] & SH_FIREFLOWER)
+			{
+				player->mo->color = SKINCOLOR_WHITE;
+				G_GhostAddColor(GHC_FIREFLOWER);
+			}
+			else
+			{
+				player->mo->color = player->skincolor;
+				G_GhostAddColor(GHC_NORMAL);
+			}
+
 			if (gametype != GT_COOP)
 			{
 				HU_SetCEchoFlags(0);
 				HU_SetCEchoDuration(5);
 				HU_DoCEcho(va("%s\\is no longer super.\\\\\\\\", player_names[player-players]));
 			}
+			return;
 		}
 
 		// Deplete one ring every second while super
@@ -3200,7 +3214,7 @@ static void P_DoSuperStuff(player_t *player)
 		G_GhostAddColor(GHC_SUPER);
 
 		// Ran out of rings while super!
-		if ((player->powers[pw_super]) && (player->health <= 1 || player->exiting))
+		if (player->powers[pw_super] && (player->health <= 1 || player->exiting))
 		{
 			player->powers[pw_emeralds] = 0; // lost the power stones
 			P_SpawnGhostMobj(player->mo);
@@ -9194,7 +9208,7 @@ void P_PlayerAfterThink(player_t *player)
 			}
 		}
 	}
-	else if ((player->pflags & PF_MACESPIN) && player->mo->tracer)
+	else if ((player->pflags & PF_MACESPIN) && player->mo->tracer && player->mo->tracer->target)
 	{
 		player->mo->height = P_GetPlayerSpinHeight(player);
 		// tracer is what you're hanging onto....
@@ -9214,11 +9228,6 @@ void P_PlayerAfterThink(player_t *player)
 		if (!(player->mo->tracer->target->flags & MF_SLIDEME) // Noclimb on chain parameters gives this
 		&& !(twodlevel || player->mo->flags2 & MF2_TWOD)) // why on earth would you want to turn them in 2D mode?
 		{
-			if (cmd->buttons & BT_USE) // do we actually still want this?
-			{
-				player->mo->tracer->target->health += 50;
-				player->mo->angle += 50<<ANGLETOFINESHIFT; // 2048 --> ANGLE_MAX
-			}
 			player->mo->tracer->target->health += cmd->sidemove;
 			player->mo->angle += cmd->sidemove<<ANGLETOFINESHIFT; // 2048 --> ANGLE_MAX
 
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 9c0f521fbabe5a7261536699853fad1acafb7988..1e03edd88382245dea28ea95ea4ecad8b27df1f4 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2278,8 +2278,12 @@ void I_Quit(void)
 	G_SaveGameData(); // Tails 12-08-2002
 	//added:16-02-98: when recording a demo, should exit using 'q' key,
 	//        but sometimes we forget and use 'F10'.. so save here too.
-	if (demorecording || metalrecording)
+
+	if (demorecording)
 		G_CheckDemoStatus();
+	if (metalrecording)
+		G_StopMetalRecording();
+
 	D_QuitNetGame();
 	I_ShutdownMusic();
 	I_ShutdownSound();
@@ -2431,8 +2435,10 @@ void I_Error(const char *error, ...)
 	G_SaveGameData(); // Tails 12-08-2002
 
 	// Shutdown. Here might be other errors.
-	if (demorecording || metalrecording)
+	if (demorecording)
 		G_CheckDemoStatus();
+	if (metalrecording)
+		G_StopMetalRecording();
 
 	D_QuitNetGame();
 	I_ShutdownMusic();
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index f31b8b1c463ffb7d3bb5b4849e1335b83bc48f27..efb0be463128feb1005f98a4d275c8e18b8b8d58 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -665,8 +665,10 @@ void I_Error(const char *error, ...)
 
 	// save demo, could be useful for debug
 	// NOTE: demos are normally not saved here.
-	if (demorecording || metalrecording)
+	if (demorecording)
 		G_CheckDemoStatus();
+	if (metalrecording)
+		G_StopMetalRecording();
 
 	D_QuitNetGame();
 
@@ -749,8 +751,10 @@ void I_Quit(void)
 	DWORD mode;
 	// when recording a demo, should exit using 'q',
 	// but sometimes we forget and use Alt+F4, so save here too.
-	if (demorecording || metalrecording)
+	if (demorecording)
 		G_CheckDemoStatus();
+	if (metalrecording)
+		G_StopMetalRecording();
 
 	M_SaveConfig(NULL); // save game config, cvars..
 #ifndef NONET