From 02a3b0776c5d5649164967bc9305decf5bac2ad6 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Mon, 14 Apr 2014 01:14:58 -0400
Subject: [PATCH] SRB2 2.1.7 release

---
 src/command.c          | 106 ++++++++++++-------
 src/command.h          |   1 +
 src/d_main.c           |   9 +-
 src/d_netcmd.c         |  18 +++-
 src/d_netcmd.h         |   2 +-
 src/d_netfil.c         |   6 +-
 src/dehacked.c         |  23 ++---
 src/dehacked.h         |   3 +-
 src/doomdef.h          |  13 ++-
 src/f_finale.c         |   8 +-
 src/g_game.c           | 164 +++++++++++++++++++++++-------
 src/g_game.h           |   1 +
 src/hardware/hw_draw.c |  14 ++-
 src/hardware/hw_md2.c  |  18 +++-
 src/hardware/hw_md2.h  |   6 +-
 src/info.c             |  61 ++++++++---
 src/info.h             |   3 +
 src/lua_baselib.c      |  53 ++++++++++
 src/lua_consolelib.c   |  47 +++++----
 src/lua_hooklib.c      |  55 ++++++----
 src/lua_hudlib.c       |   8 +-
 src/lua_mobjlib.c      |   3 +
 src/lua_script.h       |   3 +
 src/m_anigif.c         |   4 +-
 src/m_menu.c           |   6 +-
 src/p_enemy.c          | 133 +++++++++++-------------
 src/p_fab.c            |   1 +
 src/p_inter.c          |   7 ++
 src/p_mobj.c           | 226 ++++++++---------------------------------
 src/p_saveg.c          |   2 +-
 src/p_user.c           |  61 +++++++----
 src/r_draw.c           |  43 +++++++-
 src/r_things.c         |  22 ++--
 src/r_things.h         |  35 +++++++
 src/st_stuff.c         |   3 +-
 src/y_inter.c          |  63 ++++++------
 36 files changed, 729 insertions(+), 502 deletions(-)

diff --git a/src/command.c b/src/command.c
index 42cabe1b5..e62795570 100644
--- a/src/command.c
+++ b/src/command.c
@@ -391,6 +391,39 @@ void COM_AddCommand(const char *name, com_func_t func)
 	com_commands = cmd;
 }
 
+/** Adds a console command for Lua.
+  * No I_Errors allowed; return a negative code instead.
+  *
+  * \param name Name of the command.
+  */
+int COM_AddLuaCommand(const char *name)
+{
+	xcommand_t *cmd;
+
+	// fail if the command is a variable name
+	if (CV_StringValue(name)[0] != '\0')
+		return -1;
+
+	// command already exists
+	for (cmd = com_commands; cmd; cmd = cmd->next)
+	{
+		if (!stricmp(name, cmd->name)) //case insensitive now that we have lower and uppercase!
+		{
+			// replace the built in command.
+			cmd->function = COM_Lua_f;
+			return 1;
+		}
+	}
+
+	// Add a new command.
+	cmd = ZZ_Alloc(sizeof *cmd);
+	cmd->name = name;
+	cmd->function = COM_Lua_f;
+	cmd->next = com_commands;
+	com_commands = cmd;
+	return 0;
+}
+
 /** Tests if a command exists.
   *
   * \param com_name Name to test for.
@@ -558,7 +591,7 @@ static void COM_CEchoFlags_f(void)
 			HU_SetCEchoFlags(atoi(arg));
 	}
 	else
-		CONS_Printf(M_GetText("cechoflags <flags>: set CEcho flags, prepend with 0x to use hexadecimal"));
+		CONS_Printf(M_GetText("cechoflags <flags>: set CEcho flags, prepend with 0x to use hexadecimal\n"));
 }
 
 /** Sets the duration for CECHO commands to stay on the screen
@@ -1017,6 +1050,8 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 	if (var->PossibleValue)
 	{
 		INT32 v = atoi(valstr);
+		if (!v && valstr[0] != '0')
+			v = INT32_MIN; // Invalid integer trigger
 
 		if (var->PossibleValue[0].strvalue && !stricmp(var->PossibleValue[0].strvalue, "MIN")) // bounded cvar
 		{
@@ -1029,20 +1064,23 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 			if (!var->PossibleValue[i].strvalue)
 				I_Error("Bounded cvar \"%s\" without maximum!\n", var->name);
 #endif
-			if (v < var->PossibleValue[0].value || !stricmp(valstr, "MIN"))
+
+			if ((v != INT32_MIN && v < var->PossibleValue[0].value) || !stricmp(valstr, "MIN"))
 			{
 				v = var->PossibleValue[0].value;
 				valstr = var->PossibleValue[0].strvalue;
 				override = true;
 				overrideval = v;
 			}
-			if (v > var->PossibleValue[i].value || !stricmp(valstr, "MAX"))
+			else if ((v != INT32_MIN && v > var->PossibleValue[i].value) || !stricmp(valstr, "MAX"))
 			{
 				v = var->PossibleValue[i].value;
 				valstr = var->PossibleValue[i].strvalue;
 				override = true;
 				overrideval = v;
 			}
+			if (v == INT32_MIN)
+				goto badinput;
 		}
 		else
 		{
@@ -1052,48 +1090,32 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 			for (i = 0; var->PossibleValue[i].strvalue; i++)
 				if (!stricmp(var->PossibleValue[i].strvalue, valstr))
 					goto found;
-			if (!v)
-				if (strcmp(valstr, "0"))
-					goto error;
-			// check INT32 now
-			for (i = 0; var->PossibleValue[i].strvalue; i++)
-				if (v == var->PossibleValue[i].value)
-					goto found;
-
-error:
-			// not found
-
-			// But wait, there's hope!
-			if (var->PossibleValue == CV_OnOff
-				|| var->PossibleValue == CV_YesNo)
+			if (v != INT32_MIN)
 			{
-				INT32 hopevalue = -1;
+				// check INT32 now
+				for (i = 0; var->PossibleValue[i].strvalue; i++)
+					if (v == var->PossibleValue[i].value)
+						goto found;
+			}
+			// Not found ... but wait, there's hope!
+			if (var->PossibleValue == CV_OnOff || var->PossibleValue == CV_YesNo)
+			{
+				overrideval = -1;
+				if (!stricmp(valstr, "on") || !stricmp(valstr, "yes"))
+					overrideval = 1;
+				else if (!stricmp(valstr, "off") || !stricmp(valstr, "no"))
+					overrideval = 0;
 
-				if (!stricmp(valstr, "on"))
-					hopevalue = 1;
-				else if (!stricmp(valstr, "off"))
-					hopevalue = 0;
-				else if (!stricmp(valstr, "yes"))
-					hopevalue = 1;
-				else if (!stricmp(valstr, "no"))
-					hopevalue = 0;
-
-				if (hopevalue != -1)
+				if (overrideval != -1)
 				{
 					for (i = 0; var->PossibleValue[i].strvalue; i++)
-						if (hopevalue == var->PossibleValue[i].value)
+						if (overrideval == var->PossibleValue[i].value)
 							goto found;
 				}
 			}
 
 			// ...or not.
-			if (var != &cv_nextmap) // Suppress errors for cv_nextmap
-				CONS_Printf(M_GetText("\"%s\" is not a possible value for \"%s\"\n"), valstr, var->name);
-
-			if (var->defaultvalue == valstr)
-				I_Error("Variable %s default value \"%s\" is not a possible value\n",
-					var->name, var->defaultvalue);
-			return;
+			goto badinput;
 found:
 			var->value = var->PossibleValue[i].value;
 			var->string = var->PossibleValue[i].strvalue;
@@ -1141,6 +1163,18 @@ finish:
 #endif
 	if (var->flags & CV_CALL && !stealth)
 		var->func();
+
+	return;
+
+// landing point for possiblevalue failures
+badinput:
+
+	if (var != &cv_nextmap) // Suppress errors for cv_nextmap
+		CONS_Printf(M_GetText("\"%s\" is not a possible value for \"%s\"\n"), valstr, var->name);
+
+	// default value not valid... ?!
+	if (var->defaultvalue == valstr)
+		I_Error("Variable %s default value \"%s\" is not a possible value\n", var->name, var->defaultvalue);
 }
 
 //
diff --git a/src/command.h b/src/command.h
index c7962faf6..a17e8eacc 100644
--- a/src/command.h
+++ b/src/command.h
@@ -23,6 +23,7 @@
 typedef void (*com_func_t)(void);
 
 void COM_AddCommand(const char *name, com_func_t func);
+int COM_AddLuaCommand(const char *name);
 
 size_t COM_Argc(void);
 const char *COM_Argv(size_t arg); // if argv > argc, returns empty string
diff --git a/src/d_main.c b/src/d_main.c
index 9e00049ee..2f3dd8b61 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1086,15 +1086,14 @@ void D_SRB2Main(void)
 #endif
 	D_CleanFile();
 
-#if 1 // md5s last updated 3/22/14
+#if 1 // md5s last updated 4/13/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(1, "e956466eff2c79f7b1cdefad24761bce"); // zones.dta
+	W_VerifyFileMD5(2, "95a4cdbed287323dd361243f357a5fd2"); // player.dta
 	W_VerifyFileMD5(3, "85901ad4bf94637e5753d2ac2c03ea26"); // rings.dta
-	W_VerifyFileMD5(4, "386ab4ffc8c9fb0fa62f788a16e5c218"); // patch.dta
-
+	W_VerifyFileMD5(4, "1f37fe7bcc608a23eadb0e2c2d7c7124"); // 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.
 #endif
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 13d4cb9d5..1a1777a4d 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -134,6 +134,7 @@ static void Command_Skynum_f(void);
 
 static void Command_ExitLevel_f(void);
 static void Command_Showmap_f(void);
+static void Command_Mapmd5_f(void);
 
 static void Command_Teamchange_f(void);
 static void Command_Teamchange2_f(void);
@@ -330,7 +331,6 @@ consvar_t cv_itemfinder = {"itemfinder", "Off", CV_CALL, CV_OnOff, ItemFinder_On
 consvar_t cv_match_scoring = {"matchscoring", "Normal", CV_NETVAR|CV_CHEAT, match_scoring_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_realnames = {"realnames", "Off", CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_rollingdemos = {"rollingdemos", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_timetic = {"timerres", "Normal", 0, timetic_cons_t, NULL, CV_SAVE, NULL, NULL, 0, 0, NULL}; // use tics in display
@@ -431,6 +431,7 @@ void D_RegisterServerCommands(void)
 	COM_AddCommand("retry", Command_Retry_f);
 	COM_AddCommand("exitlevel", Command_ExitLevel_f);
 	COM_AddCommand("showmap", Command_Showmap_f);
+	COM_AddCommand("mapmd5", Command_Mapmd5_f);
 
 	COM_AddCommand("addfile", Command_Addfile);
 	COM_AddCommand("listwad", Command_ListWADS_f);
@@ -643,7 +644,6 @@ void D_RegisterClientCommands(void)
 #ifdef SEENAMES
 	CV_RegisterVar(&cv_seenames);
 #endif
-	CV_RegisterVar(&cv_realnames);
 	CV_RegisterVar(&cv_rollingdemos);
 	CV_RegisterVar(&cv_netstat);
 
@@ -3866,6 +3866,20 @@ static void Command_Showmap_f(void)
 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
 }
 
+static void Command_Mapmd5_f(void)
+{
+	if (gamestate == GS_LEVEL)
+	{
+		INT32 i;
+		char md5tmp[33];
+		for (i = 0; i < 16; ++i)
+			sprintf(&md5tmp[i*2], "%02x", mapmd5[i]);
+		CONS_Printf("%s: %s\n", G_BuildMapName(gamemap), md5tmp);
+	}
+	else
+		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
+}
+
 static void Command_ExitLevel_f(void)
 {
 	if (!(netgame || (multiplayer && gametype != GT_COOP)) && !cv_debug)
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 932d77664..c1cc27cea 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -105,7 +105,7 @@ extern consvar_t cv_overtime;
 extern consvar_t cv_startinglives;
 
 // for F_finale.c
-extern consvar_t cv_realnames, cv_rollingdemos;
+extern consvar_t cv_rollingdemos;
 
 extern consvar_t cv_resetmusic;
 
diff --git a/src/d_netfil.c b/src/d_netfil.c
index bc2ff87c3..84fe0fbf7 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -60,6 +60,8 @@
 #include "md5.h"
 #include "filesrch.h"
 
+#include <errno.h>
+
 static void SendFile(INT32 node, const char *filename, UINT8 fileid);
 
 // sender structure
@@ -652,7 +654,7 @@ void Got_Filetxpak(void)
 	{
 		if (fileneeded[filenum].phandle) I_Error("Got_Filetxpak: allready open file\n");
 			fileneeded[filenum].phandle = fopen(fileneeded[filenum].filename, "wb");
-		if (!fileneeded[filenum].phandle) I_Error("Can't create file %s: disk full ?",fileneeded[filenum].filename);
+		if (!fileneeded[filenum].phandle) I_Error("Can't create file %s: %s",fileneeded[filenum].filename, strerror(errno));
 			CONS_Printf("\r%s...\n",fileneeded[filenum].filename);
 		fileneeded[filenum].currentsize = 0;
 		fileneeded[filenum].status = FS_DOWNLOADING;
@@ -672,7 +674,7 @@ void Got_Filetxpak(void)
 		// we can receive packet in the wrong order, anyway all os support gaped file
 		fseek(fileneeded[filenum].phandle,pos,SEEK_SET);
 		if (fwrite(netbuffer->u.filetxpak.data,size,1,fileneeded[filenum].phandle)!=1)
-			I_Error("Can't write %s: disk full ? or %s\n",fileneeded[filenum].filename, strerror(ferror(fileneeded[filenum].phandle)));
+			I_Error("Can't write to %s: %s\n",fileneeded[filenum].filename, strerror(ferror(fileneeded[filenum].phandle)));
 		fileneeded[filenum].currentsize += size;
 
 		// finished?
diff --git a/src/dehacked.c b/src/dehacked.c
index 35e672eb1..cf40ac3e6 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -71,8 +71,6 @@ static powertype_t get_power(const char *word);
 #endif
 
 boolean deh_loaded = false;
-boolean modcredits = false; // Whether a mod creator's name will show in the credits.
-char modcreditname[32];
 static int dbg_line;
 
 static boolean gamedataadded = false;
@@ -3285,12 +3283,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					}
 					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
-				else if (fastcmp(word, "MODBY"))
-				{
-					memset(modcreditname, 0, sizeof(char) * 32);
-					strcpy(modcreditname, origpos+6);
-					modcredits = true;
-				}
 /*				else if (fastcmp(word, "ANIMTEX"))
 				{
 					readAnimTex(f, i);
@@ -4451,6 +4443,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_CYBRAKDEMONTARGETRETICULE13",
 	"S_CYBRAKDEMONTARGETRETICULE14",
 
+	"S_CYBRAKDEMONTARGETDOT",
+
 	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY1",
 	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY2",
 	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY3",
@@ -6648,6 +6642,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_CYBRAKDEMON_FLAMESHOT",
 	"MT_CYBRAKDEMON_FLAMEREST",
 	"MT_CYBRAKDEMON_TARGET_RETICULE",
+	"MT_CYBRAKDEMON_TARGET_DOT",
 	"MT_CYBRAKDEMON_NAPALM_BOMB_LARGE",
 	"MT_CYBRAKDEMON_NAPALM_BOMB_SMALL",
 	"MT_CYBRAKDEMON_NAPALM_FLAMES",
@@ -7301,12 +7296,12 @@ static const char *const POWERS_LIST[] = {
 
 	// Weapon ammunition
 	"INFINITYRING",
-	"BOUNCERING",
-	"RAILRING",
 	"AUTOMATICRING",
-	"EXPLOSIONRING",
+	"BOUNCERING",
 	"SCATTERRING",
 	"GRENADERING",
+	"EXPLOSIONRING",
+	"RAILRING",
 
 	// Power Stones
 	"EMERALDS", // stored like global 'emeralds' variable
@@ -7441,6 +7436,7 @@ struct {
 	{"TOL_MATCH",TOL_MATCH},
 	{"TOL_TAG",TOL_TAG},
 	{"TOL_CTF",TOL_CTF},
+	{"TOL_CUSTOM",TOL_CUSTOM},
 	{"TOL_2D",TOL_2D},
 	{"TOL_MARIO",TOL_MARIO},
 	{"TOL_NIGHTS",TOL_NIGHTS},
@@ -7926,8 +7922,9 @@ static fixed_t find_const(const char **rword)
 		free(word);
 		return r;
 	}
-	if (*word >= 'A' && !*(word+1)) { // Turn a single A-z symbol into numbers, like sprite frames.
-		r = *word-'A';
+	if (!*(word+1) && // Turn a single A-z symbol into numbers, like sprite frames.
+	 (*word >= 'A' && *word <= 'Z') || (*word >= 'a' && *word <= 'z')) {
+		r = R_Char2Frame(*word);
 		free(word);
 		return r;
 	}
diff --git a/src/dehacked.h b/src/dehacked.h
index 7ef376f3c..d87d22679 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -47,8 +47,7 @@ const char *LUA_GetActionName(void *action);
 void LUA_SetActionByName(void *state, const char *actiontocompare);
 #endif
 
-extern boolean deh_loaded, modcredits;
-extern char modcreditname[32];
+extern boolean deh_loaded;
 
 #define MAXRECURSION 30
 extern const char *superactions[MAXRECURSION];
diff --git a/src/doomdef.h b/src/doomdef.h
index b38bb5074..93503de3f 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 6  // more precise version number
-#define VERSIONSTRING "v2.1.6"
+#define SUBVERSION 7  // more precise version number
+#define VERSIONSTRING "v2.1.7"
 #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 11
+#define MODVERSION 12
 
 
 
@@ -259,6 +259,13 @@ typedef enum
 	SKINCOLOR_SUPER4,
 	SKINCOLOR_SUPER5,
 
+	// Super Tails
+	SKINCOLOR_TSUPER1,
+	SKINCOLOR_TSUPER2,
+	SKINCOLOR_TSUPER3,
+	SKINCOLOR_TSUPER4,
+	SKINCOLOR_TSUPER5,
+
 	// Super Knuckles
 	SKINCOLOR_KSUPER1,
 	SKINCOLOR_KSUPER2,
diff --git a/src/f_finale.c b/src/f_finale.c
index 4b7432385..0b40eecd1 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -991,7 +991,7 @@ static const char *credits[] = {
 	"\1Texture Artists",
 	"Ryan \"Blaze Hedgehog\" Bloom",
 	"Buddy \"KinkaJoy\" Fischer",
-	"Pedro \"Nev3r\" Iceta",
+	"Kepa \"Nev3r\" Iceta",
 	"Jarrett \"JEV3\" Voight",
 	"",
 	"\1Music and Sound",
@@ -1002,7 +1002,7 @@ static const char *credits[] = {
 	"David \"Bulmybag\" Bulmer",
 	"Paul \"Boinciel\" Clempson",
 	"Cyan Helkaraxe",
-	"Pedro \"Nev3r\" Iceta",
+	"Kepa \"Nev3r\" Iceta",
 	"\"Monster\" Iestyn Jealous",
 	"Jarel \"Arrow\" Jones",
 	"Stefan \"Stuf\" Rimalia",
@@ -1020,7 +1020,7 @@ static const char *credits[] = {
 	"Ben \"Mystic\" Geyer",
 	"Nathan \"Jazz\" Giroux",
 	"Dan \"Blitzzo\" Hagerstrand",
-	"Pedro \"Nev3r\" Iceta",
+	"Kepa \"Nev3r\" Iceta",
 	"Thomas \"Shadow Hog\" Igoe",
 	"Erik \"Torgo\" Nielsen",
 	"Wessel \"Spherallic\" Smit",
@@ -1046,7 +1046,9 @@ static const char *credits[] = {
 	"Alex \"MistaED\" Fuller",
 	"FreeDoom Project", // Used some of the mancubus and rocket launcher sprites for Brak
 	"Randy Heit (<!>)", // For his MSPaint <!> sprite that we nicked
+#if 0 // (don't take your anger out on me anymore, ok, JTE...?)
 	"Abigail \"Raspberry\" Fox", // (Inuyasha's girlfriend. >_> <_< >_>)
+#endif
 	"",
 	"\1Thank you",
 	"\1for playing!",
diff --git a/src/g_game.c b/src/g_game.c
index 414f27d06..3d42f9b8e 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -234,13 +234,16 @@ static UINT8 *demobuffer = NULL;
 static UINT8 *demo_p, *demotime_p;
 static UINT8 *demoend;
 static UINT8 demoflags;
+static UINT16 demoversion;
 boolean singledemo; // quit after playing a demo from cmdline
 boolean demo_start; // don't start playing demo right away
+static boolean demosynced = true; // console warning message
 
 boolean metalrecording; // recording as metal sonic
 mobj_t *metalplayback;
 static UINT8 *metalbuffer = NULL;
 static UINT8 *metal_p;
+static UINT16 metalversion;
 boolean metal_start;
 
 // extra data stuff (events registered this frame while recording)
@@ -262,7 +265,8 @@ static struct {
 // There is no conflict here.
 typedef struct demoghost {
 	UINT8 checksum[16];
-	UINT8 *buffer, *p;
+	UINT8 *buffer, *p, color;
+	UINT16 version;
 	mobj_t oldmo, *mo;
 	struct demoghost *next;
 } demoghost;
@@ -3577,7 +3581,7 @@ char *G_BuildMapTitle(INT32 mapnum)
 // DEMO RECORDING
 //
 
-#define DEMOVERSION 0x0008
+#define DEMOVERSION 0x0009
 #define DEMOHEADER  "\xF0" "SRB2Replay" "\x0F"
 
 #define DF_GHOST        0x01 // This demo contains ghost data too!
@@ -3608,6 +3612,8 @@ static ticcmd_t oldcmd;
 // GZT_EXTRA flags
 #define EZT_THOK   0x01 // Spawned a thok object
 #define EZT_SPIN   0x02 // Because one type of thok object apparently wasn't enough
+#define EZT_REV    0x03 // And two types wasn't enough either yet
+#define EZT_THOKMASK 0x03
 #define EZT_COLOR  0x04 // Changed color (Super transformation, Mario fireflowers/invulnerability, etc.)
 #define EZT_FLIP   0x08 // Reversed gravity
 #define EZT_SCALE  0x10 // Changed size
@@ -3725,14 +3731,21 @@ void G_GhostAddThok(void)
 {
 	if (!demorecording || !(demoflags & DF_GHOST))
 		return;
-	ghostext.flags |= EZT_THOK;
+	ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_THOK;
 }
 
 void G_GhostAddSpin(void)
 {
 	if (!demorecording || !(demoflags & DF_GHOST))
 		return;
-	ghostext.flags |= EZT_SPIN;
+	ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_SPIN;
+}
+
+void G_GhostAddRev(void)
+{
+	if (!demorecording || !(demoflags & DF_GHOST))
+		return;
+	ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_REV;
 }
 
 void G_GhostAddFlip(void)
@@ -3928,9 +3941,6 @@ void G_WriteGhostTic(mobj_t *ghost)
 	}
 }
 
-// console warning messages
-UINT8 demosynced = true;
-
 // Uses ghost data to do consistency checks on your position.
 // This fixes desynchronising demos when fighting eggman.
 void G_ConsGhostTic(void)
@@ -4017,12 +4027,12 @@ void G_ConsGhostTic(void)
 	}
 
 	// Re-synchronise
-	px = (players[0].mo->x>>8)&UINT16_MAX;
-	py = (players[0].mo->y>>8)&UINT16_MAX;
-	pz = (players[0].mo->z>>8)&UINT16_MAX;
-	gx = (oldghost.x>>8)&UINT16_MAX;
-	gy = (oldghost.y>>8)&UINT16_MAX;
-	gz = (oldghost.z>>8)&UINT16_MAX;
+	px = players[0].mo->x>>FRACBITS;
+	py = players[0].mo->y>>FRACBITS;
+	pz = players[0].mo->z>>FRACBITS;
+	gx = oldghost.x>>FRACBITS;
+	gy = oldghost.y>>FRACBITS;
+	gz = oldghost.z>>FRACBITS;
 
 	if (px != gx || py != gy || pz != gz)
 	{
@@ -4036,8 +4046,6 @@ void G_ConsGhostTic(void)
 		P_SetThingPosition(players[0].mo);
 		players[0].mo->z = oldghost.z;
 	}
-	else
-		demosynced = true;
 
 	if (*demo_p == DEMOMARKER)
 	{
@@ -4105,17 +4113,16 @@ void G_GhostTicker(void)
 			ziptic = READUINT8(g->p);
 			if (ziptic & EZT_COLOR)
 			{
-				switch(READUINT8(g->p))
+				g->color = READUINT8(g->p);
+				switch(g->color)
 				{
 				default:
 				case GHC_NORMAL: // Go back to skin color
 					g->mo->color = g->oldmo.color;
 					break;
-				case GHC_SUPER: // Super Sonic
-					g->mo->color = SKINCOLOR_SUPER4;
-					break;
-				case GHC_INVINCIBLE: /// \todo Mario invincibility
-					g->mo->color = SKINCOLOR_SUPER4;
+				// Handled below
+				case GHC_SUPER:
+				case GHC_INVINCIBLE:
 					break;
 				case GHC_FIREFLOWER: // Fireflower
 					g->mo->color = SKINCOLOR_WHITE;
@@ -4130,17 +4137,25 @@ void G_GhostTicker(void)
 				if (g->mo->destscale != g->mo->scale)
 					P_SetScale(g->mo, g->mo->destscale);
 			}
-			if (ziptic & (EZT_THOK|EZT_SPIN))
+			if (ziptic & EZT_THOKMASK)
 			{ // Let's only spawn ONE of these per frame, thanks.
 				mobj_t *mobj;
 				INT32 type = -1;
 				if (g->mo->skin)
 				{
 					skin_t *skin = (skin_t *)g->mo->skin;
-					if (ziptic & EZT_THOK)
+					switch (ziptic & EZT_THOKMASK)
+					{
+					case EZT_THOK:
 						type = skin->thokitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].painchance : (UINT32)skin->thokitem;
-					else
+						break;
+					case EZT_SPIN:
 						type = skin->spinitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].damage : (UINT32)skin->spinitem;
+						break;
+					case EZT_REV:
+						type = skin->revitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].raisestate : (UINT32)skin->revitem;
+						break;
+					}
 				}
 				if (type == MT_GHOST)
 				{
@@ -4201,6 +4216,32 @@ void G_GhostTicker(void)
 				g->mo->sprite = READUINT8(g->p);
 		}
 
+		// Tick ghost colors (Super and Mario Invincibility flashing)
+		switch(g->color)
+		{
+		case GHC_SUPER: // Super Sonic (P_DoSuperStuff)
+			// Yousa yellow now!
+			g->mo->color = SKINCOLOR_SUPER1 + (leveltime/2) % 5;
+			if (g->mo->skin)
+				switch (((skin_t*)g->mo->skin)-skins)
+				{
+				case 1: // Golden orange supertails.
+					g->mo->color = SKINCOLOR_TSUPER1 + (leveltime/2) % 5;
+					break;
+				case 2: // Pink superknux.
+					g->mo->color = SKINCOLOR_KSUPER1 + (leveltime/2) % 5;
+					break;
+				default:
+					break;
+				}
+			break;
+		case GHC_INVINCIBLE: // Mario invincibility (P_CheckInvincibilityTimer)
+			g->mo->color = (UINT8)(leveltime % MAXSKINCOLORS);
+			break;
+		default:
+			break;
+		}
+
 		// Demo ends after ghost data.
 		if (*g->p == DEMOMARKER)
 		{
@@ -4483,7 +4524,7 @@ void G_BeginRecording(void)
 
 	// game data
 	M_Memcpy(demo_p, "PLAY", 4); demo_p += 4;
-	WRITEUINT8(demo_p,gamemap);
+	WRITEINT16(demo_p,gamemap);
 	M_Memcpy(demo_p, mapmd5, 16); demo_p += 16;
 
 	WRITEUINT8(demo_p,demoflags);
@@ -4563,6 +4604,10 @@ void G_BeginRecording(void)
 		oldghost.y = player->mo->y;
 		oldghost.z = player->mo->z;
 		oldghost.angle = player->mo->angle;
+
+		// preticker started us gravity flipped
+		if (player->mo->eflags & MFE_VERTICALFLIP)
+			ghostext.flags |= EZT_FLIP;
 	}
 }
 
@@ -4613,7 +4658,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	UINT8 *buffer,*p;
 	UINT8 flags;
 	UINT32 oldtime, newtime, oldscore, newscore;
-	UINT16 oldrings, newrings;
+	UINT16 oldrings, newrings, oldversion;
 	size_t bufsize ATTRUNUSED;
 	UINT8 c;
 	UINT16 s ATTRUNUSED;
@@ -4636,7 +4681,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	p += 16; // demo checksum
 	I_Assert(!memcmp(p, "PLAY", 4));
 	p += 4; // PLAY
-	p++; // gamemap
+	p += 2; // gamemap
 	p += 16; // map md5
 	flags = READUINT8(p); // demoflags
 	I_Assert(flags & DF_RECORDATTACK);
@@ -4664,8 +4709,15 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	} p += 12; // DEMOHEADER
 	p++; // VERSION
 	p++; // SUBVERSION
-	if (READUINT16(p) != DEMOVERSION)
+	oldversion = READUINT16(p);
+	switch(oldversion) // demoversion
 	{
+	case DEMOVERSION: // latest always supported
+	// compatibility available?
+	case 0x0008:
+		break;
+	// too old, cannot support.
+	default:
 		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' invalid format. It will be overwritten.\n"), oldname);
 		Z_Free(buffer);
 		return UINT8_MAX;
@@ -4677,7 +4729,10 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 		Z_Free(buffer);
 		return UINT8_MAX;
 	} p += 4; // "PLAY"
-	p++; // gamemap
+	if (oldversion <= 0x0008)
+		p++; // gamemap
+	else
+		p += 2; // gamemap
 	p += 16; // mapmd5
 	flags = READUINT8(p);
 	if (!(flags & DF_RECORDATTACK))
@@ -4783,9 +4838,16 @@ void G_DoPlayDemo(char *defdemoname)
 
 	version = READUINT8(demo_p);
 	subversion = READUINT8(demo_p);
-	if (DEMOVERSION != READUINT16(demo_p))
+	demoversion = READUINT16(demo_p);
+	switch(demoversion)
 	{
-		snprintf(msg, 1024, M_GetText("%s is a different replay format and cannot be played.\n"), pdemoname);
+	case DEMOVERSION: // latest always supported
+	// compatibility available?
+	case 0x0008:
+		break;
+	// too old, cannot support.
+	default:
+		snprintf(msg, 1024, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemoname);
 		CONS_Alert(CONS_ERROR, "%s", msg);
 		M_StartMessage(msg, NULL, MM_NOTHING);
 		Z_Free(pdemoname);
@@ -4807,7 +4869,10 @@ void G_DoPlayDemo(char *defdemoname)
 		return;
 	}
 	demo_p += 4; // "PLAY"
-	gamemap = READUINT8(demo_p);
+	if (demoversion <= 0x0008)
+		gamemap = READUINT8(demo_p);
+	else
+		gamemap = READINT16(demo_p);
 	demo_p += 16; // mapmd5
 
 	demoflags = READUINT8(demo_p);
@@ -4950,7 +5015,7 @@ void G_AddGhost(char *defdemoname)
 	UINT8 flags;
 	UINT8 *buffer,*p;
 	mapthing_t *mthing;
-	UINT16 count;
+	UINT16 count, ghostversion;
 
 	name[16] = '\0';
 	skin[16] = '\0';
@@ -4996,9 +5061,16 @@ void G_AddGhost(char *defdemoname)
 	} p += 12; // DEMOHEADER
 	p++; // VERSION
 	p++; // SUBVERSION
-	if (DEMOVERSION != READUINT16(p))
+	ghostversion = READUINT16(p);
+	switch(ghostversion)
 	{
-		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Demo format unacceptable.\n"), pdemoname);
+	case DEMOVERSION: // latest always supported
+	// compatibility available?
+	case 0x0008:
+		break;
+	// too old, cannot support.
+	default:
+		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Demo version incompatible.\n"), pdemoname);
 		Z_Free(pdemoname);
 		Z_Free(buffer);
 		return;
@@ -5019,7 +5091,10 @@ void G_AddGhost(char *defdemoname)
 		Z_Free(buffer);
 		return;
 	} p += 4; // "PLAY"
-	p++; // gamemap
+	if (ghostversion <= 0x0008)
+		p++; // gamemap
+	else
+		p += 2; // gamemap
 	p += 16; // mapmd5 (possibly check for consistency?)
 	flags = READUINT8(p);
 	if (!(flags & DF_GHOST))
@@ -5094,6 +5169,8 @@ void G_AddGhost(char *defdemoname)
 	gh->p = p;
 
 	ghosts = gh;
+
+	gh->version = ghostversion;
 	mthing = playerstarts[0];
 	I_Assert(mthing);
 	{ // A bit more complex than P_SpawnPlayer because ghosts aren't solid and won't just push themselves out of the ceiling.
@@ -5124,20 +5201,24 @@ void G_AddGhost(char *defdemoname)
 	gh->oldmo.z = gh->mo->z;
 
 	// Set skin
+	gh->mo->skin = &skins[0];
 	for (i = 0; i < numskins; i++)
 		if (!stricmp(skins[i].name,skin))
 		{
 			gh->mo->skin = &skins[i];
 			break;
 		}
+	gh->oldmo.skin = gh->mo->skin;
 
 	// Set color
+	gh->mo->color = ((skin_t*)gh->mo->skin)->prefcolor;
 	for (i = 0; i < MAXSKINCOLORS; i++)
 		if (!stricmp(Color_Names[i],color))
 		{
 			gh->mo->color = (UINT8)i;
 			break;
 		}
+	gh->oldmo.color = gh->mo->color;
 
 	CONS_Printf(M_GetText("Added ghost %s from %s\n"), name, pdemoname);
 	Z_Free(pdemoname);
@@ -5199,9 +5280,16 @@ void G_DoPlayMetal(void)
     metal_p += 12; // DEMOHEADER
 	metal_p++; // VERSION
 	metal_p++; // SUBVERSION
-	if (DEMOVERSION != READUINT16(metal_p))
+	metalversion = READUINT16(metal_p);
+	switch(metalversion)
 	{
-		CONS_Alert(CONS_WARNING, M_GetText("Failed to load bot recording for this map, format version mismatch.\n"));
+	case DEMOVERSION: // latest always supported
+	// compatibility available?
+	case 0x0008:
+		break;
+	// too old, cannot support.
+	default:
+		CONS_Alert(CONS_WARNING, M_GetText("Failed to load bot recording for this map, format version incompatible.\n"));
 		Z_Free(metalbuffer);
 		return;
 	}
diff --git a/src/g_game.h b/src/g_game.h
index af37fc8a5..03bbce606 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -137,6 +137,7 @@ void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
 void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
 void G_GhostAddThok(void);
 void G_GhostAddSpin(void);
+void G_GhostAddRev(void);
 void G_GhostAddColor(ghostcolor_t color);
 void G_GhostAddFlip(void);
 void G_GhostAddScale(UINT16 scale);
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 47fab579b..e2229ea92 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -183,34 +183,32 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 	{
 		v[0].x = v[3].x = (cx*sdupx-(gpatch->width-gpatch->leftoffset)*pdupx)/vid.width - 1;
 		v[2].x = v[1].x = (cx*sdupx+gpatch->leftoffset*pdupx)/vid.width - 1;
-		v[0].y = v[1].y = 1-(cy*sdupy-gpatch->topoffset*pdupy)/vid.height;
-		v[2].y = v[3].y = 1-(cy*sdupy+(gpatch->height-gpatch->topoffset)*pdupy)/vid.height;
 	}
 	else
 	{
 		v[0].x = v[3].x = (cx*sdupx-gpatch->leftoffset*pdupx)/vid.width - 1;
 		v[2].x = v[1].x = (cx*sdupx+(gpatch->width-gpatch->leftoffset)*pdupx)/vid.width - 1;
-		v[0].y = v[1].y = 1-(cy*sdupy-gpatch->topoffset*pdupy)/vid.height;
-		v[2].y = v[3].y = 1-(cy*sdupy+(gpatch->height-gpatch->topoffset)*pdupy)/vid.height;
 	}
 
+	v[0].y = v[1].y = 1-(cy*sdupy-gpatch->topoffset*pdupy)/vid.height;
+	v[2].y = v[3].y = 1-(cy*sdupy+(gpatch->height-gpatch->topoffset)*pdupy)/vid.height;
+
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
 	if (option & V_FLIP)
 	{
 		v[0].sow = v[3].sow = gpatch->max_s;
 		v[2].sow = v[1].sow = 0.0f;
-		v[0].tow = v[1].tow = 0.0f;
-		v[2].tow = v[3].tow = gpatch->max_t;
 	}
 	else
 	{
 		v[0].sow = v[3].sow = 0.0f;
 		v[2].sow = v[1].sow = gpatch->max_s;
-		v[0].tow = v[1].tow = 0.0f;
-		v[2].tow = v[3].tow = gpatch->max_t;
 	}
 
+	v[0].tow = v[1].tow = 0.0f;
+	v[2].tow = v[3].tow = gpatch->max_t;
+
 	flags = BLENDMODE|PF_Clip|PF_NoZClip|PF_NoDepthTest;
 
 	if (option & V_WRAPX)
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 8a2e3a6a0..b968fee02 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -881,6 +881,9 @@ static void md2_loadTexture(md2_t *model)
 	HWR_UnlockCachedPatch(grpatch);
 }
 
+// Don't spam the console, or the OS with fopen requests!
+static boolean nomd2s = false;
+
 void HWR_InitMD2(void)
 {
 	size_t i;
@@ -906,12 +909,14 @@ void HWR_InitMD2(void)
 		md2_models[i].skin = -1;
 		md2_models[i].notfound = true;
 	}
-	// read the md2.dat file
 
+	// read the md2.dat file
 	f = fopen("md2.dat", "rt");
+
 	if (!f)
 	{
 		CONS_Printf("%s", M_GetText("Error while loading md2.dat\n"));
+		nomd2s = true;
 		return;
 	}
 	while (fscanf(f, "%19s %31s %f %f", name, filename, &scale, &offset) == 4)
@@ -966,14 +971,18 @@ void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
 	char name[18], filename[32];
 	float scale, offset;
 
+	if (nomd2s)
+		return;
+
 	CONS_Printf("AddPlayerMD2()...\n");
 
 	// read the md2.dat file
-
 	f = fopen("md2.dat", "rt");
+
 	if (!f)
 	{
 		CONS_Printf("Error while loading md2.dat\n");
+		nomd2s = true;
 		return;
 	}
 
@@ -1009,13 +1018,16 @@ void HWR_AddSpriteMD2(size_t spritenum) // For MD2s that were added after startu
 	char name[18], filename[32];
 	float scale, offset;
 
-	// Read the md2.dat file
+	if (nomd2s)
+		return;
 
+	// Read the md2.dat file
 	f = fopen("md2.dat", "rt");
 
 	if (!f)
 	{
 		CONS_Printf("Error while loading md2.dat\n");
+		nomd2s = true;
 		return;
 	}
 
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 1166f10f2..0fb486ea0 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -23,9 +23,9 @@
 
 #include "hw_glob.h"
 
-#define MD2_MAX_TRIANGLES               4096
-#define MD2_MAX_VERTICES                2048
-#define MD2_MAX_TEXCOORDS               2048
+#define MD2_MAX_TRIANGLES               8192
+#define MD2_MAX_VERTICES                4096
+#define MD2_MAX_TEXCOORDS               4096
 #define MD2_MAX_FRAMES                  512
 #define MD2_MAX_SKINS                   32
 #define MD2_MAX_FRAMESIZE               (MD2_MAX_VERTICES * 4 + 128)
diff --git a/src/info.c b/src/info.c
index d0ba598b1..01c258e44 100644
--- a/src/info.c
+++ b/src/info.c
@@ -831,21 +831,23 @@ state_t states[NUMSTATES] =
 	{SPR_NULL, 0, 0, {A_SpawnFreshCopy}, 0, 0, S_CYBRAKDEMONELECTRICBARRIER_REVIVE3}, // S_CYBRAKDEMONELECTRICBARRIER_REVIVE2
 	{SPR_NULL, 0, TICRATE, {A_PlaySound}, sfx_s3k79, 0, S_NULL}, // S_CYBRAKDEMONELECTRICBARRIER_INIT1
 
-	{SPR_TARG, 0 + FF_FULLBRIGHT, 1, {A_VileFire}, sfx_s3k9d, 0, S_CYBRAKDEMONTARGETRETICULE2}, // S_CYBRAKDEMONTARGETRETICULE1
-	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE3}, // S_CYBRAKDEMONTARGETRETICULE2
-	{SPR_TARG, 1 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE4}, // S_CYBRAKDEMONTARGETRETICULE3
-	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE5}, // S_CYBRAKDEMONTARGETRETICULE4
-	{SPR_TARG, 2 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE6}, // S_CYBRAKDEMONTARGETRETICULE5
-	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE7}, // S_CYBRAKDEMONTARGETRETICULE6
-	{SPR_TARG, 3 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE8}, // S_CYBRAKDEMONTARGETRETICULE7
-	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE9}, // S_CYBRAKDEMONTARGETRETICULE8
-	{SPR_TARG, 4 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE10}, // S_CYBRAKDEMONTARGETRETICULE9
-	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE11}, // S_CYBRAKDEMONTARGETRETICULE10
-	{SPR_TARG, 5 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE12}, // S_CYBRAKDEMONTARGETRETICULE11
-	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE13}, // S_CYBRAKDEMONTARGETRETICULE12
-	{SPR_TARG, 0 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, 0, S_CYBRAKDEMONTARGETRETICULE14}, // S_CYBRAKDEMONTARGETRETICULE13
+	{SPR_TARG, 0 + FF_FULLBRIGHT, 1, {A_VileFire}, sfx_s3k9d, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE2}, // S_CYBRAKDEMONTARGETRETICULE1
+	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE3}, // S_CYBRAKDEMONTARGETRETICULE2
+	{SPR_TARG, 1 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE4}, // S_CYBRAKDEMONTARGETRETICULE3
+	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE5}, // S_CYBRAKDEMONTARGETRETICULE4
+	{SPR_TARG, 2 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE6}, // S_CYBRAKDEMONTARGETRETICULE5
+	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE7}, // S_CYBRAKDEMONTARGETRETICULE6
+	{SPR_TARG, 3 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE8}, // S_CYBRAKDEMONTARGETRETICULE7
+	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE9}, // S_CYBRAKDEMONTARGETRETICULE8
+	{SPR_TARG, 4 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE10}, // S_CYBRAKDEMONTARGETRETICULE9
+	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE11}, // S_CYBRAKDEMONTARGETRETICULE10
+	{SPR_TARG, 5 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE12}, // S_CYBRAKDEMONTARGETRETICULE11
+	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE13}, // S_CYBRAKDEMONTARGETRETICULE12
+	{SPR_TARG, 0 + FF_FULLBRIGHT, 1, {A_VileFire}, 0, MT_CYBRAKDEMON_TARGET_DOT, S_CYBRAKDEMONTARGETRETICULE14}, // S_CYBRAKDEMONTARGETRETICULE13
 	{SPR_TARG, 6 + FF_FULLBRIGHT, 1, {A_Repeat}, 6, S_CYBRAKDEMONTARGETRETICULE2, S_NULL}, // S_CYBRAKDEMONTARGETRETICULE14
 
+	{SPR_HOOP, 0 + FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_CYBRAKDEMONTARGETDOT
+
 	{SPR_NPLM, 0, 2, {NULL}, 0, 0, S_CYBRAKDEMONNAPALMBOMBLARGE_FLY2}, //S_CYBRAKDEMONNAPALMBOMBLARGE_FLY1,
 	{SPR_NPLM, 1, 2, {NULL}, 0, 0, S_CYBRAKDEMONNAPALMBOMBLARGE_FLY3}, //S_CYBRAKDEMONNAPALMBOMBLARGE_FLY2,
 	{SPR_NPLM, 2, 2, {NULL}, 0, 0, S_CYBRAKDEMONNAPALMBOMBLARGE_FLY4}, //S_CYBRAKDEMONNAPALMBOMBLARGE_FLY3,
@@ -4770,7 +4772,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-    {           // MT_CYBRAKDEMON_TARGET_RETICULE
+	{           // MT_CYBRAKDEMON_TARGET_RETICULE
 		-1,             // doomednum
 		S_CYBRAKDEMONTARGETRETICULE1, // spawnstate
 		1000,           // spawnhealth
@@ -4797,6 +4799,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_CYBRAKDEMON_TARGET_DOT
+		-1,             // doomednum
+		S_CYBRAKDEMONTARGETDOT, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_BPLD1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		10*FRACUNIT,    // speed
+		32*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_CYBRAKDEMON_NAPALM_BOMB_LARGE
 		-1,             // doomednum
 		S_CYBRAKDEMONNAPALMBOMBLARGE_FLY1, // spawnstate
@@ -10463,7 +10492,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8,              // speed
 		64*FRACUNIT,    // radius
 		64*FRACUNIT,    // height
-		0,              // display offset
+		2,              // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
@@ -13284,7 +13313,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8,              // speed
 		32*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
-		1,              // display offset
+		2,              // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
diff --git a/src/info.h b/src/info.h
index 63fe1a638..bc9b7f8fb 100644
--- a/src/info.h
+++ b/src/info.h
@@ -1353,6 +1353,8 @@ typedef enum state
 	S_CYBRAKDEMONTARGETRETICULE13,
 	S_CYBRAKDEMONTARGETRETICULE14,
 
+	S_CYBRAKDEMONTARGETDOT,
+
 	S_CYBRAKDEMONNAPALMBOMBLARGE_FLY1,
 	S_CYBRAKDEMONNAPALMBOMBLARGE_FLY2,
 	S_CYBRAKDEMONNAPALMBOMBLARGE_FLY3,
@@ -3567,6 +3569,7 @@ typedef enum mobj_type
 	MT_CYBRAKDEMON_FLAMESHOT,
 	MT_CYBRAKDEMON_FLAMEREST,
 	MT_CYBRAKDEMON_TARGET_RETICULE,
+	MT_CYBRAKDEMON_TARGET_DOT,
 	MT_CYBRAKDEMON_NAPALM_BOMB_LARGE,
 	MT_CYBRAKDEMON_NAPALM_BOMB_SMALL,
 	MT_CYBRAKDEMON_NAPALM_FLAMES,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index ece3d4298..ac801cddd 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -16,6 +16,7 @@
 #include "p_setup.h" // So we can have P_SetupLevelSky
 #include "z_zone.h"
 #include "r_main.h"
+#include "r_things.h"
 #include "m_random.h"
 #include "s_sound.h"
 #include "g_game.h"
@@ -724,6 +725,27 @@ static int lib_pHomingAttack(lua_State *L)
 	return 0;
 }
 
+static int lib_pSuperReady(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	//HUDSAFE
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	lua_pushboolean(L, P_SuperReady(player));
+	return 1;
+}
+
+static int lib_pDoJump(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	boolean soundandstate = (boolean)lua_opttrueboolean(L, 2);
+	NOHUD
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	P_DoJump(player, soundandstate);
+	return 0;
+}
+
 // P_MAP
 ///////////
 
@@ -1309,6 +1331,31 @@ static int lib_rPointInSubsector(lua_State *L)
 	return 1;
 }
 
+// R_THINGS
+////////////
+
+static int lib_rChar2Frame(lua_State *L)
+{
+	const char *p = luaL_checkstring(L, 1);
+	//HUDSAFE
+	lua_pushinteger(L, R_Char2Frame(*p)); // first character only
+	return 1;
+}
+
+static int lib_rFrame2Char(lua_State *L)
+{
+	UINT8 ch = (UINT8)luaL_checkinteger(L, 1);
+	char c[2] = "";
+	//HUDSAFE
+
+	c[0] = R_Frame2Char(ch);
+	c[1] = 0;
+
+	lua_pushstring(L, c);
+	lua_pushinteger(L, c[0]);
+	return 2;
+}
+
 // S_SOUND
 ////////////
 
@@ -1593,6 +1640,8 @@ static luaL_Reg lib[] = {
 	{"P_LookForEnemies",lib_pLookForEnemies},
 	{"P_NukeEnemies",lib_pNukeEnemies},
 	{"P_HomingAttack",lib_pHomingAttack},
+	{"P_SuperReady",lib_pSuperReady},
+	{"P_DoJump",lib_pDoJump},
 
 	// p_map
 	{"P_CheckPosition",lib_pCheckPosition},
@@ -1645,6 +1694,10 @@ static luaL_Reg lib[] = {
 	{"R_PointToDist2",lib_rPointToDist2},
 	{"R_PointInSubsector",lib_rPointInSubsector},
 
+	// r_things (sprite)
+	{"R_Char2Frame",lib_rChar2Frame},
+	{"R_Frame2Char",lib_rFrame2Char},
+
 	// s_sound
 	{"S_StartSound",lib_sStartSound},
 	{"S_StartSoundAtVolume",lib_sStartSoundAtVolume},
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index d153020f3..6f185c69c 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -79,7 +79,7 @@ void Got_Luacmd(UINT8 **cp, INT32 playernum)
 }
 
 // Wrapper for COM_AddCommand commands
-static void COM_Lua_f(void)
+void COM_Lua_f(void)
 {
 	char *buf, *p;
 	UINT8 i, flags;
@@ -90,9 +90,13 @@ static void COM_Lua_f(void)
 	lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command
 	I_Assert(lua_istable(gL, -1));
 
-	lua_getfield(gL, -1, COM_Argv(0)); // push command info table
+	// use buf temporarily -- must use lowercased string
+	buf = Z_StrDup(COM_Argv(0));
+	strlwr(buf);
+	lua_getfield(gL, -1, buf); // push command info table
 	I_Assert(lua_istable(gL, -1));
 	lua_remove(gL, -2); // pop COM_Command
+	Z_Free(buf);
 
 	lua_rawgeti(gL, -1, 2); // push flags from command info table
 	if (lua_isboolean(gL, -1))
@@ -158,8 +162,13 @@ static void COM_Lua_f(void)
 // Wrapper for COM_AddCommand
 static int lib_comAddCommand(lua_State *L)
 {
-	boolean exists = false;
-	const char *name = luaL_checkstring(L, 1);
+	int com_return = -1;
+	const char *luaname = luaL_checkstring(L, 1);
+
+	// must store in all lowercase
+	char *name = Z_StrDup(luaname);
+	strlwr(name);
+
 	luaL_checktype(L, 2, LUA_TFUNCTION);
 	NOHUD
 	if (lua_gettop(L) >= 3)
@@ -177,11 +186,6 @@ static int lib_comAddCommand(lua_State *L)
 	lua_getfield(L, LUA_REGISTRYINDEX, "COM_Command");
 	I_Assert(lua_istable(L, -1));
 
-	lua_getfield(L, -1, name);
-	if (!lua_isnil(L, -1))
-		exists = true;
-	lua_pop(L, 1);
-
 	lua_createtable(L, 2, 0);
 		lua_pushvalue(L, 2);
 		lua_rawseti(L, -2, 1);
@@ -190,14 +194,23 @@ static int lib_comAddCommand(lua_State *L)
 		lua_rawseti(L, -2, 2);
 	lua_setfield(L, -2, name);
 
-	// This makes it only add a new command if another
-	// Lua command by the same name doesn't already exist.
-	//
-	// UNFORTUNATELY COM_AddCommand will still cause I_Errors
-	// if you attempt to override an existing hardcoded command.
-	//
-	if (!exists)
-		COM_AddCommand(name, COM_Lua_f);
+	// Try to add the Lua command
+	com_return = COM_AddLuaCommand(name);
+
+	if (com_return < 0)
+	{ // failed to add -- free the lowercased name and return error
+		Z_Free(name);
+		return luaL_error(L, "Couldn't add a new console command \"%s\"", luaname);
+	}
+	else if (com_return == 1)
+	{ // command existed already -- free our name as the old string will continue to be used
+		CONS_Printf("Replaced command \"%s\"\n", name);
+		Z_Free(name);
+	}
+	else
+	{ // new command was added -- do NOT free the string as it will forever be used by the console
+		CONS_Printf("Added command \"%s\"\n", name);
+	}
 	return 0;
 }
 
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 1b4bf1dd7..8bd1b49ec 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -23,6 +23,8 @@
 #include "lua_hook.h"
 #include "lua_hud.h" // hud_running errors
 
+static UINT8 hooksAvailable[(hook_MAX/8)+1];
+
 const char *const hookNames[hook_MAX+1] = {
 	"NetVars",
 	"MapChange",
@@ -183,6 +185,9 @@ static int lib_addHook(lua_State *L)
 
 	if (subfield)
 		Z_Free(subfield);
+
+
+	hooksAvailable[hook/8] |= 1<<(hook%8);
 	return 0;
 }
 
@@ -190,6 +195,8 @@ int LUA_HookLib(lua_State *L)
 {
 	// Create all registry tables
 	enum hook i;
+	memset(hooksAvailable,0,sizeof(UINT8[(hook_MAX/8)+1]));
+
 	lua_newtable(L);
 	for (i = 0; i < hook_MAX; i++)
 	{
@@ -225,7 +232,7 @@ int LUA_HookLib(lua_State *L)
 boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
 {
 	boolean hooked = false;
-	if (!gL)
+	if (!gL || !(hooksAvailable[which/8] & (1<<(which%8))))
 		return false;
 
 	// clear the stack (just in case)
@@ -315,7 +322,7 @@ boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
 // Hook for map change (before load)
 void LUAh_MapChange(void)
 {
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_MapChange/8] & (1<<(hook_MapChange%8))))
 		return;
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "hook");
@@ -337,7 +344,7 @@ void LUAh_MapChange(void)
 // Hook for map load
 void LUAh_MapLoad(void)
 {
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_MapLoad/8] & (1<<(hook_MapLoad%8))))
 		return;
 
 	lua_pop(gL, -1);
@@ -361,7 +368,7 @@ void LUAh_MapLoad(void)
 // Hook for Got_AddPlayer
 void LUAh_PlayerJoin(int playernum)
 {
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_PlayerJoin/8] & (1<<(hook_PlayerJoin%8))))
 		return;
 
 	lua_pop(gL, -1);
@@ -385,7 +392,7 @@ void LUAh_PlayerJoin(int playernum)
 // Hook for frame (after mobj and player thinkers)
 void LUAh_ThinkFrame(void)
 {
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_ThinkFrame/8] & (1<<(hook_ThinkFrame%8))))
 		return;
 
 	lua_pop(gL, -1);
@@ -420,7 +427,7 @@ void LUAh_ThinkFrame(void)
 UINT8 LUAh_MobjCollide(mobj_t *thing1, mobj_t *thing2)
 {
 	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_MobjCollide/8] & (1<<(hook_MobjCollide%8))))
 		return 0;
 
 	// clear the stack
@@ -471,7 +478,7 @@ UINT8 LUAh_MobjCollide(mobj_t *thing1, mobj_t *thing2)
 UINT8 LUAh_MobjMoveCollide(mobj_t *thing1, mobj_t *thing2)
 {
 	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_MobjMoveCollide/8] & (1<<(hook_MobjMoveCollide%8))))
 		return 0;
 
 	// clear the stack
@@ -522,7 +529,7 @@ UINT8 LUAh_MobjMoveCollide(mobj_t *thing1, mobj_t *thing2)
 boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 {
 	boolean hooked = false;
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_TouchSpecial/8] & (1<<(hook_TouchSpecial%8))))
 		return false;
 
 	// clear the stack
@@ -551,8 +558,14 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 	while (lua_next(gL, 1) != 0) {
 		lua_pushvalue(gL, 2); // special
 		lua_pushvalue(gL, 3); // toucher
-		LUA_Call(gL, 2); // pops hook function, special, toucher
-		hooked = true;
+		if (lua_pcall(gL, 2, 1, 0)) { // pops hook function, special, toucher, pushes 1 return result
+			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+			lua_pop(gL, 1);
+			continue;
+		}
+		if (lua_toboolean(gL, -1)) // if return true,
+			hooked = true; // override vanilla behavior
+		lua_pop(gL, 1); // pop return value
 	}
 
 	lua_pop(gL, -1);
@@ -564,7 +577,7 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage)
 {
 	UINT8 shouldDamage = 0; // 0 = default, 1 = force yes, 2 = force no.
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_ShouldDamage/8] & (1<<(hook_ShouldDamage%8))))
 		return 0;
 
 	// clear the stack
@@ -619,7 +632,7 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage)
 {
 	boolean handled = false;
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_MobjDamage/8] & (1<<(hook_MobjDamage%8))))
 		return false;
 
 	// clear the stack
@@ -669,7 +682,7 @@ boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source)
 {
 	boolean handled = false;
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_MobjDeath/8] & (1<<(hook_MobjDeath%8))))
 		return false;
 
 	// clear the stack
@@ -717,7 +730,7 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source)
 boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
 {
 	boolean hooked = false;
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_BotTiccmd/8] & (1<<(hook_BotTiccmd%8))))
 		return false;
 
 	// clear the stack
@@ -737,8 +750,14 @@ boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
 	while (lua_next(gL, 1)) {
 		lua_pushvalue(gL, 2); // bot
 		lua_pushvalue(gL, 3); // cmd
-		LUA_Call(gL, 2);
-		hooked = true;
+		if (lua_pcall(gL, 2, 1, 0)) {
+			CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+			lua_pop(gL, 1);
+			continue;
+		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1); // pop return value
 	}
 
 	lua_pop(gL, -1);
@@ -749,7 +768,7 @@ boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
 // Hook for B_BuildTailsTiccmd by skin name
 boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 {
-	if (!gL || !tails->skin)
+	if (!gL || !tails->skin || !(hooksAvailable[hook_BotAI/8] & (1<<(hook_BotAI%8))))
 		return false;
 
 	// clear the stack
@@ -813,7 +832,7 @@ boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 // Hook for linedef executors
 boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo)
 {
-	if (!gL)
+	if (!gL || !(hooksAvailable[hook_LinedefExecute/8] & (1<<(hook_LinedefExecute%8))))
 		return false;
 
 	// clear the stack
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 98f0ba37a..176b816ec 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -26,6 +26,8 @@
 boolean hud_running = false;
 static UINT8 hud_enabled[(hud_MAX/8)+1];
 
+static UINT8 hudAvailable; // hud hooks field
+
 // must match enum hud in lua_hud.h
 static const char *const hud_disable_options[] = {
 	"stagetitle",
@@ -399,6 +401,8 @@ static int lib_hudadd(lua_State *L)
 
 	lua_pushvalue(L, 1);
 	lua_rawseti(L, -2, (int)(lua_objlen(L, -2) + 1));
+
+	hudAvailable |= 1<<field;
 	return 0;
 }
 
@@ -472,7 +476,7 @@ boolean LUA_HudEnabled(enum hud option)
 // Hook for HUD rendering
 void LUAh_GameHUD(player_t *stplyr)
 {
-	if (!gL)
+	if (!gL || !(hudAvailable & (1<<hudhook_game)))
 		return;
 
 	hud_running = true;
@@ -502,7 +506,7 @@ void LUAh_GameHUD(player_t *stplyr)
 
 void LUAh_ScoresHUD(void)
 {
-	if (!gL)
+	if (!gL || !(hudAvailable & (1<<hudhook_scores)))
 		return;
 
 	hud_running = true;
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index c6bb274d5..6fb70ccfc 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -729,6 +729,9 @@ static int mapthing_set(lua_State *L)
 static int lib_iterateMapthings(lua_State *L)
 {
 	size_t i = 0;
+	if (lua_gettop(L) < 2)
+		return luaL_error(L, "Don't call mapthings.iterate() directly, use it as 'for mapthing in mapthings.iterate do <block> end'.");
+	lua_settop(L, 2);
 	lua_remove(L, 1); // state is unused.
 	if (!lua_isnil(L, 1))
 		i = (size_t)(*((mapthing_t **)luaL_checkudata(L, 1, META_MAPTHING)) - mapthings) + 1;
diff --git a/src/lua_script.h b/src/lua_script.h
index 343f873dd..eaef13d1e 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -43,6 +43,9 @@ void LUA_CVarChanged(const char *name); // lua_consolelib.c
 int Lua_optoption(lua_State *L, int narg,
 	const char *def, const char *const lst[]);
 
+// Console wrapper
+void COM_Lua_f(void);
+
 #define LUA_Call(L,a)\
 {\
 	if (lua_pcall(L, a, 0, 0)) {\
diff --git a/src/m_anigif.c b/src/m_anigif.c
index fb2eb8f4f..c5efe876c 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -22,7 +22,7 @@
 // GIFs are always little-endian
 #include "byteptr.h"
 
-consvar_t cv_gif_optimize = {"gif_optimize", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_gif_optimize = {"gif_optimize", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_gif_downscale =  {"gif_downscale", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 #ifdef HAVE_ANIGIF
@@ -357,7 +357,7 @@ static void GIF_lzw(void)
 		if (gifbwr_bufsize >= 250)
 			break;
 	}
-	if (scrbuf_pos >= scrbuf_writeend)
+	if (scrbuf_pos > scrbuf_writeend)
 	{
 		GIF_bwrwrite(giflzw_workingCode);
 		GIF_bwrwrite(GIFLZW_DATAEND);
diff --git a/src/m_menu.c b/src/m_menu.c
index 30876cbcb..887ac5fcb 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -365,7 +365,7 @@ static CV_PossibleValue_t map_cons_t[] = {
 	{1,"MIN"},
 	{NUMMAPS, "MAX"}
 };
-consvar_t cv_nextmap = {"nextmap", "MAP01", CV_HIDEN|CV_CALL, map_cons_t, Nextmap_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_nextmap = {"nextmap", "1", CV_HIDEN|CV_CALL, map_cons_t, Nextmap_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 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};
@@ -2338,8 +2338,8 @@ void M_Drawer(void)
 	{
 		if (customversionstring[0] != '\0')
 		{
-			V_DrawThinString(vid.dupx, BASEVIDHEIGHT - 17*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT, "Mod version:");
-			V_DrawThinString(vid.dupx, BASEVIDHEIGHT - 9*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, customversionstring);
+			V_DrawThinString(vid.dupx, vid.height - 17*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT, "Mod version:");
+			V_DrawThinString(vid.dupx, vid.height - 9*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, customversionstring);
 		}
 		else
 #if VERSION > 0 || SUBVERSION > 0
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 560b7d34a..e20e3a5c8 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -2600,70 +2600,26 @@ void A_MonitorPop(mobj_t *actor)
 	{
 		case MT_QUESTIONBOX: // Random!
 		{
-			mobjtype_t spawnchance[128];
-			SINT8 numchoices = 0;
-			SINT8 target = 0;
+			mobjtype_t spawnchance[256];
+			INT32 numchoices = 0, i = 0;
 
-			if (cv_superring.value)
-			{
-				for (target += (SINT8)cv_superring.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_SUPERRINGBOX;
-			}
-			if (cv_supersneakers.value)
-			{
-				for (target += (SINT8)cv_supersneakers.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_SNEAKERTV;
-			}
-			if (cv_invincibility.value)
-			{
-				for (target += (SINT8)cv_invincibility.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_INV;
-			}
-			if (cv_jumpshield.value)
-			{
-				for (target += (SINT8)cv_jumpshield.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_WHITETV;
-			}
-			if (cv_watershield.value)
-			{
-				for (target += (SINT8)cv_watershield.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_GREENTV;
-			}
-			if (cv_ringshield.value)
-			{
-				for (target += (SINT8)cv_ringshield.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_YELLOWTV;
-			}
-			if (cv_forceshield.value)
-			{
-				for (target += (SINT8)cv_forceshield.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_BLUETV;
-			}
-			if (cv_bombshield.value)
-			{
-				for (target += (SINT8)cv_bombshield.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_BLACKTV;
-			}
-			if (cv_1up.value)
-			{
-				for (target += (SINT8)cv_1up.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_PRUP;
-			}
-			if (cv_eggmanbox.value)
-			{
-				for (target += (SINT8)cv_eggmanbox.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_EGGMANBOX;
-			}
-			if (cv_teleporters.value)
-			{
-				for (target += (SINT8)cv_teleporters.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_MIXUPBOX;
-			}
-			if (cv_recycler.value)
-			{
-				for (target += (SINT8)cv_recycler.value; numchoices < target;)
-					spawnchance[numchoices++] = MT_RECYCLETV;
-			}
+#define QUESTIONBOXCHANCES(type, cvar) \
+for (i = cvar.value; i; --i) spawnchance[numchoices++] = type
+
+			QUESTIONBOXCHANCES(MT_SUPERRINGBOX,	cv_superring);
+			QUESTIONBOXCHANCES(MT_SNEAKERTV,	cv_supersneakers);
+			QUESTIONBOXCHANCES(MT_INV,			cv_invincibility);
+			QUESTIONBOXCHANCES(MT_WHITETV,		cv_jumpshield);
+			QUESTIONBOXCHANCES(MT_GREENTV,		cv_watershield);
+			QUESTIONBOXCHANCES(MT_YELLOWTV,		cv_ringshield);
+			QUESTIONBOXCHANCES(MT_BLUETV,		cv_forceshield);
+			QUESTIONBOXCHANCES(MT_BLACKTV,		cv_bombshield);
+			QUESTIONBOXCHANCES(MT_PRUP,			cv_1up);
+			QUESTIONBOXCHANCES(MT_EGGMANBOX,	cv_eggmanbox);
+			QUESTIONBOXCHANCES(MT_MIXUPBOX,		cv_teleporters);
+			QUESTIONBOXCHANCES(MT_RECYCLETV,	cv_recycler);
+
+#undef QUESTIONBOXCHANCES
 
 			if (numchoices == 0)
 			{
@@ -3020,7 +2976,7 @@ void A_JumpShield(mobj_t *actor)
 
 	if ((player->powers[pw_shield] & SH_NOSTACK) != SH_JUMP)
 	{
-		player->powers[pw_shield] = SH_JUMP+(player->powers[pw_shield] & SH_STACK);
+		player->powers[pw_shield] = SH_JUMP|(player->powers[pw_shield] & SH_STACK);
 		P_SpawnShieldOrb(player);
 	}
 
@@ -3052,7 +3008,7 @@ void A_RingShield(mobj_t *actor)
 
 	if ((player->powers[pw_shield] & SH_NOSTACK) != SH_ATTRACT)
 	{
-		player->powers[pw_shield] = SH_ATTRACT+(player->powers[pw_shield] & SH_STACK);
+		player->powers[pw_shield] = SH_ATTRACT|(player->powers[pw_shield] & SH_STACK);
 		P_SpawnShieldOrb(player);
 	}
 
@@ -3149,7 +3105,7 @@ void A_SuperSneakers(mobj_t *actor)
 
 	actor->target->player->powers[pw_sneakers] = sneakertics + 1;
 
-	if (P_IsLocalPlayer(player) && (!player->powers[pw_super]))
+	if (P_IsLocalPlayer(player) && !player->powers[pw_super])
 	{
 		if (S_SpeedMusic(0.0f) && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC))
 			S_SpeedMusic(1.4f);
@@ -3254,7 +3210,7 @@ void A_BombShield(mobj_t *actor)
 
 	if ((player->powers[pw_shield] & SH_NOSTACK) != SH_BOMB)
 	{
-		player->powers[pw_shield] = SH_BOMB+(player->powers[pw_shield] & SH_STACK);
+		player->powers[pw_shield] = SH_BOMB|(player->powers[pw_shield] & SH_STACK);
 		P_SpawnShieldOrb(player);
 	}
 
@@ -3286,7 +3242,7 @@ void A_WaterShield(mobj_t *actor)
 
 	if ((player->powers[pw_shield] & SH_NOSTACK) != SH_ELEMENTAL)
 	{
-		player->powers[pw_shield] = SH_ELEMENTAL+(player->powers[pw_shield] & SH_FIREFLOWER);
+		player->powers[pw_shield] = SH_ELEMENTAL|(player->powers[pw_shield] & SH_STACK);
 		P_SpawnShieldOrb(player);
 	}
 
@@ -9847,14 +9803,18 @@ void A_VileAttack(mobj_t *actor)
 // Function: A_VileFire
 //
 // Description: Kind of like A_CapeChase; keeps this object in front of its tracer, unless its target can't see it.
-//              Originally used by Archviles to keep their hellfire pillars on top of the player, hence the name (although it was just "A_Fire" there; added "Vile" to make it more specific).
+//				Originally used by Archviles to keep their hellfire pillars on top of the player, hence the name (although it was just "A_Fire" there; added "Vile" to make it more specific).
+//				Added some functionality to optionally draw a line directly to the enemy doing the targetting. Y'know, to hammer things in a bit.
 //
 // var1 = sound to play
-// var2 = unused
+// var2:
+//		Lower 16 bits = mobj to spawn (0 doesn't spawn a line at all)
+//		Upper 16 bits = # to spawn (default is 8)
 //
 void A_VileFire(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
 	mobj_t *dest;
 
 #ifdef HAVE_BLUA
@@ -9870,10 +9830,10 @@ void A_VileFire(mobj_t *actor)
 	if (!P_CheckSight(actor->target, dest))
 		return;
 
-	// keep to same scale and gravity as target ALWAYS
-	actor->destscale = actor->target->scale;
+	// keep to same scale and gravity as tracer ALWAYS
+	actor->destscale = dest->scale;
 	P_SetScale(actor, actor->destscale);
-	if (actor->target->eflags & MFE_VERTICALFLIP)
+	if (dest->eflags & MFE_VERTICALFLIP)
 	{
 		actor->eflags |= MFE_VERTICALFLIP;
 		actor->flags2 |= MF2_OBJECTFLIP;
@@ -9893,6 +9853,33 @@ void A_VileFire(mobj_t *actor)
 	// Play sound, if one's specified
 	if (locvar1 > 0 && locvar1 < NUMSFX)
 		S_StartSound(actor, (sfxenum_t)locvar1);
+
+	// Now draw the line to the actor's target
+	if (locvar2 & 0xFFFF)
+	{
+		mobjtype_t lineMobj;
+		UINT16 numLineMobjs;
+		fixed_t distX;
+		fixed_t distY;
+		fixed_t distZ;
+		UINT16 i;
+
+		lineMobj = (mobjtype_t)(locvar2 & 0xFFFF);
+		numLineMobjs = (UINT16)(locvar2 >> 16);
+		if (numLineMobjs == 0) {
+			numLineMobjs = 8;
+		}
+
+		// Get distance for each step
+		distX = (actor->target->x - actor->x) / numLineMobjs;
+		distY = (actor->target->y - actor->y) / numLineMobjs;
+		distZ = ((actor->target->z + FixedMul(actor->target->height/2, actor->target->scale)) - (actor->z + FixedMul(actor->height/2, actor->scale))) / numLineMobjs;
+
+		for (i = 1; i <= numLineMobjs; i++)
+		{
+			P_SpawnMobj(actor->x + (distX * i), actor->y + (distY * i), actor->z + (distZ * i) + FixedMul(actor->height/2, actor->scale), lineMobj);
+		}
+	}
 }
 
 // Function: A_BrakChase
diff --git a/src/p_fab.c b/src/p_fab.c
index 134e2c398..77abccccd 100644
--- a/src/p_fab.c
+++ b/src/p_fab.c
@@ -91,6 +91,7 @@ static void P_SetTranslucencies(void)
 	R_SetTrans(S_CYBRAKDEMONFLAMESHOT_FLY1, S_CYBRAKDEMONFLAMESHOT_DIE, tr_trans50); // Flame
 	R_SetTrans(S_CYBRAKDEMONFLAMEREST, 0, tr_trans50); // Flame
 	R_SetTrans(S_CYBRAKDEMONTARGETRETICULE1, S_CYBRAKDEMONTARGETRETICULE14, tr_trans50); // Target
+	R_SetTrans(S_CYBRAKDEMONTARGETDOT, S_CYBRAKDEMONTARGETDOT, tr_trans50); // Target
 
 	R_SetTrans(S_FOG1, S_FOG14, tr_trans50);
 
diff --git a/src/p_inter.c b/src/p_inter.c
index 75a728341..149c16b05 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2404,6 +2404,8 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 	{
 		if (!(inflictor->flags & MF_FIRE))
 			P_GivePlayerRings(player, 1);
+		if (inflictor->flags2 & MF2_BOUNCERING)
+			inflictor->fuse = 1;
 		return false;
 	}
 
@@ -2489,6 +2491,8 @@ static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj
 		{
 			if (!(inflictor->flags & MF_FIRE))
 				P_GivePlayerRings(target->player, 1);
+			if (inflictor->flags2 & MF2_BOUNCERING)
+				inflictor->fuse = 1;
 
 			return false;
 		}
@@ -2636,7 +2640,10 @@ void P_RemoveShield(player_t *player)
 		player->powers[pw_shield] = SH_NONE;
 		// Reset fireflower
 		if (!player->powers[pw_super])
+		{
 			player->mo->color = player->skincolor;
+			G_GhostAddColor(GHC_NORMAL);
+		}
 	}
 	else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_BOMB) // Give them what's coming to them!
 	{
diff --git a/src/p_mobj.c b/src/p_mobj.c
index dd45e3fe5..98e0c9f8f 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -6565,179 +6565,27 @@ void P_MobjThinker(mobj_t *mobj)
 					if ((mobj->flags & MF_AMBUSH || mobj->flags2 & MF2_STRONGBOX) && mobj->type != MT_QUESTIONBOX)
 					{
 						mobjtype_t spawnchance[64];
-						INT32 i = 0;
-						INT32 oldi = 0;
-						INT32 increment = 0;
-						INT32 numchoices = 0;
+						INT32 numchoices = 0, i = 0;
 
-						//if (cv_superring.value)
-						{
-							oldi = i;
+// This define should make it a lot easier to organize and change monitor weights
+#define SETMONITORCHANCES(type, strongboxamt, weakboxamt) \
+for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) spawnchance[numchoices++] = type
 
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 0;
-							else //weak box
-								increment = 0;
+						//                Type            SRM WRM
+						SETMONITORCHANCES(MT_SNEAKERTV,     0, 10); // Super Sneakers
+						SETMONITORCHANCES(MT_INV,           2,  0); // Invincibility
+						SETMONITORCHANCES(MT_WHITETV,       3,  8); // Whirlwind Shield
+						SETMONITORCHANCES(MT_GREENTV,       3,  8); // Elemental Shield
+						SETMONITORCHANCES(MT_YELLOWTV,      2,  0); // Attraction Shield
+						SETMONITORCHANCES(MT_BLUETV,        3,  3); // Force Shield
+						SETMONITORCHANCES(MT_BLACKTV,       2,  0); // Armageddon Shield
+						SETMONITORCHANCES(MT_MIXUPBOX,      0,  1); // Teleporters
+						SETMONITORCHANCES(MT_RECYCLETV,     0,  1); // Recycler
+						SETMONITORCHANCES(MT_PRUP,          1,  1); // 1-Up
+						// ======================================
+						//                Total            16  32
 
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_SUPERRINGBOX;
-								numchoices++;
-							}
-						}
-						//if (cv_supersneakers.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 0;
-							else //weak box
-								increment = 10;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_SNEAKERTV;
-								numchoices++;
-							}
-						}
-						//if (cv_invincibility.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 4;
-							else //weak box
-								increment = 0;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_INV;
-								numchoices++;
-							}
-						}
-						//if (cv_jumpshield.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 6;
-							else //weak box
-								increment = 8;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_WHITETV;
-								numchoices++;
-							}
-						}
-						//if (cv_watershield.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 6;
-							else //weak box
-								increment = 8;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_GREENTV;
-								numchoices++;
-							}
-						}
-						//if (cv_ringshield.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 4;
-							else //weak box
-								increment = 0;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_YELLOWTV;
-								numchoices++;
-							}
-						}
-						//if (cv_forceshield.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 6;
-							else //weak box
-								increment = 3;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_BLUETV;
-								numchoices++;
-							}
-						}
-						//if (cv_bombshield.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 4;
-							else //weak box
-								increment = 0;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_BLACKTV;
-								numchoices++;
-							}
-						}
-
-						//if (cv_teleporters.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 0;
-							else //weak box
-								increment = 1;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_MIXUPBOX;
-								numchoices++;
-							}
-						}
-
-						//if (cv_recycler.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 0;
-							else //weak box
-								increment = 1;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_RECYCLETV;
-								numchoices++;
-							}
-						}
-
-						//if (cv_1up.value)
-						{
-							oldi = i;
-
-							if (mobj->flags2 & MF2_STRONGBOX) //strong box
-								increment = 2;
-							else //weak box
-								increment = 1;
-
-							for (; i < oldi + increment; i++)
-							{
-								spawnchance[i] = MT_PRUP;
-								numchoices++;
-							}
-						}
+#undef SETMONITORCHANCES
 
 						i = P_RandomKey(numchoices); // Gotta love those random numbers!
 						newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, spawnchance[i]);
@@ -8342,9 +8190,14 @@ void P_SpawnMapThing(mapthing_t *mthing)
 		}
 	}
 
-	if (ultimatemode && !G_IsSpecialStage(gamemap)
-	    && (i == MT_SUPERRINGBOX || i == MT_GREENTV || i == MT_YELLOWTV || i == MT_BLUETV || i == MT_BLACKTV || i == MT_WHITETV))
-		return; // No rings/shields in Ultimate mode
+	if (ultimatemode)
+	{
+		if (i == MT_PITYTV || i == MT_GREENTV || i == MT_YELLOWTV || i == MT_BLUETV || i == MT_BLACKTV || i == MT_WHITETV)
+			return; // No shields in Ultimate mode
+
+		if (i == MT_SUPERRINGBOX && !G_IsSpecialStage(gamemap))
+			return; // No rings in Ultimate mode (except special stages)
+	}
 
 	if (i == MT_EMMY && (gametype != GT_COOP || ultimatemode || tokenbits == 30 || tokenlist & (1 << tokenbits++)))
 		return; // you already got this token, or there are too many, or the gametype's not right
@@ -8853,27 +8706,36 @@ ML_NOCLIMB : Direction not controllable
 				mobj->flags2 |= MF2_STANDONME;
 			}
 
-			if (mthing->type != mobjinfo[MT_AXIS].doomednum &&
+			if (mobj->flags & MF_MONITOR)
+			{
+				// flag for strong/weak random boxes
+				if (mthing->type == mobjinfo[MT_SUPERRINGBOX].doomednum || mthing->type == mobjinfo[MT_PRUP].doomednum ||
+					mthing->type == mobjinfo[MT_SNEAKERTV].doomednum || mthing->type == mobjinfo[MT_INV].doomednum ||
+					mthing->type == mobjinfo[MT_WHITETV].doomednum || mthing->type == mobjinfo[MT_GREENTV].doomednum ||
+					mthing->type == mobjinfo[MT_YELLOWTV].doomednum || mthing->type == mobjinfo[MT_BLUETV].doomednum ||
+					mthing->type == mobjinfo[MT_BLACKTV].doomednum || mthing->type == mobjinfo[MT_PITYTV].doomednum ||
+					mthing->type == mobjinfo[MT_RECYCLETV].doomednum || mthing->type == mobjinfo[MT_MIXUPBOX].doomednum)
+						mobj->flags |= MF_AMBUSH;
+			}
+
+			else if (mthing->type != mobjinfo[MT_AXIS].doomednum &&
 				mthing->type != mobjinfo[MT_AXISTRANSFER].doomednum &&
 				mthing->type != mobjinfo[MT_AXISTRANSFERLINE].doomednum &&
 				mthing->type != mobjinfo[MT_NIGHTSBUMPER].doomednum &&
-				mthing->type != mobjinfo[MT_STARPOST].doomednum &&
-				mthing->type != mobjinfo[MT_GRAVITYBOX].doomednum &&
-				mthing->type != mobjinfo[MT_EGGMANBOX].doomednum)
+				mthing->type != mobjinfo[MT_STARPOST].doomednum)
 				mobj->flags |= MF_AMBUSH;
 		}
 
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
 			// flag for strong/weak random boxes
-			if (mthing->type == mobjinfo[MT_QUESTIONBOX].doomednum || mthing->type == mobjinfo[MT_SUPERRINGBOX].doomednum ||
+			if (mthing->type == mobjinfo[MT_SUPERRINGBOX].doomednum || mthing->type == mobjinfo[MT_PRUP].doomednum ||
 				mthing->type == mobjinfo[MT_SNEAKERTV].doomednum || mthing->type == mobjinfo[MT_INV].doomednum ||
 				mthing->type == mobjinfo[MT_WHITETV].doomednum || mthing->type == mobjinfo[MT_GREENTV].doomednum ||
 				mthing->type == mobjinfo[MT_YELLOWTV].doomednum || mthing->type == mobjinfo[MT_BLUETV].doomednum ||
-				mthing->type == mobjinfo[MT_RECYCLETV].doomednum ||
-				mthing->type == mobjinfo[MT_BLACKTV].doomednum || mthing->type == mobjinfo[MT_MIXUPBOX].doomednum ||
-				mthing->type == mobjinfo[MT_PRUP].doomednum)
-				mobj->flags2 |= MF2_STRONGBOX;
+				mthing->type == mobjinfo[MT_BLACKTV].doomednum || mthing->type == mobjinfo[MT_PITYTV].doomednum ||
+				mthing->type == mobjinfo[MT_RECYCLETV].doomednum || mthing->type == mobjinfo[MT_MIXUPBOX].doomednum)
+					mobj->flags2 |= MF2_STRONGBOX;
 
 			// Requires you to be in bonus time to activate
 			if (mobj->flags & MF_NIGHTSITEM)
diff --git a/src/p_saveg.c b/src/p_saveg.c
index baa042112..462e68afe 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1336,7 +1336,7 @@ static void P_NetArchiveThinkers(void)
 
 				if ((mobj->x != mobj->spawnpoint->x << FRACBITS) ||
 					(mobj->y != mobj->spawnpoint->y << FRACBITS) ||
-					(mobj->angle != (angle_t)(ANGLE_45 * (mobj->spawnpoint->angle/45))))
+					(mobj->angle != FixedAngle(mobj->spawnpoint->angle*FRACUNIT)))
 					diff |= MD_POS;
 
 				if (mobj->info->doomednum != mobj->spawnpoint->type)
diff --git a/src/p_user.c b/src/p_user.c
index 322f2cd1e..8b793e810 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1135,8 +1135,16 @@ void P_RestoreMusic(player_t *player)
 		S_ChangeMusic(mus_supers, true);
 	else if (player->powers[pw_invulnerability] > 1)
 		S_ChangeMusic((mariomode) ? mus_minvnc : mus_invinc, false);
-	else if (player->powers[pw_sneakers] > 1)
-		S_ChangeMusic(mus_shoes, true);
+	else if (player->powers[pw_sneakers] > 1 && !player->powers[pw_super])
+	{
+		if (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC)
+		{
+			S_SpeedMusic(1.4f);
+			S_ChangeMusic(mapmusic, true);
+		}
+		else
+			S_ChangeMusic(mus_shoes, true);
+	}
 	else
 		S_ChangeMusic(mapmusic, true);
 }
@@ -1537,8 +1545,6 @@ static void P_SpawnSpinMobj(player_t *player, mobjtype_t type)
 	}
 
 	P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do
-	if (demorecording)
-		G_GhostAddSpin();
 }
 
 //
@@ -3207,10 +3213,18 @@ static void P_DoSuperStuff(player_t *player)
 			player->mo->health--;
 		}
 
-		if (player->skin == 2) // Pink superknux.
+		switch (player->skin)
+		{
+		case 1: // Golden orange supertails.
+			player->mo->color = SKINCOLOR_TSUPER1 + (leveltime/2) % 5;
+			break;
+		case 2: // Pink superknux.
 			player->mo->color = SKINCOLOR_KSUPER1 + (leveltime/2) % 5;
-		else // Yousa yellow now!
+			break;
+		default: // Yousa yellow now!
 			player->mo->color = SKINCOLOR_SUPER1 + (leveltime/2) % 5;
+			break;
+		}
 		G_GhostAddColor(GHC_SUPER);
 
 		// Ran out of rings while super!
@@ -3485,6 +3499,8 @@ static void P_DoSpinDash(player_t *player, ticcmd_t *cmd)
 
 				// Now spawn the color thok circle.
 				P_SpawnSpinMobj(player, player->revitem);
+				if (demorecording)
+					G_GhostAddRev();
 			}
 		}
 		// If not moving up or down, and travelling faster than a speed of four while not holding
@@ -4578,12 +4594,9 @@ static void P_3dMovement(player_t *player)
 			{
 				movepushside >>= 2;
 
-				//Lower speed if over "max" flight speed and greatly reduce movepushslide.
+				// Reduce movepushslide even more if over "max" flight speed
 				if (player->powers[pw_tailsfly] && player->speed > topspeed)
-				{
-					player->speed = topspeed - 1;
-					movepushside /= 4;
-				}
+					movepushside >>= 2;
 			}
 
 			// Allow a bit of movement while spinning
@@ -6525,6 +6538,10 @@ static void P_MovePlayer(player_t *player)
 	if (player->pflags & PF_GLIDING)
 	{
 		fixed_t leeway;
+		fixed_t glidespeed = player->actionspd;
+
+		if (player->powers[pw_super])
+			glidespeed *= 2;
 
 		if (player->mo->eflags & MFE_VERTICALFLIP)
 		{
@@ -6542,7 +6559,7 @@ static void P_MovePlayer(player_t *player)
 
 		if (player->skidtime) // ground gliding
 		{
-			fixed_t speed = FixedMul(player->actionspd, FRACUNIT - (FRACUNIT>>2));
+			fixed_t speed = FixedMul(glidespeed, FRACUNIT - (FRACUNIT>>2));
 			if (player->mo->eflags & MFE_UNDERWATER)
 				speed >>= 1;
 			speed = FixedMul(speed - player->glidetime*FRACUNIT, player->mo->scale);
@@ -6551,9 +6568,9 @@ static void P_MovePlayer(player_t *player)
 			P_InstaThrust(player->mo, player->mo->angle-leeway, speed);
 		}
 		else if (player->mo->eflags & MFE_UNDERWATER)
-			P_InstaThrust(player->mo, player->mo->angle-leeway, FixedMul((player->actionspd>>1) + player->glidetime*750, player->mo->scale));
+			P_InstaThrust(player->mo, player->mo->angle-leeway, FixedMul((glidespeed>>1) + player->glidetime*750, player->mo->scale));
 		else
-			P_InstaThrust(player->mo, player->mo->angle-leeway, FixedMul(player->actionspd + player->glidetime*1500, player->mo->scale));
+			P_InstaThrust(player->mo, player->mo->angle-leeway, FixedMul(glidespeed + player->glidetime*1500, player->mo->scale));
 
 		player->glidetime++;
 
@@ -6568,6 +6585,7 @@ static void P_MovePlayer(player_t *player)
 			}
 			else
 			{
+				player->pflags |= PF_THOKKED;
 				player->mo->momx >>= 1;
 				player->mo->momy >>= 1;
 				P_SetPlayerMobjState(player->mo, S_PLAY_FALL1);
@@ -6744,7 +6762,11 @@ static void P_MovePlayer(player_t *player)
 
 	// Show the "THOK!" graphic when spinning quickly across the ground. (even applies to non-spinners, in the case of zoom tubes)
 	if (player->pflags & PF_SPINNING && player->speed > FixedMul(15<<FRACBITS, player->mo->scale) && !(player->pflags & PF_JUMPED))
+	{
 		P_SpawnSpinMobj(player, player->spinitem);
+		if (demorecording)
+			G_GhostAddSpin();
+	}
 
 
 	////////////////////////////
@@ -7481,11 +7503,8 @@ boolean P_LookForEnemies(player_t *player)
 			continue; // not a mobj thinker
 
 		mo = (mobj_t *)think;
-		if (!(mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->flags & MF_MONITOR
-			|| mo->flags & MF_SPRING))
-		{
+		if (!(mo->flags & (MF_ENEMY|MF_BOSS|MF_MONITOR|MF_SPRING)))
 			continue; // not a valid enemy
-		}
 
 		if (mo->health <= 0) // dead
 			continue;
@@ -7496,14 +7515,14 @@ boolean P_LookForEnemies(player_t *player)
 		if (mo->flags2 & MF2_FRET)
 			continue;
 
-		if ((mo->flags & MF_ENEMY || mo->flags & MF_BOSS) && !(mo->flags & MF_SHOOTABLE)) // don't aim at something you can't shoot at anyway (see Egg Guard or Minus)
+		if ((mo->flags & (MF_ENEMY|MF_BOSS)) && !(mo->flags & MF_SHOOTABLE)) // don't aim at something you can't shoot at anyway (see Egg Guard or Minus)
 			continue;
 
 		if (mo->type == MT_DETON) // Don't be STUPID, Sonic!
 			continue;
 
 		if (((mo->z > player->mo->z+FixedMul(MAXSTEPMOVE, player->mo->scale)) && !(player->mo->eflags & MFE_VERTICALFLIP))
-			|| ((mo->z+mo->height < player->mo->z+player->mo->height-FixedMul(MAXSTEPMOVE, player->mo->scale)) && (player->mo->eflags & MFE_VERTICALFLIP))) // Reverse gravity check - Flame.
+		|| ((mo->z+mo->height < player->mo->z+player->mo->height-FixedMul(MAXSTEPMOVE, player->mo->scale)) && (player->mo->eflags & MFE_VERTICALFLIP))) // Reverse gravity check - Flame.
 			continue; // Don't home upwards!
 
 		if (P_AproxDistance(P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y),
@@ -7572,7 +7591,7 @@ void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 	if (dist < 1)
 		dist = 1;
 
-	if (source->type == MT_DETON && enemy->player) // For Deton Chase
+	if (source->type == MT_DETON && enemy->player) // For Deton Chase (Unused)
 	{
 		fixed_t ns = FixedDiv(FixedMul(enemy->player->normalspeed, enemy->scale), FixedDiv(20*FRACUNIT,17*FRACUNIT));
 		source->momx = FixedMul(FixedDiv(enemy->x - source->x, dist), ns);
diff --git a/src/r_draw.c b/src/r_draw.c
index 11dffb6bd..cd219c15f 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -376,10 +376,10 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 	// Super colors, from lightest to darkest!
 	case SKINCOLOR_SUPER1:
 		// Super White
-		for (i = 0; i < 14; i++)
+		for (i = 0; i < 10; i++)
 			dest_colormap[starttranscolor + i] = 120; // True white
-		for (; i < SKIN_RAMP_LENGTH; i++)
-			dest_colormap[starttranscolor + i] = 112; // Golden shine
+		for (; i < SKIN_RAMP_LENGTH; i++) // White-yellow fade
+			dest_colormap[starttranscolor + i] = (UINT8)(96 + (i-10));
 		break;
 
 	case SKINCOLOR_SUPER2:
@@ -422,6 +422,43 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 		dest_colormap[starttranscolor + 15] = 155;
 		break;
 
+	// Super Tails
+	case SKINCOLOR_TSUPER1:
+		for (i = 0; i < 10; i++) // white
+			dest_colormap[starttranscolor + i] = 120;
+		for (; i < SKIN_RAMP_LENGTH; i++) // orange
+			dest_colormap[starttranscolor + i] = (UINT8)(80 + (i-10));
+		break;
+
+	case SKINCOLOR_TSUPER2:
+		for (i = 0; i < 4; i++) // white
+			dest_colormap[starttranscolor + i] = 120;
+		for (; i < SKIN_RAMP_LENGTH; i++) // orange
+			dest_colormap[starttranscolor + i] = (UINT8)(80 + ((i-4)>>1));
+		break;
+
+	case SKINCOLOR_TSUPER3:
+		dest_colormap[starttranscolor] = 120; // pure white
+		dest_colormap[starttranscolor+1] = 120;
+		for (i = 2; i < SKIN_RAMP_LENGTH; i++) // orange
+			dest_colormap[starttranscolor + i] = (UINT8)(80 + ((i-2)>>1));
+		break;
+
+	case SKINCOLOR_TSUPER4:
+		dest_colormap[starttranscolor] = 120; // pure white
+		for (i = 1; i < 9; i++) // orange
+			dest_colormap[starttranscolor + i] = (UINT8)(80 + (i-1));
+		for (; i < SKIN_RAMP_LENGTH; i++) // gold
+			dest_colormap[starttranscolor + i] = (UINT8)(115 + (5*(i-9)/7));
+		break;
+
+	case SKINCOLOR_TSUPER5:
+		for (i = 0; i < 8; i++) // orange
+			dest_colormap[starttranscolor + i] = (UINT8)(80 + i);
+		for (; i < SKIN_RAMP_LENGTH; i++) // gold
+			dest_colormap[starttranscolor + i] = (UINT8)(115 + (5*(i-8)/8));
+		break;
+
 	// Super Knuckles
 	case SKINCOLOR_KSUPER1:
 		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
diff --git a/src/r_things.c b/src/r_things.c
index f440527c2..c0f33dafb 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -92,6 +92,8 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
                                 UINT8 rotation,
                                 UINT8 flipped)
 {
+	char cn = R_Frame2Char(frame); // for debugging
+
 	INT32 r;
 	lumpnum_t lumppat = wad;
 	lumppat <<= 16;
@@ -107,10 +109,10 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 	{
 		// the lump should be used for all rotations
 		if (sprtemp[frame].rotate == 0)
-			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has multiple rot = 0 lump\n", spritename, 'A'+frame);
+			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has multiple rot = 0 lump\n", spritename, cn);
 
 		if (sprtemp[frame].rotate == 1)
-			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has rotations and a rot = 0 lump\n", spritename, 'A'+frame);
+			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has rotations and a rot = 0 lump\n", spritename, cn);
 
 		sprtemp[frame].rotate = 0;
 		for (r = 0; r < 8; r++)
@@ -124,7 +126,7 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 
 	// the lump is only used for one rotation
 	if (sprtemp[frame].rotate == 0)
-		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has rotations and a rot = 0 lump\n", spritename, 'A'+frame);
+		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has rotations and a rot = 0 lump\n", spritename, cn);
 
 	sprtemp[frame].rotate = 1;
 
@@ -132,7 +134,7 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 	rotation--;
 
 	if (sprtemp[frame].lumppat[rotation] != LUMPERROR)
-		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s: %c:%c has two lumps mapped to it\n", spritename, 'A'+frame, '1'+rotation);
+		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s: %c%c has two lumps mapped to it\n", spritename, cn, '1'+rotation);
 
 	// lumppat & lumpid are the same for original Doom, but different
 	// when using sprites in pwad : the lumppat points the new graphics
@@ -189,7 +191,7 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 	{
 		if (memcmp(lumpinfo[l].name,sprname,4)==0)
 		{
-			frame = (UINT8)(lumpinfo[l].name[4] - 'A');
+			frame = R_Char2Frame(lumpinfo[l].name[4]);
 			rotation = (UINT8)(lumpinfo[l].name[5] - '0');
 
 			if (frame >= 64 || rotation > 8) // Give an actual NAME error -_-...
@@ -225,7 +227,7 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 
 			if (lumpinfo[l].name[6])
 			{
-				frame = (UINT8)(lumpinfo[l].name[6] - 'A');
+				frame = R_Char2Frame(lumpinfo[l].name[6]);
 				rotation = (UINT8)(lumpinfo[l].name[7] - '0');
 				R_InstallSpriteLump(wadnum, l, numspritelumps, frame, rotation, 1);
 			}
@@ -277,8 +279,7 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 		{
 			case 0xff:
 			// no rotations were found for that frame at all
-			I_Error("R_AddSingleSpriteDef: No patches found "
-			        "for %s frame %c", sprname, frame+'A');
+			I_Error("R_AddSingleSpriteDef: No patches found for %s frame %c", sprname, R_Frame2Char(frame));
 			break;
 
 			case 0:
@@ -291,9 +292,8 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 				// we test the patch lump, or the id lump whatever
 				// if it was not loaded the two are LUMPERROR
 				if (sprtemp[frame].lumppat[rotation] == LUMPERROR)
-					I_Error("R_AddSingleSpriteDef: Sprite %s frame %c "
-					        "is missing rotations",
-					        sprname, frame+'A');
+					I_Error("R_AddSingleSpriteDef: Sprite %s frame %c is missing rotations",
+					        sprname, R_Frame2Char(frame));
 			break;
 		}
 	}
diff --git a/src/r_things.h b/src/r_things.h
index 8c5c78192..226b8e476 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -191,4 +191,39 @@ void R_InitDrawNodes(void);
 
 char *GetPlayerFacePic(INT32 skinnum);
 
+// Functions to go from sprite character ID to frame number
+// for 2.1 compatibility this still uses the old 'A' + frame code
+// The use of symbols tends to be painful for wad editors though
+// So the future version of this tries to avoid using symbols
+// as much as possible while also defining all 64 slots in a sane manner
+// 2.1:    [[ ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~   ]]
+// Future: [[ ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz!@ ]]
+FUNCMATH FUNCINLINE static ATTRINLINE char R_Frame2Char(UINT8 frame)
+{
+#if 1 // 2.1 compat
+	return 'A' + frame;
+#else
+	if (frame < 26) return 'A' + frame;
+	if (frame < 36) return '0' + (frame - 26);
+	if (frame < 62) return 'a' + (frame - 36);
+	if (frame == 62) return '!';
+	if (frame == 63) return '@';
+	return '\xFF';
+#endif
+}
+
+FUNCMATH FUNCINLINE static ATTRINLINE UINT8 R_Char2Frame(char cn)
+{
+#if 1 // 2.1 compat
+	return cn - 'A';
+#else
+	if (cn >= 'A' && cn <= 'Z') return cn - 'A';
+	if (cn >= '0' && cn <= '9') return (cn - '0') + 26;
+	if (cn >= 'a' && cn <= 'z') return (cn - 'a') + 36;
+	if (cn == '!') return 62;
+	if (cn == '@') return 63;
+	return 255;
+#endif
+}
+
 #endif //__R_THINGS__
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 780101d19..4bba8eb52 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1833,11 +1833,12 @@ static void ST_overlayDrawer(void)
 		{
 			ST_drawFirstPersonHUD();
 		}
+	}
 
 #ifdef HAVE_BLUA
+	if (!(netgame || multiplayer) || !hu_showscores)
 		LUAh_GameHUD(stplyr);
 #endif
-	}
 
 #if 0 // Pope XVI
 	if (!(netgame || multiplayer) && !modifiedgame && gamemap == 11 && ALL7EMERALDS(emeralds)
diff --git a/src/y_inter.c b/src/y_inter.c
index cd2201de9..35ecf62aa 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -57,8 +57,8 @@ typedef union
 {
 	struct
 	{
-		char passed1[13]; // KNUCKLES GOT
-		char passed2[16]; // THROUGH THE ACT
+		char passed1[14]; // KNUCKLES GOT    / CRAWLA HONCHO
+		char passed2[16]; // THROUGH THE ACT / PASSED THE ACT
 		INT32 passedx1;
 		INT32 passedx2;
 
@@ -998,43 +998,40 @@ void Y_StartIntermission(void)
 			usetile = false;
 
 			// set up the "got through act" message according to skin name
-			if (strlen(skins[players[consoleplayer].skin].realname) <= 8)
+			// too long so just show "YOU GOT THROUGH THE ACT"
+			if (strlen(skins[players[consoleplayer].skin].realname) > 13)
 			{
-				snprintf(data.coop.passed1,
-					sizeof data.coop.passed1, "%s GOT",
+				strcpy(data.coop.passed1, "YOU GOT");
+				strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "THROUGH ACT" : "THROUGH THE ACT");
+			}
+			// long enough that "X GOT" won't fit so use "X PASSED THE ACT"
+			else if (strlen(skins[players[consoleplayer].skin].realname) > 8)
+			{
+				strcpy(data.coop.passed1, skins[players[consoleplayer].skin].realname);
+				strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "PASSED ACT" : "PASSED THE ACT");
+			}
+			// length is okay for normal use
+			else
+			{
+				snprintf(data.coop.passed1, sizeof data.coop.passed1, "%s GOT",
 					skins[players[consoleplayer].skin].realname);
-				data.coop.passed1[sizeof data.coop.passed1 - 1] = '\0';
-				if (mapheaderinfo[gamemap-1]->actnum)
-				{
-					strcpy(data.coop.passed2, "THROUGH ACT");
-					data.coop.passedx1 = 62 + (176 - V_LevelNameWidth(data.coop.passed1))/2;
-					data.coop.passedx2 = 62 + (176 - V_LevelNameWidth(data.coop.passed2))/2;
-				}
-				else
-				{
-					strcpy(data.coop.passed2, "THROUGH THE ACT");
-					data.coop.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed1))/2;
-					data.coop.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed2))/2;
-				}
-				// The above value is not precalculated because it needs only be computed once
-				// at the start of intermission, and precalculating it would preclude mods
-				// changing the font to one of a slightly different width.
+				strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "THROUGH ACT" : "THROUGH THE ACT");
+			}
+
+			// set X positions
+			if (mapheaderinfo[gamemap-1]->actnum)
+			{
+				data.coop.passedx1 = 62 + (176 - V_LevelNameWidth(data.coop.passed1))/2;
+				data.coop.passedx2 = 62 + (176 - V_LevelNameWidth(data.coop.passed2))/2;
 			}
 			else
 			{
-				strcpy(data.coop.passed1, skins[players[consoleplayer].skin].realname);
-				data.coop.passedx1 = 62 + (176 - V_LevelNameWidth(data.coop.passed1))/2;
-				if (mapheaderinfo[gamemap-1]->actnum)
-				{
-					strcpy(data.coop.passed2, "PASSED ACT");
-					data.coop.passedx2 = 62 + (176 - V_LevelNameWidth(data.coop.passed2))/2;
-				}
-				else
-				{
-					strcpy(data.coop.passed2, "PASSED THE ACT");
-					data.coop.passedx2 = 62 + (240 - V_LevelNameWidth(data.coop.passed2))/2;
-				}
+				data.coop.passedx1 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed1))/2;
+				data.coop.passedx2 = (BASEVIDWIDTH - V_LevelNameWidth(data.coop.passed2))/2;
 			}
+			// The above value is not precalculated because it needs only be computed once
+			// at the start of intermission, and precalculating it would preclude mods
+			// changing the font to one of a slightly different width.
 			break;
 		}