diff --git a/src/console.c b/src/console.c
index a504af06d7f4d28f88c0d1f1c40e806c9f5e2b2a..b785aa18a0b5a61d0574345c41b53f75fb5012c3 100644
--- a/src/console.c
+++ b/src/console.c
@@ -226,13 +226,9 @@ static void CONS_Bind_f(void)
 // Font colormap colors
 // TODO: This could probably be improved somehow...
 // These colormaps are 99% identical, with just a few changed bytes
-UINT8 *yellowmap;
-UINT8 *purplemap;
-UINT8 *lgreenmap;
-UINT8 *bluemap;
-UINT8 *graymap;
-UINT8 *redmap;
-UINT8 *orangemap;
+// This could EASILY be handled by modifying a centralised colormap
+// for software depending on the prior state - but yknow, OpenGL...
+UINT8 *yellowmap, *magentamap, *lgreenmap, *bluemap, *graymap, *redmap, *orangemap, *skymap, *purplemap, *aquamap, *peridotmap, *azuremap, *brownmap, *rosymap, *invertmap;
 
 // Console BG color
 UINT8 *consolebgmap = NULL;
@@ -280,45 +276,55 @@ static void CONS_backcolor_Change(void)
 static void CON_SetupColormaps(void)
 {
 	INT32 i;
-
-	yellowmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	graymap   = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	purplemap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	lgreenmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	bluemap   = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	redmap    = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
-	orangemap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
+	UINT8 *memorysrc = (UINT8 *)Z_Malloc((256*15), PU_STATIC, NULL);
+
+	magentamap = memorysrc;
+	yellowmap  = (magentamap+256);
+	lgreenmap  = (yellowmap+256);
+	bluemap    = (lgreenmap+256);
+	redmap     = (bluemap+256);
+	graymap    = (redmap+256);
+	orangemap  = (graymap+256);
+	skymap     = (orangemap+256);
+	purplemap  = (skymap+256);
+	aquamap    = (purplemap+256);
+	peridotmap = (aquamap+256);
+	azuremap   = (peridotmap+256);
+	brownmap   = (azuremap+256);
+	rosymap    = (brownmap+256);
+	invertmap  = (rosymap+256);
 
 	// setup the other colormaps, for console text
 
 	// these don't need to be aligned, unless you convert the
 	// V_DrawMappedPatch() into optimised asm.
 
-	for (i = 0; i < 256; i++)
-	{
-		yellowmap[i] = (UINT8)i; // remap each color to itself...
-		graymap[i] = (UINT8)i;
-		purplemap[i] = (UINT8)i;
-		lgreenmap[i] = (UINT8)i;
-		bluemap[i] = (UINT8)i;
-		redmap[i] = (UINT8)i;
-		orangemap[i] = (UINT8)i;
-	}
-
-	yellowmap[3] = (UINT8)73;
-	yellowmap[9] = (UINT8)66;
-	purplemap[3] = (UINT8)184;
-	purplemap[9] = (UINT8)186;
-	lgreenmap[3] = (UINT8)98;
-	lgreenmap[9] = (UINT8)106;
-	bluemap[3]   = (UINT8)147;
-	bluemap[9]   = (UINT8)158;
-	graymap[3]   = (UINT8)10;
-	graymap[9]   = (UINT8)15;
-	redmap[3]    = (UINT8)210;
-	redmap[9]    = (UINT8)32;
-	orangemap[3] = (UINT8)52;
-	orangemap[9] = (UINT8)57;
+	for (i = 0; i < (256*15); i++, ++memorysrc)
+		*memorysrc = (UINT8)(i & 0xFF); // remap each color to itself...
+
+#define colset(map, a, b, c) \
+	map[1] = (UINT8)a;\
+	map[3] = (UINT8)b;\
+	map[9] = (UINT8)c
+
+	colset(magentamap, 177, 178, 184);
+	colset(yellowmap,   82,  73,  66);
+	colset(lgreenmap,   97,  98, 106);
+	colset(bluemap,    146, 147, 155);
+	colset(redmap,     210,  32,  39);
+	colset(graymap,      8,  10,  15);
+	colset(orangemap,   51,  52,  57);
+	colset(skymap,     129, 130, 133);
+	colset(purplemap,  160, 161, 163);
+	colset(aquamap,    120, 121, 123);
+	colset(peridotmap,  88, 188, 190);
+	colset(azuremap,   144, 145, 170);
+	colset(brownmap,   219, 221, 224);
+	colset(rosymap,    200, 201, 203);
+	colset(invertmap,   27,  26,  22);
+	invertmap[26] = (UINT8)3;
+
+#undef colset
 
 	// Init back colormap
 	CON_SetupBackColormap();
@@ -839,8 +845,9 @@ boolean CON_Responder(event_t *ev)
 			return true;
 		}
 
-		// don't eat the key
-		return false;
+		// ...why shouldn't it eat the key? if it doesn't, it just means you
+		// can control Sonic from the console, which is silly
+		return true; //return false;
 	}
 
 	// command completion forward (tab) and backward (shift-tab)
@@ -1039,7 +1046,7 @@ boolean CON_Responder(event_t *ev)
 
 	// enter a char into the command prompt
 	if (key < 32 || key > 127)
-		return false;
+		return true; // even if key can't be printed, eat it anyway
 
 	// add key to cmd line here
 	if (key >= 'A' && key <= 'Z' && !shiftdown) //this is only really necessary for dedicated servers
diff --git a/src/console.h b/src/console.h
index 1e510e89a204c2774edf779fa77efe770638183d..970f841d0e8a950cc00d91fdd5d9517f13867c98 100644
--- a/src/console.h
+++ b/src/console.h
@@ -34,7 +34,7 @@ extern UINT32 con_scalefactor; // console text scale factor
 
 extern consvar_t cons_backcolor;
 
-extern UINT8 *yellowmap, *purplemap, *lgreenmap, *bluemap, *graymap, *redmap, *orangemap;
+extern UINT8 *yellowmap, *magentamap, *lgreenmap, *bluemap, *graymap, *redmap, *orangemap, *skymap, *purplemap, *aquamap, *peridotmap, *azuremap, *brownmap, *rosymap, *invertmap;
 
 // Console bg color (auto updated to match)
 extern UINT8 *consolebgmap;
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index bb80e88ef2cfeec5c8f9f49d66b8202b3422a845..834b0a327d6533fb21c6d0210ef98e3695d34801 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1125,7 +1125,7 @@ static inline void CL_DrawConnectionStatus(void)
 	INT32 ccstime = I_GetTime();
 
 	// Draw background fade
-	V_DrawFadeScreen();
+	V_DrawFadeScreen(0xFF00, 16);
 
 	// Draw the bottom box.
 	M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1);
@@ -1173,22 +1173,37 @@ static inline void CL_DrawConnectionStatus(void)
 		if (lastfilenum != -1)
 		{
 			INT32 dldlength;
-			static char tempname[32];
+			static char tempname[28];
+			fileneeded_t *file = &fileneeded[lastfilenum];
+			char *filename = file->filename;
 
 			Net_GetNetStat();
-			dldlength = (INT32)((fileneeded[lastfilenum].currentsize/(double)fileneeded[lastfilenum].totalsize) * 256);
+			dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256);
 			if (dldlength > 256)
 				dldlength = 256;
 			V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 111);
 			V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, dldlength, 8, 96);
 
 			memset(tempname, 0, sizeof(tempname));
-			nameonly(strncpy(tempname, fileneeded[lastfilenum].filename, 31));
+			// offset filename to just the name only part
+			filename += strlen(filename) - nameonlylength(filename);
+
+			if (strlen(filename) > sizeof(tempname)-1) // too long to display fully
+			{
+				size_t endhalfpos = strlen(filename)-10;
+				// display as first 14 chars + ... + last 10 chars
+				// which should add up to 27 if our math(s) is correct
+				snprintf(tempname, sizeof(tempname), "%.14s...%.10s", filename, filename+endhalfpos);
+			}
+			else // we can copy the whole thing in safely
+			{
+				strncpy(tempname, filename, sizeof(tempname)-1);
+			}
 
 			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
 				va(M_GetText("Downloading \"%s\""), tempname));
 			V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
-				va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,fileneeded[lastfilenum].totalsize>>10));
+				va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,file->totalsize>>10));
 			V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
 				va("%3.1fK/s ", ((double)getbps)/1024));
 		}
@@ -2239,7 +2254,7 @@ static void Command_connect(void)
 	// Assume we connect directly.
 	boolean viams = false;
 
-	if (COM_Argc() < 2)
+	if (COM_Argc() < 2 || *COM_Argv(1) == 0)
 	{
 		CONS_Printf(M_GetText(
 			"Connect <serveraddress> (port): connect to a server\n"
diff --git a/src/d_main.c b/src/d_main.c
index 05aa3e67570bdbb8ad14f368035c42437049c74d..a5e1a025424dd43ade761e52627e21ea382a84aa 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -734,11 +734,6 @@ void D_StartTitle(void)
 	CON_ToggleOff();
 
 	// Reset the palette
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		HWR_SetPaletteColor(0);
-	else
-#endif
 	if (rendermode != render_none)
 		V_SetPaletteLump("PLAYPAL");
 }
@@ -1049,7 +1044,7 @@ void D_SRB2Main(void)
 
 	// add any files specified on the command line with -file wadfile
 	// to the wad list
-	if (!(M_CheckParm("-connect")))
+	if (!(M_CheckParm("-connect") && !M_CheckParm("-server")))
 	{
 		if (M_CheckParm("-file"))
 		{
@@ -1204,7 +1199,15 @@ void D_SRB2Main(void)
 	R_Init();
 
 	// setting up sound
-	CONS_Printf("S_Init(): Setting up sound.\n");
+	if (dedicated)
+	{
+		nosound = true;
+		nomidimusic = nodigimusic = true;
+	}
+	else
+	{
+		CONS_Printf("S_Init(): Setting up sound.\n");
+	}
 	if (M_CheckParm("-nosound"))
 		nosound = true;
 	if (M_CheckParm("-nomusic")) // combines -nomidimusic and -nodigmusic
@@ -1316,7 +1319,7 @@ void D_SRB2Main(void)
 		}
 	}
 
-	if (autostart || netgame || M_CheckParm("+connect") || M_CheckParm("-connect"))
+	if (autostart || netgame)
 	{
 		gameaction = ga_nothing;
 
@@ -1350,8 +1353,7 @@ void D_SRB2Main(void)
 			}
 		}
 
-		if (server && !M_CheckParm("+map") && !M_CheckParm("+connect")
-			&& !M_CheckParm("-connect"))
+		if (server && !M_CheckParm("+map"))
 		{
 			// Prevent warping to nonexistent levels
 			if (W_CheckNumForName(G_BuildMapName(pstartmap)) == LUMPERROR)
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 94eada15217c652984bd3ee7453e096228ee786e..e0117815577b26a28b7df2d511823f181a6c481a 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -309,9 +309,12 @@ consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL,
 
 consvar_t cv_rollingdemos = {"rollingdemos", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-static CV_PossibleValue_t timetic_cons_t[] = {{0, "Normal"}, {1, "Tics"}, {2, "Centiseconds"}, {3, "Mania"}, {0, NULL}};
+static CV_PossibleValue_t timetic_cons_t[] = {{0, "Normal"}, {1, "Centiseconds"}, {2, "Mania"}, {3, "Tics"}, {0, NULL}};
 consvar_t cv_timetic = {"timerres", "Normal", CV_SAVE, timetic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; // use tics in display
 
+static CV_PossibleValue_t powerupdisplay_cons_t[] = {{0, "Never"}, {1, "First-person only"}, {2, "Always"}, {0, NULL}};
+consvar_t cv_powerupdisplay = {"powerupdisplay", "First-person only", CV_SAVE, powerupdisplay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 consvar_t cv_resetmusic = {"resetmusic", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t pointlimit_cons_t[] = {{0, "MIN"}, {999999990, "MAX"}, {0, NULL}};
@@ -670,6 +673,7 @@ void D_RegisterClientCommands(void)
 
 	// HUD
 	CV_RegisterVar(&cv_timetic);
+	CV_RegisterVar(&cv_powerupdisplay);
 	CV_RegisterVar(&cv_itemfinder);
 	CV_RegisterVar(&cv_showinputjoy);
 
diff --git a/src/d_netfil.c b/src/d_netfil.c
index 614d2064e374ddd7aff45703b6309617de8c1f3e..889de9466760b858e2129d934375cb2ca713de77 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -950,15 +950,37 @@ filestatus_t checkfilemd5(char *filename, const UINT8 *wantedmd5sum)
 	return FS_FOUND; // will never happen, but makes the compiler shut up
 }
 
+// Rewritten by Monster Iestyn to be less stupid
+// Note: if completepath is true, "filename" is modified, but only if FS_FOUND is going to be returned
+// (Don't worry about WinCE's version of filesearch, nobody cares about that OS anymore)
 filestatus_t findfile(char *filename, const UINT8 *wantedmd5sum, boolean completepath)
 {
-	filestatus_t homecheck = filesearch(filename, srb2home, wantedmd5sum, false, 10);
-	if (homecheck == FS_FOUND)
-		return filesearch(filename, srb2home, wantedmd5sum, completepath, 10);
+	filestatus_t homecheck; // store result of last file search
+	boolean badmd5 = false; // store whether md5 was bad from either of the first two searches (if nothing was found in the third)
 
-	homecheck = filesearch(filename, srb2path, wantedmd5sum, false, 10);
-	if (homecheck == FS_FOUND)
-		return filesearch(filename, srb2path, wantedmd5sum, completepath, 10);
+	// first, check SRB2's "home" directory
+	homecheck = filesearch(filename, srb2home, wantedmd5sum, completepath, 10);
 
-	return filesearch(filename, ".", wantedmd5sum, completepath, 10);
+	if (homecheck == FS_FOUND) // we found the file, so return that we have :)
+		return FS_FOUND;
+	else if (homecheck == FS_MD5SUMBAD) // file has a bad md5; move on and look for a file with the right md5
+		badmd5 = true;
+	// if not found at all, just move on without doing anything
+
+	// next, check SRB2's "path" directory
+	homecheck = filesearch(filename, srb2path, wantedmd5sum, completepath, 10);
+
+	if (homecheck == FS_FOUND) // we found the file, so return that we have :)
+		return FS_FOUND;
+	else if (homecheck == FS_MD5SUMBAD) // file has a bad md5; move on and look for a file with the right md5
+		badmd5 = true;
+	// if not found at all, just move on without doing anything
+
+	// finally check "." directory
+	homecheck = filesearch(filename, ".", wantedmd5sum, completepath, 10);
+
+	if (homecheck != FS_NOTFOUND) // if not found this time, fall back on the below return statement
+		return homecheck; // otherwise return the result we got
+
+	return (badmd5 ? FS_MD5SUMBAD : FS_NOTFOUND); // md5 sum bad or file not found
 }
diff --git a/src/dehacked.c b/src/dehacked.c
index cb76c663e74b947cfed0635bcdcf6f69f4a97ae4..d546a8538d126458db24449c4342b157789ecd31 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1584,6 +1584,10 @@ static void readhuditem(MYFILE *f, INT32 num)
 			{
 				hudinfo[num].y = i;
 			}
+			else if (fastcmp(word, "F"))
+			{
+				hudinfo[num].f = i;
+			}
 			else
 				deh_warning("Level header %d: unknown word '%s'", num, word);
 		}
@@ -1819,7 +1823,6 @@ static void readframe(MYFILE *f, INT32 num)
 	char *word1;
 	char *word2 = NULL;
 	char *tmp;
-	INT32 j;
 
 	do
 	{
@@ -1834,16 +1837,6 @@ static void readframe(MYFILE *f, INT32 num)
 			if (s == tmp)
 				continue; // Skip comment lines, but don't break.
 
-			for (j = 0; s[j] != '\n'; j++)
-			{
-				if (s[j] == '=')
-				{
-					j += 2;
-					j = atoi(&s[j]);
-					break;
-				}
-			}
-
 			word1 = strtok(s, " ");
 			if (word1)
 				strupr(word1);
@@ -4528,6 +4521,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BOXSPARKLE1",
 	"S_BOXSPARKLE2",
 	"S_BOXSPARKLE3",
+	"S_BOXSPARKLE4",
 
 	"S_BOX_FLICKER",
 	"S_BOX_POP1",
@@ -5021,6 +5015,22 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ARMF14",
 	"S_ARMF15",
 	"S_ARMF16",
+	"S_ARMF17",
+	"S_ARMF18",
+	"S_ARMF19",
+	"S_ARMF20",
+	"S_ARMF21",
+	"S_ARMF22",
+	"S_ARMF23",
+	"S_ARMF24",
+	"S_ARMF25",
+	"S_ARMF26",
+	"S_ARMF27",
+	"S_ARMF28",
+	"S_ARMF29",
+	"S_ARMF30",
+	"S_ARMF31",
+	"S_ARMF32",
 
 	"S_ARMB1",
 	"S_ARMB2",
@@ -5038,6 +5048,22 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ARMB14",
 	"S_ARMB15",
 	"S_ARMB16",
+	"S_ARMB17",
+	"S_ARMB18",
+	"S_ARMB19",
+	"S_ARMB20",
+	"S_ARMB21",
+	"S_ARMB22",
+	"S_ARMB23",
+	"S_ARMB24",
+	"S_ARMB25",
+	"S_ARMB26",
+	"S_ARMB27",
+	"S_ARMB28",
+	"S_ARMB29",
+	"S_ARMB30",
+	"S_ARMB31",
+	"S_ARMB32",
 
 	"S_WIND1",
 	"S_WIND2",
@@ -5518,8 +5544,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Got Flag Sign
 	"S_GOTFLAG",
-	"S_GOTREDFLAG",
-	"S_GOTBLUEFLAG",
 
 	"S_CORK",
 
@@ -5859,6 +5883,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_XPLD_FLICKY",
 	"S_XPLD1",
 	"S_XPLD2",
+	"S_XPLD3",
+	"S_XPLD4",
+	"S_XPLD5",
+	"S_XPLD6",
 	"S_XPLD_EGGTRAP",
 
 	// Underwater Explosion
@@ -6526,7 +6554,6 @@ static const char *const MOBJFLAG2_LIST[] = {
 	"AMBUSH",         // Alternate behaviour typically set by MTF_AMBUSH
 	"LINKDRAW",       // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	"SHIELD",         // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
-	"MACEROTATE",     // Thinker calls P_MaceRotate around tracer
 	NULL
 };
 
@@ -6799,32 +6826,22 @@ static const char *const POWERS_LIST[] = {
 };
 
 static const char *const HUDITEMS_LIST[] = {
-	"LIVESNAME",
-	"LIVESPIC",
-	"LIVESNUM",
-	"LIVESX",
+	"LIVES",
 
 	"RINGS",
-	"RINGSSPLIT",
 	"RINGSNUM",
-	"RINGSNUMSPLIT",
 
 	"SCORE",
 	"SCORENUM",
 
 	"TIME",
-	"TIMESPLIT",
 	"MINUTES",
-	"MINUTESSPLIT",
 	"TIMECOLON",
-	"TIMECOLONSPLIT",
 	"SECONDS",
-	"SECONDSSPLIT",
 	"TIMETICCOLON",
 	"TICS",
 
 	"SS_TOTALRINGS",
-	"SS_TOTALRINGS_SPLIT",
 
 	"GETRINGS",
 	"GETRINGSNUM",
@@ -6832,7 +6849,7 @@ static const char *const HUDITEMS_LIST[] = {
 	"TIMELEFTNUM",
 	"TIMEUP",
 	"HUNTPICS",
-	"GRAVBOOTSICO",
+	"POWERUPS",
 	"LAP"
 };
 
@@ -7321,13 +7338,21 @@ struct {
 	{"V_6WIDTHSPACE",V_6WIDTHSPACE},
 	{"V_OLDSPACING",V_OLDSPACING},
 	{"V_MONOSPACE",V_MONOSPACE},
-	{"V_PURPLEMAP",V_PURPLEMAP},
+	{"V_MAGENTAMAP",V_MAGENTAMAP},
 	{"V_YELLOWMAP",V_YELLOWMAP},
 	{"V_GREENMAP",V_GREENMAP},
 	{"V_BLUEMAP",V_BLUEMAP},
 	{"V_REDMAP",V_REDMAP},
 	{"V_GRAYMAP",V_GRAYMAP},
 	{"V_ORANGEMAP",V_ORANGEMAP},
+	{"V_SKYMAP",V_SKYMAP},
+	{"V_PURPLEMAP",V_PURPLEMAP},
+	{"V_AQUAMAP",V_AQUAMAP},
+	{"V_PERIDOTMAP",V_PERIDOTMAP},
+	{"V_AZUREMAP",V_AZUREMAP},
+	{"V_BROWNMAP",V_BROWNMAP},
+	{"V_ROSYMAP",V_ROSYMAP},
+	{"V_INVERTMAP",V_INVERTMAP},
 	{"V_TRANSLUCENT",V_TRANSLUCENT},
 	{"V_10TRANS",V_10TRANS},
 	{"V_20TRANS",V_20TRANS},
@@ -7353,7 +7378,7 @@ struct {
 	{"V_WRAPX",V_WRAPX},
 	{"V_WRAPY",V_WRAPY},
 	{"V_NOSCALESTART",V_NOSCALESTART},
-	{"V_SPLITSCREEN",V_SPLITSCREEN},
+	{"V_PERPLAYER",V_PERPLAYER},
 
 	{"V_PARAMMASK",V_PARAMMASK},
 	{"V_SCALEPATCHMASK",V_SCALEPATCHMASK},
@@ -7503,7 +7528,7 @@ static hudnum_t get_huditem(const char *word)
 		if (fastcmp(word, HUDITEMS_LIST[i]))
 			return i;
 	deh_warning("Couldn't find huditem named 'HUD_%s'",word);
-	return HUD_LIVESNAME;
+	return HUD_LIVES;
 }
 
 #ifndef HAVE_BLUA
diff --git a/src/doomdef.h b/src/doomdef.h
index 04628c14caa245eb282480bce0222b65c65c9ed4..32771163e2fc1e7c336e6ee48e45bf0e7f802152 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -560,9 +560,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// Experimental attempts at preventing MF_PAPERCOLLISION objects from getting stuck in walls.
 //#define PAPER_COLLISIONCORRECTION
 
-/// Hudname padding.
-#define SKINNAMEPADDING
-
 /// FINALLY some real clipping that doesn't make walls dissappear AND speeds the game up
 /// (that was the original comment from SRB2CB, sadly it is a lie and actually slows game down)
 /// on the bright side it fixes some weird issues with translucent walls
diff --git a/src/doomstat.h b/src/doomstat.h
index de260ef065678933e4b6975ff6003fc8489e3963..d4735f6b218ecf64f2b4db2caec8b1e937d0fc86 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -498,6 +498,7 @@ extern boolean singletics;
 #include "d_clisrv.h"
 
 extern consvar_t cv_timetic; // display high resolution timer
+extern consvar_t cv_powerupdisplay; // display powerups
 extern consvar_t cv_showinputjoy; // display joystick in time attack
 extern consvar_t cv_forceskin; // force clients to use the server's skin
 extern consvar_t cv_downloading; // allow clients to downloading WADs.
diff --git a/src/f_finale.c b/src/f_finale.c
index 05c3d5fc0cf6edbca1a4768ecd09e40d0c47219b..db62ddf0924d2fcd389f356a3290b6aa667a51c3 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -242,11 +242,19 @@ static void F_SkyScroll(INT32 scrollspeed)
 #ifdef HWRENDER
 	else if (rendermode != render_none)
 	{ // if only software rendering could be this simple and retarded
-		scrolled = animtimer;
-		if (scrolled > 0)
-			V_DrawScaledPatch(scrolled - patwidth, 0, 0, pat);
-		for (x = 0; x < fakedwidth; x += patwidth)
-			V_DrawScaledPatch(x + scrolled, 0, 0, pat);
+		INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
+		INT32 y, pw = patwidth * dupz, ph = SHORT(pat->height) * dupz;
+		scrolled = animtimer * dupz;
+		for (x = 0; x < vid.width; x += pw)
+		{
+			for (y = 0; y < vid.height; y += ph)
+			{
+				if (scrolled > 0)
+					V_DrawScaledPatch(scrolled - pw, y, V_NOSCALESTART, pat);
+
+				V_DrawScaledPatch(x + scrolled, y, V_NOSCALESTART, pat);
+			}
+		}
 	}
 #endif
 
@@ -322,7 +330,7 @@ void F_StartIntro(void)
 	"hovers around the planet.\xBF It suddenly\n"
 	"appears from nowhere, circles around, and\n"
 	"\xB6- just as mysteriously as it arrives -\xB6\n"
-	"vanishes after about two months.\xBF\n"
+	"vanishes after about one week.\xBF\n"
 	"No one knows why it appears, or how.\n#");
 
 	introtext[5] = M_GetText(
@@ -334,11 +342,11 @@ void F_StartIntro(void)
 	"the screen, and just shrugged it off.\n#");
 
 	introtext[6] = M_GetText(
-	"It was only later\n"
+	"It was hours later\n"
 	"that he had an\n"
 	"idea. \xBF\xA7\"The Black\n"
-	"Rock usually has a\n"
-	"lot of energy\n"
+	"Rock has a large\n"
+	"amount of energy\n"
 	"within it\xAC...\xA7\xBF\n"
 	"If I can somehow\n"
 	"harness this,\xB8 I\n"
@@ -356,37 +364,37 @@ void F_StartIntro(void)
 	"a reunion party...\n#");
 
 	introtext[8] = M_GetText(
-	"\xA5\"We're\xB6 ready\xB6 to\xB4 fire\xB6 in\xB6 15\xB6 seconds!\"\xA8\xB8\n"
-	"The robot said, his voice crackling a\n"
-	"little down the com-link. \xBF\xA7\"Good!\"\xA8\xB8\n"
-	"Eggman sat back in his Egg-Mobile and\n"
+	"\xA5\"PRE-""\xB6""PARING-""\xB6""TO-""\xB4""FIRE-\xB6IN-""\xB6""15-""\xB6""SECONDS!\"\xA8\xB8\n"
+	"his targeting system crackled\n"
+	"robotically down the com-link. \xBF\xA7\"Good!\"\xA8\xB8\n"
+	"Eggman sat back in his eggmobile and\n"
 	"began to count down as he saw the\n"
-	"GreenFlower city on the main monitor.\n#");
+	"Greenflower mountain on the monitor.\n#");
 
 	introtext[9] = M_GetText(
 	"\xA5\"10...\xD2""9...\xD2""8...\"\xA8\xD2\n"
 	"Meanwhile, Sonic was tearing across the\n"
 	"zones. Everything became a blur as he\n"
-	"ran around loops, skimmed over water,\n"
+	"ran up slopes, skimmed over water,\n"
 	"and catapulted himself off rocks with\n"
 	"his phenomenal speed.\n#");
 
 	introtext[10] = M_GetText(
 	"\xA5\"6...\xD2""5...\xD2""4...\"\xA8\xD2\n"
 	"Sonic knew he was getting closer to the\n"
-	"City, and pushed himself harder.\xB4 Finally,\n"
-	"the city appeared in the horizon.\xD2\xD2\n"
+	"zone, and pushed himself harder.\xB4 Finally,\n"
+	"the mountain appeared in the horizon.\xD2\xD2\n"
 	"\xA5\"3...\xD2""2...\xD2""1...\xD2""Zero.\"\n#");
 
 	introtext[11] = M_GetText(
-	"GreenFlower City was gone.\xC4\n"
+	"Greenflower Mountain was no more.\xC4\n"
 	"Sonic arrived just in time to see what\n"
 	"little of the 'ruins' were left.\n"
-	"Everyone and everything in the city\n"
+	"The natural beauty of the zone\n"
 	"had been obliterated.\n#");
 
 	introtext[12] = M_GetText(
-	"\xA7\"You're not quite as dead as we thought,\n"
+	"\xA7\"You're not quite as gone as we thought,\n"
 	"huh?\xBF Are you going to tell us your plan as\n"
 	"usual or will I \xA8\xB4'have to work it out'\xA7 or\n"
 	"something?\"\xD2\xD2\n"
@@ -400,8 +408,8 @@ void F_StartIntro(void)
 	"leaving Sonic\n"
 	"and Tails behind.\xB6\n"
 	"Tails looked at\n"
-	"the ruins of the\n"
-	"Greenflower City\n"
+	"the once-perfect\n"
+	"mountainside\n"
 	"with a grim face\n"
 	"and sighed.\xC6\n"
 	"\xA7\"Now\xB6 what do we\n"
@@ -979,7 +987,7 @@ static const char *credits[] = {
 	"\1Programming",
 	"Alam \"GBC\" Arias",
 	"Logan \"GBA\" Arias",
-	"Tim \"RedEnchilada\" Bordelon",
+	"Colette \"fickle\" Bordelon",
 	"Callum Dickinson",
 	"Scott \"Graue\" Feeney",
 	"Nathan \"Jazz\" Giroux",
@@ -988,12 +996,12 @@ static const char *credits[] = {
 	"Ronald \"Furyhunter\" Kinard", // The SDL2 port
 	"John \"JTE\" Muniz",
 	"Ehab \"Wolfy\" Saeed",
+	"\"Kaito Sinclaire\"",
 	"\"SSNTails\"",
-	"Matthew \"Inuyasha\" Walsh",
 	"",
 	"\1Programming",
 	"\1Assistance",
-	"\"chi.miru\"", // Red's secret weapon, the REAL reason slopes exist (also helped port drawing code from ZDoom)
+	"\"chi.miru\"", // helped port slope drawing code from ZDoom
 	"Andrew \"orospakr\" Clunis",
 	"Gregor \"Oogaland\" Dick",
 	"Louis-Antoine \"LJSonic\" de Moulins", // for fixing 2.1's netcode (de Rochefort doesn't quite fit on the screen sorry lol)
@@ -1028,7 +1036,7 @@ static const char *credits[] = {
 	"\1Music and Sound",
 	"\1Production",
 	"Malcolm \"RedXVI\" Brown",
-	"David \"Bulmybag\" Bulmer",
+	"Dave \"DemonTomatoDave\" Bulmer",
 	"Paul \"Boinciel\" Clempson",
 	"Cyan Helkaraxe",
 	"Kepa \"Nev3r\" Iceta",
@@ -1053,13 +1061,13 @@ static const char *credits[] = {
 	"Kepa \"Nev3r\" Iceta",
 	"Thomas \"Shadow Hog\" Igoe",
 	"Erik \"Torgo\" Nielsen",
+	"\"Kaito Sinclaire\"",
 	"Wessel \"Spherallic\" Smit",
 	"\"Spazzo\"",
 	"\"SSNTails\"",
 	"Rob Tisdell",
 	"Jarrett \"JEV3\" Voight",
 	"Johnny \"Sonikku\" Wallbank",
-	"Matthew \"Inuyasha\" Walsh",
 	"Marco \"Digiku\" Zafra",
 	"",
 	"\1Boss Design",
diff --git a/src/g_game.c b/src/g_game.c
index 9db9c413b3a4c9ab448ff8e78dfa137c7fb871c6..e5077fb217c8cbfdf11f7b1a11ebb7a9c5a07e8d 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -133,8 +133,8 @@ boolean useNightsSS = false;
 
 UINT8 skincolor_redteam = SKINCOLOR_RED;
 UINT8 skincolor_blueteam = SKINCOLOR_BLUE;
-UINT8 skincolor_redring = SKINCOLOR_RED;
-UINT8 skincolor_bluering = SKINCOLOR_AZURE;
+UINT8 skincolor_redring = SKINCOLOR_SALMON;
+UINT8 skincolor_bluering = SKINCOLOR_CORNFLOWER;
 
 tic_t countdowntimer = 0;
 boolean countdowntimeup = false;
@@ -888,7 +888,9 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 
 	// why build a ticcmd if we're paused?
 	// Or, for that matter, if we're being reborn.
-	if (paused || P_AutoPause() || (gamestate == GS_LEVEL && player->playerstate == PST_REBORN))
+	// ...OR if we're blindfolded. No looking into the floor.
+	if (paused || P_AutoPause() || (gamestate == GS_LEVEL && (player->playerstate == PST_REBORN || ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+	&& (leveltime < hidetime * TICRATE) && (player->pflags & PF_TAGIT)))))
 	{
 		cmd->angleturn = (INT16)(localangle >> 16);
 		cmd->aiming = G_ClipAimingPitch(&localaiming);
@@ -3813,7 +3815,8 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 		unlocktriggers = 0;
 
 		// clear itemfinder, just in case
-		CV_StealthSetValue(&cv_itemfinder, 0);
+		if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds
+			CV_StealthSetValue(&cv_itemfinder, 0);
 	}
 
 	// internal game map
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 78fc31afccceb1397b08bc40bc3a17e247ff0d89..395fc2e4be22b68e4d57e6fc6efb2cb11ecb5852 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -62,19 +62,31 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 	UINT8 texel;
 	UINT16 texelu16;
 
+	if (!ptexturewidth)
+		return;
+
 	x1 = originx;
 	x2 = x1 + SHORT(realpatch->width);
 
+	if (x1 > ptexturewidth || x2 < 0)
+		return; // patch not located within texture's x bounds, ignore
+
+	if (originy > ptextureheight || (originy + SHORT(realpatch->height)) < 0)
+		return; // patch not located within texture's y bounds, ignore
+
+	// patch is actually inside the texture!
+	// now check if texture is partly off-screen and adjust accordingly
+
+	// left edge
 	if (x1 < 0)
 		x = 0;
 	else
 		x = x1;
 
+	// right edge
 	if (x2 > ptexturewidth)
 		x2 = ptexturewidth;
 
-	if (!ptexturewidth)
-		return;
 
 	col = x * pblockwidth / ptexturewidth;
 	ncols = ((x2 - x) * pblockwidth) / ptexturewidth;
@@ -581,8 +593,8 @@ void HWR_FreeTextureCache(void)
 
 	// free all hardware-converted graphics cached in the heap
 	// our gool is only the textures since user of the texture is the texture cache
-	Z_FreeTags(PU_HWRCACHE, PU_HWRCACHE);
-	Z_FreeTags(PU_HWRCACHE_UNLOCKED, PU_HWRCACHE_UNLOCKED);
+	Z_FreeTag(PU_HWRCACHE);
+	Z_FreeTag(PU_HWRCACHE_UNLOCKED);
 
 	// Alam: free the Z_Blocks before freeing it's users
 
@@ -629,8 +641,8 @@ void HWR_SetPalette(RGBA_t *palette)
 	// now flush data texture cache so 32 bit texture are recomputed
 	if (patchformat == GR_RGBA || textureformat == GR_RGBA)
 	{
-		Z_FreeTags(PU_HWRCACHE, PU_HWRCACHE);
-		Z_FreeTags(PU_HWRCACHE_UNLOCKED, PU_HWRCACHE_UNLOCKED);
+		Z_FreeTag(PU_HWRCACHE);
+		Z_FreeTag(PU_HWRCACHE_UNLOCKED);
 	}
 }
 
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 47c7c02a0b76a6e7f229f20a2036e243338eaffc..5dcead77cb9c59ed4d192feadf64ecde878802ad 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -20,8 +20,8 @@
 #define _HWR_DEFS_
 #include "../doomtype.h"
 
-#define ZCLIP_PLANE 4.0f
-#define NZCLIP_PLANE 0.9f
+#define ZCLIP_PLANE 4.0f // Used for the actual game drawing
+#define NZCLIP_PLANE 0.9f // Seems to be only used for the HUD and screen textures
 
 // ==========================================================================
 //                                                               SIMPLE TYPES
@@ -127,12 +127,13 @@ enum EPolyFlags
 
 	PF_Masked           = 0x00000001,   // Poly is alpha scaled and 0 alpha pels are discarded (holes in texture)
 	PF_Translucent      = 0x00000002,   // Poly is transparent, alpha = level of transparency
-	PF_Additive         = 0x00000024,   // Poly is added to the frame buffer
+	PF_Additive         = 0x00000004,   // Poly is added to the frame buffer
 	PF_Environment      = 0x00000008,   // Poly should be drawn environment mapped.
 	                                    // Hurdler: used for text drawing
 	PF_Substractive     = 0x00000010,   // for splat
 	PF_NoAlphaTest      = 0x00000020,   // hiden param
-	PF_Blending         = (PF_Environment|PF_Additive|PF_Translucent|PF_Masked|PF_Substractive)&~PF_NoAlphaTest,
+	PF_Fog              = 0x00000040,   // Fog blocks
+	PF_Blending         = (PF_Environment|PF_Additive|PF_Translucent|PF_Masked|PF_Substractive|PF_Fog)&~PF_NoAlphaTest,
 
 		// other flag bits
 
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index dc97f70146a636b300c3b773b7a534b5be163143..02608892e50d0c1ecf5eb0f972e47154876afbf0 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -33,6 +33,8 @@
 #include "../z_zone.h"
 #include "../v_video.h"
 #include "../st_stuff.h"
+#include "../p_local.h" // stplyr
+#include "../g_game.h" // players
 
 #include <fcntl.h>
 #include "../i_video.h"  // for rendermode != render_glide
@@ -147,14 +149,11 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 //  | /|
 //  |/ |
 //  0--1
-	float sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	float sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
-	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
+	float dupx, dupy, fscalew, fscaleh, fwidth, fheight;
 
-	if (alphalevel == 12)
-		alphalevel = 0;
-	else if (alphalevel >= 10 && alphalevel < 13)
+	UINT8 perplayershuffle = 0;
+
+	if (alphalevel >= 10 && alphalevel < 13)
 		return;
 
 	// make patch ready in hardware cache
@@ -163,40 +162,179 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 	else
 		HWR_GetMappedPatch(gpatch, colormap);
 
+	dupx = (float)vid.dupx;
+	dupy = (float)vid.dupy;
+
 	switch (option & V_SCALEPATCHMASK)
 	{
 	case V_NOSCALEPATCH:
-		pdupx = pdupy = 2.0f;
+		dupx = dupy = 1.0f;
 		break;
 	case V_SMALLSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupy);
+		dupx = (float)vid.smalldupx;
+		dupy = (float)vid.smalldupy;
 		break;
 	case V_MEDSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fmeddupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fmeddupy);
+		dupx = (float)vid.meddupx;
+		dupy = (float)vid.meddupy;
 		break;
 	}
 
-	if (option & V_NOSCALESTART)
-		sdupx = sdupy = 2.0f;
+	dupx = dupy = (dupx < dupy ? dupx : dupy);
+	fscalew = fscaleh = FIXED_TO_FLOAT(pscale);
 
-	if (option & V_SPLITSCREEN)
-		sdupy /= 2.0f;
+	if (option & V_OFFSET)
+	{
+		cx -= (float)gpatch->leftoffset * dupx * fscalew;
+		cy -= (float)gpatch->topoffset * dupy * fscaleh;
+	}
+	else
+	{
+		cy -= (float)gpatch->topoffset * fscaleh;
+		if (option & V_FLIP)
+			cx -= ((float)gpatch->width - (float)gpatch->leftoffset) * fscalew;
+		else
+			cx -= (float)gpatch->leftoffset * fscalew;
+	}
 
-	if (option & V_FLIP) // Need to flip both this and sow
+	if (splitscreen && (option & V_PERPLAYER))
 	{
-		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;
+		float adjusty = ((option & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+		fscaleh /= 2;
+		cy /= 2;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			float adjustx = ((option & V_NOSCALESTART) ? vid.width : BASEVIDWIDTH)/2.0f;
+			fscalew /= 2;
+			cx /= 2;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				option &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				cx += adjustx;
+				option &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				cy += adjusty;
+				option &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				cx += adjustx;
+				cy += adjusty;
+				option &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 1;
+				option &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 2;
+				cy += adjusty;
+				option &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	if (!(option & V_NOSCALESTART))
+	{
+		cx = cx * dupx;
+		cy = cy * dupy;
+
+		if (!(option & V_SCALEPATCHMASK))
+		{
+			// if it's meant to cover the whole screen, black out the rest
+			// cx and cy are possibly *slightly* off from float maths
+			// This is done before here compared to software because we directly alter cx and cy to centre
+			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
+			{
+				// Need to temporarily cache the real patch to get the colour of the top left pixel
+				patch_t *realpatch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
+				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
+				const UINT8 *source = (const UINT8 *)(column) + 3;
+				HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				Z_Free(realpatch);
+			}
+			// centre screen
+			if (vid.width != BASEVIDWIDTH * vid.dupx)
+			{
+				if (option & V_SNAPTORIGHT)
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+				else if (!(option & V_SNAPTOLEFT))
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/2;
+				if (perplayershuffle & 4)
+					cx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/4;
+				else if (perplayershuffle & 8)
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/4;
+			}
+			if (vid.height != BASEVIDHEIGHT * vid.dupy)
+			{
+				if (option & V_SNAPTOBOTTOM)
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+				else if (!(option & V_SNAPTOTOP))
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/2;
+				if (perplayershuffle & 1)
+					cy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/4;
+				else if (perplayershuffle & 2)
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/4;
+			}
+		}
+	}
+
+	if (pscale != FRACUNIT || (splitscreen && option & V_PERPLAYER))
+	{
+		fwidth = (float)gpatch->width * fscalew * dupx;
+		fheight = (float)gpatch->height * fscaleh * dupy;
 	}
 	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;
+		fwidth = (float)gpatch->width * dupx;
+		fheight = (float)gpatch->height * dupy;
 	}
 
-	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;
+	// positions of the cx, cy, are between 0 and vid.width/vid.height now, we need them to be between -1 and 1
+	cx = -1 + (cx / (vid.width/2));
+	cy = 1 - (cy / (vid.height/2));
+
+	// fwidth and fheight are similar
+	fwidth /= vid.width / 2;
+	fheight /= vid.height / 2;
+
+	// set the polygon vertices to the right positions
+	v[0].x = v[3].x = cx;
+	v[2].x = v[1].x = cx + fwidth;
+
+	v[0].y = v[1].y = cy;
+	v[2].y = v[3].y = cy - fheight;
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
@@ -249,48 +387,125 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 //  | /|
 //  |/ |
 //  0--1
-	float sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	float sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
-	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
+	float dupx, dupy, fscale, fwidth, fheight;
 
-	if (alphalevel == 12)
-		alphalevel = 0;
-	else if (alphalevel >= 10 && alphalevel < 13)
+	if (alphalevel >= 10 && alphalevel < 13)
 		return;
 
 	// make patch ready in hardware cache
 	HWR_GetPatch(gpatch);
 
+	dupx = (float)vid.dupx;
+	dupy = (float)vid.dupy;
+
 	switch (option & V_SCALEPATCHMASK)
 	{
 	case V_NOSCALEPATCH:
-		pdupx = pdupy = 2.0f;
+		dupx = dupy = 1.0f;
 		break;
 	case V_SMALLSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupy);
+		dupx = (float)vid.smalldupx;
+		dupy = (float)vid.smalldupy;
 		break;
 	case V_MEDSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fmeddupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fmeddupy);
+		dupx = (float)vid.meddupx;
+		dupy = (float)vid.meddupy;
 		break;
 	}
 
-	if (option & V_NOSCALESTART)
-		sdupx = sdupy = 2.0f;
+	dupx = dupy = (dupx < dupy ? dupx : dupy);
+	fscale = FIXED_TO_FLOAT(pscale);
+
+	// fuck it, no GL support for croppedpatch v_perplayer right now. it's not like it's accessible to Lua or anything, and we only use it for menus...
+
+	cy -= (float)gpatch->topoffset * fscale;
+	cx -= (float)gpatch->leftoffset * fscale;
+
+	if (!(option & V_NOSCALESTART))
+	{
+		cx = cx * dupx;
+		cy = cy * dupy;
+
+		if (!(option & V_SCALEPATCHMASK))
+		{
+			// if it's meant to cover the whole screen, black out the rest
+			// cx and cy are possibly *slightly* off from float maths
+			// This is done before here compared to software because we directly alter cx and cy to centre
+			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
+			{
+				// Need to temporarily cache the real patch to get the colour of the top left pixel
+				patch_t *realpatch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
+				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
+				const UINT8 *source = (const UINT8 *)(column) + 3;
+				HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				Z_Free(realpatch);
+			}
+			// centre screen
+			if (vid.width != BASEVIDWIDTH * vid.dupx)
+			{
+				if (option & V_SNAPTORIGHT)
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+				else if (!(option & V_SNAPTOLEFT))
+					cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/2;
+			}
+			if (vid.height != BASEVIDHEIGHT * vid.dupy)
+			{
+				if (option & V_SNAPTOBOTTOM)
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+				else if (!(option & V_SNAPTOTOP))
+					cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/2;
+			}
+		}
+	}
+
+	fwidth = w;
+	fheight = h;
+
+	if (fwidth > gpatch->width)
+		fwidth = gpatch->width;
 
-	v[0].x = v[3].x =     (cx*sdupx -           gpatch->leftoffset  * pdupx) / vid.width - 1;
-	v[2].x = v[1].x =     (cx*sdupx + ((w) - 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 + ((h) - gpatch->topoffset)  * pdupy) / vid.height;
+	if (fheight > gpatch->height)
+		fheight = gpatch->height;
+
+	if (pscale != FRACUNIT)
+	{
+		fwidth *=  fscale * dupx;
+		fheight *=  fscale * dupy;
+	}
+	else
+	{
+		fwidth *= dupx;
+		fheight *= dupy;
+	}
+
+	// positions of the cx, cy, are between 0 and vid.width/vid.height now, we need them to be between -1 and 1
+	cx = -1 + (cx / (vid.width/2));
+	cy = 1 - (cy / (vid.height/2));
+
+	// fwidth and fheight are similar
+	fwidth /= vid.width / 2;
+	fheight /= vid.height / 2;
+
+	// set the polygon vertices to the right positions
+	v[0].x = v[3].x = cx;
+	v[2].x = v[1].x = cx + fwidth;
+
+	v[0].y = v[1].y = cy;
+	v[2].y = v[3].y = cy - fheight;
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
-	v[0].sow = v[3].sow = ((sx  )/(float)gpatch->width )*gpatch->max_s;
-	v[2].sow = v[1].sow = ((sx+w)/(float)gpatch->width )*gpatch->max_s;
-	v[0].tow = v[1].tow = ((sy  )/(float)gpatch->height)*gpatch->max_t;
-	v[2].tow = v[3].tow = ((sy+h)/(float)gpatch->height)*gpatch->max_t;
+	v[0].sow = v[3].sow     = ((sx  )/(float)gpatch->width )*gpatch->max_s;
+	if (sx + w > gpatch->width)
+		v[2].sow = v[1].sow = gpatch->max_s;
+	else
+		v[2].sow = v[1].sow = ((sx+w)/(float)gpatch->width )*gpatch->max_s;
+
+	v[0].tow = v[1].tow     = ((sy  )/(float)gpatch->height)*gpatch->max_t;
+	if (sy + h > gpatch->height)
+		v[2].tow = v[3].tow = gpatch->max_t;
+	else
+		v[2].tow = v[3].tow = ((sy+h)/(float)gpatch->height)*gpatch->max_t;
 
 	flags = BLENDMODE|PF_Clip|PF_NoZClip|PF_NoDepthTest;
 
@@ -434,18 +649,14 @@ void HWR_DrawFlatFill (INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum
 //  | /|
 //  |/ |
 //  0--1
-void HWR_FadeScreenMenuBack(UINT32 color, INT32 height)
+void HWR_FadeScreenMenuBack(UINT16 color, UINT8 strength)
 {
 	FOutVector  v[4];
 	FSurfaceInfo Surf;
 
-	// setup some neat-o translucency effect
-	if (!height) //cool hack 0 height is full height
-		height = vid.height;
-
 	v[0].x = v[3].x = -1.0f;
 	v[2].x = v[1].x =  1.0f;
-	v[0].y = v[1].y =  1.0f-((height<<1)/(float)vid.height);
+	v[0].y = v[1].y = -1.0f;
 	v[2].y = v[3].y =  1.0f;
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
@@ -454,8 +665,16 @@ void HWR_FadeScreenMenuBack(UINT32 color, INT32 height)
 	v[0].tow = v[1].tow = 1.0f;
 	v[2].tow = v[3].tow = 0.0f;
 
-	Surf.FlatColor.rgba = UINT2RGBA(color);
-	Surf.FlatColor.s.alpha = (UINT8)((0xff/2) * ((float)height / vid.height)); //calum: varies console alpha
+	if (color & 0xFF00) // Do COLORMAP fade.
+	{
+		Surf.FlatColor.rgba = UINT2RGBA(0x01010160);
+		Surf.FlatColor.s.alpha = (strength*8);
+	}
+	else // Do TRANSMAP** fade.
+	{
+		Surf.FlatColor.rgba = pLocalPalette[color].rgba;
+		Surf.FlatColor.s.alpha = (UINT8)(strength*25.5f);
+	}
 	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
 }
 
@@ -660,7 +879,9 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 {
 	FOutVector v[4];
 	FSurfaceInfo Surf;
-	float sdupx, sdupy;
+	float fx, fy, fw, fh;
+
+	UINT8 perplayershuffle = 0;
 
 	if (w < 0 || h < 0)
 		return; // consistency w/ software
@@ -669,16 +890,155 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 //  | /|
 //  |/ |
 //  0--1
-	sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
 
-	if (color & V_NOSCALESTART)
-		sdupx = sdupy = 2.0f;
+	if (splitscreen && (color & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				color &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	fx = (float)x;
+	fy = (float)y;
+	fw = (float)w;
+	fh = (float)h;
+
+	if (!(color & V_NOSCALESTART))
+	{
+		float dupx = (float)vid.dupx, dupy = (float)vid.dupy;
+
+		if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
+		{
+			RGBA_t rgbaColour = V_GetColor(color);
+			FRGBAFloat clearColour;
+			clearColour.red = (float)rgbaColour.s.red / 255;
+			clearColour.green = (float)rgbaColour.s.green / 255;
+			clearColour.blue = (float)rgbaColour.s.blue / 255;
+			clearColour.alpha = 1;
+			HWD.pfnClearBuffer(true, false, &clearColour);
+			return;
+		}
+
+		fx *= dupx;
+		fy *= dupy;
+		fw *= dupx;
+		fh *= dupy;
 
-	v[0].x = v[3].x = (x*sdupx)/vid.width - 1;
-	v[2].x = v[1].x = (x*sdupx + w*sdupx)/vid.width - 1;
-	v[0].y = v[1].y = 1-(y*sdupy)/vid.height;
-	v[2].y = v[3].y = 1-(y*sdupy + h*sdupy)/vid.height;
+		if (vid.width != BASEVIDWIDTH * vid.dupx)
+		{
+			if (color & V_SNAPTORIGHT)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+			else if (!(color & V_SNAPTOLEFT))
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				fx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+		}
+		if (vid.height != BASEVIDHEIGHT * dupy)
+		{
+			// same thing here
+			if (color & V_SNAPTOBOTTOM)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+			else if (!(color & V_SNAPTOTOP))
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				fy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+		}
+	}
+
+	if (fx >= vid.width || fy >= vid.height)
+		return;
+	if (fx < 0)
+	{
+		fw += fx;
+		fx = 0;
+	}
+	if (fy < 0)
+	{
+		fh += fy;
+		fy = 0;
+	}
+
+	if (fw <= 0 || fh <= 0)
+		return;
+	if (fx + fw > vid.width)
+		fw = (float)vid.width - fx;
+	if (fy + fh > vid.height)
+		fh = (float)vid.height - fy;
+
+	fx = -1 + fx / (vid.width / 2);
+	fy = 1 - fy / (vid.height / 2);
+	fw = fw / (vid.width / 2);
+	fh = fh / (vid.height / 2);
+
+	v[0].x = v[3].x = fx;
+	v[2].x = v[1].x = fx + fw;
+	v[0].y = v[1].y = fy;
+	v[2].y = v[3].y = fy - fh;
 
 	//Hurdler: do we still use this argb color? if not, we should remove it
 	v[0].argb = v[1].argb = v[2].argb = v[3].argb = 0xff00ff00; //;
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 7672f47c26a71febd941e0ec2157dafd5446e24d..a5ac82001084f5bf755c6025b1bbcfdccb40432d 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -79,6 +79,7 @@ EXPORT char *HWRAPI(GetRenderer) (void);
 #define SCREENVERTS 10
 EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2]);
 #endif
+EXPORT void HWRAPI(FlushScreenTextures) (void);
 EXPORT void HWRAPI(StartScreenWipe) (void);
 EXPORT void HWRAPI(EndScreenWipe) (void);
 EXPORT void HWRAPI(DoScreenWipe) (float alpha);
@@ -124,6 +125,7 @@ struct hwdriver_s
 #ifdef SHUFFLE
 	PostImgRedraw       pfnPostImgRedraw;
 #endif
+	FlushScreenTextures pfnFlushScreenTextures;
 	StartScreenWipe     pfnStartScreenWipe;
 	EndScreenWipe       pfnEndScreenWipe;
 	DoScreenWipe        pfnDoScreenWipe;
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 597990a62f5705da15c65d6828f6629738bfd41e..f23209379809603634a36cb5aa2c0efa65b5dc6d 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -122,7 +122,7 @@ consvar_t cv_grfiltermode = {"gr_filtermode", "Nearest", CV_CALL, grfiltermode_c
 consvar_t cv_granisotropicmode = {"gr_anisotropicmode", "1", CV_CALL, granisotropicmode_cons_t,
                              CV_anisotropic_ONChange, 0, NULL, NULL, 0, 0, NULL};
 //static consvar_t cv_grzbuffer = {"gr_zbuffer", "On", 0, CV_OnOff};
-consvar_t cv_grcorrecttricks = {"gr_correcttricks", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_grcorrecttricks = {"gr_correcttricks", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grsolvetjoin = {"gr_solvetjoin", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static void CV_FogDensity_ONChange(void)
@@ -491,10 +491,10 @@ UINT32 HWR_Lighting(INT32 light, UINT32 color, UINT32 fadecolor, boolean fogbloc
 }
 
 
-static UINT8 HWR_FogBlockAlpha(INT32 light, UINT32 color, UINT32 fadecolor) // Let's see if this can work
+static UINT8 HWR_FogBlockAlpha(INT32 light, UINT32 color) // Let's see if this can work
 {
-	RGBA_t realcolor, fogcolor, surfcolor;
-	INT32 alpha, fogalpha;
+	RGBA_t realcolor, surfcolor;
+	INT32 alpha;
 
 	// Don't go out of bounds
 	if (light < 0)
@@ -503,13 +503,11 @@ static UINT8 HWR_FogBlockAlpha(INT32 light, UINT32 color, UINT32 fadecolor) // L
 		light = 255;
 
 	realcolor.rgba = color;
-	fogcolor.rgba = fadecolor;
 
 	alpha = (realcolor.s.alpha*255)/25;
-	fogalpha = (fogcolor.s.alpha*255)/25;
 
-	// Fog blocks seem to get slightly more opaque with more opaque colourmap opacity, and much more opaque with darker brightness
-	surfcolor.s.alpha = (UINT8)(CALCLIGHT(light, ((0xFF-light)+alpha)/2)+CALCLIGHT(0xFF-light, ((light)+fogalpha)/2));
+	// at 255 brightness, alpha is between 0 and 127, at 0 brightness alpha will always be 255
+	surfcolor.s.alpha = (alpha*light)/(2*256)+255-light;
 
 	return surfcolor.s.alpha;
 }
@@ -769,10 +767,10 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, NORMALFOG, FADEFOG, false, true);
 	}
 
-	if (PolyFlags & PF_Translucent)
+	if (PolyFlags & (PF_Translucent|PF_Fog))
 	{
 		Surf.FlatColor.s.alpha = (UINT8)alpha;
-		PolyFlags |= PF_Modulated|PF_Occlude|PF_Clip;
+		PolyFlags |= PF_Modulated|PF_Clip;
 	}
 	else
 		PolyFlags |= PF_Masked|PF_Modulated|PF_Clip;
@@ -1065,12 +1063,11 @@ static float HWR_ClipViewSegment(INT32 x, polyvertex_t *v1, polyvertex_t *v2)
 //
 // HWR_SplitWall
 //
-static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum, FSurfaceInfo* Surf, UINT32 cutflag)
+static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum, FSurfaceInfo* Surf, UINT32 cutflag, ffloor_t *pfloor)
 {
 	/* SoM: split up and light walls according to the
 	 lightlist. This may also include leaving out parts
 	 of the wall that can't be seen */
-	GLTexture_t * glTex;
 
 	float realtop, realbot, top, bot;
 	float pegt, pegb, pegmul;
@@ -1093,8 +1090,8 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 	INT32   solid, i;
 	lightlist_t *  list = sector->lightlist;
 	const UINT8 alpha = Surf->FlatColor.s.alpha;
-	FUINT lightnum;
-	extracolormap_t *colormap;
+	FUINT lightnum = sector->lightlevel;
+	extracolormap_t *colormap = NULL;
 
 	realtop = top = wallVerts[3].y;
 	realbot = bot = wallVerts[0].y;
@@ -1110,7 +1107,7 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 	endpegmul = (endpegb - endpegt) / (endtop - endbot);
 #endif
 
-	for (i = 1; i < sector->numlights; i++)
+	for (i = 0; i < sector->numlights; i++)
 	{
 #ifdef ESLOPE
         if (endtop < endrealbot)
@@ -1118,35 +1115,38 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 		if (top < realbot)
 			return;
 
-	//Hurdler: fix a crashing bug, but is it correct?
-//		if (!list[i].caster)
-//			continue;
+	// There's a compiler warning here if this comment isn't here because of indentation
+		if (!(list[i].flags & FF_NOSHADE))
+		{
+			if (pfloor && (pfloor->flags & FF_FOG))
+			{
+				lightnum = pfloor->master->frontsector->lightlevel;
+				colormap = pfloor->master->frontsector->extra_colormap;
+			}
+			else
+			{
+				lightnum = *list[i].lightlevel;
+				colormap = list[i].extra_colormap;
+			}
+		}
 
 		solid = false;
 
-		if (list[i].caster)
+		if ((sector->lightlist[i].flags & FF_CUTSOLIDS) && !(cutflag & FF_EXTRA))
+			solid = true;
+		else if ((sector->lightlist[i].flags & FF_CUTEXTRA) && (cutflag & FF_EXTRA))
 		{
-			if (sector->lightlist[i].caster->flags & FF_CUTSOLIDS && !(cutflag & FF_EXTRA))
-				solid = true;
-			else if (sector->lightlist[i].caster->flags & FF_CUTEXTRA && cutflag & FF_EXTRA)
+			if (sector->lightlist[i].flags & FF_EXTRA)
 			{
-				if (sector->lightlist[i].caster->flags & FF_EXTRA)
-				{
-					if (sector->lightlist[i].caster->flags == cutflag) // Only merge with your own types
-						solid = true;
-				}
-				else
+				if ((sector->lightlist[i].flags & (FF_FOG|FF_SWIMMABLE)) == (cutflag & (FF_FOG|FF_SWIMMABLE))) // Only merge with your own types
 					solid = true;
 			}
 			else
-				solid = false;
+				solid = true;
 		}
 		else
 			solid = false;
 
-		if (cutflag == FF_CUTSOLIDS) // These are regular walls sent in from StoreWallRange, they shouldn't be cut from this
-			solid = false;
-
 #ifdef ESLOPE
 		if (list[i].slope)
 		{
@@ -1186,34 +1186,55 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 			if (solid && endtop > endbheight)
 				endtop = endbheight;
 #endif
-			continue;
 		}
 
+#ifdef ESLOPE
+		if (i + 1 < sector->numlights)
+		{
+			if (list[i+1].slope)
+			{
+				temp = P_GetZAt(list[i+1].slope, v1x, v1y);
+				bheight = FIXED_TO_FLOAT(temp);
+				temp = P_GetZAt(list[i+1].slope, v2x, v2y);
+				endbheight = FIXED_TO_FLOAT(temp);
+			}
+			else
+				bheight = endbheight = FIXED_TO_FLOAT(list[i+1].height);
+		}
+		else
+		{
+			bheight = realbot;
+			endbheight = endrealbot;
+		}
+#else
+		if (i + 1 < sector->numlights)
+		{
+			bheight = FIXED_TO_FLOAT(list[i+1].height);
+		}
+		else
+		{
+			bheight = realbot;
+		}
+#endif
+
+#ifdef ESLOPE
+		if (endbheight >= endtop)
+#endif
+		if (bheight >= top)
+			continue;
+
 		//Found a break;
-		bot = height;
+		bot = bheight;
 
 		if (bot < realbot)
 			bot = realbot;
 
 #ifdef ESLOPE
-		endbot = endheight;
+		endbot = endbheight;
 
 		if (endbot < endrealbot)
 			endbot = endrealbot;
 #endif
-
-		// colormap test
-		if (list[i-1].caster)
-		{
-			lightnum = *list[i-1].lightlevel;
-			colormap = list[i-1].extra_colormap;
-		}
-		else
-		{
-			lightnum = sector->lightlevel;
-			colormap = sector->extra_colormap;
-		}
-
 		Surf->FlatColor.s.alpha = alpha;
 
 #ifdef ESLOPE
@@ -1236,23 +1257,16 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 		wallVerts[0].y = wallVerts[1].y = bot;
 #endif
 
-		glTex = HWR_GetTexture(texnum);
-		if (cutflag & FF_TRANSLUCENT)
+		if (cutflag & FF_FOG)
+			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Fog|PF_NoTexture, true, lightnum, colormap);
+		else if (cutflag & FF_TRANSLUCENT)
 			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Translucent, false, lightnum, colormap);
-		else if (glTex->mipmap.flags & TF_TRANSPARENT)
-			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Environment, false, lightnum, colormap);
 		else
 			HWR_ProjectWall(wallVerts, Surf, PF_Masked, lightnum, colormap);
 
-		if (solid)
-			top = bheight;
-		else
-			top = height;
+		top = bot;
 #ifdef ESLOPE
-		if (solid)
-			endtop = endbheight;
-		else
-			endtop = endheight;
+		endtop = endbot;
 #endif
 	}
 
@@ -1264,17 +1278,7 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 	if (top <= realbot)
 		return;
 
-	if (list[i-1].caster)
-	{
-		lightnum = *list[i-1].lightlevel;
-		colormap = list[i-1].extra_colormap;
-	}
-	else
-	{
-		lightnum = sector->lightlevel;
-		colormap = sector->extra_colormap;
-	}
-		Surf->FlatColor.s.alpha = alpha;
+	Surf->FlatColor.s.alpha = alpha;
 
 #ifdef ESLOPE
 	wallVerts[3].t = pegt + ((realtop - top) * pegmul);
@@ -1296,119 +1300,14 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
     wallVerts[0].y = wallVerts[1].y = bot;
 #endif
 
-	glTex = HWR_GetTexture(texnum);
-	if (cutflag & FF_TRANSLUCENT)
+	if (cutflag & FF_FOG)
+		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Fog|PF_NoTexture, true, lightnum, colormap);
+	else if (cutflag & FF_TRANSLUCENT)
 		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Translucent, false, lightnum, colormap);
-	else if (glTex->mipmap.flags & TF_TRANSPARENT)
-		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Environment, false, lightnum, colormap);
 	else
 		HWR_ProjectWall(wallVerts, Surf, PF_Masked, lightnum, colormap);
 }
 
-//
-// HWR_SplitFog
-// Exclusively for fog
-//
-static void HWR_SplitFog(sector_t *sector, wallVert3D *wallVerts, FSurfaceInfo* Surf, UINT32 cutflag, FUINT lightnum, extracolormap_t *colormap)
-{
-	/* SoM: split up and light walls according to the
-	 lightlist. This may also include leaving out parts
-	 of the wall that can't be seen */
-	float realtop, realbot, top, bot;
-	float pegt, pegb, pegmul;
-	float height = 0.0f, bheight = 0.0f;
-	INT32   solid, i;
-	lightlist_t *  list = sector->lightlist;
-	const UINT8 alpha = Surf->FlatColor.s.alpha;
-
-	realtop = top = wallVerts[2].y;
-	realbot = bot = wallVerts[0].y;
-	pegt = wallVerts[2].t;
-	pegb = wallVerts[0].t;
-	pegmul = (pegb - pegt) / (top - bot);
-
-	for (i = 1; i < sector->numlights; i++)
-	{
-		if (top < realbot)
-			return;
-
-	//Hurdler: fix a crashing bug, but is it correct?
-//		if (!list[i].caster)
-//			continue;
-
-		solid = false;
-
-		if (list[i].caster)
-		{
-			if (sector->lightlist[i].caster->flags & FF_FOG && cutflag & FF_FOG) // Only fog cuts fog
-			{
-				if (sector->lightlist[i].caster->flags & FF_EXTRA)
-				{
-					if (sector->lightlist[i].caster->flags == cutflag) // only cut by the same
-						solid = true;
-				}
-				else
-					solid = true;
-			}
-		}
-
-		height = FIXED_TO_FLOAT(list[i].height);
-
-		if (solid)
-			bheight = FIXED_TO_FLOAT(*list[i].caster->bottomheight);
-
-		if (height >= top)
-		{
-			if (solid && top > bheight)
-				top = bheight;
-			continue;
-		}
-
-		//Found a break;
-		bot = height;
-
-		if (bot < realbot)
-			bot = realbot;
-
-		{
-
-
-
-			Surf->FlatColor.s.alpha = alpha;
-		}
-
-		wallVerts[3].t = wallVerts[2].t = pegt + ((realtop - top) * pegmul);
-		wallVerts[0].t = wallVerts[1].t = pegt + ((realtop - bot) * pegmul);
-
-		// set top/bottom coords
-		wallVerts[2].y = wallVerts[3].y = top;
-		wallVerts[0].y = wallVerts[1].y = bot;
-
-		if (!solid) // Don't draw it if there's more fog behind it
-			HWR_AddTransparentWall(wallVerts, Surf, 0, PF_Translucent|PF_NoTexture, true, lightnum, colormap);
-
-		top = height;
-	}
-
-	bot = realbot;
-	if (top <= realbot)
-		return;
-
-	{
-
-		Surf->FlatColor.s.alpha = alpha;
-	}
-
-	wallVerts[3].t = wallVerts[2].t = pegt + ((realtop - top) * pegmul);
-	wallVerts[0].t = wallVerts[1].t = pegt + ((realtop - bot) * pegmul);
-
-	// set top/bottom coords
-	wallVerts[2].y = wallVerts[3].y = top;
-	wallVerts[0].y = wallVerts[1].y = bot;
-
-	HWR_AddTransparentWall(wallVerts, Surf, 0, PF_Translucent|PF_NoTexture, true, lightnum, colormap);
-}
-
 // HWR_DrawSkyWall
 // Draw walls into the depth buffer so that anything behind is culled properly
 static void HWR_DrawSkyWall(wallVert3D *wallVerts, FSurfaceInfo *Surf)
@@ -1648,7 +1547,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 #endif
 
 			if (gr_frontsector->numlights)
-				HWR_SplitWall(gr_frontsector, wallVerts, gr_toptexture, &Surf, FF_CUTSOLIDS);
+				HWR_SplitWall(gr_frontsector, wallVerts, gr_toptexture, &Surf, FF_CUTLEVEL, NULL);
 			else if (grTex->mipmap.flags & TF_TRANSPARENT)
 				HWR_AddTransparentWall(wallVerts, &Surf, gr_toptexture, PF_Environment, false, lightnum, colormap);
 			else
@@ -1730,7 +1629,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 #endif
 
 			if (gr_frontsector->numlights)
-				HWR_SplitWall(gr_frontsector, wallVerts, gr_bottomtexture, &Surf, FF_CUTSOLIDS);
+				HWR_SplitWall(gr_frontsector, wallVerts, gr_bottomtexture, &Surf, FF_CUTLEVEL, NULL);
 			else if (grTex->mipmap.flags & TF_TRANSPARENT)
 				HWR_AddTransparentWall(wallVerts, &Surf, gr_bottomtexture, PF_Environment, false, lightnum, colormap);
 			else
@@ -1991,15 +1890,14 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 			}
 #endif
 
-			if (grTex->mipmap.flags & TF_TRANSPARENT)
-				blendmode = PF_Translucent;
-
 			if (gr_frontsector->numlights)
 			{
 				if (!(blendmode & PF_Masked))
-					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_TRANSLUCENT);
+					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_TRANSLUCENT, NULL);
 				else
-					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTSOLIDS);
+				{
+					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTLEVEL, NULL);
+				}
 			}
 			else if (!(blendmode & PF_Masked))
 				HWR_AddTransparentWall(wallVerts, &Surf, gr_midtexture, blendmode, false, lightnum, colormap);
@@ -2107,7 +2005,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 #endif
 			// I don't think that solid walls can use translucent linedef types...
 			if (gr_frontsector->numlights)
-				HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTSOLIDS);
+				HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTLEVEL, NULL);
 			else
 			{
 				if (grTex->mipmap.flags & TF_TRANSPARENT)
@@ -2247,7 +2145,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				{
 					FBITFIELD blendmode;
 
-					blendmode = PF_Translucent|PF_NoTexture;
+					blendmode = PF_Fog|PF_NoTexture;
 
 					lightnum = rover->master->frontsector->lightlevel;
 					colormap = rover->master->frontsector->extra_colormap;
@@ -2255,15 +2153,15 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 					if (rover->master->frontsector->extra_colormap)
 					{
 
-						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,rover->master->frontsector->extra_colormap->rgba,rover->master->frontsector->extra_colormap->fadergba);
+						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,rover->master->frontsector->extra_colormap->rgba);
 					}
 					else
 					{
-						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,NORMALFOG,FADEFOG);
+						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,NORMALFOG);
 					}
 
 					if (gr_frontsector->numlights)
-						HWR_SplitFog(gr_frontsector, wallVerts, &Surf, rover->flags, lightnum, colormap);
+						HWR_SplitWall(gr_frontsector, wallVerts, 0, &Surf, rover->flags, rover);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -2271,18 +2169,14 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				{
 					FBITFIELD blendmode = PF_Masked;
 
-					if (rover->flags & FF_TRANSLUCENT)
+					if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
 					{
 						blendmode = PF_Translucent;
 						Surf.FlatColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
 					}
-					else if (grTex->mipmap.flags & TF_TRANSPARENT)
-					{
-						blendmode = PF_Environment;
-					}
 
 					if (gr_frontsector->numlights)
-						HWR_SplitWall(gr_frontsector, wallVerts, texnum, &Surf, rover->flags);
+						HWR_SplitWall(gr_frontsector, wallVerts, texnum, &Surf, rover->flags, rover);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -2371,22 +2265,22 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				{
 					FBITFIELD blendmode;
 
-					blendmode = PF_Translucent|PF_NoTexture;
+					blendmode = PF_Fog|PF_NoTexture;
 
 					lightnum = rover->master->frontsector->lightlevel;
 					colormap = rover->master->frontsector->extra_colormap;
 
 					if (rover->master->frontsector->extra_colormap)
 					{
-						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,rover->master->frontsector->extra_colormap->rgba,rover->master->frontsector->extra_colormap->fadergba);
+						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,rover->master->frontsector->extra_colormap->rgba);
 					}
 					else
 					{
-						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,NORMALFOG,FADEFOG);
+						Surf.FlatColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel,NORMALFOG);
 					}
 
 					if (gr_backsector->numlights)
-						HWR_SplitFog(gr_backsector, wallVerts, &Surf, rover->flags, lightnum, colormap);
+						HWR_SplitWall(gr_backsector, wallVerts, 0, &Surf, rover->flags, rover);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -2394,18 +2288,14 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				{
 					FBITFIELD blendmode = PF_Masked;
 
-					if (rover->flags & FF_TRANSLUCENT)
+					if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
 					{
 						blendmode = PF_Translucent;
 						Surf.FlatColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
 					}
-					else if (grTex->mipmap.flags & TF_TRANSPARENT)
-					{
-						blendmode = PF_Environment;
-					}
 
 					if (gr_backsector->numlights)
-						HWR_SplitWall(gr_backsector, wallVerts, texnum, &Surf, rover->flags);
+						HWR_SplitWall(gr_backsector, wallVerts, texnum, &Surf, rover->flags, rover);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -2816,7 +2706,7 @@ static void HWR_AddLine(seg_t * line)
 #endif
 
 	// SoM: Backsector needs to be run through R_FakeFlat
-	sector_t tempsec;
+	static sector_t tempsec;
 
 	fixed_t v1x, v1y, v2x, v2y; // the seg's vertexes as fixed_t
 #ifdef POLYOBJECTS
@@ -3142,8 +3032,8 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	return gld_clipper_SafeCheckRange(angle2, angle1);
 #else
 	// check clip list for an open space
-	angle1 = R_PointToAngle(px1, py1) - dup_viewangle;
-	angle2 = R_PointToAngle(px2, py2) - dup_viewangle;
+	angle1 = R_PointToAngle2(dup_viewx>>1, dup_viewy>>1, px1>>1, py1>>1) - dup_viewangle;
+	angle2 = R_PointToAngle2(dup_viewx>>1, dup_viewy>>1, px2>>1, py2>>1) - dup_viewangle;
 
 	span = angle1 - angle2;
 
@@ -3477,7 +3367,7 @@ static void HWR_Subsector(size_t num)
 	INT16 count;
 	seg_t *line;
 	subsector_t *sub;
-	sector_t tempsec; //SoM: 4/7/2000
+	static sector_t tempsec; //SoM: 4/7/2000
 	INT32 floorlightlevel;
 	INT32 ceilinglightlevel;
 	INT32 locFloorHeight, locCeilingHeight;
@@ -3659,8 +3549,6 @@ static void HWR_Subsector(size_t num)
 	{
 		/// \todo fix light, xoffs, yoffs, extracolormap ?
 		ffloor_t * rover;
-
-		R_Prep3DFloors(gr_frontsector);
 		for (rover = gr_frontsector->ffloors;
 			rover; rover = rover->next)
 		{
@@ -3694,19 +3582,19 @@ static void HWR_Subsector(size_t num)
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 
 					if (rover->master->frontsector->extra_colormap)
-						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap->rgba, rover->master->frontsector->extra_colormap->fadergba);
+						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap->rgba);
 					else
-						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG, FADEFOG);
+						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG);
 
 					HWR_AddTransparentFloor(0,
 					                       &extrasubsectors[num],
 										   false,
 					                       *rover->bottomheight,
 					                       *gr_frontsector->lightlist[light].lightlevel,
-					                       alpha, rover->master->frontsector, PF_Translucent|PF_NoTexture,
+					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, rover->master->frontsector->extra_colormap);
 				}
-				else if (rover->flags & FF_TRANSLUCENT) // SoM: Flags are more efficient
+				else if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256) // SoM: Flags are more efficient
 				{
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 #ifndef SORTING
@@ -3757,19 +3645,19 @@ static void HWR_Subsector(size_t num)
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 
 					if (rover->master->frontsector->extra_colormap)
-						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap->rgba, rover->master->frontsector->extra_colormap->fadergba);
+						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap->rgba);
 					else
-						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG, FADEFOG);
+						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG);
 
 					HWR_AddTransparentFloor(0,
 					                       &extrasubsectors[num],
 										   true,
 					                       *rover->topheight,
 					                       *gr_frontsector->lightlist[light].lightlevel,
-					                       alpha, rover->master->frontsector, PF_Translucent|PF_NoTexture,
+					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, rover->master->frontsector->extra_colormap);
 				}
-				else if (rover->flags & FF_TRANSLUCENT)
+				else if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
 				{
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 #ifndef SORTING
@@ -4171,12 +4059,10 @@ static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float v
 
 static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float this_scale)
 {
-	UINT8 i;
-	float tr_x, tr_y;
-	FOutVector *wv;
 	FOutVector swallVerts[4];
 	FSurfaceInfo sSurf;
 	fixed_t floorheight, mobjfloor;
+	float offset = 0;
 
 	mobjfloor = HWR_OpaqueFloorAtPos(
 		spr->mobj->x, spr->mobj->y,
@@ -4215,6 +4101,8 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t
 		}
 
 		floorheight = FixedInt(spr->mobj->z - floorheight);
+
+		offset = floorheight;
 	}
 	else
 		floorheight = FixedInt(spr->mobj->z - mobjfloor);
@@ -4227,47 +4115,42 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t
 	//  0--1
 
 	// x1/x2 were already scaled in HWR_ProjectSprite
+	// First match the normal sprite
 	swallVerts[0].x = swallVerts[3].x = spr->x1;
 	swallVerts[2].x = swallVerts[1].x = spr->x2;
+	swallVerts[0].z = swallVerts[3].z = spr->z1;
+	swallVerts[2].z = swallVerts[1].z = spr->z2;
 
 	if (spr->mobj && this_scale != 1.0f)
 	{
 		// Always a pixel above the floor, perfectly flat.
 		swallVerts[0].y = swallVerts[1].y = swallVerts[2].y = swallVerts[3].y = spr->ty - gpatch->topoffset * this_scale - (floorheight+3);
 
-		swallVerts[0].z = swallVerts[1].z = spr->tz - (gpatch->height-gpatch->topoffset) * this_scale;
-		swallVerts[2].z = swallVerts[3].z = spr->tz + gpatch->topoffset * this_scale;
+		// Now transform the TOP vertices along the floor in the direction of the camera
+		swallVerts[3].x = spr->x1 + ((gpatch->height * this_scale) + offset) * gr_viewcos;
+		swallVerts[2].x = spr->x2 + ((gpatch->height * this_scale) + offset) * gr_viewcos;
+		swallVerts[3].z = spr->z1 + ((gpatch->height * this_scale) + offset) * gr_viewsin;
+		swallVerts[2].z = spr->z2 + ((gpatch->height * this_scale) + offset) * gr_viewsin;
 	}
 	else
 	{
 		// Always a pixel above the floor, perfectly flat.
 		swallVerts[0].y = swallVerts[1].y = swallVerts[2].y = swallVerts[3].y = spr->ty - gpatch->topoffset - (floorheight+3);
 
-		// Spread out top away from the camera. (Fixme: Make it always move out in the same direction!... somehow.)
-		swallVerts[0].z = swallVerts[1].z = spr->tz - (gpatch->height-gpatch->topoffset);
-		swallVerts[2].z = swallVerts[3].z = spr->tz + gpatch->topoffset;
+		// Now transform the TOP vertices along the floor in the direction of the camera
+		swallVerts[3].x = spr->x1 + (gpatch->height + offset) * gr_viewcos;
+		swallVerts[2].x = spr->x2 + (gpatch->height + offset) * gr_viewcos;
+		swallVerts[3].z = spr->z1 + (gpatch->height + offset) * gr_viewsin;
+		swallVerts[2].z = spr->z2 + (gpatch->height + offset) * gr_viewsin;
 	}
 
-	// transform
-	wv = swallVerts;
-
-	for (i = 0; i < 4; i++,wv++)
+	// We also need to move the bottom ones away when shadowoffs is on
+	if (cv_shadowoffs.value)
 	{
-		// Offset away from the camera based on height from floor.
-		if (cv_shadowoffs.value)
-			wv->z += floorheight;
-		wv->z += 3;
-
-		//look up/down ----TOTAL SUCKS!!!--- do the 2 in one!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-		tr_x = wv->z;
-		tr_y = wv->y;
-		wv->y = (tr_x * gr_viewludcos) + (tr_y * gr_viewludsin);
-		wv->z = (tr_x * gr_viewludsin) - (tr_y * gr_viewludcos);
-		// ---------------------- mega lame test ----------------------------------
-
-		//scale y before frustum so that frustum can be scaled to screen height
-		wv->y *= ORIGINAL_ASPECT * gr_fovlud;
-		wv->x *= gr_fovlud;
+		swallVerts[0].x = spr->x1 + offset * gr_viewcos;
+		swallVerts[1].x = spr->x2 + offset * gr_viewcos;
+		swallVerts[0].z = spr->z1 + offset * gr_viewsin;
+		swallVerts[1].z = spr->z2 + offset * gr_viewsin;
 	}
 
 	if (spr->flip)
@@ -4347,83 +4230,74 @@ static void HWR_DrawSpriteShadow(gr_vissprite_t *spr, GLPatch_t *gpatch, float t
 	}
 }
 
-// -----------------+
-// HWR_DrawSprite   : Draw flat sprites
-//                  : (monsters, bonuses, weapons, lights, ...)
-// Returns          :
-// -----------------+
-static void HWR_DrawSprite(gr_vissprite_t *spr)
+static void HWR_SplitSprite(gr_vissprite_t *spr)
 {
-	UINT8 i;
-	float tr_x, tr_y, this_scale = 1.0f;
+	float this_scale = 1.0f;
 	FOutVector wallVerts[4];
-	FOutVector *wv;
-	GLPatch_t *gpatch; // sprite patch converted to hardware
+	GLPatch_t *gpatch;
 	FSurfaceInfo Surf;
 	const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
-	//const boolean papersprite = (spr->mobj && (spr->mobj->frame & FF_PAPERSPRITE));
-	if (spr->mobj)
-		this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
-	if (hires)
-		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)spr->mobj->skin)->highresscale);
+	extracolormap_t *colormap;
+	FUINT lightlevel;
+	FBITFIELD blend = 0;
+	UINT8 alpha;
 
-	if (!spr->mobj)
-		return;
+	INT32 i;
+	float realtop, realbot, top, bot;
+	float towtop, towbot, towmult;
+	float bheight;
+	const sector_t *sector = spr->mobj->subsector->sector;
+	const lightlist_t *list = sector->lightlist;
+#ifdef ESLOPE
+	float endrealtop, endrealbot, endtop, endbot;
+	float endbheight;
+	fixed_t temp;
+	fixed_t v1x, v1y, v2x, v2y;
+#endif
 
-	if (!spr->mobj->subsector)
-		return;
+	this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
 
-	// cache sprite graphics
-	//12/12/99: Hurdler:
-	//          OK, I don't change anything for MD2 support because I want to be
-	//          sure to do it the right way. So actually, we keep normal sprite
-	//          in memory and we add the md2 model if it exists for that sprite
+	if (hires)
+		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)spr->mobj->skin)->highresscale);
 
 	gpatch = W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
 
+	// cache the patch in the graphics card memory
+	//12/12/99: Hurdler: same comment as above (for md2)
+	//Hurdler: 25/04/2000: now support colormap in hardware mode
+	HWR_GetMappedPatch(gpatch, spr->colormap);
+
+	// Draw shadow BEFORE sprite
+	if (cv_shadow.value // Shadows enabled
+		&& (spr->mobj->flags & (MF_SCENERY|MF_SPAWNCEILING|MF_NOGRAVITY)) != (MF_SCENERY|MF_SPAWNCEILING|MF_NOGRAVITY) // Ceiling scenery have no shadow.
+		&& !(spr->mobj->flags2 & MF2_DEBRIS) // Debris have no corona or shadow.
 #ifdef ALAM_LIGHTING
-	if (!(spr->mobj->flags2 & MF2_DEBRIS) && (spr->mobj->sprite != SPR_PLAY ||
-	 (spr->mobj->player && spr->mobj->player->powers[pw_super])))
-		HWR_DL_AddLight(spr, gpatch);
+		&& !(t_lspr[spr->mobj->sprite]->type // Things with dynamic lights have no shadow.
+		&& (!spr->mobj->player || spr->mobj->player->powers[pw_super])) // Except for non-super players.
 #endif
+		&& (spr->mobj->z >= spr->mobj->floorz)) // Without this, your shadow shows on the floor, even after you die and fall through the ground.
+	{
+		////////////////////
+		// SHADOW SPRITE! //
+		////////////////////
+		HWR_DrawSpriteShadow(spr, gpatch, this_scale);
+	}
 
-	// create the sprite billboard
-	//
-	//  3--2
-	//  | /|
-	//  |/ |
-	//  0--1
-
-	// these were already scaled in HWR_ProjectSprite
 	wallVerts[0].x = wallVerts[3].x = spr->x1;
 	wallVerts[2].x = wallVerts[1].x = spr->x2;
+	wallVerts[0].z = wallVerts[3].z = spr->z1;
+	wallVerts[1].z = wallVerts[2].z = spr->z2;
+
 	wallVerts[2].y = wallVerts[3].y = spr->ty;
 	if (spr->mobj && this_scale != 1.0f)
 		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height * this_scale;
 	else
 		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height;
 
-	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
-	// and the 2d map coords of start/end vertices
-	wallVerts[0].z = wallVerts[3].z = spr->z1;
-	wallVerts[2].z = wallVerts[1].z = spr->z2;
-
-	// transform
-	wv = wallVerts;
-
-	for (i = 0; i < 4; i++,wv++)
-	{
-		//look up/down ----TOTAL SUCKS!!!--- do the 2 in one!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-		tr_x = wv->z;
-		tr_y = wv->y;
-		wv->y = (tr_x * gr_viewludcos) + (tr_y * gr_viewludsin);
-		wv->z = (tr_x * gr_viewludsin) - (tr_y * gr_viewludcos);
-		// ---------------------- mega lame test ----------------------------------
-
-		//scale y before frustum so that frustum can be scaled to screen height
-		wv->y *= ORIGINAL_ASPECT * gr_fovlud;
-		wv->x *= gr_fovlud;
-	}
+	v1x = FLOAT_TO_FIXED(spr->x1);
+	v1y = FLOAT_TO_FIXED(spr->z1);
+	v2x = FLOAT_TO_FIXED(spr->x2);
+	v2y = FLOAT_TO_FIXED(spr->z2);
 
 	if (spr->flip)
 	{
@@ -4444,57 +4318,320 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 		wallVerts[0].tow = wallVerts[1].tow = gpatch->max_t;
 	}
 
-	// cache the patch in the graphics card memory
-	//12/12/99: Hurdler: same comment as above (for md2)
-	//Hurdler: 25/04/2000: now support colormap in hardware mode
-	HWR_GetMappedPatch(gpatch, spr->colormap);
+	realtop = top = wallVerts[3].y;
+	realbot = bot = wallVerts[0].y;
+	towtop = wallVerts[3].tow;
+	towbot = wallVerts[0].tow;
+	towmult = (towbot - towtop) / (top - bot);
 
-	// Draw shadow BEFORE sprite
-	if (cv_shadow.value // Shadows enabled
-		&& (spr->mobj->flags & (MF_SCENERY|MF_SPAWNCEILING|MF_NOGRAVITY)) != (MF_SCENERY|MF_SPAWNCEILING|MF_NOGRAVITY) // Ceiling scenery have no shadow.
-		&& !(spr->mobj->flags2 & MF2_DEBRIS) // Debris have no corona or shadow.
-#ifdef ALAM_LIGHTING
-		&& !(t_lspr[spr->mobj->sprite]->type // Things with dynamic lights have no shadow.
-		&& (!spr->mobj->player || spr->mobj->player->powers[pw_super])) // Except for non-super players.
+#ifdef ESLOPE
+	endrealtop = endtop = wallVerts[2].y;
+	endrealbot = endbot = wallVerts[1].y;
 #endif
-		&& (spr->mobj->z >= spr->mobj->floorz)) // Without this, your shadow shows on the floor, even after you die and fall through the ground.
+
+	if (!cv_translucency.value) // translucency disabled
 	{
-		////////////////////
-		// SHADOW SPRITE! //
-		////////////////////
-		HWR_DrawSpriteShadow(spr, gpatch, this_scale);
+		Surf.FlatColor.s.alpha = 0xFF;
+		blend = PF_Translucent|PF_Occlude;
+	}
+	else if (spr->mobj->flags2 & MF2_SHADOW)
+	{
+		Surf.FlatColor.s.alpha = 0x40;
+		blend = PF_Translucent;
+	}
+	else if (spr->mobj->frame & FF_TRANSMASK)
+		blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
+	else
+	{
+		// BP: i agree that is little better in environement but it don't
+		//     work properly under glide nor with fogcolor to ffffff :(
+		// Hurdler: PF_Environement would be cool, but we need to fix
+		//          the issue with the fog before
+		Surf.FlatColor.s.alpha = 0xFF;
+		blend = PF_Translucent|PF_Occlude;
 	}
 
-	// This needs to be AFTER the shadows so that the regular sprites aren't drawn completely black.
-	// sprite lighting by modulating the RGB components
-	/// \todo coloured
+	alpha = Surf.FlatColor.s.alpha;
 
-	// colormap test
-	{
-		sector_t *sector = spr->mobj->subsector->sector;
-		UINT8 lightlevel = 255;
-		extracolormap_t *colormap = sector->extra_colormap;
+	// Start with the lightlevel and colormap from the top of the sprite
+	lightlevel = *list[sector->numlights - 1].lightlevel;
+	colormap = list[sector->numlights - 1].extra_colormap;
+	i = 0;
+	temp = FLOAT_TO_FIXED(realtop);
 
-		if (sector->numlights)
+	if (spr->mobj->frame & FF_FULLBRIGHT)
+		lightlevel = 255;
+
+#ifdef ESLOPE
+	for (i = 1; i < sector->numlights; i++)
+	{
+		fixed_t h = sector->lightlist[i].slope ? P_GetZAt(sector->lightlist[i].slope, spr->mobj->x, spr->mobj->y)
+					: sector->lightlist[i].height;
+		if (h <= temp)
 		{
-			INT32 light;
+			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+				lightlevel = *list[i-1].lightlevel;
+			colormap = list[i-1].extra_colormap;
+			break;
+		}
+	}
+#else
+	i = R_GetPlaneLight(sector, temp, false);
+	if (!(spr->mobj->frame & FF_FULLBRIGHT))
+		lightlevel = *list[i].lightlevel;
+	colormap = list[i].extra_colormap;
+#endif
 
-			light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before
+	for (i = 0; i < sector->numlights; i++)
+	{
+#ifdef ESLOPE
+		if (endtop < endrealbot)
+#endif
+		if (top < realbot)
+			return;
 
+		// even if we aren't changing colormap or lightlevel, we still need to continue drawing down the sprite
+		if (!(list[i].flags & FF_NOSHADE) && (list[i].flags & FF_CUTSPRITES))
+		{
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = *sector->lightlist[light].lightlevel;
+				lightlevel = *list[i].lightlevel;
+			colormap = list[i].extra_colormap;
+		}
 
-			if (sector->lightlist[light].extra_colormap)
-				colormap = sector->lightlist[light].extra_colormap;
+#ifdef ESLOPE
+		if (i + 1 < sector->numlights)
+		{
+			if (list[i+1].slope)
+			{
+				temp = P_GetZAt(list[i+1].slope, v1x, v1y);
+				bheight = FIXED_TO_FLOAT(temp);
+				temp = P_GetZAt(list[i+1].slope, v2x, v2y);
+				endbheight = FIXED_TO_FLOAT(temp);
+			}
+			else
+				bheight = endbheight = FIXED_TO_FLOAT(list[i+1].height);
 		}
 		else
 		{
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = sector->lightlevel;
-
-			if (sector->extra_colormap)
-				colormap = sector->extra_colormap;
+			bheight = realbot;
+			endbheight = endrealbot;
 		}
+#else
+		if (i + 1 < sector->numlights)
+		{
+			bheight = FIXED_TO_FLOAT(list[i+1].height);
+		}
+		else
+		{
+			bheight = realbot;
+		}
+#endif
+
+#ifdef ESLOPE
+		if (endbheight >= endtop)
+#endif
+		if (bheight >= top)
+			continue;
+
+		bot = bheight;
+
+		if (bot < realbot)
+			bot = realbot;
+
+#ifdef ESLOPE
+		endbot = endbheight;
+
+		if (endbot < endrealbot)
+			endbot = endrealbot;
+#endif
+
+#ifdef ESLOPE
+		wallVerts[3].tow = towtop + ((realtop - top) * towmult);
+		wallVerts[2].tow = towtop + ((endrealtop - endtop) * towmult);
+		wallVerts[0].tow = towtop + ((realtop - bot) * towmult);
+		wallVerts[1].tow = towtop + ((endrealtop - endbot) * towmult);
+
+		wallVerts[3].y = top;
+		wallVerts[2].y = endtop;
+		wallVerts[0].y = bot;
+		wallVerts[1].y = endbot;
+#else
+		wallVerts[3].tow = wallVerts[2].tow = towtop + ((realtop - top) * towmult);
+		wallVerts[0].tow = wallVerts[1].tow = towtop + ((realtop - bot) * towmult);
+
+		wallVerts[2].y = wallVerts[3].y = top;
+		wallVerts[0].y = wallVerts[1].y = bot;
+#endif
+
+		if (colormap)
+			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, colormap->rgba, colormap->fadergba, false, false);
+		else
+			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, NORMALFOG, FADEFOG, false, false);
+
+		Surf.FlatColor.s.alpha = alpha;
+
+		HWD.pfnDrawPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip);
+
+		top = bot;
+#ifdef ESLOPE
+		endtop = endbot;
+#endif
+	}
+
+	bot = realbot;
+#ifdef ESLOPE
+	endbot = endrealbot;
+	if (endtop <= endrealbot)
+#endif
+	if (top <= realbot)
+		return;
+
+	// If we're ever down here, somehow the above loop hasn't draw all the light levels of sprite
+#ifdef ESLOPE
+	wallVerts[3].tow = towtop + ((realtop - top) * towmult);
+	wallVerts[2].tow = towtop + ((endrealtop - endtop) * towmult);
+	wallVerts[0].tow = towtop + ((realtop - bot) * towmult);
+	wallVerts[1].tow = towtop + ((endrealtop - endbot) * towmult);
+
+	wallVerts[3].y = top;
+	wallVerts[2].y = endtop;
+	wallVerts[0].y = bot;
+	wallVerts[1].y = endbot;
+#else
+	wallVerts[3].tow = wallVerts[2].tow = towtop + ((realtop - top) * towmult);
+	wallVerts[0].tow = wallVerts[1].tow = towtop + ((realtop - bot) * towmult);
+
+	wallVerts[2].y = wallVerts[3].y = top;
+	wallVerts[0].y = wallVerts[1].y = bot;
+#endif
+
+	if (colormap)
+		Surf.FlatColor.rgba = HWR_Lighting(lightlevel, colormap->rgba, colormap->fadergba, false, false);
+	else
+		Surf.FlatColor.rgba = HWR_Lighting(lightlevel, NORMALFOG, FADEFOG, false, false);
+
+	Surf.FlatColor.s.alpha = alpha;
+
+	HWD.pfnDrawPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip);
+}
+
+// -----------------+
+// HWR_DrawSprite   : Draw flat sprites
+//                  : (monsters, bonuses, weapons, lights, ...)
+// Returns          :
+// -----------------+
+static void HWR_DrawSprite(gr_vissprite_t *spr)
+{
+	float this_scale = 1.0f;
+	FOutVector wallVerts[4];
+	GLPatch_t *gpatch; // sprite patch converted to hardware
+	FSurfaceInfo Surf;
+	const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
+	//const boolean papersprite = (spr->mobj && (spr->mobj->frame & FF_PAPERSPRITE));
+	if (spr->mobj)
+		this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
+	if (hires)
+		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)spr->mobj->skin)->highresscale);
+
+	if (!spr->mobj)
+		return;
+
+	if (!spr->mobj->subsector)
+		return;
+
+	if (spr->mobj->subsector->sector->numlights)
+	{
+		HWR_SplitSprite(spr);
+		return;
+	}
+
+	// cache sprite graphics
+	//12/12/99: Hurdler:
+	//          OK, I don't change anything for MD2 support because I want to be
+	//          sure to do it the right way. So actually, we keep normal sprite
+	//          in memory and we add the md2 model if it exists for that sprite
+
+	gpatch = W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+
+#ifdef ALAM_LIGHTING
+	if (!(spr->mobj->flags2 & MF2_DEBRIS) && (spr->mobj->sprite != SPR_PLAY ||
+	 (spr->mobj->player && spr->mobj->player->powers[pw_super])))
+		HWR_DL_AddLight(spr, gpatch);
+#endif
+
+	// create the sprite billboard
+	//
+	//  3--2
+	//  | /|
+	//  |/ |
+	//  0--1
+
+	// these were already scaled in HWR_ProjectSprite
+	wallVerts[0].x = wallVerts[3].x = spr->x1;
+	wallVerts[2].x = wallVerts[1].x = spr->x2;
+	wallVerts[2].y = wallVerts[3].y = spr->ty;
+	if (spr->mobj && this_scale != 1.0f)
+		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height * this_scale;
+	else
+		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height;
+
+	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
+	// and the 2d map coords of start/end vertices
+	wallVerts[0].z = wallVerts[3].z = spr->z1;
+	wallVerts[1].z = wallVerts[2].z = spr->z2;
+
+	if (spr->flip)
+	{
+		wallVerts[0].sow = wallVerts[3].sow = gpatch->max_s;
+		wallVerts[2].sow = wallVerts[1].sow = 0;
+	}else{
+		wallVerts[0].sow = wallVerts[3].sow = 0;
+		wallVerts[2].sow = wallVerts[1].sow = gpatch->max_s;
+	}
+
+	// flip the texture coords (look familiar?)
+	if (spr->vflip)
+	{
+		wallVerts[3].tow = wallVerts[2].tow = gpatch->max_t;
+		wallVerts[0].tow = wallVerts[1].tow = 0;
+	}else{
+		wallVerts[3].tow = wallVerts[2].tow = 0;
+		wallVerts[0].tow = wallVerts[1].tow = gpatch->max_t;
+	}
+
+	// cache the patch in the graphics card memory
+	//12/12/99: Hurdler: same comment as above (for md2)
+	//Hurdler: 25/04/2000: now support colormap in hardware mode
+	HWR_GetMappedPatch(gpatch, spr->colormap);
+
+	// Draw shadow BEFORE sprite
+	if (cv_shadow.value // Shadows enabled
+		&& (spr->mobj->flags & (MF_SCENERY|MF_SPAWNCEILING|MF_NOGRAVITY)) != (MF_SCENERY|MF_SPAWNCEILING|MF_NOGRAVITY) // Ceiling scenery have no shadow.
+		&& !(spr->mobj->flags2 & MF2_DEBRIS) // Debris have no corona or shadow.
+#ifdef ALAM_LIGHTING
+		&& !(t_lspr[spr->mobj->sprite]->type // Things with dynamic lights have no shadow.
+		&& (!spr->mobj->player || spr->mobj->player->powers[pw_super])) // Except for non-super players.
+#endif
+		&& (spr->mobj->z >= spr->mobj->floorz)) // Without this, your shadow shows on the floor, even after you die and fall through the ground.
+	{
+		////////////////////
+		// SHADOW SPRITE! //
+		////////////////////
+		HWR_DrawSpriteShadow(spr, gpatch, this_scale);
+	}
+
+	// This needs to be AFTER the shadows so that the regular sprites aren't drawn completely black.
+	// sprite lighting by modulating the RGB components
+	/// \todo coloured
+
+	// colormap test
+	{
+		sector_t *sector = spr->mobj->subsector->sector;
+		UINT8 lightlevel = 255;
+		extracolormap_t *colormap = sector->extra_colormap;
+
+		if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			lightlevel = sector->lightlevel;
 
 		if (colormap)
 			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, colormap->rgba, colormap->fadergba, false, false);
@@ -4534,11 +4671,8 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 // Sprite drawer for precipitation
 static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 {
-	UINT8 i;
 	FBITFIELD blend = 0;
-	float tr_x, tr_y;
 	FOutVector wallVerts[4];
-	FOutVector *wv;
 	GLPatch_t *gpatch; // sprite patch converted to hardware
 	FSurfaceInfo Surf;
 
@@ -4564,24 +4698,8 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 
 	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
 	// and the 2d map coords of start/end vertices
-	wallVerts[0].z = wallVerts[1].z = wallVerts[2].z = wallVerts[3].z = spr->tz;
-
-	// transform
-	wv = wallVerts;
-
-	for (i = 0; i < 4; i++, wv++)
-	{
-		//look up/down ----TOTAL SUCKS!!!--- do the 2 in one!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-		tr_x = wv->z;
-		tr_y = wv->y;
-		wv->y = (tr_x * gr_viewludcos) + (tr_y * gr_viewludsin);
-		wv->z = (tr_x * gr_viewludsin) - (tr_y * gr_viewludcos);
-		// ---------------------- mega lame test ----------------------------------
-
-		//scale y before frustum so that frustum can be scaled to screen height
-		wv->y *= ORIGINAL_ASPECT * gr_fovlud;
-		wv->x *= gr_fovlud;
-	}
+	wallVerts[0].z = wallVerts[3].z = spr->z1;
+	wallVerts[1].z = wallVerts[2].z = spr->z2;
 
 	wallVerts[0].sow = wallVerts[3].sow = 0;
 	wallVerts[2].sow = wallVerts[1].sow = gpatch->max_s;
@@ -4714,6 +4832,33 @@ static void HWR_SortVisSprites(void)
 		gr_vsprsortedhead.prev->next = best;
 		gr_vsprsortedhead.prev = best;
 	}
+
+	// Sryder:	Oh boy, while it's nice having ALL the sprites sorted properly, it fails when we bring MD2's into the
+	//			mix and they want to be translucent. So let's place all the translucent sprites and MD2's AFTER
+	//			everything else, but still ordered of course, the depth buffer can handle the opaque ones plenty fine.
+	//			We just need to move all translucent ones to the end in order
+	// TODO:	Fully sort all sprites and MD2s with walls and floors, this part will be unnecessary after that
+	best = gr_vsprsortedhead.next;
+	for (i = 0; i < gr_visspritecount; i++)
+	{
+		if ((best->mobj->flags2 & MF2_SHADOW) || (best->mobj->frame & FF_TRANSMASK))
+		{
+			if (best == gr_vsprsortedhead.next)
+			{
+				gr_vsprsortedhead.next = best->next;
+			}
+			best->prev->next = best->next;
+			best->next->prev = best->prev;
+			best->prev = gr_vsprsortedhead.prev;
+			gr_vsprsortedhead.prev->next = best;
+			gr_vsprsortedhead.prev = best;
+			ds = best;
+			best = best->next;
+			ds->next = &gr_vsprsortedhead;
+		}
+		else
+			best = best->next;
+	}
 }
 
 // A drawnode is something that points to a 3D floor, 3D side, or masked
@@ -5045,6 +5190,7 @@ static void HWR_CreateDrawNodes(void)
 //  Draw all vissprites
 // --------------------------------------------------------------------------
 #ifdef SORTING
+// added the stransform so they can be switched as drawing happenes so MD2s and sprites are sorted correctly with each other
 static void HWR_DrawSprites(void)
 {
 	if (gr_visspritecount > 0)
@@ -5065,44 +5211,20 @@ static void HWR_DrawSprites(void)
 				{
 					if (!cv_grmd2.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f)
 						HWR_DrawSprite(spr);
+					else
+						HWR_DrawMD2(spr);
 				}
-				else if (!cv_grmd2.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
-					HWR_DrawSprite(spr);
-		}
-	}
-}
-#endif
-// --------------------------------------------------------------------------
-//  Draw all MD2
-// --------------------------------------------------------------------------
-static void HWR_DrawMD2S(void)
-{
-	if (gr_visspritecount > 0)
-	{
-		gr_vissprite_t *spr;
-
-		// draw all MD2 back to front
-		for (spr = gr_vsprsortedhead.next;
-			spr != &gr_vsprsortedhead;
-			spr = spr->next)
-		{
-#ifdef HWPRECIP
-			if (!spr->precip)
-			{
-#endif
-				if (spr->mobj && spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
+				else
 				{
-					if (md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound == false && md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale > 0.0f)
+					if (!cv_grmd2.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
+						HWR_DrawSprite(spr);
+					else
 						HWR_DrawMD2(spr);
 				}
-				else if (md2_models[spr->mobj->sprite].notfound == false && md2_models[spr->mobj->sprite].scale > 0.0f)
-					HWR_DrawMD2(spr);
-#ifdef HWPRECIP
-			}
-#endif
 		}
 	}
 }
+#endif
 
 // --------------------------------------------------------------------------
 // HWR_AddSprites
@@ -5187,8 +5309,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 {
 	gr_vissprite_t *vis;
 	float tr_x, tr_y;
-	float tx, tz;
+	float tz;
 	float x1, x2;
+	float rightsin, rightcos;
 	float this_scale;
 	float gz, gzt;
 	spritedef_t *sprdef;
@@ -5201,8 +5324,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	angle_t ang;
 	INT32 heightsec, phs;
 	const boolean papersprite = (thing->frame & FF_PAPERSPRITE);
-	float offset;
-	float ang_scale = 1.0f, ang_scalez = 0.0f;
+	angle_t mobjangle = (thing->player ? thing->player->drawangle : thing->angle);
 	float z1, z2;
 
 	if (!thing)
@@ -5221,7 +5343,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (tz < ZCLIP_PLANE && !papersprite && (!cv_grmd2.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
 		return;
 
-	tx = (tr_x * gr_viewsin) - (tr_y * gr_viewcos);
+	// The above can stay as it works for cutting sprites that are too close
+	tr_x = FIXED_TO_FLOAT(thing->x);
+	tr_y = FIXED_TO_FLOAT(thing->y);
 
 	// decide which patch to use for sprite relative to player
 #ifdef RANGECHECK
@@ -5256,26 +5380,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		I_Error("sprframes NULL for sprite %d\n", thing->sprite);
 #endif
 
-	if (papersprite)
-	{
-		// Use the actual view angle, rather than the angle formed
-		// between the view point and the thing
-		// this makes sure paper sprites always appear at the right angle!
-		// Note: DO NOT do this in software mode version, it actually
-		// makes papersprites look WORSE there (I know, I've tried)
-		// Monster Iestyn - 13/05/17
-		ang = dup_viewangle - (thing->player ? thing->player->drawangle : thing->angle);
-		ang_scale = FIXED_TO_FLOAT(FINESINE(ang>>ANGLETOFINESHIFT));
-		ang_scalez = FIXED_TO_FLOAT(FINECOSINE(ang>>ANGLETOFINESHIFT));
-
-		if (ang_scale < 0)
-		{
-			ang_scale = -ang_scale;
-			ang_scalez = -ang_scalez;
-		}
-	}
-	else if (sprframe->rotate != SRF_SINGLE)
-		ang = R_PointToAngle (thing->x, thing->y) - (thing->player ? thing->player->drawangle : thing->angle);
+	ang = R_PointToAngle (thing->x, thing->y) - mobjangle;
 
 	if (sprframe->rotate == SRF_SINGLE)
 	{
@@ -5283,6 +5388,14 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		rot = 0;                        //Fab: for vis->patch below
 		lumpoff = sprframe->lumpid[0];     //Fab: see note above
 		flip = sprframe->flip; // Will only be 0x00 or 0xFF
+
+		if (papersprite && ang < ANGLE_180)
+		{
+			if (flip)
+				flip = 0;
+			else
+				flip = 255;
+		}
 	}
 	else
 	{
@@ -5297,38 +5410,57 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		//Fab: lumpid is the index for spritewidth,spriteoffset... tables
 		lumpoff = sprframe->lumpid[rot];
 		flip = sprframe->flip & (1<<rot);
+
+		if (papersprite && ang < ANGLE_180)
+		{
+			if (flip)
+				flip = 0;
+			else
+				flip = 1<<rot;
+		}
 	}
 
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
 		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)thing->skin)->highresscale);
 
-	// calculate edges of the shape
-	if (flip)
-		offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale;
+	if (papersprite)
+	{
+		rightsin = FIXED_TO_FLOAT(FINESINE((mobjangle)>>ANGLETOFINESHIFT));
+		rightcos = FIXED_TO_FLOAT(FINECOSINE((mobjangle)>>ANGLETOFINESHIFT));
+	}
 	else
-		offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale;
-
-	z1 = tz - (offset * ang_scalez);
-	tx -= offset * ang_scale;
-
-	// project x
-	x1 = gr_windowcenterx + (tx * gr_centerx / tz);
-
-	//faB : tr_x doesnt matter
-	// hurdler: it's used in cliptosolidsegs
-	tr_x = x1;
-
-	x1 = tx;
+	{
+		rightsin = FIXED_TO_FLOAT(FINESINE((viewangle + ANGLE_90)>>ANGLETOFINESHIFT));
+		rightcos = FIXED_TO_FLOAT(FINECOSINE((viewangle + ANGLE_90)>>ANGLETOFINESHIFT));
+	}
 
-	offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width) * this_scale;
+	if (flip)
+	{
+		x1 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale);
+		x2 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale);
+	}
+	else
+	{
+		x1 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale);
+		x2 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale);
+	}
 
-	z2 = z1 + (offset * ang_scalez);
-	tx += offset * ang_scale;
+	// test if too close
+/*
+	if (papersprite)
+	{
+		z1 = tz - x1 * angle_scalez;
+		z2 = tz + x2 * angle_scalez;
 
-	if (papersprite && max(z1, z2) < ZCLIP_PLANE)
-		return;
+		if (max(z1, z2) < ZCLIP_PLANE)
+			return;
+	}
+*/
 
-	x2 = gr_windowcenterx + (tx * gr_centerx / tz);
+	z1 = tr_y + x1 * rightsin;
+	z2 = tr_y - x2 * rightsin;
+	x1 = tr_x + x1 * rightcos;
+	x2 = tr_x - x2 * rightcos;
 
 	if (vflip)
 	{
@@ -5365,13 +5497,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	// store information in a vissprite
 	vis = HWR_NewVisSprite();
 	vis->x1 = x1;
-#if 0
 	vis->x2 = x2;
-#else
-	(void)x2;
-#endif
-	vis->x2 = tx;
-	vis->tz = tz;
+	vis->tz = tz; // Keep tz for the simple sprite sorting that happens
 	vis->dispoffset = thing->info->dispoffset; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
 	vis->patchlumpnum = sprframe->lumppat[rot];
 	vis->flip = flip;
@@ -5404,7 +5531,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		vis->colormap = colormaps;
 
 	// set top/bottom coords
-	vis->ty = gzt - gr_viewz;
+	vis->ty = gzt;
 
 	//CONS_Debug(DBG_RENDER, "------------------\nH: sprite  : %d\nH: frame   : %x\nH: type    : %d\nH: sname   : %s\n\n",
 	//            thing->sprite, thing->frame, thing->type, sprnames[thing->sprite]);
@@ -5420,8 +5547,10 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 {
 	gr_vissprite_t *vis;
 	float tr_x, tr_y;
-	float tx, tz;
+	float tz;
 	float x1, x2;
+	float z1, z2;
+	float rightsin, rightcos;
 	spritedef_t *sprdef;
 	spriteframe_t *sprframe;
 	size_t lumpoff;
@@ -5439,7 +5568,8 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	if (tz < ZCLIP_PLANE)
 		return;
 
-	tx = (tr_x * gr_viewsin) - (tr_y * gr_viewcos);
+	tr_x = FIXED_TO_FLOAT(thing->x);
+	tr_y = FIXED_TO_FLOAT(thing->y);
 
 	// decide which patch to use for sprite relative to player
 	if ((unsigned)thing->sprite >= numsprites)
@@ -5466,32 +5596,32 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	lumpoff = sprframe->lumpid[0];
 	flip = sprframe->flip; // Will only be 0x00 or 0xFF
 
-	// calculate edges of the shape
-	tx -= FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset);
-
-	// project x
-	x1 = gr_windowcenterx + (tx * gr_centerx / tz);
-
-	//faB : tr_x doesnt matter
-	// hurdler: it's used in cliptosolidsegs
-	tr_x = x1;
-
-	x1 = tx;
+	rightsin = FIXED_TO_FLOAT(FINESINE((viewangle + ANGLE_90)>>ANGLETOFINESHIFT));
+	rightcos = FIXED_TO_FLOAT(FINECOSINE((viewangle + ANGLE_90)>>ANGLETOFINESHIFT));
+	if (flip)
+	{
+		x1 = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset);
+		x2 = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset);
+	}
+	else
+	{
+		x1 = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset);
+		x2 = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset);
+	}
 
-	tx += FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width);
-	x2 = gr_windowcenterx + (tx * gr_centerx / tz);
+	z1 = tr_y + x1 * rightsin;
+	z2 = tr_y - x2 * rightsin;
+	x1 = tr_x + x1 * rightcos;
+	x2 = tr_x - x2 * rightcos;
 
 	//
 	// store information in a vissprite
 	//
 	vis = HWR_NewVisSprite();
 	vis->x1 = x1;
-#if 0
 	vis->x2 = x2;
-#else
-	(void)x2;
-#endif
-	vis->x2 = tx;
+	vis->z1 = z1;
+	vis->z2 = z2;
 	vis->tz = tz;
 	vis->dispoffset = 0; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
 	vis->patchlumpnum = sprframe->lumppat[rot];
@@ -5501,7 +5631,7 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	vis->colormap = colormaps;
 
 	// set top/bottom coords
-	vis->ty = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset) - gr_viewz;
+	vis->ty = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset);
 
 	vis->precip = true;
 }
@@ -5529,12 +5659,13 @@ static void HWR_DrawSkyBackground(player_t *player)
 	//Hurdler: the sky is the only texture who need 4.0f instead of 1.0
 	//         because it's called just after clearing the screen
 	//         and thus, the near clipping plane is set to 3.99
-	v[0].x = v[3].x = -4.0f;
-	v[1].x = v[2].x =  4.0f;
-	v[0].y = v[1].y = -4.0f;
-	v[2].y = v[3].y =  4.0f;
+	// Sryder: Just use the near clipping plane value then
+	v[0].x = v[3].x = -ZCLIP_PLANE-1;
+	v[1].x = v[2].x =  ZCLIP_PLANE+1;
+	v[0].y = v[1].y = -ZCLIP_PLANE-1;
+	v[2].y = v[3].y =  ZCLIP_PLANE+1;
 
-	v[0].z = v[1].z = v[2].z = v[3].z = 4.0f;
+	v[0].z = v[1].z = v[2].z = v[3].z = ZCLIP_PLANE+1;
 
 	// X
 
@@ -5603,7 +5734,7 @@ static inline void HWR_ClearView(void)
 	                 (INT32)gr_viewwindowy,
 	                 (INT32)(gr_viewwindowx + gr_viewwidth),
 	                 (INT32)(gr_viewwindowy + gr_viewheight),
-	                 3.99f);
+	                 ZCLIP_PLANE);
 	HWD.pfnClearBuffer(false, true, 0);
 
 	//disable clip window - set to full size
@@ -5642,6 +5773,8 @@ void HWR_SetViewSize(void)
 
 	gr_pspritexscale = gr_viewwidth / BASEVIDWIDTH;
 	gr_pspriteyscale = ((vid.height*gr_pspritexscale*BASEVIDWIDTH)/BASEVIDHEIGHT)/vid.width;
+
+	HWD.pfnFlushScreenTextures();
 }
 
 // ==========================================================================
@@ -5650,7 +5783,6 @@ void HWR_SetViewSize(void)
 void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 {
 	const float fpov = FIXED_TO_FLOAT(cv_grfov.value+player->fovadd);
-	FTransform stransform;
 	postimg_t *type;
 
 	if (splitscreen && player == &players[secondarydisplayplayer])
@@ -5714,31 +5846,12 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	atransform.y      = gr_viewy;  // FIXED_TO_FLOAT(viewy)
 	atransform.z      = gr_viewz;  // FIXED_TO_FLOAT(viewz)
 	atransform.scalex = 1;
-	atransform.scaley = ORIGINAL_ASPECT;
+	atransform.scaley = (float)vid.width/vid.height;
 	atransform.scalez = 1;
 	atransform.fovxangle = fpov; // Tails
 	atransform.fovyangle = fpov; // Tails
 	atransform.splitscreen = splitscreen;
 
-	// Transform for sprites
-	stransform.anglex = 0.0f;
-	stransform.angley = -270.0f;
-
-	if (*type == postimg_flip)
-		stransform.flip = true;
-	else
-		stransform.flip = false;
-
-	stransform.x      = 0.0f;
-	stransform.y      = 0.0f;
-	stransform.z      = 0.0f;
-	stransform.scalex = 1;
-	stransform.scaley = 1;
-	stransform.scalez = 1;
-	stransform.fovxangle = 90.0f;
-	stransform.fovyangle = 90.0f;
-	stransform.splitscreen = splitscreen;
-
 	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
 
 	//------------------------------------------------------------------------
@@ -5827,10 +5940,7 @@ if (0)
 #ifdef SORTING
 	HWR_SortVisSprites();
 #endif
-	HWR_DrawMD2S();
 
-	// Draw the sprites with trivial transform
-	HWD.pfnSetTransform(&stransform);
 #ifdef SORTING
 	HWR_DrawSprites();
 #endif
@@ -5875,7 +5985,6 @@ if (0)
 void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 {
 	const float fpov = FIXED_TO_FLOAT(cv_grfov.value+player->fovadd);
-	FTransform stransform;
 	postimg_t *type;
 
 	const boolean skybox = (skyboxmo[0] && cv_skybox.value); // True if there's a skybox object and skyboxes are on
@@ -5954,31 +6063,12 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	atransform.y      = gr_viewy;  // FIXED_TO_FLOAT(viewy)
 	atransform.z      = gr_viewz;  // FIXED_TO_FLOAT(viewz)
 	atransform.scalex = 1;
-	atransform.scaley = ORIGINAL_ASPECT;
+	atransform.scaley = (float)vid.width/vid.height;
 	atransform.scalez = 1;
 	atransform.fovxangle = fpov; // Tails
 	atransform.fovyangle = fpov; // Tails
 	atransform.splitscreen = splitscreen;
 
-	// Transform for sprites
-	stransform.anglex = 0.0f;
-	stransform.angley = -270.0f;
-
-	if (*type == postimg_flip)
-		stransform.flip = true;
-	else
-		stransform.flip = false;
-
-	stransform.x      = 0.0f;
-	stransform.y      = 0.0f;
-	stransform.z      = 0.0f;
-	stransform.scalex = 1;
-	stransform.scaley = 1;
-	stransform.scalez = 1;
-	stransform.fovxangle = 90.0f;
-	stransform.fovyangle = 90.0f;
-	stransform.splitscreen = splitscreen;
-
 	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
 
 	//------------------------------------------------------------------------
@@ -6067,10 +6157,7 @@ if (0)
 #ifdef SORTING
 	HWR_SortVisSprites();
 #endif
-	HWR_DrawMD2S();
 
-	// Draw the sprites with trivial transform
-	HWD.pfnSetTransform(&stransform);
 #ifdef SORTING
 	HWR_DrawSprites();
 #endif
@@ -6258,6 +6345,7 @@ void HWR_Shutdown(void)
 	HWR_FreeExtraSubsectors();
 	HWR_FreePolyPool();
 	HWR_FreeTextureCache();
+	HWD.pfnFlushScreenTextures();
 }
 
 void transform(float *cx, float *cy, float *cz)
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index cb49f817ce1cf1f3e294755eebde0c19e9d08b74..1ae2d8fc39f578b7dc0a9687c18c8c90cde619b5 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -33,7 +33,7 @@ void HWR_Shutdown(void);
 
 void HWR_clearAutomap(void);
 void HWR_drawAMline(const fline_t *fl, INT32 color);
-void HWR_FadeScreenMenuBack(UINT32 color, INT32 height);
+void HWR_FadeScreenMenuBack(UINT16 color, UINT8 strength);
 void HWR_DrawConsoleBack(UINT32 color, INT32 height);
 void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player);
 void HWR_RenderPlayerView(INT32 viewnumber, player_t *player);
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 15ab53013c7c20a80b23972f9e815422f1fcb871..6ab268a8503faa258460c5676dfb2d8de7c9ac32 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -57,7 +57,7 @@ typedef struct GLRGBAFloat GLRGBAFloat;
 #define      N_PI_DEMI               (M_PIl/2.0f) //(1.5707963268f)
 
 #define      ASPECT_RATIO            (1.0f)  //(320.0f/200.0f)
-#define      FAR_CLIPPING_PLANE      150000.0f // Draw further! Tails 01-21-2001
+#define      FAR_CLIPPING_PLANE      32768.0f // Draw further! Tails 01-21-2001
 static float NEAR_CLIPPING_PLANE =   NZCLIP_PLANE;
 
 // **************************************************************************
@@ -101,10 +101,19 @@ static GLint       viewport[4];
 #endif
 
 // Yay for arbitrary  numbers! NextTexAvail is buggy for some reason.
-static GLuint screentexture = 60000;
-static GLuint startScreenWipe = 60001;
-static GLuint endScreenWipe = 60002;
-static GLuint finalScreenTexture = 60003;
+// Sryder:	NextTexAvail is broken for these because palette changes or changes to the texture filter or antialiasing
+//			flush all of the stored textures, leaving them unavailable at times such as between levels
+//			These need to start at 0 and be set to their number, and be reset to 0 when deleted so that intel GPUs
+//			can know when the textures aren't there, as textures are always considered resident in their virtual memory
+// TODO:	Store them in a more normal way
+#define SCRTEX_SCREENTEXTURE 65535
+#define SCRTEX_STARTSCREENWIPE 65534
+#define SCRTEX_ENDSCREENWIPE 65533
+#define SCRTEX_FINALSCREENTEXTURE 65532
+static GLuint screentexture = 0;
+static GLuint startScreenWipe = 0;
+static GLuint endScreenWipe = 0;
+static GLuint finalScreenTexture = 0;
 #if 0
 GLuint screentexture = FIRST_TEX_AVAIL;
 #endif
@@ -245,6 +254,7 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
 #define pglBindTexture glBindTexture
 /* texture mapping */ //GL_EXT_copy_texture
 #define pglCopyTexImage2D glCopyTexImage2D
+#define pglCopyTexSubImage2D glCopyTexSubImage2D
 
 #else //!STATIC_OPENGL
 
@@ -361,6 +371,8 @@ static PFNglBindTexture pglBindTexture;
 /* texture mapping */ //GL_EXT_copy_texture
 typedef void (APIENTRY * PFNglCopyTexImage2D) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border);
 static PFNglCopyTexImage2D pglCopyTexImage2D;
+typedef void (APIENTRY * PFNglCopyTexSubImage2D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);
+static PFNglCopyTexSubImage2D pglCopyTexSubImage2D;
 #endif
 /* GLU functions */
 typedef GLint (APIENTRY * PFNgluBuild2DMipmaps) (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *data);
@@ -460,6 +472,7 @@ boolean SetupGLfunc(void)
 	GETOPENGLFUNC(pglBindTexture , glBindTexture)
 
 	GETOPENGLFUNC(pglCopyTexImage2D , glCopyTexImage2D)
+	GETOPENGLFUNC(pglCopyTexSubImage2D , glCopyTexSubImage2D)
 
 #undef GETOPENGLFUNC
 
@@ -597,6 +610,10 @@ void SetModelView(GLint w, GLint h)
 {
 //	DBG_Printf("SetModelView(): %dx%d\n", (int)w, (int)h);
 
+	// The screen textures need to be flushed if the width or height change so that they be remade for the correct size
+	if (screen_width != w || screen_height != h)
+		FlushScreenTextures();
+
 	screen_width = w;
 	screen_height = h;
 
@@ -734,6 +751,7 @@ void Flush(void)
 		screentexture = FIRST_TEX_AVAIL;
 	}
 #endif
+
 	tex_downloaded = 0;
 }
 
@@ -948,26 +966,38 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 			switch (PolyFlags & PF_Blending) {
 				case PF_Translucent & PF_Blending:
 					pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
 				case PF_Masked & PF_Blending:
 					// Hurdler: does that mean lighting is only made by alpha src?
 					// it sounds ok, but not for polygonsmooth
 					pglBlendFunc(GL_SRC_ALPHA, GL_ZERO);                // 0 alpha = holes in texture
+					pglAlphaFunc(GL_GREATER, 0.5f);
 					break;
 				case PF_Additive & PF_Blending:
 					pglBlendFunc(GL_SRC_ALPHA, GL_ONE);                 // src * alpha + dest
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
 				case PF_Environment & PF_Blending:
 					pglBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
 				case PF_Substractive & PF_Blending:
 					// good for shadow
 					// not realy but what else ?
 					pglBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
+					break;
+				case PF_Fog & PF_Fog:
+					// Sryder: Fog
+					// multiplies input colour by input alpha, and destination colour by input colour, then adds them
+					pglBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR);
+					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
 				default : // must be 0, otherwise it's an error
 					// No blending
 					pglBlendFunc(GL_ONE, GL_ZERO);   // the same as no blending
+					pglAlphaFunc(GL_GREATER, 0.5f);
 					break;
 			}
 		}
@@ -1120,6 +1150,7 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 						tex[w*j+i].s.green = 0;
 						tex[w*j+i].s.blue  = 0;
 						tex[w*j+i].s.alpha = 0;
+						pTexInfo->flags |= TF_TRANSPARENT; // there is a hole in it
 					}
 					else
 					{
@@ -1189,8 +1220,17 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 		tex_downloaded = pTexInfo->downloaded;
 		pglBindTexture(GL_TEXTURE_2D, pTexInfo->downloaded);
 
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
+		// disable texture filtering on any texture that has holes so there's no dumb borders or blending issues
+		if (pTexInfo->flags & TF_TRANSPARENT)
+		{
+			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		}
+		else
+		{
+			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
+			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
+		}
 
 #ifdef USE_PALETTED_TEXTURE
 			//Hurdler: not really supported and not tested recently
@@ -1559,12 +1599,6 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 			ambient[1] = 0.75f;
 		if (ambient[2] > 0.75f)
 			ambient[2] = 0.75f;
-
-		if (color[3] < 255)
-		{
-			pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-			pglDepthMask(GL_FALSE);
-		}
 	}
 
 	pglEnable(GL_CULL_FACE);
@@ -1589,10 +1623,12 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 		pglMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
 		pglMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
 #endif
+		if (color[3] < 255)
+			SetBlend(PF_Translucent|PF_Modulated|PF_Clip);
+		else
+			SetBlend(PF_Masked|PF_Modulated|PF_Occlude|PF_Clip);
 	}
 
-	DrawPolygon(NULL, NULL, 0, PF_Masked|PF_Modulated|PF_Occlude|PF_Clip);
-
 	pglPushMatrix(); // should be the same as glLoadIdentity
 	//Hurdler: now it seems to work
 	pglTranslatef(pos->x, pos->z, pos->y);
@@ -1600,14 +1636,6 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 		scaley = -scaley;
 	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
 	pglRotatef(pos->anglex, -1.0f, 0.0f, 0.0f);
-	//pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
-
-	// Remove depth mask when the model is transparent so it doesn't cut thorugh sprites // SRB2CBTODO: For all stuff too?!
-	if (color && color[3] < 255)
-	{
-		pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
-		pglDepthMask(GL_FALSE);
-	}
 
 	val = *gl_cmd_buffer++;
 
@@ -1675,7 +1703,6 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	if (color)
 		pglDisable(GL_LIGHTING);
 	pglShadeModel(GL_FLAT);
-	pglDepthMask(GL_TRUE);
 	pglDisable(GL_CULL_FACE);
 }
 
@@ -1822,10 +1849,25 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 }
 #endif //SHUFFLE
 
+// Sryder:	This needs to be called whenever the screen changes resolution in order to reset the screen textures to use
+//			a new size
+EXPORT void HWRAPI(FlushScreenTextures) (void)
+{
+	pglDeleteTextures(1, &screentexture);
+	pglDeleteTextures(1, &startScreenWipe);
+	pglDeleteTextures(1, &endScreenWipe);
+	pglDeleteTextures(1, &finalScreenTexture);
+	screentexture = 0;
+	startScreenWipe = 0;
+	endScreenWipe = 0;
+	finalScreenTexture = 0;
+}
+
 // Create Screen to fade from
 EXPORT void HWRAPI(StartScreenWipe) (void)
 {
 	INT32 texsize = 2048;
+	boolean firstTime = (startScreenWipe == 0);
 
 	// Use a power of two texture, dammit
 	if(screen_width <= 512)
@@ -1834,20 +1876,29 @@ EXPORT void HWRAPI(StartScreenWipe) (void)
 		texsize = 1024;
 
 	// Create screen texture
+	if (firstTime)
+		startScreenWipe = SCRTEX_STARTSCREENWIPE;
 	pglBindTexture(GL_TEXTURE_2D, startScreenWipe);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	Clamp2D(GL_TEXTURE_WRAP_S);
-	Clamp2D(GL_TEXTURE_WRAP_T);
-	pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	if (firstTime)
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		Clamp2D(GL_TEXTURE_WRAP_S);
+		Clamp2D(GL_TEXTURE_WRAP_T);
+		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
+	}
+	else
+		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
+
+	tex_downloaded = startScreenWipe;
 }
 
 // Create Screen to fade to
 EXPORT void HWRAPI(EndScreenWipe)(void)
 {
 	INT32 texsize = 2048;
+	boolean firstTime = (endScreenWipe == 0);
 
 	// Use a power of two texture, dammit
 	if(screen_width <= 512)
@@ -1856,14 +1907,22 @@ EXPORT void HWRAPI(EndScreenWipe)(void)
 		texsize = 1024;
 
 	// Create screen texture
+	if (firstTime)
+		endScreenWipe = SCRTEX_ENDSCREENWIPE;
 	pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	Clamp2D(GL_TEXTURE_WRAP_S);
-	Clamp2D(GL_TEXTURE_WRAP_T);
-	pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	if (firstTime)
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		Clamp2D(GL_TEXTURE_WRAP_S);
+		Clamp2D(GL_TEXTURE_WRAP_T);
+		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
+	}
+	else
+		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
+
+	tex_downloaded = endScreenWipe;
 }
 
 
@@ -1905,7 +1964,7 @@ EXPORT void HWRAPI(DrawIntermissionBG)(void)
 
 	pglEnd();
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	tex_downloaded = screentexture;
 }
 
 // Do screen fades!
@@ -1993,6 +2052,7 @@ EXPORT void HWRAPI(DoScreenWipe)(float alpha)
 
 		pglDisable(GL_TEXTURE_2D); // disable the texture in the 2nd texture unit
 		pglActiveTexture(GL_TEXTURE0);
+		tex_downloaded = endScreenWipe;
 	}
 	else
 	{
@@ -2017,9 +2077,8 @@ EXPORT void HWRAPI(DoScreenWipe)(float alpha)
 			pglTexCoord2f(xfix, 0.0f);
 			pglVertex3f(1.0f, -1.0f, 1.0f);
 		pglEnd();
+		tex_downloaded = endScreenWipe;
 	}
-
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
 }
 
 
@@ -2027,6 +2086,7 @@ EXPORT void HWRAPI(DoScreenWipe)(float alpha)
 EXPORT void HWRAPI(MakeScreenTexture) (void)
 {
 	INT32 texsize = 2048;
+	boolean firstTime = (screentexture == 0);
 
 	// Use a power of two texture, dammit
 	if(screen_width <= 512)
@@ -2035,19 +2095,28 @@ EXPORT void HWRAPI(MakeScreenTexture) (void)
 		texsize = 1024;
 
 	// Create screen texture
+	if (firstTime)
+		screentexture = SCRTEX_SCREENTEXTURE;
 	pglBindTexture(GL_TEXTURE_2D, screentexture);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	Clamp2D(GL_TEXTURE_WRAP_S);
-	Clamp2D(GL_TEXTURE_WRAP_T);
-	pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	if (firstTime)
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		Clamp2D(GL_TEXTURE_WRAP_S);
+		Clamp2D(GL_TEXTURE_WRAP_T);
+		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
+	}
+	else
+		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
+
+	tex_downloaded = screentexture;
 }
 
 EXPORT void HWRAPI(MakeScreenFinalTexture) (void)
 {
 	INT32 texsize = 2048;
+	boolean firstTime = (finalScreenTexture == 0);
 
 	// Use a power of two texture, dammit
 	if(screen_width <= 512)
@@ -2056,20 +2125,31 @@ EXPORT void HWRAPI(MakeScreenFinalTexture) (void)
 		texsize = 1024;
 
 	// Create screen texture
+	if (firstTime)
+		finalScreenTexture = SCRTEX_FINALSCREENTEXTURE;
 	pglBindTexture(GL_TEXTURE_2D, finalScreenTexture);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	Clamp2D(GL_TEXTURE_WRAP_S);
-	Clamp2D(GL_TEXTURE_WRAP_T);
-	pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
 
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	if (firstTime)
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		Clamp2D(GL_TEXTURE_WRAP_S);
+		Clamp2D(GL_TEXTURE_WRAP_T);
+		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
+	}
+	else
+		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
+
+	tex_downloaded = finalScreenTexture;
 
 }
 
 EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 {
 	float xfix, yfix;
+	float origaspect, newaspect;
+	float xoff = 1, yoff = 1; // xoffset and yoffset for the polygon to have black bars around the screen
+	FRGBAFloat clearColour;
 	INT32 texsize = 2048;
 
 	if(screen_width <= 1024)
@@ -2080,35 +2160,47 @@ EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
 
-	//pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+	origaspect = (float)screen_width / screen_height;
+	newaspect = (float)width / height;
+	if (origaspect < newaspect)
+	{
+		xoff = origaspect / newaspect;
+		yoff = 1;
+	}
+	else if (origaspect > newaspect)
+	{
+		xoff = 1;
+		yoff = newaspect / origaspect;
+	}
+
 	pglViewport(0, 0, width, height);
 
+	clearColour.red = clearColour.green = clearColour.blue = 0;
+	clearColour.alpha = 1;
+	ClearBuffer(true, false, &clearColour);
 	pglBindTexture(GL_TEXTURE_2D, finalScreenTexture);
 	pglBegin(GL_QUADS);
 
 		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
 		// Bottom left
 		pglTexCoord2f(0.0f, 0.0f);
-		pglVertex3f(-1, -1, 1.0f);
+		pglVertex3f(-xoff, -yoff, 1.0f);
 
 		// Top left
 		pglTexCoord2f(0.0f, yfix);
-		pglVertex3f(-1, 1, 1.0f);
+		pglVertex3f(-xoff, yoff, 1.0f);
 
 		// Top right
 		pglTexCoord2f(xfix, yfix);
-		pglVertex3f(1, 1, 1.0f);
+		pglVertex3f(xoff, yoff, 1.0f);
 
 		// Bottom right
 		pglTexCoord2f(xfix, 0.0f);
-		pglVertex3f(1, -1, 1.0f);
+		pglVertex3f(xoff, -yoff, 1.0f);
 
 	pglEnd();
 
-	SetModelView(screen_width, screen_height);
-	SetStates();
-
-	tex_downloaded = 0; // 0 so it knows it doesn't have any of the cached patches downloaded right now
+	tex_downloaded = finalScreenTexture;
 }
 
 #endif //HWRENDER
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 484daaf49e68f04c1ba9a5223dd53916caf054cd..ed89339b1a3d9659a687b28c114091c6b9e1a9b8 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -90,8 +90,7 @@ patch_t *tallinfin;
 //              coop hud
 //-------------------------------------------
 
-patch_t *emeraldpics[7];
-patch_t *tinyemeraldpics[7];
+patch_t *emeraldpics[3][7]; // 0 = normal, 1 = tiny, 2 = coinbox
 static patch_t *emblemicon;
 patch_t *tokenicon;
 static patch_t *exiticon;
@@ -258,20 +257,29 @@ void HU_LoadGraphics(void)
 	tokenicon = W_CachePatchName("TOKNICON", PU_HUDGFX);
 	exiticon = W_CachePatchName("EXITICON", PU_HUDGFX);
 
-	emeraldpics[0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
-	emeraldpics[1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
-	emeraldpics[2] = W_CachePatchName("CHAOS3", PU_HUDGFX);
-	emeraldpics[3] = W_CachePatchName("CHAOS4", PU_HUDGFX);
-	emeraldpics[4] = W_CachePatchName("CHAOS5", PU_HUDGFX);
-	emeraldpics[5] = W_CachePatchName("CHAOS6", PU_HUDGFX);
-	emeraldpics[6] = W_CachePatchName("CHAOS7", PU_HUDGFX);
-	tinyemeraldpics[0] = W_CachePatchName("TEMER1", PU_HUDGFX);
-	tinyemeraldpics[1] = W_CachePatchName("TEMER2", PU_HUDGFX);
-	tinyemeraldpics[2] = W_CachePatchName("TEMER3", PU_HUDGFX);
-	tinyemeraldpics[3] = W_CachePatchName("TEMER4", PU_HUDGFX);
-	tinyemeraldpics[4] = W_CachePatchName("TEMER5", PU_HUDGFX);
-	tinyemeraldpics[5] = W_CachePatchName("TEMER6", PU_HUDGFX);
-	tinyemeraldpics[6] = W_CachePatchName("TEMER7", PU_HUDGFX);
+	emeraldpics[0][0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
+	emeraldpics[0][1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
+	emeraldpics[0][2] = W_CachePatchName("CHAOS3", PU_HUDGFX);
+	emeraldpics[0][3] = W_CachePatchName("CHAOS4", PU_HUDGFX);
+	emeraldpics[0][4] = W_CachePatchName("CHAOS5", PU_HUDGFX);
+	emeraldpics[0][5] = W_CachePatchName("CHAOS6", PU_HUDGFX);
+	emeraldpics[0][6] = W_CachePatchName("CHAOS7", PU_HUDGFX);
+
+	emeraldpics[1][0] = W_CachePatchName("TEMER1", PU_HUDGFX);
+	emeraldpics[1][1] = W_CachePatchName("TEMER2", PU_HUDGFX);
+	emeraldpics[1][2] = W_CachePatchName("TEMER3", PU_HUDGFX);
+	emeraldpics[1][3] = W_CachePatchName("TEMER4", PU_HUDGFX);
+	emeraldpics[1][4] = W_CachePatchName("TEMER5", PU_HUDGFX);
+	emeraldpics[1][5] = W_CachePatchName("TEMER6", PU_HUDGFX);
+	emeraldpics[1][6] = W_CachePatchName("TEMER7", PU_HUDGFX);
+
+	emeraldpics[2][0] = W_CachePatchName("EMBOX1", PU_HUDGFX);
+	emeraldpics[2][1] = W_CachePatchName("EMBOX2", PU_HUDGFX);
+	emeraldpics[2][2] = W_CachePatchName("EMBOX3", PU_HUDGFX);
+	emeraldpics[2][3] = W_CachePatchName("EMBOX4", PU_HUDGFX);
+	emeraldpics[2][4] = W_CachePatchName("EMBOX5", PU_HUDGFX);
+	emeraldpics[2][5] = W_CachePatchName("EMBOX6", PU_HUDGFX);
+	emeraldpics[2][6] = W_CachePatchName("EMBOX7", PU_HUDGFX);
 }
 
 // Initialise Heads up
@@ -947,7 +955,7 @@ static void HU_DrawCEcho(void)
 	INT32 y = (BASEVIDHEIGHT/2)-4;
 	INT32 pnumlines = 0;
 
-	UINT32 realflags = cechoflags;
+	UINT32 realflags = cechoflags|V_PERPLAYER; // requested as part of splitscreen's stuff
 	INT32 realalpha = (INT32)((cechoflags & V_ALPHAMASK) >> V_ALPHASHIFT);
 
 	char *line;
@@ -990,6 +998,12 @@ static void HU_DrawCEcho(void)
 		*line = '\0';
 
 		V_DrawCenteredString(BASEVIDWIDTH/2, y, realflags, echoptr);
+		if (splitscreen)
+		{
+			stplyr = ((stplyr == &players[displayplayer]) ? &players[secondarydisplayplayer] : &players[displayplayer]);
+			V_DrawCenteredString(BASEVIDWIDTH/2, y, realflags, echoptr);
+			stplyr = ((stplyr == &players[displayplayer]) ? &players[secondarydisplayplayer] : &players[displayplayer]);
+		}
 		y += ((realflags & V_RETURN8) ? 8 : 12);
 
 		echoptr = line;
@@ -1474,25 +1488,25 @@ void HU_DrawEmeralds(INT32 x, INT32 y, INT32 pemeralds)
 {
 	//Draw the emeralds, in the CORRECT order, using tiny emerald sprites.
 	if (pemeralds & EMERALD1)
-		V_DrawSmallScaledPatch(x  , y-6, 0, tinyemeraldpics[0]);
+		V_DrawSmallScaledPatch(x  , y-6, 0, emeraldpics[1][0]);
 
 	if (pemeralds & EMERALD2)
-		V_DrawSmallScaledPatch(x+4, y-3, 0, tinyemeraldpics[1]);
+		V_DrawSmallScaledPatch(x+4, y-3, 0, emeraldpics[1][1]);
 
 	if (pemeralds & EMERALD3)
-		V_DrawSmallScaledPatch(x+4, y+3, 0, tinyemeraldpics[2]);
+		V_DrawSmallScaledPatch(x+4, y+3, 0, emeraldpics[1][2]);
 
 	if (pemeralds & EMERALD4)
-		V_DrawSmallScaledPatch(x  , y+6, 0, tinyemeraldpics[3]);
+		V_DrawSmallScaledPatch(x  , y+6, 0, emeraldpics[1][3]);
 
 	if (pemeralds & EMERALD5)
-		V_DrawSmallScaledPatch(x-4, y+3, 0, tinyemeraldpics[4]);
+		V_DrawSmallScaledPatch(x-4, y+3, 0, emeraldpics[1][4]);
 
 	if (pemeralds & EMERALD6)
-		V_DrawSmallScaledPatch(x-4, y-3, 0, tinyemeraldpics[5]);
+		V_DrawSmallScaledPatch(x-4, y-3, 0, emeraldpics[1][5]);
 
 	if (pemeralds & EMERALD7)
-		V_DrawSmallScaledPatch(x,   y,   0, tinyemeraldpics[6]);
+		V_DrawSmallScaledPatch(x,   y,   0, emeraldpics[1][6]);
 }
 
 //
@@ -1547,7 +1561,7 @@ static inline void HU_DrawSpectatorTicker(void)
 					templength = length;
 				}
 
-				V_DrawString(templength, height + 8, V_TRANSLUCENT, current);
+				V_DrawString(templength, height + 8, V_TRANSLUCENT|V_ALLOWLOWERCASE, current);
 			}
 
 			length += (signed)strlen(player_names[i]) * 8 + 16;
@@ -1559,7 +1573,6 @@ static inline void HU_DrawSpectatorTicker(void)
 //
 static void HU_DrawRankings(void)
 {
-	patch_t *p;
 	playersort_t tab[MAXPLAYERS];
 	INT32 i, j, scorelines;
 	boolean completed[MAXPLAYERS];
@@ -1568,43 +1581,12 @@ static void HU_DrawRankings(void)
 	// draw the current gametype in the lower right
 	HU_drawGametype();
 
-	if (G_GametypeHasTeams())
-	{
-		if (gametype == GT_CTF)
-			p = bflagico;
-		else
-			p = bmatcico;
-
-		V_DrawSmallScaledPatch(128 - SHORT(p->width)/4, 4, 0, p);
-		V_DrawCenteredString(128, 16, 0, va("%u", bluescore));
-
-		if (gametype == GT_CTF)
-			p = rflagico;
-		else
-			p = rmatcico;
-
-		V_DrawSmallScaledPatch(192 - SHORT(p->width)/4, 4, 0, p);
-		V_DrawCenteredString(192, 16, 0, va("%u", redscore));
-	}
-
 	if (gametype != GT_RACE && gametype != GT_COMPETITION && gametype != GT_COOP)
 	{
 		if (cv_timelimit.value && timelimitintics > 0)
 		{
-			INT32 timeval = (timelimitintics+1-leveltime)/TICRATE;
-
-			if (leveltime <= timelimitintics)
-			{
-				V_DrawCenteredString(64, 8, 0, "TIME LEFT");
-				V_DrawCenteredString(64, 16, 0, va("%u", timeval));
-			}
-
-			// overtime
-			if ((leveltime > (timelimitintics + TICRATE/2)) && cv_overtime.value)
-			{
-				V_DrawCenteredString(64, 8, 0, "TIME LEFT");
-				V_DrawCenteredString(64, 16, 0, "OVERTIME");
-			}
+			V_DrawCenteredString(64, 8, 0, "TIME");
+			V_DrawCenteredString(64, 16, 0, va("%i:%02i", G_TicsToMinutes(stplyr->realtime, true), G_TicsToSeconds(stplyr->realtime)));
 		}
 
 		if (cv_pointlimit.value > 0)
@@ -1761,19 +1743,19 @@ static void HU_DrawCoopOverlay(void)
 #endif
 
 	if (emeralds & EMERALD1)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)-32, 0, emeraldpics[0]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)-32, 0, emeraldpics[0][0]);
 	if (emeralds & EMERALD2)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[1]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[0][1]);
 	if (emeralds & EMERALD3)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[2]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[0][2]);
 	if (emeralds & EMERALD4)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)+32, 0, emeraldpics[3]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)+32, 0, emeraldpics[0][3]);
 	if (emeralds & EMERALD5)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[4]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[0][4]);
 	if (emeralds & EMERALD6)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[5]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[0][5]);
 	if (emeralds & EMERALD7)
-		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)   , 0, emeraldpics[6]);
+		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)   , 0, emeraldpics[0][6]);
 }
 
 static void HU_DrawNetplayCoopOverlay(void)
@@ -1788,7 +1770,7 @@ static void HU_DrawNetplayCoopOverlay(void)
 	for (i = 0; i < 7; ++i)
 	{
 		if (emeralds & (1 << i))
-			V_DrawScaledPatch(20 + (i * 20), 6, 0, emeraldpics[i]);
+			V_DrawScaledPatch(20 + (i * 20), 6, 0, emeraldpics[0][i]);
 	}
 }
 
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index b7fa0abb0cae2ca09d6c2e5779428cab1f6f47c5..389fb4203d11adefee1d63534c46b995dddcc1fd 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -64,8 +64,7 @@ extern patch_t *nightsnum[10];
 extern patch_t *lt_font[LT_FONTSIZE];
 extern patch_t *cred_font[CRED_FONTSIZE];
 extern patch_t *ttlnum[20];
-extern patch_t *emeraldpics[7];
-extern patch_t *tinyemeraldpics[7];
+extern patch_t *emeraldpics[3][7];
 extern patch_t *rflagico;
 extern patch_t *bflagico;
 extern patch_t *rmatcico;
diff --git a/src/info.c b/src/info.c
index acb12379a9e021c93fa07155ab668b1592dc075b..5bca057902bf1d7f11de410459ea58719738693b 100644
--- a/src/info.c
+++ b/src/info.c
@@ -680,7 +680,7 @@ state_t states[NUMSTATES] =
 	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_OBJPLACE_DUMMY}, //S_OBJPLACE_DUMMY
 
 	// 1-Up box sprites (uses player sprite)
-	{SPR_PLAY, SPR2_LIFE,  2, {NULL}, 0, 16, S_PLAY_BOX2},  // S_PLAY_BOX1
+	{SPR_PLAY, SPR2_LIFE,  2, {NULL}, 0, 18, S_PLAY_BOX2},  // S_PLAY_BOX1
 	{SPR_NULL,         0,  1, {NULL}, 0,  0, S_PLAY_BOX1},  // S_PLAY_BOX2
 	{SPR_PLAY, SPR2_LIFE,  4, {NULL}, 0,  4, S_PLAY_ICON2}, // S_PLAY_ICON1
 	{SPR_NULL,         0, 12, {NULL}, 0,  0, S_PLAY_ICON3}, // S_PLAY_ICON2
@@ -1727,9 +1727,10 @@ state_t states[NUMSTATES] =
 	{SPR_NULL, 0, 2, {A_SetRandomTics},     TICRATE/2, 3*TICRATE, S_CANNONLAUNCHER1}, // S_CANNONLAUNCHER3
 
 	// Monitor Miscellany
-	{SPR_NSPK, FF_TRANS40, 20, {NULL}, 0, 0, S_BOXSPARKLE2}, // S_BOXSPARKLE1
-	{SPR_NSPK, FF_TRANS60, 10, {NULL}, 0, 0, S_BOXSPARKLE3}, // S_BOXSPARKLE2
-	{SPR_NSPK, FF_TRANS80,  5, {NULL}, 0, 0, S_NULL}, // S_BOXSPARKLE3
+	{SPR_NSPK, 0, 16, {NULL}, 0, 0, S_BOXSPARKLE2}, // S_BOXSPARKLE1
+	{SPR_NSPK, 1, 12, {NULL}, 0, 0, S_BOXSPARKLE3}, // S_BOXSPARKLE2
+	{SPR_NSPK, 2,  8, {NULL}, 0, 0, S_BOXSPARKLE4}, // S_BOXSPARKLE3
+	{SPR_NSPK, 3,  4, {NULL}, 0, 0, S_NULL},        // S_BOXSPARKLE4
 
 	{SPR_MSTV, 0,  1, {NULL}, 0, 0, S_SPAWNSTATE},  // S_BOX_FLICKER
 	{SPR_MSTV, 0,  4, {A_MonitorPop}, 0, 0, S_BOX_POP2}, // S_BOX_POP1
@@ -2214,39 +2215,71 @@ state_t states[NUMSTATES] =
 	{SPR_ARMA, FF_TRANS40|14, 2, {NULL}, 0, 0, S_ARMA16}, // S_ARMA15
 	{SPR_ARMA, FF_TRANS40|15, 2, {NULL}, 0, 0, S_ARMA1 }, // S_ARMA16
 
-	{SPR_ARMF, FF_FULLBRIGHT   , 3, {NULL}, 0, 0, S_ARMF2 }, // S_ARMF1
-	{SPR_ARMF, FF_FULLBRIGHT| 1, 3, {NULL}, 0, 0, S_ARMF3 }, // S_ARMF2
-	{SPR_ARMF, FF_FULLBRIGHT| 2, 3, {NULL}, 0, 0, S_ARMF4 }, // S_ARMF3
-	{SPR_ARMF, FF_FULLBRIGHT| 3, 3, {NULL}, 0, 0, S_ARMF5 }, // S_ARMF4
-	{SPR_ARMF, FF_FULLBRIGHT| 4, 3, {NULL}, 0, 0, S_ARMF6 }, // S_ARMF5
-	{SPR_ARMF, FF_FULLBRIGHT| 5, 3, {NULL}, 0, 0, S_ARMF7 }, // S_ARMF6
-	{SPR_ARMF, FF_FULLBRIGHT| 6, 3, {NULL}, 0, 0, S_ARMF8 }, // S_ARMF7
-	{SPR_ARMF, FF_FULLBRIGHT| 7, 3, {NULL}, 0, 0, S_ARMF9 }, // S_ARMF8
-	{SPR_ARMF, FF_FULLBRIGHT| 8, 3, {NULL}, 0, 0, S_ARMF10}, // S_ARMF9
-	{SPR_ARMF, FF_FULLBRIGHT| 9, 3, {NULL}, 0, 0, S_ARMF11}, // S_ARMF10
-	{SPR_ARMF, FF_FULLBRIGHT|10, 3, {NULL}, 0, 0, S_ARMF12}, // S_ARMF11
-	{SPR_ARMF, FF_FULLBRIGHT|11, 3, {NULL}, 0, 0, S_ARMF13}, // S_ARMF12
-	{SPR_ARMF, FF_FULLBRIGHT|12, 3, {NULL}, 0, 0, S_ARMF14}, // S_ARMF13
-	{SPR_ARMF, FF_FULLBRIGHT|13, 3, {NULL}, 0, 0, S_ARMF15}, // S_ARMF14
-	{SPR_ARMF, FF_FULLBRIGHT|14, 3, {NULL}, 0, 0, S_ARMF16}, // S_ARMF15
-	{SPR_ARMF, FF_FULLBRIGHT|15, 3, {NULL}, 0, 0, S_ARMF1 }, // S_ARMF16
-
-	{SPR_ARMB, FF_FULLBRIGHT| 0, 3, {NULL}, 1, 0, S_ARMB2 }, // S_ARMB1
-	{SPR_ARMB, FF_FULLBRIGHT| 1, 3, {NULL}, 1, 0, S_ARMB3 }, // S_ARMB2
-	{SPR_ARMB, FF_FULLBRIGHT| 2, 3, {NULL}, 1, 0, S_ARMB4 }, // S_ARMB3
-	{SPR_ARMB, FF_FULLBRIGHT| 3, 3, {NULL}, 1, 0, S_ARMB5 }, // S_ARMB4
-	{SPR_ARMB, FF_FULLBRIGHT| 4, 3, {NULL}, 1, 0, S_ARMB6 }, // S_ARMB5
-	{SPR_ARMB, FF_FULLBRIGHT| 5, 3, {NULL}, 1, 0, S_ARMB7 }, // S_ARMB6
-	{SPR_ARMB, FF_FULLBRIGHT| 6, 3, {NULL}, 1, 0, S_ARMB8 }, // S_ARMB7
-	{SPR_ARMB, FF_FULLBRIGHT| 7, 3, {NULL}, 1, 0, S_ARMB9 }, // S_ARMB8
-	{SPR_ARMB, FF_FULLBRIGHT| 8, 3, {NULL}, 1, 0, S_ARMB10}, // S_ARMB9
-	{SPR_ARMB, FF_FULLBRIGHT| 9, 3, {NULL}, 1, 0, S_ARMB11}, // S_ARMB10
-	{SPR_ARMB, FF_FULLBRIGHT|10, 3, {NULL}, 1, 0, S_ARMB12}, // S_ARMB11
-	{SPR_ARMB, FF_FULLBRIGHT|11, 3, {NULL}, 1, 0, S_ARMB13}, // S_ARMB12
-	{SPR_ARMB, FF_FULLBRIGHT|12, 3, {NULL}, 1, 0, S_ARMB14}, // S_ARMB13
-	{SPR_ARMB, FF_FULLBRIGHT|13, 3, {NULL}, 1, 0, S_ARMB15}, // S_ARMB14
-	{SPR_ARMB, FF_FULLBRIGHT|14, 3, {NULL}, 1, 0, S_ARMB16}, // S_ARMB15
-	{SPR_ARMB, FF_FULLBRIGHT|15, 3, {NULL}, 1, 0, S_ARMB1 }, // S_ARMB16
+	{SPR_ARMF, FF_FULLBRIGHT   , 2, {NULL}, 0, 0, S_ARMF2 }, // S_ARMF1
+	{SPR_ARMF, FF_FULLBRIGHT| 1, 2, {NULL}, 0, 0, S_ARMF3 }, // S_ARMF2
+	{SPR_ARMF, FF_FULLBRIGHT| 2, 2, {NULL}, 0, 0, S_ARMF4 }, // S_ARMF3
+	{SPR_ARMF, FF_FULLBRIGHT| 3, 2, {NULL}, 0, 0, S_ARMF5 }, // S_ARMF4
+	{SPR_ARMF, FF_FULLBRIGHT| 4, 2, {NULL}, 0, 0, S_ARMF6 }, // S_ARMF5
+	{SPR_ARMF, FF_FULLBRIGHT| 5, 2, {NULL}, 0, 0, S_ARMF7 }, // S_ARMF6
+	{SPR_ARMF, FF_FULLBRIGHT| 6, 2, {NULL}, 0, 0, S_ARMF8 }, // S_ARMF7
+	{SPR_ARMF, FF_FULLBRIGHT| 7, 2, {NULL}, 0, 0, S_ARMF9 }, // S_ARMF8
+	{SPR_ARMF, FF_FULLBRIGHT| 8, 2, {NULL}, 0, 0, S_ARMF10}, // S_ARMF9
+	{SPR_ARMF, FF_FULLBRIGHT| 9, 2, {NULL}, 0, 0, S_ARMF11}, // S_ARMF10
+	{SPR_ARMF, FF_FULLBRIGHT|10, 2, {NULL}, 0, 0, S_ARMF12}, // S_ARMF11
+	{SPR_ARMF, FF_FULLBRIGHT|11, 2, {NULL}, 0, 0, S_ARMF13}, // S_ARMF12
+	{SPR_ARMF, FF_FULLBRIGHT|12, 2, {NULL}, 0, 0, S_ARMF14}, // S_ARMF13
+	{SPR_ARMF, FF_FULLBRIGHT|13, 2, {NULL}, 0, 0, S_ARMF15}, // S_ARMF14
+	{SPR_ARMF, FF_FULLBRIGHT|14, 2, {NULL}, 0, 0, S_ARMF16}, // S_ARMF15
+	{SPR_ARMF, FF_FULLBRIGHT|15, 2, {NULL}, 0, 0, S_ARMF17}, // S_ARMF16
+	{SPR_ARMB, FF_FULLBRIGHT   , 2, {NULL}, 0, 0, S_ARMF18}, // S_ARMF17
+	{SPR_ARMB, FF_FULLBRIGHT| 1, 2, {NULL}, 0, 0, S_ARMF19}, // S_ARMF18
+	{SPR_ARMB, FF_FULLBRIGHT| 2, 2, {NULL}, 0, 0, S_ARMF20}, // S_ARMF19
+	{SPR_ARMB, FF_FULLBRIGHT| 3, 2, {NULL}, 0, 0, S_ARMF21}, // S_ARMF20
+	{SPR_ARMB, FF_FULLBRIGHT| 4, 2, {NULL}, 0, 0, S_ARMF22}, // S_ARMF21
+	{SPR_ARMB, FF_FULLBRIGHT| 5, 2, {NULL}, 0, 0, S_ARMF23}, // S_ARMF22
+	{SPR_ARMB, FF_FULLBRIGHT| 6, 2, {NULL}, 0, 0, S_ARMF24}, // S_ARMF23
+	{SPR_ARMB, FF_FULLBRIGHT| 7, 2, {NULL}, 0, 0, S_ARMF25}, // S_ARMF24
+	{SPR_ARMB, FF_FULLBRIGHT| 8, 2, {NULL}, 0, 0, S_ARMF26}, // S_ARMF25
+	{SPR_ARMB, FF_FULLBRIGHT| 9, 2, {NULL}, 0, 0, S_ARMF27}, // S_ARMF26
+	{SPR_ARMB, FF_FULLBRIGHT|10, 2, {NULL}, 0, 0, S_ARMF28}, // S_ARMF27
+	{SPR_ARMB, FF_FULLBRIGHT|11, 2, {NULL}, 0, 0, S_ARMF29}, // S_ARMF28
+	{SPR_ARMB, FF_FULLBRIGHT|12, 2, {NULL}, 0, 0, S_ARMF30}, // S_ARMF29
+	{SPR_ARMB, FF_FULLBRIGHT|13, 2, {NULL}, 0, 0, S_ARMF31}, // S_ARMF30
+	{SPR_ARMB, FF_FULLBRIGHT|14, 2, {NULL}, 0, 0, S_ARMF32}, // S_ARMF31
+	{SPR_ARMB, FF_FULLBRIGHT|15, 2, {NULL}, 0, 0, S_ARMF1 }, // S_ARMF32
+
+	{SPR_ARMB, FF_FULLBRIGHT   , 2, {NULL}, 1, 0, S_ARMB2 }, // S_ARMB1
+	{SPR_ARMB, FF_FULLBRIGHT| 1, 2, {NULL}, 1, 0, S_ARMB3 }, // S_ARMB2
+	{SPR_ARMB, FF_FULLBRIGHT| 2, 2, {NULL}, 1, 0, S_ARMB4 }, // S_ARMB3
+	{SPR_ARMB, FF_FULLBRIGHT| 3, 2, {NULL}, 1, 0, S_ARMB5 }, // S_ARMB4
+	{SPR_ARMB, FF_FULLBRIGHT| 4, 2, {NULL}, 1, 0, S_ARMB6 }, // S_ARMB5
+	{SPR_ARMB, FF_FULLBRIGHT| 5, 2, {NULL}, 1, 0, S_ARMB7 }, // S_ARMB6
+	{SPR_ARMB, FF_FULLBRIGHT| 6, 2, {NULL}, 1, 0, S_ARMB8 }, // S_ARMB7
+	{SPR_ARMB, FF_FULLBRIGHT| 7, 2, {NULL}, 1, 0, S_ARMB9 }, // S_ARMB8
+	{SPR_ARMB, FF_FULLBRIGHT| 8, 2, {NULL}, 1, 0, S_ARMB10}, // S_ARMB9
+	{SPR_ARMB, FF_FULLBRIGHT| 9, 2, {NULL}, 1, 0, S_ARMB11}, // S_ARMB10
+	{SPR_ARMB, FF_FULLBRIGHT|10, 2, {NULL}, 1, 0, S_ARMB12}, // S_ARMB11
+	{SPR_ARMB, FF_FULLBRIGHT|11, 2, {NULL}, 1, 0, S_ARMB13}, // S_ARMB12
+	{SPR_ARMB, FF_FULLBRIGHT|12, 2, {NULL}, 1, 0, S_ARMB14}, // S_ARMB13
+	{SPR_ARMB, FF_FULLBRIGHT|13, 2, {NULL}, 1, 0, S_ARMB15}, // S_ARMB14
+	{SPR_ARMB, FF_FULLBRIGHT|14, 2, {NULL}, 1, 0, S_ARMB16}, // S_ARMB15
+	{SPR_ARMB, FF_FULLBRIGHT|15, 2, {NULL}, 1, 0, S_ARMB17}, // S_ARMB16
+	{SPR_ARMF, FF_FULLBRIGHT   , 2, {NULL}, 1, 0, S_ARMB18}, // S_ARMB17
+	{SPR_ARMF, FF_FULLBRIGHT| 1, 2, {NULL}, 1, 0, S_ARMB19}, // S_ARMB18
+	{SPR_ARMF, FF_FULLBRIGHT| 2, 2, {NULL}, 1, 0, S_ARMB20}, // S_ARMB19
+	{SPR_ARMF, FF_FULLBRIGHT| 3, 2, {NULL}, 1, 0, S_ARMB21}, // S_ARMB20
+	{SPR_ARMF, FF_FULLBRIGHT| 4, 2, {NULL}, 1, 0, S_ARMB22}, // S_ARMB21
+	{SPR_ARMF, FF_FULLBRIGHT| 5, 2, {NULL}, 1, 0, S_ARMB23}, // S_ARMB22
+	{SPR_ARMF, FF_FULLBRIGHT| 6, 2, {NULL}, 1, 0, S_ARMB24}, // S_ARMB23
+	{SPR_ARMF, FF_FULLBRIGHT| 7, 2, {NULL}, 1, 0, S_ARMB25}, // S_ARMB24
+	{SPR_ARMF, FF_FULLBRIGHT| 8, 2, {NULL}, 1, 0, S_ARMB26}, // S_ARMB25
+	{SPR_ARMF, FF_FULLBRIGHT| 9, 2, {NULL}, 1, 0, S_ARMB27}, // S_ARMB26
+	{SPR_ARMF, FF_FULLBRIGHT|10, 2, {NULL}, 1, 0, S_ARMB28}, // S_ARMB27
+	{SPR_ARMF, FF_FULLBRIGHT|11, 2, {NULL}, 1, 0, S_ARMB29}, // S_ARMB28
+	{SPR_ARMF, FF_FULLBRIGHT|12, 2, {NULL}, 1, 0, S_ARMB30}, // S_ARMB29
+	{SPR_ARMF, FF_FULLBRIGHT|13, 2, {NULL}, 1, 0, S_ARMB31}, // S_ARMB30
+	{SPR_ARMF, FF_FULLBRIGHT|14, 2, {NULL}, 1, 0, S_ARMB32}, // S_ARMB31
+	{SPR_ARMF, FF_FULLBRIGHT|15, 2, {NULL}, 1, 0, S_ARMB1 }, // S_ARMB32
 
 	{SPR_WIND, FF_TRANS70  , 2, {NULL}, 0, 0, S_WIND2}, // S_WIND1
 	{SPR_WIND, FF_TRANS70|1, 2, {NULL}, 0, 0, S_WIND3}, // S_WIND2
@@ -2735,8 +2768,6 @@ state_t states[NUMSTATES] =
 
 	// CTF Sign
 	{SPR_GFLG,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTFLAG
-	{SPR_GFLG, 1|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTREDFLAG
-	{SPR_GFLG, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTBLUEFLAG
 
 	{SPR_CORK, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CORK
 
@@ -3121,11 +3152,15 @@ state_t states[NUMSTATES] =
 	{SPR_SPRK, FF_TRANS90|3, 1, {NULL}, 0, 0, S_NULL},   // S_SPRK16
 
 	// Robot Explosion
-	{SPR_BOM1, 0,             0, {A_FlickySpawn}, 0, 0, S_XPLD1}, // S_XPLD_FLICKY
-	{SPR_BOM1, 0,             1, {A_Scream},      0, 0, S_XPLD2}, // S_XPLD1
-	{SPR_BOM1, FF_ANIMATE|1, 15, {NULL},          2, 5, S_NULL},  // S_XPLD2
+	{SPR_BOM1, 0, 0, {A_FlickySpawn}, 0, 0, S_XPLD1}, // S_XPLD_FLICKY
+	{SPR_BOM1, 0, 2, {A_Scream},      0, 0, S_XPLD2}, // S_XPLD1
+	{SPR_BOM1, 1, 2, {NULL},          0, 0, S_XPLD3}, // S_XPLD2
+	{SPR_BOM1, 2, 3, {NULL},          0, 0, S_XPLD4}, // S_XPLD3
+	{SPR_BOM1, 3, 3, {NULL},          0, 0, S_XPLD5}, // S_XPLD4
+	{SPR_BOM1, 4, 4, {NULL},          0, 0, S_XPLD6}, // S_XPLD5
+	{SPR_BOM1, 5, 4, {NULL},          0, 0, S_NULL},  // S_XPLD6
 
-	{SPR_BOM1, FF_ANIMATE,   20, {NULL},          3, 5, S_INVISIBLE}, // S_XPLD_EGGTRAP
+	{SPR_BOM1, FF_ANIMATE,   21, {NULL},          5, 4, S_INVISIBLE}, // S_XPLD_EGGTRAP
 
 	// Underwater Explosion
 	{SPR_BOM4, 0, 3, {A_Scream}, 0, 0, S_WPLD2}, // S_WPLD1
@@ -6386,8 +6421,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_RING_ICON,   // damage
@@ -6413,8 +6448,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_PITY_ICON,   // damage
@@ -6440,8 +6475,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ATTRACT_ICON,// damage
@@ -6467,8 +6502,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_FORCE_ICON,  // damage
@@ -6494,8 +6529,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ARMAGEDDON_ICON, // damage
@@ -6521,8 +6556,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_WHIRLWIND_ICON, // damage
@@ -6548,8 +6583,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ELEMENTAL_ICON, // damage
@@ -6575,8 +6610,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_SNEAKERS_ICON, // damage
@@ -6602,8 +6637,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_INVULN_ICON, // damage
@@ -6629,8 +6664,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_1UP_ICON,    // damage
@@ -6656,8 +6691,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_EGGMAN_ICON, // damage
@@ -6683,8 +6718,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_MIXUP_ICON,  // damage
@@ -6710,8 +6745,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_UNKNOWN,     // damage
@@ -6737,8 +6772,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_GRAVITY_ICON, // damage
@@ -6764,8 +6799,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_RECYCLER_ICON, // damage
@@ -6791,8 +6826,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_SCORE1K_ICON, // damage
@@ -6818,8 +6853,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_SCORE10K_ICON, // damage
@@ -6845,8 +6880,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_FLAMEAURA_ICON, // damage
@@ -6872,8 +6907,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_BUBBLEWRAP_ICON, // damage
@@ -6899,8 +6934,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		1,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_THUNDERCOIN_ICON, // damage
@@ -6926,8 +6961,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_PITY_ICON,   // damage
@@ -6953,8 +6988,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ATTRACT_ICON,// damage
@@ -6980,8 +7015,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_FORCE_ICON,  // damage
@@ -7007,8 +7042,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ARMAGEDDON_ICON, // damage
@@ -7034,8 +7069,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_WHIRLWIND_ICON, // damage
@@ -7061,8 +7096,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_ELEMENTAL_ICON, // damage
@@ -7088,8 +7123,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_SNEAKERS_ICON, // damage
@@ -7115,8 +7150,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_INVULN_ICON, // damage
@@ -7142,8 +7177,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_EGGMAN_ICON, // damage
@@ -7169,8 +7204,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_GRAVITY_ICON, // damage
@@ -7196,8 +7231,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_FLAMEAURA_ICON, // damage
@@ -7223,8 +7258,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_BUBBLEWRAP_ICON, // damage
@@ -7250,8 +7285,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		36*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		44*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_THUNDERCOIN_ICON, // damage
@@ -7277,8 +7312,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_RING_ICON,   // damage
@@ -7304,8 +7339,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		18*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		MT_RING_ICON,   // damage
@@ -9011,7 +9046,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		10000,          // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9038,7 +9073,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		10000,          // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9065,7 +9100,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		10000,          // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9092,7 +9127,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		10000,          // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9119,7 +9154,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9146,7 +9181,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		200,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9173,7 +9208,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		200,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9195,7 +9230,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,         // deathsound
 		24*FRACUNIT,      // speed
 		24*FRACUNIT,      // radius
-		48*FRACUNIT,      // height
+		32*FRACUNIT,      // height
 		0,                // display offset
 		100,              // mass
 		1,                // damage
diff --git a/src/info.h b/src/info.h
index 35a3f5f151ea7cc744d89efb66b8788c552c7953..d0e75e2e836c4c599098423b8539b3c8556be90c 100644
--- a/src/info.h
+++ b/src/info.h
@@ -1834,6 +1834,7 @@ typedef enum state
 	S_BOXSPARKLE1,
 	S_BOXSPARKLE2,
 	S_BOXSPARKLE3,
+	S_BOXSPARKLE4,
 
 	S_BOX_FLICKER,
 	S_BOX_POP1,
@@ -2329,6 +2330,22 @@ typedef enum state
 	S_ARMF14,
 	S_ARMF15,
 	S_ARMF16,
+	S_ARMF17,
+	S_ARMF18,
+	S_ARMF19,
+	S_ARMF20,
+	S_ARMF21,
+	S_ARMF22,
+	S_ARMF23,
+	S_ARMF24,
+	S_ARMF25,
+	S_ARMF26,
+	S_ARMF27,
+	S_ARMF28,
+	S_ARMF29,
+	S_ARMF30,
+	S_ARMF31,
+	S_ARMF32,
 
 	S_ARMB1,
 	S_ARMB2,
@@ -2346,6 +2363,22 @@ typedef enum state
 	S_ARMB14,
 	S_ARMB15,
 	S_ARMB16,
+	S_ARMB17,
+	S_ARMB18,
+	S_ARMB19,
+	S_ARMB20,
+	S_ARMB21,
+	S_ARMB22,
+	S_ARMB23,
+	S_ARMB24,
+	S_ARMB25,
+	S_ARMB26,
+	S_ARMB27,
+	S_ARMB28,
+	S_ARMB29,
+	S_ARMB30,
+	S_ARMB31,
+	S_ARMB32,
 
 	S_WIND1,
 	S_WIND2,
@@ -2826,8 +2859,6 @@ typedef enum state
 
 	// Got Flag Sign
 	S_GOTFLAG,
-	S_GOTREDFLAG,
-	S_GOTBLUEFLAG,
 
 	S_CORK,
 
@@ -3167,6 +3198,10 @@ typedef enum state
 	S_XPLD_FLICKY,
 	S_XPLD1,
 	S_XPLD2,
+	S_XPLD3,
+	S_XPLD4,
+	S_XPLD5,
+	S_XPLD6,
 	S_XPLD_EGGTRAP,
 
 	// Underwater Explosion
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 9d65a583207055b35349dc64854888d845d9013b..0cb530b53dc486fc7fb92e7e69477a5665f99d61 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -214,15 +214,7 @@ static int lib_pRandomRange(lua_State *L)
 	return 1;
 }
 
-// Deprecated, macros, etc.
-static int lib_pRandom(lua_State *L)
-{
-	NOHUD
-	LUA_Deprecated(L, "P_Random", "P_RandomByte");
-	lua_pushinteger(L, P_RandomByte());
-	return 1;
-}
-
+// Macros.
 static int lib_pSignedRandom(lua_State *L)
 {
 	NOHUD
@@ -776,6 +768,19 @@ static int lib_pCanRunOnWater(lua_State *L)
 	return 1;
 }
 
+static int lib_pMaceRotate(lua_State *L)
+{
+	mobj_t *center = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	INT32 baserot = luaL_checkinteger(L, 2);
+	INT32 baseprevrot = luaL_checkinteger(L, 3);
+	NOHUD
+	INLEVEL
+	if (!center)
+		return LUA_ErrInvalid(L, "mobj_t");
+	P_MaceRotate(center, baserot, baseprevrot);
+	return 0;
+}
+
 // P_USER
 ////////////
 
@@ -2481,7 +2486,6 @@ static luaL_Reg lib[] = {
 	{"P_RandomByte",lib_pRandomByte},
 	{"P_RandomKey",lib_pRandomKey},
 	{"P_RandomRange",lib_pRandomRange},
-	{"P_Random",lib_pRandom}, // DEPRECATED
 	{"P_SignedRandom",lib_pSignedRandom}, // MACRO
 	{"P_RandomChance",lib_pRandomChance}, // MACRO
 
@@ -2526,6 +2530,7 @@ static luaL_Reg lib[] = {
 	{"P_CheckDeathPitCollide",lib_pCheckDeathPitCollide},
 	{"P_CheckSolidLava",lib_pCheckSolidLava},
 	{"P_CanRunOnWater",lib_pCanRunOnWater},
+	{"P_MaceRotate",lib_pMaceRotate},
 
 	// p_user
 	{"P_GetPlayerHeight",lib_pGetPlayerHeight},
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 97835d845934b95db6bdc1f723ba0fb27c3dcdbc..afc81c37d474092dcf1d312da504b0ae4fd64312 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -20,6 +20,7 @@
 #include "i_video.h" // rendermode
 #include "p_local.h" // camera_t
 #include "screen.h" // screen width/height
+#include "m_random.h" // m_random
 #include "v_video.h"
 #include "w_wad.h"
 #include "z_zone.h"
@@ -63,12 +64,14 @@ static const char *const hud_disable_options[] = {
 
 enum hudinfo {
 	hudinfo_x = 0,
-	hudinfo_y
+	hudinfo_y,
+	hudinfo_f
 };
 
 static const char *const hudinfo_opt[] = {
 	"x",
 	"y",
+	"f",
 	NULL};
 
 enum patch {
@@ -198,6 +201,9 @@ static int hudinfo_get(lua_State *L)
 	case hudinfo_y:
 		lua_pushinteger(L, info->y);
 		break;
+	case hudinfo_f:
+		lua_pushinteger(L, info->f);
+		break;
 	}
 	return 1;
 }
@@ -216,6 +222,9 @@ static int hudinfo_set(lua_State *L)
 	case hudinfo_y:
 		info->y = (INT32)luaL_checkinteger(L, 3);
 		break;
+	case hudinfo_f:
+		info->f = (INT32)luaL_checkinteger(L, 3);
+		break;
 	}
 	return 0;
 }
@@ -679,6 +688,30 @@ static int libd_getColormap(lua_State *L)
 	return 1;
 }
 
+static int libd_fadeScreen(lua_State *L)
+{
+	UINT16 color = luaL_checkinteger(L, 1);
+	UINT8 strength = luaL_checkinteger(L, 2);
+	const UINT8 maxstrength = ((color & 0xFF00) ? 32 : 10);
+
+	HUDONLY
+
+	if (!strength)
+		return 0;
+
+	if (strength > maxstrength)
+		return luaL_error(L, "%s fade strength %d out of range (0 - %d)", ((color & 0xFF00) ? "COLORMAP" : "TRANSMAP"), strength, maxstrength);
+
+	if (strength == maxstrength) // Allow as a shortcut for drawfill...
+	{
+		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, ((color & 0xFF00) ? 31 : color));
+		return 0;
+	}
+
+	V_DrawFadeScreen(color, strength);
+	return 0;
+}
+
 static int libd_width(lua_State *L)
 {
 	HUDONLY
@@ -720,19 +753,92 @@ static int libd_renderer(lua_State *L)
 	return 1;
 }
 
+// M_RANDOM
+//////////////
+
+static int libd_RandomFixed(lua_State *L)
+{
+	HUDONLY
+	lua_pushfixed(L, M_RandomFixed());
+	return 1;
+}
+
+static int libd_RandomByte(lua_State *L)
+{
+	HUDONLY
+	lua_pushinteger(L, M_RandomByte());
+	return 1;
+}
+
+static int libd_RandomKey(lua_State *L)
+{
+	INT32 a = (INT32)luaL_checkinteger(L, 1);
+
+	HUDONLY
+	if (a > 65536)
+		LUA_UsageWarning(L, "v.RandomKey: range > 65536 is undefined behavior");
+	lua_pushinteger(L, M_RandomKey(a));
+	return 1;
+}
+
+static int libd_RandomRange(lua_State *L)
+{
+	INT32 a = (INT32)luaL_checkinteger(L, 1);
+	INT32 b = (INT32)luaL_checkinteger(L, 2);
+
+	HUDONLY
+	if (b < a) {
+		INT32 c = a;
+		a = b;
+		b = c;
+	}
+	if ((b-a+1) > 65536)
+		LUA_UsageWarning(L, "v.RandomRange: range > 65536 is undefined behavior");
+	lua_pushinteger(L, M_RandomRange(a, b));
+	return 1;
+}
+
+// Macros.
+static int libd_SignedRandom(lua_State *L)
+{
+	HUDONLY
+	lua_pushinteger(L, M_SignedRandom());
+	return 1;
+}
+
+static int libd_RandomChance(lua_State *L)
+{
+	fixed_t p = luaL_checkfixed(L, 1);
+	HUDONLY
+	lua_pushboolean(L, M_RandomChance(p));
+	return 1;
+}
+
 static luaL_Reg lib_draw[] = {
+	// cache
 	{"patchExists", libd_patchExists},
 	{"cachePatch", libd_cachePatch},
 	{"getSpritePatch", libd_getSpritePatch},
 	{"getSprite2Patch", libd_getSprite2Patch},
+	{"getColormap", libd_getColormap},
+	// drawing
 	{"draw", libd_draw},
 	{"drawScaled", libd_drawScaled},
 	{"drawNum", libd_drawNum},
 	{"drawPaddedNum", libd_drawPaddedNum},
 	{"drawFill", libd_drawFill},
 	{"drawString", libd_drawString},
+	{"fadeScreen", libd_fadeScreen},
+	// misc
 	{"stringWidth", libd_stringWidth},
-	{"getColormap", libd_getColormap},
+	// m_random
+	{"RandomFixed",libd_RandomFixed},
+	{"RandomByte",libd_RandomByte},
+	{"RandomKey",libd_RandomKey},
+	{"RandomRange",libd_RandomRange},
+	{"SignedRandom",libd_SignedRandom}, // MACRO
+	{"RandomChance",libd_RandomChance}, // MACRO
+	// properties
 	{"width", libd_width},
 	{"height", libd_height},
 	{"dupx", libd_dupx},
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index d384b75d1b5163e7f24ffb83a3177f0e84585fd4..1583bd3c4df9f335338f5d56d7e04dc5997a6f1e 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -530,10 +530,22 @@ static int mobj_set(lua_State *L)
 	case mobj_bprev:
 		return UNIMPLEMENTED;
 	case mobj_hnext:
-		mo->hnext = luaL_checkudata(L, 3, META_MOBJ);
+		if (lua_isnil(L, 3))
+			P_SetTarget(&mo->hnext, NULL);
+		else
+		{
+			mobj_t *hnext = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+			P_SetTarget(&mo->hnext, hnext);
+		}
 		break;
 	case mobj_hprev:
-		mo->hprev = luaL_checkudata(L, 3, META_MOBJ);
+		if (lua_isnil(L, 3))
+			P_SetTarget(&mo->hprev, NULL);
+		else
+		{
+			mobj_t *hprev = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+			P_SetTarget(&mo->hprev, hprev);
+		}
 		break;
 	case mobj_type: // yeah sure, we'll let you change the mobj's type.
 	{
diff --git a/src/lua_script.c b/src/lua_script.c
index 0aebafaeeb37e98529561b90255c2117e03698cf..79927613c1010acd50d87cb88c46bf0b6f340db6 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -524,10 +524,10 @@ static const struct {
 	{NULL,          ARCH_NULL}
 };
 
-static UINT8 GetUserdataArchType(void)
+static UINT8 GetUserdataArchType(int index)
 {
 	UINT8 i;
-	lua_getmetatable(gL, -1);
+	lua_getmetatable(gL, index);
 
 	for (i = 0; meta2arch[i].meta; i++)
 	{
@@ -606,7 +606,7 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 		break;
 	}
 	case LUA_TUSERDATA:
-		switch (GetUserdataArchType())
+		switch (GetUserdataArchType(myindex))
 		{
 		case ARCH_MOBJINFO:
 		{
@@ -881,6 +881,7 @@ static void ArchiveTables(void)
 				CONS_Alert(CONS_ERROR, "Type of value for table %d entry '%s' (%s) could not be archived!\n", i, lua_tostring(gL, -1), luaL_typename(gL, -1));
 				lua_pop(gL, 1);
 			}
+
 			lua_pop(gL, 1);
 		}
 		lua_pop(gL, 1);
diff --git a/src/m_cheat.c b/src/m_cheat.c
index a4eaede3ace36eda033f9864663a2e06e6cff3cc..174e2780dde499460ac7ce1da91cf434f8ff7bd2 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -1266,7 +1266,7 @@ void Command_ObjectPlace_f(void)
 
 		if (!COM_CheckParm("-silent"))
 		{
-			HU_SetCEchoFlags(V_RETURN8|V_MONOSPACE);
+			HU_SetCEchoFlags(V_RETURN8|V_MONOSPACE|V_AUTOFADEOUT);
 			HU_SetCEchoDuration(10);
 			HU_DoCEcho(va(M_GetText(
 				"\\\\\\\\\\\\\\\\\\\\\\\\\x82"
diff --git a/src/m_fixed.c b/src/m_fixed.c
index ce7471a28dac8cbd57aa96c58ed74d04fc00ded3..014457386030740900f05ebd5048a37e522ddddf 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -33,7 +33,9 @@
 */
 fixed_t FixedMul(fixed_t a, fixed_t b)
 {
-	return (fixed_t)((((INT64)a * b) ) / FRACUNIT);
+	// Need to cast to unsigned before shifting to avoid undefined behaviour
+	// for negative integers
+	return (fixed_t)(((UINT64)((INT64)a * b)) >> FRACBITS);
 }
 
 #endif //__USE_C_FIXEDMUL__
diff --git a/src/m_menu.c b/src/m_menu.c
index 69cd4236557e94dd2430bd3f5a1c0361112ba3ff..a866dac1b95a8ac5fff4b45ba9d324fc5ea02cac 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1188,7 +1188,7 @@ static menuitem_t OP_VideoOptionsMenu[] =
 #endif
 
 	{IT_HEADER, NULL, "Color Profile", NULL, 30},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness (F11)", &cv_globalgamma,      36},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness (F11)", &cv_globalgamma,36},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_globalsaturation, 41},
 	{IT_SUBMENU|IT_STRING, NULL, "Advanced Settings...",     &OP_ColorOptionsDef,  46},
 
@@ -1196,24 +1196,25 @@ static menuitem_t OP_VideoOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Show HUD",                  &cv_showhud,          61},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
 	                      NULL, "HUD Transparency",          &cv_translucenthud,   66},
-	{IT_STRING | IT_CVAR, NULL, "Time Display",              &cv_timetic,          71},
+	{IT_STRING | IT_CVAR, NULL, "Score/Time/Rings",          &cv_timetic,          71},
+	{IT_STRING | IT_CVAR, NULL, "Show Powerups",             &cv_powerupdisplay,   76},
 #ifdef SEENAMES
-	{IT_STRING | IT_CVAR, NULL, "Show player names",         &cv_seenames,         76},
+	{IT_STRING | IT_CVAR, NULL, "Show player names",         &cv_seenames,         81},
 #endif
 
-	{IT_HEADER, NULL, "Console", NULL, 85},
-	{IT_STRING | IT_CVAR, NULL, "Background color",          &cons_backcolor,      91},
-	{IT_STRING | IT_CVAR, NULL, "Text Size",                 &cv_constextsize,     96},
+	{IT_HEADER, NULL, "Console", NULL, 90},
+	{IT_STRING | IT_CVAR, NULL, "Background color",          &cons_backcolor,      96},
+	{IT_STRING | IT_CVAR, NULL, "Text Size",                 &cv_constextsize,    101},
 
-	{IT_HEADER, NULL, "Level", NULL, 105},
-	{IT_STRING | IT_CVAR, NULL, "Draw Distance",             &cv_drawdist,        111},
-	{IT_STRING | IT_CVAR, NULL, "NiGHTS Draw Dist.",         &cv_drawdist_nights, 116},
-	{IT_STRING | IT_CVAR, NULL, "Weather Draw Dist.",        &cv_drawdist_precip, 121},
-	{IT_STRING | IT_CVAR, NULL, "Weather Density",           &cv_precipdensity,   126},
+	{IT_HEADER, NULL, "Level", NULL, 110},
+	{IT_STRING | IT_CVAR, NULL, "Draw Distance",             &cv_drawdist,        116},
+	{IT_STRING | IT_CVAR, NULL, "NiGHTS Draw Dist.",         &cv_drawdist_nights, 121},
+	{IT_STRING | IT_CVAR, NULL, "Weather Draw Dist.",        &cv_drawdist_precip, 126},
+	{IT_STRING | IT_CVAR, NULL, "Weather Density",           &cv_precipdensity,   131},
 
-	{IT_HEADER, NULL, "Diagnostic", NULL, 135},
-	{IT_STRING | IT_CVAR, NULL, "Show FPS",                  &cv_ticrate,         141},
-	{IT_STRING | IT_CVAR, NULL, "Clear Before Redraw",       &cv_homremoval,      146},
+	{IT_HEADER, NULL, "Diagnostic", NULL, 140},
+	{IT_STRING | IT_CVAR, NULL, "Show FPS",                  &cv_ticrate,         146},
+	{IT_STRING | IT_CVAR, NULL, "Clear Before Redraw",       &cv_homremoval,      151},
 };
 
 static menuitem_t OP_VideoModeMenu[] =
@@ -2049,35 +2050,7 @@ static void Newgametype_OnChange(void)
 			P_AllocMapHeader((INT16)(cv_nextmap.value-1));
 
 		if (!M_CanShowLevelOnPlatter(cv_nextmap.value-1, cv_newgametype.value))
-		{
-			INT32 value = 0;
-
-			switch (cv_newgametype.value)
-			{
-				case GT_COOP:
-					value = TOL_COOP;
-					break;
-				case GT_COMPETITION:
-					value = TOL_COMPETITION;
-					break;
-				case GT_RACE:
-					value = TOL_RACE;
-					break;
-				case GT_MATCH:
-				case GT_TEAMMATCH:
-					value = TOL_MATCH;
-					break;
-				case GT_TAG:
-				case GT_HIDEANDSEEK:
-					value = TOL_TAG;
-					break;
-				case GT_CTF:
-					value = TOL_CTF;
-					break;
-			}
-
-			CV_SetValue(&cv_nextmap, M_GetFirstLevelInList(value));
-		}
+			CV_SetValue(&cv_nextmap, M_GetFirstLevelInList(cv_newgametype.value));
 	}
 }
 
@@ -2611,7 +2584,7 @@ void M_Drawer(void)
 	{
 		// now that's more readable with a faded background (yeah like Quake...)
 		if (!WipeInAction)
-			V_DrawFadeScreen();
+			V_DrawFadeScreen(0xFF00, 16);
 
 		if (currentMenu->drawroutine)
 			currentMenu->drawroutine(); // call current menu Draw routine
@@ -4453,14 +4426,14 @@ static void M_DrawLevelPlatterMenu(void)
 		V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
 
 	// finds row at top of the screen
-	while (y > 0)
+	while (y > -8)
 	{
 		iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
 		y -= lsvseperation(iter);
 	}
 
 	// draw from top to bottom
-	while (y < 200)
+	while (y < (vid.height/vid.dupy))
 	{
 		M_DrawLevelPlatterRow(iter, y);
 		y += lsvseperation(iter);
@@ -5710,7 +5683,7 @@ static void M_DrawChecklist(void)
 												beat = va("Get %d points in %s", cond[condnum].requirement, level);
 												break;
 											case UC_MAPTIME:
-												beat = va("Beat %s in %d:%d.%d", level,
+												beat = va("Beat %s in %d:%02d.%02d", level,
 												G_TicsToMinutes(cond[condnum].requirement, true),
 												G_TicsToSeconds(cond[condnum].requirement),
 												G_TicsToCentiseconds(cond[condnum].requirement));
@@ -5735,7 +5708,7 @@ static void M_DrawChecklist(void)
 											beat = va("Get %d points over all maps", cond[condnum].requirement);
 											break;
 										case UC_OVERALLTIME:
-											beat = va("Get a total time of less than %d:%d.%d",
+											beat = va("Get a total time of less than %d:%02d.%02d",
 											G_TicsToMinutes(cond[condnum].requirement, true),
 											G_TicsToSeconds(cond[condnum].requirement),
 											G_TicsToCentiseconds(cond[condnum].requirement));
@@ -5783,12 +5756,12 @@ static void M_DrawChecklist(void)
 												break;
 											case UC_NIGHTSTIME:
 												if (cond[condnum].extrainfo2)
-													beat = va("Beat %s, mare %d in %d:%d.%d", level, cond[condnum].extrainfo2,
+													beat = va("Beat %s, mare %d in %d:%02d.%02d", level, cond[condnum].extrainfo2,
 													G_TicsToMinutes(cond[condnum].requirement, true),
 													G_TicsToSeconds(cond[condnum].requirement),
 													G_TicsToCentiseconds(cond[condnum].requirement));
 												else
-													beat = va("Beat %s in %d:%d.%d",
+													beat = va("Beat %s in %d:%02d.%02d",
 													level,
 													G_TicsToMinutes(cond[condnum].requirement, true),
 													G_TicsToSeconds(cond[condnum].requirement),
@@ -6193,7 +6166,7 @@ static void M_DrawLoadGameData(void)
 			{
 				V_DrawSmallScaledPatch(x+2, y+64, 0, savselp[5]);
 			}
-#ifndef PERFECTSAVE // disabled, don't touch
+#ifdef PERFECTSAVE // disabled on request
 			else if ((savegameinfo[savetodraw].skinnum == 1)
 			&& (savegameinfo[savetodraw].lives == 99)
 			&& (savegameinfo[savetodraw].gamemap & 8192)
@@ -6281,7 +6254,7 @@ static void M_DrawLoadGameData(void)
 			for (j = 0; j < 7; ++j)
 			{
 				if (savegameinfo[savetodraw].numemeralds & (1 << j))
-					V_DrawScaledPatch(workx, y, 0, tinyemeraldpics[j]);
+					V_DrawScaledPatch(workx, y, 0, emeraldpics[1][j]);
 				workx += 10;
 			}
 		}
@@ -8468,6 +8441,13 @@ Update the maxplayers label...
 static void M_ConnectIP(INT32 choice)
 {
 	(void)choice;
+
+	if (*setupm_ip == 0)
+	{
+		M_StartMessage("You must specify an IP address.\n", NULL, MM_NOTHING);
+		return;
+	}
+
 	COM_BufAddText(va("connect \"%s\"\n", setupm_ip));
 
 	// A little "please wait" message.
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 9acc8430ec991881b102a173d4f08d3edd05f82f..d142e2886f32cde777d17c8d1b0433ba55920b97 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -3243,14 +3243,7 @@ void A_ExtraLife(mobj_t *actor)
 		return;
 	}
 
-	// In shooter gametypes, give the player 100 rings instead of an extra life.
-	if (gametype != GT_COOP && gametype != GT_COMPETITION)
-	{
-		P_GivePlayerRings(player, 100);
-		P_PlayLivesJingle(player);
-	}
-	else
-		P_GiveCoopLives(player, 1, true);
+	P_GiveCoopLives(player, 1, true);
 }
 
 // Function: A_GiveShield
@@ -4917,6 +4910,7 @@ void A_SlingAppear(mobj_t *actor)
 	boolean firsttime = true;
 	UINT8 mlength = 4;
 	mobj_t *spawnee;
+	mobj_t *hprev = actor;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_SlingAppear", actor))
 		return;
@@ -4927,7 +4921,6 @@ void A_SlingAppear(mobj_t *actor)
 	P_SetThingPosition(actor);
 	actor->lastlook = 128;
 	actor->movecount = actor->lastlook;
-	actor->health = actor->angle>>ANGLETOFINESHIFT;
 	actor->threshold = 0;
 	actor->movefactor = actor->threshold;
 	actor->friction = 128;
@@ -4936,10 +4929,13 @@ void A_SlingAppear(mobj_t *actor)
 	{
 		spawnee = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SMALLMACECHAIN);
 
-		P_SetTarget(&spawnee->target, actor);
+		P_SetTarget(&spawnee->tracer, actor);
+		P_SetTarget(&spawnee->hprev, hprev);
+		P_SetTarget(&hprev->hnext, spawnee);
+		hprev = spawnee;
 
-		spawnee->threshold = 0;
-		spawnee->reactiontime = mlength;
+		spawnee->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
+		spawnee->movecount = mlength;
 
 		if (firsttime)
 		{
diff --git a/src/p_floor.c b/src/p_floor.c
index c72de6b7030ba0dbb1922188431f524230e9b5f9..427dc293d36b99817d1276e5e524d3249ba0d398 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -2144,6 +2144,7 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 	boolean floortouch = false;
 	fixed_t bottomheight, topheight;
 	msecnode_t *node;
+	ffloor_t *rover;
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -2191,6 +2192,19 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 			{
 				targetsec = &sectors[targetsecnum];
 
+				// Find the FOF corresponding to the control linedef
+				for (rover = targetsec->ffloors; rover; rover = rover->next)
+				{
+					if (rover->master == sec->lines[i])
+						break;
+				}
+
+				if (!rover) // This should be impossible, but don't complain if it is the case somehow
+					continue;
+
+				if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
+					continue;
+
 				for (j = 0; j < MAXPLAYERS; j++)
 				{
 					if (!playeringame[j])
diff --git a/src/p_inter.c b/src/p_inter.c
index 4c9e231fe9587742977c74583311e8fb2ad1a62b..7892e0bcfa6f9f243808126816d9b3a8d5a43236 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -577,25 +577,27 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		case MT_TOKEN:
 			if (player->bot)
 				return;
-			tokenlist += special->health;
 
 			P_AddPlayerScore(player, 1000);
 
-			if (!modeattacking) // score only there...
+			if (gametype != GT_COOP || modeattacking) // score only?
+				break;
+
+			tokenlist += special->health;
+
+			if (ALL7EMERALDS(emeralds)) // Got all 7
 			{
-				if (ALL7EMERALDS(emeralds)) // Got all 7
+				if (!(netgame || multiplayer))
 				{
-					if (!(netgame || multiplayer))
-					{
-						player->continues += 1;
-						players->gotcontinue = true;
-						if (P_IsLocalPlayer(player))
-							S_StartSound(NULL, sfx_s3kac);
-					}
+					player->continues += 1;
+					players->gotcontinue = true;
+					if (P_IsLocalPlayer(player))
+						S_StartSound(NULL, sfx_s3kac);
 				}
-				else
-					token++;
 			}
+			else
+				token++;
+
 			break;
 
 		// Emerald Hunt
@@ -1503,10 +1505,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (player->powers[pw_flashing])
 				return;
 
-			if (special->movefactor && special->tracer && (angle_t)special->tracer->health != ANGLE_90 && (angle_t)special->tracer->health != ANGLE_270)
+			if (special->movefactor && special->tracer && special->tracer->angle != ANGLE_90 && special->tracer->angle != ANGLE_270)
 			{ // I don't expect you to understand this, Mr Bond...
-				angle_t ang = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y) - special->tracer->threshold;
-				if ((special->movefactor > 0) == ((angle_t)special->tracer->health > ANGLE_90 && (angle_t)special->tracer->health < ANGLE_270))
+				angle_t ang = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y) - special->tracer->angle;
+				if ((special->movefactor > 0) == (special->tracer->angle > ANGLE_90 && special->tracer->angle < ANGLE_270))
 					ang += ANGLE_180;
 				if (ang < ANGLE_180)
 					return; // I expect you to die.
@@ -3365,8 +3367,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			P_KillPlayer(player, source, damage);
 		}
 
-		P_HitDeathMessages(player, inflictor, source, damagetype);
-
 		P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
 	}
 
@@ -3381,6 +3381,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 	else
 		target->health -= damage;
 
+	if (player)
+		P_HitDeathMessages(player, inflictor, source, damagetype);
+
 	if (source && source->player && target)
 		G_GhostAddHit(target);
 
diff --git a/src/p_local.h b/src/p_local.h
index 54ae37ed24d241f8ddf17ab00377211a562ff61e..49d3ed614793431cd473caab570636c53c973b64 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -280,6 +280,8 @@ mobj_t *P_GetClosestAxis(mobj_t *source);
 
 boolean P_CanRunOnWater(player_t *player, ffloor_t *rover);
 
+void P_MaceRotate(mobj_t *center, INT32 baserot, INT32 baseprevrot);
+
 void P_FlashPal(player_t *pl, UINT16 type, UINT16 duration);
 #define PAL_WHITE    1
 #define PAL_MIXUP    2
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 8695d57e4098898e48a24530f718818e861b2bb8..a5b808cd94aebc40faaa589bdb94fac2e7d206cd 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -6176,124 +6176,217 @@ static void P_NightsItemChase(mobj_t *thing)
 
 //
 // P_MaceRotate
-// Spins an object around its target, or, swings it from side to side.
+// Spins a hnext-chain of objects around its centerpoint, side to side or periodically.
 //
-static void P_MaceRotate(mobj_t *mobj)
+void P_MaceRotate(mobj_t *center, INT32 baserot, INT32 baseprevrot)
 {
-	TVector v;
+	TVector unit_lengthways, unit_sideways, pos_lengthways, pos_sideways;
 	TVector *res;
-	fixed_t radius, dist;
+	fixed_t radius, dist, zstore;
 	angle_t fa;
-	INT32 prevswing;
-	boolean donetwice = false;
+	boolean dosound = false;
+	mobj_t *mobj = center->hnext, *hnext = NULL;
 
-	// Tracer was removed.
-	if (!mobj->health)
-		return;
-	else if (!mobj->tracer)
+	INT32 rot = (baserot &= FINEMASK);
+	INT32 prevrot = (baseprevrot &= FINEMASK);
+
+	INT32 lastthreshold = FINEMASK; // needs to never be equal at start of loop
+	fixed_t lastfriction = INT32_MIN; // ditto; almost certainly never, but...
+
+	dist = pos_sideways[0] = pos_sideways[1] = pos_sideways[2] = pos_sideways[3] = unit_sideways[3] = pos_lengthways[0] = pos_lengthways[1] = pos_lengthways[2] = pos_lengthways[3] = 0;
+
+	while (mobj)
 	{
-		P_KillMobj(mobj, NULL, NULL, 0);
-		return;
-	}
+		if (!mobj->health)
+		{
+			mobj = mobj->hnext;
+			continue;
+		}
 
-	mobj->momx = mobj->momy = mobj->momz = 0;
+		mobj->momx = mobj->momy = mobj->momz = 0;
 
-	prevswing = mobj->threshold;
-	mobj->threshold += mobj->tracer->lastlook;
-	mobj->threshold &= FINEMASK;
+		if (mobj->threshold != lastthreshold
+		|| mobj->friction != lastfriction)
+		{
+			rot = (baserot + mobj->threshold) & FINEMASK;
+			prevrot = (baseprevrot + mobj->threshold) & FINEMASK;
 
-	dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
+			pos_lengthways[0] = pos_lengthways[1] = pos_lengthways[2] = pos_lengthways[3] = 0;
 
-	// Radius of the link's rotation.
-	radius = FixedMul(dist * mobj->movecount, mobj->tracer->scale) + mobj->tracer->extravalue1;
+			dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
+			dist = ((center->scale == FRACUNIT) ? dist : FixedMul(dist, center->scale));
 
-maceretry:
+			fa = (FixedAngle(center->movefactor*FRACUNIT) >> ANGLETOFINESHIFT);
+			radius = FixedMul(dist, FINECOSINE(fa));
+			unit_lengthways[1] = -FixedMul(dist, FINESINE(fa));
+			unit_lengthways[3] = FRACUNIT;
 
-	fa = (FixedAngle(mobj->tracer->movefactor*FRACUNIT) >> ANGLETOFINESHIFT);
-	radius = FixedMul(FINECOSINE(fa), radius);
-	v[1] = -FixedMul(FINESINE(fa), radius)
-	+ FixedMul(dist * mobj->movefactor, mobj->tracer->scale);
-	v[3] = FRACUNIT;
+			// Swinging Chain.
+			if (center->flags2 & MF2_STRONGBOX)
+			{
+				fixed_t swingmag = FixedMul(FINECOSINE(rot), center->lastlook << FRACBITS);
+				fixed_t prevswingmag = FINECOSINE(prevrot);
 
-	// Swinging Chain.
-	if (mobj->tracer->flags2 & MF2_STRONGBOX)
-	{
-		fixed_t swingmagnitude = FixedMul(FINECOSINE(mobj->threshold), mobj->tracer->lastlook << FRACBITS);
-		prevswing = FINECOSINE(prevswing);
+				if ((prevswingmag > 0) != (swingmag > 0)) // just passed its lowest point
+					dosound = true;
 
-		if (!donetwice
-		&& (mobj->flags2 & MF2_BOSSNOTRAP) // at the end of the chain and can play a sound
-		&& ((prevswing > 0) != (swingmagnitude > 0))) // just passed its lowest point
-			S_StartSound(mobj, mobj->info->activesound);
+				fa = ((FixedAngle(swingmag) >> ANGLETOFINESHIFT) + mobj->friction) & FINEMASK;
 
-		fa = ((FixedAngle(swingmagnitude) >> ANGLETOFINESHIFT) + mobj->friction) & FINEMASK;
+				unit_lengthways[0] = FixedMul(FINESINE(fa), -radius);
+				unit_lengthways[2] = FixedMul(FINECOSINE(fa), -radius);
+			}
+			// Rotating Chain.
+			else
+			{
+				angle_t prevfa = (prevrot + mobj->friction) & FINEMASK;
+				fa = (rot + mobj->friction) & FINEMASK;
 
-		v[0] = FixedMul(FINESINE(fa), -radius);
-		v[2] = FixedMul(FINECOSINE(fa), -radius);
-	}
-	// Rotating Chain.
-	else
-	{
-		prevswing = (prevswing + mobj->friction) & FINEMASK;
-		fa = (mobj->threshold + mobj->friction) & FINEMASK;
+				if (!(prevfa > (FINEMASK/2)) && (fa > (FINEMASK/2))) // completed a full swing
+					dosound = true;
+
+				unit_lengthways[0] = FixedMul(FINECOSINE(fa), radius);
+				unit_lengthways[2] = FixedMul(FINESINE(fa), radius);
+			}
 
-		if (!donetwice
-		&& (mobj->flags2 & MF2_BOSSNOTRAP) // at the end of the chain and can play a sound
-		&& (!(prevswing > (FINEMASK/2)) && (fa > (FINEMASK/2)))) // completed a full swing
+			// Calculate the angle matrixes for the link.
+			res = VectorMatrixMultiply(unit_lengthways, *RotateXMatrix(center->threshold << ANGLETOFINESHIFT));
+			M_Memcpy(&unit_lengthways, res, sizeof(unit_lengthways));
+			res = VectorMatrixMultiply(unit_lengthways, *RotateZMatrix(center->angle));
+			M_Memcpy(&unit_lengthways, res, sizeof(unit_lengthways));
+
+			lastthreshold = mobj->threshold;
+			lastfriction = mobj->friction;
+		}
+
+		if (dosound && (mobj->flags2 & MF2_BOSSNOTRAP))
+		{
 			S_StartSound(mobj, mobj->info->activesound);
+			dosound = false;
+		}
 
-		v[0] = FixedMul(FINECOSINE(fa), radius);
-		v[2] = FixedMul(FINESINE(fa), radius);
-	}
+		if (pos_sideways[3] != mobj->movefactor)
+		{
+			if (!unit_sideways[3])
+			{
+				unit_sideways[1] = dist;
+				unit_sideways[0] = unit_sideways[2] = 0;
+				unit_sideways[3] = FRACUNIT;
 
-	// Calculate the angle matrixes for the link.
-	res = VectorMatrixMultiply(v, *RotateXMatrix(mobj->tracer->threshold << ANGLETOFINESHIFT));
-	M_Memcpy(&v, res, sizeof(v));
-	res = VectorMatrixMultiply(v, *RotateZMatrix(mobj->tracer->health << ANGLETOFINESHIFT));
-	M_Memcpy(&v, res, sizeof(v));
+				res = VectorMatrixMultiply(unit_sideways, *RotateXMatrix(center->threshold << ANGLETOFINESHIFT));
+				M_Memcpy(&unit_sideways, res, sizeof(unit_sideways));
+				res = VectorMatrixMultiply(unit_sideways, *RotateZMatrix(center->angle));
+				M_Memcpy(&unit_sideways, res, sizeof(unit_sideways));
+			}
 
-	// Cut the height to align the link with the axis.
-	if (mobj->type == MT_SMALLMACECHAIN || mobj->type == MT_BIGMACECHAIN)
-		v[2] -= P_MobjFlip(mobj)*mobj->height/4;
-	else
-		v[2] -= P_MobjFlip(mobj)*mobj->height/2;
+			if (pos_sideways[3] > mobj->movefactor)
+			{
+				do
+				{
+					pos_sideways[0] -= unit_sideways[0];
+					pos_sideways[1] -= unit_sideways[1];
+					pos_sideways[2] -= unit_sideways[2];
+				}
+				while ((--pos_sideways[3]) != mobj->movefactor);
+			}
+			else
+			{
+				do
+				{
+					pos_sideways[0] += unit_sideways[0];
+					pos_sideways[1] += unit_sideways[1];
+					pos_sideways[2] += unit_sideways[2];
+				}
+				while ((++pos_sideways[3]) != mobj->movefactor);
+			}
+		}
 
-	P_UnsetThingPosition(mobj);
+		hnext = mobj->hnext; // just in case the mobj is removed
 
-	// Add on the appropriate distances to the center's co-ordinates.
-	mobj->x = mobj->tracer->x + v[0];
-	mobj->y = mobj->tracer->y + v[1];
-	mobj->z = mobj->tracer->z + v[2];
+		if (pos_lengthways[3] > mobj->movecount)
+		{
+			do
+			{
+				pos_lengthways[0] -= unit_lengthways[0];
+				pos_lengthways[1] -= unit_lengthways[1];
+				pos_lengthways[2] -= unit_lengthways[2];
+			}
+			while ((--pos_lengthways[3]) != mobj->movecount);
+		}
+		else if (pos_lengthways[3] < mobj->movecount)
+		{
+			do
+			{
+				pos_lengthways[0] += unit_lengthways[0];
+				pos_lengthways[1] += unit_lengthways[1];
+				pos_lengthways[2] += unit_lengthways[2];
+			}
+			while ((++pos_lengthways[3]) != mobj->movecount);
+		}
 
-	P_SetThingPosition(mobj);
+		P_UnsetThingPosition(mobj);
 
-	if (donetwice || P_MobjWasRemoved(mobj))
-		return;
+		mobj->x = center->x;
+		mobj->y = center->y;
+		mobj->z = center->z;
 
-	if (mobj->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT))
-		return;
+		// Add on the appropriate distances to the center's co-ordinates.
+		if (pos_lengthways[3])
+		{
+			mobj->x += pos_lengthways[0];
+			mobj->y += pos_lengthways[1];
+			zstore = pos_lengthways[2] + pos_sideways[2];
+		}
+		else
+			zstore = pos_sideways[2];
 
-	if ((fa = ((mobj->tracer->threshold & (FINEMASK/2)) << ANGLETOFINESHIFT)) > ANGLE_45 && fa < ANGLE_135) // only move towards center when the motion is towards/away from the ground, rather than alongside it
-		return;
+		mobj->x += pos_sideways[0];
+		mobj->y += pos_sideways[1];
 
-	if (mobj->subsector->sector->ffloors)
-		P_AdjustMobjFloorZ_FFloors(mobj, mobj->subsector->sector, 2);
+		// Cut the height to align the link with the axis.
+		if (mobj->type == MT_SMALLMACECHAIN || mobj->type == MT_BIGMACECHAIN)
+			zstore -= P_MobjFlip(mobj)*mobj->height/4;
+		else
+			zstore -= P_MobjFlip(mobj)*mobj->height/2;
 
-	// Variable reuse
-	if (mobj->floorz > mobj->z)
-		dist = (mobj->floorz - mobj->tracer->z);
-	else if (mobj->ceilingz < mobj->z)
-		dist = (mobj->ceilingz - mobj->tracer->z);
-	else
-		return;
+		mobj->z += zstore;
 
-	if ((dist = FixedDiv(dist, v[2])) > FRACUNIT)
-		return;
+#if 0 // toaster's testing flashie!
+		if (!(mobj->movecount & 1) && !(leveltime & TICRATE)) // I had a brainfart and the flashing isn't exactly what I expected it to be, but it's actually much more useful.
+			mobj->flags2 ^= MF2_DONTDRAW;
+#endif
+
+		P_SetThingPosition(mobj);
+
+#if 0 // toaster's height-clipping dealie!
+		if (!pos_lengthways[3] || P_MobjWasRemoved(mobj) || (mobj->flags & MF_NOCLIPHEIGHT))
+			goto cont;
+
+		if ((fa = ((center->threshold & (FINEMASK/2)) << ANGLETOFINESHIFT)) > ANGLE_45 && fa < ANGLE_135) // only move towards center when the motion is towards/away from the ground, rather than alongside it
+			goto cont;
+
+		if (mobj->subsector->sector->ffloors)
+			P_AdjustMobjFloorZ_FFloors(mobj, mobj->subsector->sector, 2);
+
+		if (mobj->floorz > mobj->z)
+			zstore = (mobj->floorz - zstore);
+		else if (mobj->ceilingz < mobj->z)
+			zstore = (mobj->ceilingz - mobj->height - zstore);
+		else
+			goto cont;
+
+		zstore = FixedDiv(zstore, dist); // Still needs work... scaling factor is wrong!
 
-	radius = FixedMul(radius, dist);
-	donetwice = true;
-	dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
-	goto maceretry;
+		P_UnsetThingPosition(mobj);
+
+		mobj->x -= FixedMul(unit_lengthways[0], zstore);
+		mobj->y -= FixedMul(unit_lengthways[1], zstore);
+
+		P_SetThingPosition(mobj);
+
+cont:
+#endif
+		mobj = hnext;
+	}
 }
 
 static boolean P_ShieldLook(mobj_t *thing, shieldtype_t shield)
@@ -6664,13 +6757,6 @@ void P_MobjThinker(mobj_t *mobj)
 		}
 	}
 
-	if (mobj->flags2 & MF2_MACEROTATE)
-	{
-		P_MaceRotate(mobj);
-		if (P_MobjWasRemoved(mobj))
-			return;
-	}
-
 	// Special thinker for scenery objects
 	if (mobj->flags & MF_SCENERY)
 	{
@@ -6687,6 +6773,18 @@ void P_MobjThinker(mobj_t *mobj)
 
 		switch (mobj->type)
 		{
+			case MT_MACEPOINT:
+			case MT_CHAINMACEPOINT:
+			case MT_SPRINGBALLPOINT:
+			case MT_CHAINPOINT:
+			case MT_FIREBARPOINT:
+			case MT_CUSTOMMACEPOINT:
+			case MT_HIDDEN_SLING:
+				// The following was pretty good, but liked breaking whenever mobj->lastlook changed.
+				//P_MaceRotate(mobj, ((leveltime + 1) * mobj->lastlook), (leveltime * mobj->lastlook));
+				P_MaceRotate(mobj, mobj->movedir + mobj->lastlook, mobj->movedir);
+				mobj->movedir = (mobj->movedir + mobj->lastlook) & FINEMASK;
+				break;
 			case MT_HOOP:
 				if (mobj->fuse > 1)
 					P_MoveHoop(mobj);
@@ -7337,19 +7435,6 @@ void P_MobjThinker(mobj_t *mobj)
 				}
 			}
 			break;
-		case MT_CHAINPOINT:
-		case MT_CHAINMACEPOINT:
-			if (leveltime & 1)
-			{
-				if (mobj->lastlook > mobj->movecount)
-					mobj->lastlook--;
-/*
-				if (mobj->threshold > mobj->movefactor)
-					mobj->threshold -= FRACUNIT;
-				else if (mobj->threshold < mobj->movefactor)
-					mobj->threshold += FRACUNIT;*/
-			}
-			break;
 		case MT_EGGCAPSULE:
 			if (!mobj->reactiontime)
 			{
@@ -8615,6 +8700,13 @@ void P_RemoveMobj(mobj_t *mobj)
 	// Remove any references to other mobjs.
 	P_SetTarget(&mobj->target, P_SetTarget(&mobj->tracer, NULL));
 
+	if (mobj->hnext && !P_MobjWasRemoved(mobj->hnext))
+		P_SetTarget(&mobj->hnext->hprev, mobj->hprev);
+	if (mobj->hprev && !P_MobjWasRemoved(mobj->hprev))
+		P_SetTarget(&mobj->hprev->hnext, mobj->hnext);
+
+	P_SetTarget(&mobj->hnext, P_SetTarget(&mobj->hprev, NULL));
+
 	// free block
 	// DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
 	if (mobj->flags & MF_NOTHINK && !mobj->thinker.next)
@@ -9301,6 +9393,7 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	mobj_t *mobj;
 	fixed_t x, y, z;
 	subsector_t *ss;
+	boolean doangle = true;
 
 	if (!mthing->type)
 		return; // Ignore type-0 things as NOPs
@@ -9539,7 +9632,7 @@ void P_SpawnMapThing(mapthing_t *mthing)
 		// They're likely facets of the level's design and therefore required to progress.
 	}
 
-	if (i == MT_TOKEN && (gametype != GT_COOP || ultimatemode || tokenbits == 30 || tokenlist & (1 << tokenbits++)))
+	if (i == MT_TOKEN && ((gametype != GT_COOP && gametype != GT_COMPETITION) || ultimatemode || tokenbits == 30 || tokenlist & (1 << tokenbits++)))
 		return; // you already got this token, or there are too many, or the gametype's not right
 
 	// Objectplace landing point
@@ -9706,11 +9799,11 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	case MT_FIREBARPOINT:
 	case MT_CUSTOMMACEPOINT:
 	{
-		fixed_t mlength, mlengthset, mspeed, mphase, myaw, mpitch, mmaxspeed, mnumspokes, mnumspokesset, mpinch, mroll, mnumnospokes, mwidth, mmin, msound, radiusfactor;
+		fixed_t mlength, mmaxlength, mlengthset, mspeed, mphase, myaw, mpitch, mminlength, mnumspokes, mpinch, mroll, mnumnospokes, mwidth, mwidthset, mmin, msound, radiusfactor, widthfactor;
 		angle_t mspokeangle;
 		mobjtype_t chainlink, macetype, firsttype, linktype;
-		boolean mdoall = true;
-		mobj_t *spawnee;
+		boolean mdosound, mdocenter;
+		mobj_t *spawnee = NULL, *hprev = mobj;
 		mobjflag_t mflagsapply;
 		mobjflag2_t mflags2apply;
 		mobjeflag_t meflagsapply;
@@ -9747,8 +9840,10 @@ ML_EFFECT4 : Don't clip inside the ground
 		mlength = abs(lines[line].dx >> FRACBITS);
 		mspeed = abs(lines[line].dy >> (FRACBITS - 4));
 		mphase = (sides[lines[line].sidenum[0]].textureoffset >> FRACBITS) % 360;
-		if ((mmaxspeed = sides[lines[line].sidenum[0]].rowoffset >> (FRACBITS - 4)) < mspeed)
-			mmaxspeed = mspeed << 1;
+		if ((mminlength = -sides[lines[line].sidenum[0]].rowoffset>>FRACBITS) < 0)
+			mminlength = 0;
+		else if (mminlength > mlength-1)
+			mminlength = mlength-1;
 		mpitch = (lines[line].frontsector->floorheight >> FRACBITS) % 360;
 		myaw = (lines[line].frontsector->ceilingheight >> FRACBITS) % 360;
 
@@ -9767,30 +9862,29 @@ ML_EFFECT4 : Don't clip inside the ground
 			mpinch = mroll = mnumnospokes = mwidth = 0;
 
 		CONS_Debug(DBG_GAMELOGIC, "Mace/Chain (mapthing #%s):\n"
-				"Length is %d\n"
+				"Length is %d (minus %d)\n"
 				"Speed is %d\n"
 				"Phase is %d\n"
 				"Yaw is %d\n"
 				"Pitch is %d\n"
-				"Max. speed is %d\n"
-				"No. of spokes is %d\n"
+				"No. of spokes is %d (%d antispokes)\n"
 				"Pinch is %d\n"
 				"Roll is %d\n"
-				"No. of antispokes is %d\n"
 				"Width is %d\n",
-				sizeu1(mthingi), mlength, mspeed, mphase, myaw, mpitch, mmaxspeed, mnumspokes, mpinch, mroll, mnumnospokes, mwidth);
+				sizeu1(mthingi), mlength, mminlength, mspeed, mphase, myaw, mpitch, mnumspokes, mnumnospokes, mpinch, mroll, mwidth);
 
 		if (mnumnospokes > 0 && (mnumnospokes < mnumspokes))
 			mnumnospokes = mnumspokes/mnumnospokes;
 		else
-			mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes - 1) : 0);
+			mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes) : 0);
 
 		mobj->lastlook = mspeed;
 		mobj->movecount = mobj->lastlook;
-		mobj->health = (FixedAngle(myaw*FRACUNIT)>>ANGLETOFINESHIFT);
+		mobj->angle = FixedAngle(myaw*FRACUNIT);
+		doangle = false;
 		mobj->threshold = (FixedAngle(mpitch*FRACUNIT)>>ANGLETOFINESHIFT);
-		mobj->friction = mmaxspeed;
 		mobj->movefactor = mpinch;
+		mobj->movedir = 0;
 
 		// Mobjtype selection
 		switch(mobj->type)
@@ -9828,7 +9922,7 @@ ML_EFFECT4 : Don't clip inside the ground
 				break;
 		}
 
-		if (!macetype)
+		if (!macetype && !chainlink)
 			break;
 
 		if (mobj->type != MT_CHAINPOINT)
@@ -9857,7 +9951,7 @@ ML_EFFECT4 : Don't clip inside the ground
 			mmin = mnumspokes;
 
 		// Make the links the same type as the end - repeated below
-		if ((mobj->type != MT_CHAINPOINT) && ((lines[line].flags & ML_EFFECT2) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
+		if ((mobj->type != MT_CHAINPOINT) && (((lines[line].flags & ML_EFFECT2) == ML_EFFECT2) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
 		{
 			linktype = macetype;
 			radiusfactor = 2; // Double the radius.
@@ -9865,8 +9959,10 @@ ML_EFFECT4 : Don't clip inside the ground
 		else
 			radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
 
+		widthfactor = ((firsttype == chainlink) ? 1 : 2);
+
 		mflagsapply = ((lines[line].flags & ML_EFFECT4) ? 0 : (MF_NOCLIP|MF_NOCLIPHEIGHT));
-		mflags2apply = (MF2_MACEROTATE|((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0));
+		mflags2apply = ((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0);
 		meflagsapply = ((mthing->options & MTF_OBJECTFLIP) ? MFE_VERTICALFLIP : 0);
 
 		msound = ((firsttype == chainlink) ? 0 : (mwidth & 1));
@@ -9875,25 +9971,27 @@ ML_EFFECT4 : Don't clip inside the ground
 		mphase = (FixedAngle(mphase*FRACUNIT)>>ANGLETOFINESHIFT);
 		mroll = (FixedAngle(mroll*FRACUNIT)>>ANGLETOFINESHIFT);
 
-#define makemace(mobjtype, dist, moreflags2) P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
-				P_SetTarget(&spawnee->tracer, mobj);\
-				spawnee->threshold = mphase;\
-				spawnee->friction = mroll;\
-				spawnee->movefactor = mwidth;\
-				spawnee->movecount = dist;\
-				spawnee->angle = myaw;\
-				spawnee->flags |= (MF_NOGRAVITY|mflagsapply);\
-				spawnee->flags2 |= (mflags2apply|moreflags2);\
-				spawnee->eflags |= meflagsapply
-
-domaceagain:
-		mnumspokesset = mnumspokes;
+#define makemace(mobjtype, dist, moreflags2) {\
+	spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
+	P_SetTarget(&spawnee->tracer, mobj);\
+	spawnee->threshold = mphase;\
+	spawnee->friction = mroll;\
+	spawnee->movefactor = mwidthset;\
+	spawnee->movecount = dist;\
+	spawnee->angle = myaw;\
+	spawnee->flags |= (MF_NOGRAVITY|mflagsapply);\
+	spawnee->flags2 |= (mflags2apply|moreflags2);\
+	spawnee->eflags |= meflagsapply;\
+	P_SetTarget(&hprev->hnext, spawnee);\
+	P_SetTarget(&spawnee->hprev, hprev);\
+	hprev = spawnee;\
+}
 
-		if (mdoall && lines[line].flags & ML_EFFECT3) // Innermost mace/link
-			{ spawnee = makemace(macetype, 0, MF2_AMBUSH); }
+		mdosound = (mspeed && !(mthing->options & MTF_OBJECTSPECIAL));
+		mdocenter = (macetype && (lines[line].flags & ML_EFFECT3));
 
 		// The actual spawning of spokes
-		while (mnumspokesset-- > 0)
+		while (mnumspokes-- > 0)
 		{
 			// Offsets
 			if (lines[line].flags & ML_EFFECT1) // Swinging
@@ -9901,14 +9999,14 @@ domaceagain:
 			else // Spinning
 				mphase = (mphase - mspokeangle) & FINEMASK;
 
-			if (mnumnospokes && !(mnumspokesset % mnumnospokes)) // Skipping a "missing" spoke
+			if (mnumnospokes && !(mnumspokes % mnumnospokes)) // Skipping a "missing" spoke
 			{
 				if (mobj->type != MT_CHAINMACEPOINT)
 					continue;
 
 				firsttype = linktype = chainlink;
-				mlengthset = 1 + (mlength - 1)*radiusfactor;
-				radiusfactor = 1;
+				mmaxlength = 1 + (mlength - 1)*radiusfactor;
+				radiusfactor = widthfactor = 1;
 			}
 			else
 			{
@@ -9927,36 +10025,73 @@ domaceagain:
 					}
 
 					firsttype = macetype;
+					widthfactor = 2;
 				}
 
-				mlengthset = mlength;
+				mmaxlength = mlength;
+			}
+
+			mwidthset = mwidth;
+			mlengthset = mminlength;
+
+			if (mdocenter) // Innermost mace/link
+				makemace(macetype, 0, 0);
+
+			// Out from the center...
+			if (linktype)
+			{
+				while ((++mlengthset) < mmaxlength)
+					makemace(linktype, radiusfactor*mlengthset, 0);
 			}
+			else
+				mlengthset = mmaxlength;
 
 			// Outermost mace/link
-			spawnee = makemace(firsttype, radiusfactor*(mlengthset--), MF2_AMBUSH);
+			if (firsttype)
+				makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
 
-			if (mspeed && (mwidth == msound) && !(mthing->options & MTF_OBJECTSPECIAL) && mnumspokesset <= mmin) // Can it make a sound?
-				spawnee->flags2 |= MF2_BOSSNOTRAP;
+			if (!mwidth)
+			{
+				if (mdosound && mnumspokes <= mmin) // Can it make a sound?
+					spawnee->flags2 |= MF2_BOSSNOTRAP;
+			}
+			else
+			{
+				// Across the bar!
+				if (!firsttype)
+					mwidthset = -mwidth;
+				else if (mwidth > 0)
+				{
+					while ((mwidthset -= widthfactor) > -mwidth)
+					{
+						makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
+						if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
+							spawnee->flags2 |= MF2_BOSSNOTRAP;
+					}
+				}
+				else
+				{
+					while ((mwidthset += widthfactor) < -mwidth)
+					{
+						makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
+						if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
+							spawnee->flags2 |= MF2_BOSSNOTRAP;
+					}
+				}
+				mwidth = -mwidth;
 
-			if (!mdoall || !linktype)
-				continue;
+				// Outermost mace/link again!
+				if (firsttype)
+					makemace(firsttype, radiusfactor*(mlengthset--), MF2_AMBUSH);
 
-			// The rest of the links
-			while (mlengthset > 0)
-				{ spawnee = makemace(linktype, radiusfactor*(mlengthset--), 0); }
-		}
+				// ...and then back into the center!
+				if (linktype)
+					while (mlengthset > mminlength)
+						makemace(linktype, radiusfactor*(mlengthset--), 0);
 
-		if (mwidth > 0)
-		{
-			mwidth *= -1;
-			goto domaceagain;
-		}
-		else if (mwidth != 0)
-		{
-			if ((mwidth = -(mwidth + ((firsttype == chainlink) ? 1 : 2))) < 0)
-				break;
-			mdoall = false;
-			goto domaceagain;
+				if (mdocenter) // Innermost mace/link
+					makemace(macetype, 0, 0);
+			}
 		}
 
 #undef makemace
@@ -10060,9 +10195,6 @@ domaceagain:
 		// the bumper in 30 degree increments.
 		mobj->threshold = (mthing->options & 15) % 12; // It loops over, etc
 		P_SetMobjState(mobj, mobj->info->spawnstate+mobj->threshold);
-
-		// you can shut up now, OBJECTFLIP.  And all of the other options, for that matter.
-		mthing->options &= ~0xF;
 		break;
 	case MT_EGGCAPSULE:
 		if (mthing->angle <= 0)
@@ -10264,7 +10396,15 @@ domaceagain:
 		}
 	}
 
-	mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
+	if (doangle)
+		mobj->angle = FixedAngle(mthing->angle*FRACUNIT);
+
+	// ignore MTF_ flags and return early
+	if (i == MT_NIGHTSBUMPER)
+	{
+		mthing->mobj = mobj;
+		return;
+	}
 
 	if ((mthing->options & MTF_AMBUSH)
 	&& (mthing->options & MTF_OBJECTSPECIAL)
diff --git a/src/p_mobj.h b/src/p_mobj.h
index c6e1bfbf2317b3f6a4f2f83f42fe04f9e63102fb..f6ebd3cadc497465312a139860ee2975b74619cc 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -194,7 +194,6 @@ typedef enum
 	MF2_AMBUSH         = 1<<27, // Alternate behaviour typically set by MTF_AMBUSH
 	MF2_LINKDRAW       = 1<<28, // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	MF2_SHIELD         = 1<<29, // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
-	MF2_MACEROTATE     = 1<<30, // Thinker calls P_MaceRotate around tracer
 	// free: to and including 1<<31
 } mobjflag2_t;
 
diff --git a/src/p_setup.c b/src/p_setup.c
index a9fc57652f63d54a5f1b88966c4ac8db49e81649..a5544c26b74f57e11b06c318d047461dc142808c 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2675,11 +2675,6 @@ boolean P_SetupLevel(boolean skipprecip)
 
 
 	// Reset the palette
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		HWR_SetPaletteColor(0);
-	else
-#endif
 	if (rendermode != render_none)
 		V_SetPaletteLump("PLAYPAL");
 
@@ -2737,6 +2732,7 @@ boolean P_SetupLevel(boolean skipprecip)
 	{
 		tic_t starttime = I_GetTime();
 		tic_t endtime = starttime + (3*TICRATE)/2;
+		tic_t nowtime;
 
 		S_StartSound(NULL, sfx_s3kaf);
 
@@ -2746,9 +2742,17 @@ boolean P_SetupLevel(boolean skipprecip)
 		F_WipeEndScreen();
 		F_RunWipe(wipedefs[wipe_speclevel_towhite], false);
 
+		nowtime = lastwipetic;
 		// Hold on white for extra effect.
-		while (I_GetTime() < endtime)
-			I_Sleep();
+		while (nowtime < endtime)
+		{
+			// wait loop
+			while (!((nowtime = I_GetTime()) - lastwipetic))
+				I_Sleep();
+			lastwipetic = nowtime;
+			if (moviemode) // make sure we save frames for the white hold too
+				M_SaveFrame();
+		}
 
 		ranspecialwipe = 1;
 	}
@@ -3336,7 +3340,7 @@ boolean P_AddWadFile(const char *wadfilename)
 	if ((numlumps = W_InitFile(wadfilename)) == INT16_MAX)
 	{
 		refreshdirmenu |= REFRESHDIR_NOTLOADED;
-		CONS_Printf(M_GetText("Errors occured while loading %s; not added.\n"), wadfilename);
+		CONS_Printf(M_GetText("Errors occurred while loading %s; not added.\n"), wadfilename);
 		return false;
 	}
 	else
diff --git a/src/p_slopes.c b/src/p_slopes.c
index f480b6e4f9d234e95e451ae5b9a503794c624f03..7c84a2db50913732a54e103caeaa1f97c0c4c77a 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -251,7 +251,7 @@ void P_SpawnSlope_Line(int linenum)
 	UINT8 flags = 0; // Slope flags
 	if (line->flags & ML_NOSONIC)
 		flags |= SL_NOPHYSICS;
-	if (line->flags & ML_NOTAILS)
+	if (!(line->flags & ML_NOTAILS))
 		flags |= SL_NODYNAMIC;
 	if (line->flags & ML_NOKNUX)
 		flags |= SL_ANCHORVERTEX;
diff --git a/src/p_spec.c b/src/p_spec.c
index 0b005baff88d86ce2e55ee57d5df88fd029039d7..93b5c89ca1bf26d6118bf40b5c542fb638ec3991 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -31,6 +31,7 @@
 #include "p_polyobj.h"
 #include "p_slopes.h"
 #include "hu_stuff.h"
+#include "v_video.h" // V_AUTOFADEOUT|V_ALLOWLOWERCASE
 #include "m_misc.h"
 #include "m_cond.h" //unlock triggers
 #include "lua_hook.h" // LUAh_LinedefExecute
@@ -3815,9 +3816,9 @@ DoneSection2:
 					if (!P_IsFlagAtBase(MT_REDFLAG))
 						break;
 
-					HU_SetCEchoFlags(0);
+					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
 					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("%s\\captured the blue flag.\\\\\\\\"), player_names[player-players]));
+					HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sBLUE FLAG%s.\\\\\\\\"), "\x85", player_names[player-players], "\x80", "\x84", "\x80"));
 
 					if (splitscreen || players[consoleplayer].ctfteam == 1)
 						S_StartSound(NULL, sfx_flgcap);
@@ -3848,9 +3849,9 @@ DoneSection2:
 					if (!P_IsFlagAtBase(MT_BLUEFLAG))
 						break;
 
-					HU_SetCEchoFlags(0);
+					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
 					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("%s\\captured the red flag.\\\\\\\\"), player_names[player-players]));
+					HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sRED FLAG%s.\\\\\\\\"), "\x84", player_names[player-players], "\x80", "\x85", "\x80"));
 
 					if (splitscreen || players[consoleplayer].ctfteam == 2)
 						S_StartSound(NULL, sfx_flgcap);
@@ -5526,6 +5527,30 @@ void P_InitSpecials(void)
 	P_InitTagLists();   // Create xref tables for tags
 }
 
+static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs)
+{
+	if (!(master->flags & ML_NOSONIC)) // Modify floor flat alignment unless NOSONIC flag is set
+	{
+		sector->spawn_flrpic_angle = sector->floorpic_angle = flatangle;
+		sector->floor_xoffs += xoffs;
+		sector->floor_yoffs += yoffs;
+		// saved for netgames
+		sector->spawn_flr_xoffs = sector->floor_xoffs;
+		sector->spawn_flr_yoffs = sector->floor_yoffs;
+	}
+
+	if (!(master->flags & ML_NOTAILS)) // Modify ceiling flat alignment unless NOTAILS flag is set
+	{
+		sector->spawn_ceilpic_angle = sector->ceilingpic_angle = flatangle;
+		sector->ceiling_xoffs += xoffs;
+		sector->ceiling_yoffs += yoffs;
+		// saved for netgames
+		sector->spawn_ceil_xoffs = sector->ceiling_xoffs;
+		sector->spawn_ceil_yoffs = sector->ceiling_yoffs;
+	}
+
+}
+
 /** After the map has loaded, scans for specials that spawn 3Dfloors and
   * thinkers.
   *
@@ -5729,27 +5754,13 @@ void P_SpawnSpecials(INT32 fromnetsave)
 						yoffs = lines[i].v1->y;
 					}
 
-					for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+					//If no tag is given, apply to front sector
+					if (lines[i].tag == 0)
+						P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
+					else
 					{
-						if (!(lines[i].flags & ML_NOSONIC)) // Modify floor flat alignment unless NOSONIC flag is set
-						{
-							sectors[s].spawn_flrpic_angle = sectors[s].floorpic_angle = flatangle;
-							sectors[s].floor_xoffs += xoffs;
-							sectors[s].floor_yoffs += yoffs;
-							// saved for netgames
-							sectors[s].spawn_flr_xoffs = sectors[s].floor_xoffs;
-							sectors[s].spawn_flr_yoffs = sectors[s].floor_yoffs;
-						}
-
-						if (!(lines[i].flags & ML_NOTAILS)) // Modify ceiling flat alignment unless NOTAILS flag is set
-						{
-							sectors[s].spawn_ceilpic_angle = sectors[s].ceilingpic_angle = flatangle;
-							sectors[s].ceiling_xoffs += xoffs;
-							sectors[s].ceiling_yoffs += yoffs;
-							// saved for netgames
-							sectors[s].spawn_ceil_xoffs = sectors[s].ceiling_xoffs;
-							sectors[s].spawn_ceil_yoffs = sectors[s].ceiling_yoffs;
-						}
+						for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0;)
+							P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
 					}
 				}
 				else // Otherwise, print a helpful warning. Can I do no less?
@@ -6688,6 +6699,7 @@ void T_Scroll(scroll_t *s)
 		line_t *line;
 		size_t i;
 		INT32 sect;
+		ffloor_t *rover;
 
 		case sc_side: // scroll wall texture
 			side = sides + s->affectee;
@@ -6729,6 +6741,19 @@ void T_Scroll(scroll_t *s)
 					sector_t *psec;
 					psec = sectors + sect;
 
+					// Find the FOF corresponding to the control linedef
+					for (rover = psec->ffloors; rover; rover = rover->next)
+					{
+						if (rover->master == sec->lines[i])
+							break;
+					}
+
+					if (!rover) // This should be impossible, but don't complain if it is the case somehow
+						continue;
+
+					if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
+						continue;
+
 					for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
 					{
 						thing = node->m_thing;
@@ -6792,6 +6817,19 @@ void T_Scroll(scroll_t *s)
 					sector_t *psec;
 					psec = sectors + sect;
 
+					// Find the FOF corresponding to the control linedef
+					for (rover = psec->ffloors; rover; rover = rover->next)
+					{
+						if (rover->master == sec->lines[i])
+							break;
+					}
+
+					if (!rover) // This should be impossible, but don't complain if it is the case somehow
+						continue;
+
+					if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
+						continue;
+
 					for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
 					{
 						thing = node->m_thing;
diff --git a/src/p_tick.c b/src/p_tick.c
index 658b1e4ea82255519c1574bf0098a88ddeef8a15..d0e669ee265df2ce65309f97a04d6da3fce4840c 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -606,7 +606,8 @@ void P_Ticker(boolean run)
 	}
 
 	// Keep track of how long they've been playing!
-	totalplaytime++;
+	if (!demoplayback) // Don't increment if a demo is playing.
+		totalplaytime++;
 
 	if (!useNightsSS && G_IsSpecialStage(gamemap))
 		P_DoSpecialStageStuff();
diff --git a/src/p_user.c b/src/p_user.c
index f422e9b650994aba6311691d1aa1d5ed9a8acfa9..2bcb3bc6c8f102bd1b5023a6e667972567979547 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -909,8 +909,12 @@ void P_ResetPlayer(player_t *player)
 // Gives rings to the player, and does any special things required.
 // Call this function when you want to increment the player's health.
 //
+
 void P_GivePlayerRings(player_t *player, INT32 num_rings)
 {
+	if (!player)
+		return;
+
 	if (player->bot)
 		player = &players[consoleplayer];
 
@@ -929,7 +933,7 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 		player->rings = 0;
 
 	// Now extra life bonuses are handled here instead of in P_MovePlayer, since why not?
-	if (!ultimatemode && !modeattacking && !G_IsSpecialStage(gamemap) && G_GametypeUsesLives())
+	if (!ultimatemode && !modeattacking && !G_IsSpecialStage(gamemap) && G_GametypeUsesLives() && player->lives != 0x7f)
 	{
 		INT32 gainlives = 0;
 
@@ -941,7 +945,12 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 
 		if (gainlives)
 		{
-			P_GivePlayerLives(player, gainlives);
+			player->lives += gainlives;
+			if (player->lives > 99)
+				player->lives = 99;
+			else if (player->lives < 1)
+				player->lives = 1;
+
 			P_PlayLivesJingle(player);
 		}
 	}
@@ -955,7 +964,30 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 //
 void P_GivePlayerLives(player_t *player, INT32 numlives)
 {
-	if (player->lives == 0x7f) return;
+	if (!player)
+		return;
+
+	if (player->bot)
+		player = &players[consoleplayer];
+
+	if (gamestate == GS_LEVEL)
+	{
+		if (player->lives == 0x7f || (gametype != GT_COOP && gametype != GT_COMPETITION))
+		{
+			P_GivePlayerRings(player, 100*numlives);
+			return;
+		}
+
+		if ((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0)
+		{
+			UINT8 prevlives = player->lives;
+			P_GivePlayerRings(player, 100*numlives);
+			if (player->lives - prevlives >= numlives)
+				return;
+
+			numlives = (numlives + prevlives - player->lives);
+		}
+	}
 
 	player->lives += numlives;
 
@@ -1159,11 +1191,7 @@ void P_PlayLivesJingle(player_t *player)
 	if (player && !P_IsLocalPlayer(player))
 		return;
 
-	if ((player && player->lives == 0x7f)
-	|| (!player && &players[consoleplayer] && players[consoleplayer].lives == 0x7f)
-	|| (gametype == GT_COOP && (netgame || multiplayer) && cv_cooplives.value == 0))
-		S_StartSound(NULL, sfx_lose);
-	else if (use1upSound)
+	if (use1upSound)
 		S_StartSound(NULL, sfx_oneup);
 	else if (mariomode)
 		S_StartSound(NULL, sfx_marioa);
@@ -1725,7 +1753,7 @@ void P_DoPlayerExit(player_t *player)
 	else if (gametype == GT_RACE || gametype == GT_COMPETITION) // If in Race Mode, allow
 	{
 		if (!countdown) // a 60-second wait ala Sonic 2.
-			countdown = cv_countdowntime.value*TICRATE + 1; // Use cv_countdowntime
+			countdown = (cv_countdowntime.value - 1)*TICRATE + 1; // Use cv_countdowntime
 
 		player->exiting = 3*TICRATE;
 
@@ -1768,6 +1796,9 @@ boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
+			if (!(rover->flags & FF_EXISTS))
+				continue;
+
 			if (GETSECSPECIAL(rover->master->frontsector->special, 1) != SPACESPECIAL)
 				continue;
 #ifdef ESLOPE
@@ -2128,6 +2159,12 @@ static void P_CheckBouncySectors(player_t *player)
 
 			for (rover = node->m_sector->ffloors; rover; rover = rover->next)
 			{
+				if (!(rover->flags & FF_EXISTS))
+					continue; // FOFs should not be bouncy if they don't even "exist"
+
+				if (GETSECSPECIAL(rover->master->frontsector->special, 1) != 15)
+					continue; // this sector type is required for FOFs to be bouncy
+
 				topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 				bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 
@@ -2141,7 +2178,6 @@ static void P_CheckBouncySectors(player_t *player)
 						&& oldz + player->mo->height > P_GetFOFBottomZ(player->mo, node->m_sector, rover, oldx, oldy, NULL))
 					top = false;
 
-				if (GETSECSPECIAL(rover->master->frontsector->special, 1) == 15)
 				{
 					fixed_t linedist;
 
@@ -2336,12 +2372,17 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player)
 {
 	tic_t timeleft = (player->powers[pw_spacetime]) ? player->powers[pw_spacetime] : player->powers[pw_underwater];
 
-	if ((timeleft == 11*TICRATE + 1) // 5
-	 || (timeleft ==  9*TICRATE + 1) // 4
-	 || (timeleft ==  7*TICRATE + 1) // 3
-	 || (timeleft ==  5*TICRATE + 1) // 2
-	 || (timeleft ==  3*TICRATE + 1) // 1
-	 || (timeleft ==  1*TICRATE + 1) // 0
+	if (player->exiting)
+		player->powers[pw_underwater] = player->powers[pw_spacetime] = 0;
+
+	timeleft--; // The original code was all n*TICRATE + 1, so let's remove 1 tic for simplicity
+
+	if ((timeleft == 11*TICRATE) // 5
+	 || (timeleft ==  9*TICRATE) // 4
+	 || (timeleft ==  7*TICRATE) // 3
+	 || (timeleft ==  5*TICRATE) // 2
+	 || (timeleft ==  3*TICRATE) // 1
+	 || (timeleft ==  1*TICRATE) // 0
 	) {
 		fixed_t height = (player->mo->eflags & MFE_VERTICALFLIP)
 		? player->mo->z - FixedMul(8*FRACUNIT + mobjinfo[MT_DROWNNUMBERS].height, FixedMul(player->mo->scale, player->shieldscale))
@@ -2349,7 +2390,7 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player)
 
 		mobj_t *numbermobj = P_SpawnMobj(player->mo->x, player->mo->y, height, MT_DROWNNUMBERS);
 
-		timeleft /= (2*TICRATE); // To be strictly accurate it'd need to be (((timeleft - 1)/TICRATE) - 1)/2, but integer division rounds down for us
+		timeleft /= (2*TICRATE); // To be strictly accurate it'd need to be ((timeleft/TICRATE) - 1)/2, but integer division rounds down for us
 
 		if (player->charflags & SF_MACHINE)
 		{
@@ -2408,14 +2449,6 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player)
 			S_ChangeMusicInternal("_drown", false);
 		}
 	}
-
-	if (player->exiting)
-	{
-		if (player->powers[pw_underwater] > 1)
-			player->powers[pw_underwater] = 0;
-
-		player->powers[pw_spacetime] = 0;
-	}
 }
 
 //
@@ -2585,13 +2618,11 @@ static void P_DoPlayerHeadSigns(player_t *player)
 			}
 			else
 				sign->z += P_GetPlayerHeight(player)+FixedMul(16*FRACUNIT, player->mo->scale);
-			if (leveltime & 4)
-			{
-				if (player->gotflag & GF_REDFLAG)
-					P_SetMobjStateNF(sign, S_GOTREDFLAG);
-			}
-			else if (player->gotflag & GF_BLUEFLAG)
-				P_SetMobjStateNF(sign, S_GOTBLUEFLAG);
+
+			if (player->gotflag & GF_REDFLAG)
+				sign->frame = 1|FF_FULLBRIGHT;
+			else //if (player->gotflag & GF_BLUEFLAG)
+				sign->frame = 2|FF_FULLBRIGHT;
 		}
 	}
 }
@@ -9538,7 +9569,7 @@ void P_PlayerThink(player_t *player)
 		if (i == MAXPLAYERS && player->exiting == 3*TICRATE) // finished
 			player->exiting = (14*TICRATE)/5 + 1;
 
-		// If 10 seconds are left on the timer,
+		// If 11 seconds are left on the timer,
 		// begin the drown music for countdown!
 		if (countdown == 11*TICRATE - 1)
 		{
@@ -9711,7 +9742,7 @@ void P_PlayerThink(player_t *player)
 		}
 	}
 
-	if (player->linktimer && (player->linktimer >= (2*TICRATE - 1) || !player->powers[pw_nights_linkfreeze]))
+	if (player->linktimer && !player->powers[pw_nights_linkfreeze])
 	{
 		if (--player->linktimer <= 0) // Link timer
 			player->linkcount = 0;
@@ -10421,21 +10452,10 @@ void P_PlayerAfterThink(player_t *player)
 			player->secondjump = 0;
 			player->pflags &= ~PF_THOKKED;
 
-			if (cmd->forwardmove > 0)
-			{
-				if ((player->mo->tracer->tracer->lastlook += 2) > player->mo->tracer->tracer->friction)
-					player->mo->tracer->tracer->lastlook = player->mo->tracer->tracer->friction;
-			}
-			else if (cmd->forwardmove < 0)
-			{
-				if ((player->mo->tracer->tracer->lastlook -= 2) < player->mo->tracer->tracer->movecount)
-					player->mo->tracer->tracer->lastlook = player->mo->tracer->tracer->movecount;
-			}
-
 			if ((player->mo->tracer->tracer->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?
 			{
-				player->mo->tracer->tracer->health += cmd->sidemove;
+				player->mo->tracer->tracer->angle += cmd->sidemove<<ANGLETOFINESHIFT;
 				player->mo->angle += cmd->sidemove<<ANGLETOFINESHIFT; // 2048 --> ANGLE_MAX
 
 				if (!demoplayback || P_AnalogMove(player))
diff --git a/src/r_data.c b/src/r_data.c
index c4209fad609299fa9bebaee3db530c23e18470d8..169b042015066ca459432293604829717d8bd27b 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -439,11 +439,22 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 		height = SHORT(realpatch->height);
 		x2 = x1 + width;
 
+		if (x1 > texture->width || x2 < 0)
+			continue; // patch not located within texture's x bounds, ignore
+
+		if (patch->originy > texture->height || (patch->originy + height) < 0)
+			continue; // patch not located within texture's y bounds, ignore
+
+		// patch is actually inside the texture!
+		// now check if texture is partly off-screen and adjust accordingly
+
+		// left edge
 		if (x1 < 0)
 			x = 0;
 		else
 			x = x1;
 
+		// right edge
 		if (x2 > texture->width)
 			x2 = texture->width;
 
diff --git a/src/r_things.c b/src/r_things.c
index d201786a596b87cb87ee47b40a454df2262616a6..fad47d26b12472876a5b97dd9fe304eb0dfc84a6 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2497,11 +2497,7 @@ static void Sk_SetDefaultValue(skin_t *skin)
 	skin->flags = 0;
 
 	strcpy(skin->realname, "Someone");
-#ifdef SKINNAMEPADDING
-	strcpy(skin->hudname, "  ???");
-#else
 	strcpy(skin->hudname, "???");
-#endif
 	strncpy(skin->charsel, "CHRSONIC", 8);
 	strncpy(skin->face, "MISSING", 8);
 	strncpy(skin->superface, "MISSING", 8);
@@ -2733,11 +2729,7 @@ static UINT16 W_CheckForSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
 	return INT16_MAX; // not found
 }
 
-#ifdef SKINNAMEPADDING
-#define HUDNAMEWRITE(value) snprintf(skin->hudname, sizeof(skin->hudname), "%5s", value)
-#else
 #define HUDNAMEWRITE(value) STRBUFCPY(skin->hudname, value)
-#endif
 
 // turn _ into spaces and . into katana dot
 #define SYMBOLCONVERT(name) for (value = name; *value; value++)\
diff --git a/src/screen.c b/src/screen.c
index 28765785bda30f2071c14e7176f022371a20ddad..cd97b62fa7e58fad34e964a9c95ac68d8c33971c 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -414,7 +414,7 @@ void SCR_DisplayTicRate(void)
 	tic_t ontic = I_GetTime();
 	tic_t totaltics = 0;
 	INT32 ticcntcolor = 0;
-	INT32 offs = (cv_debug ? 8 : 0);
+	const INT32 h = vid.height-(8*vid.dupy);
 
 	for (i = lasttic + 1; i < TICRATE+lasttic && i < ontic; ++i)
 		fpsgraph[i % TICRATE] = false;
@@ -428,9 +428,9 @@ void SCR_DisplayTicRate(void)
 	if (totaltics <= TICRATE/2) ticcntcolor = V_REDMAP;
 	else if (totaltics == TICRATE) ticcntcolor = V_GREENMAP;
 
-	V_DrawString(vid.width-((24+(6*offs))*vid.dupx), vid.height-((16-offs)*vid.dupy),
-		V_YELLOWMAP|V_NOSCALESTART, "FPS");
-	V_DrawString(vid.width-(40*vid.dupx), vid.height-(8*vid.dupy),
+	V_DrawString(vid.width-(72*vid.dupx), h,
+		V_YELLOWMAP|V_NOSCALESTART, "FPS:");
+	V_DrawString(vid.width-(40*vid.dupx), h,
 		ticcntcolor|V_NOSCALESTART, va("%02d/%02u", totaltics, TICRATE));
 
 	lasttic = ontic;
@@ -440,6 +440,18 @@ void SCR_ClosedCaptions(void)
 {
 	UINT8 i;
 	boolean gamestopped = (paused || P_AutoPause());
+	INT32 basey = BASEVIDHEIGHT;
+
+	if (gamestate == GS_LEVEL)
+	{
+		if (splitscreen)
+			basey -= 8;
+		else if (((maptol & TOL_NIGHTS) && (modeattacking == ATTACKING_NIGHTS))
+		|| (cv_powerupdisplay.value == 2)
+		|| (cv_powerupdisplay.value == 1 && ((stplyr == &players[displayplayer] && !camera.chase)
+		|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))))
+			basey -= 16;
+	}
 
 	for (i = 0; i < NUMCAPTIONS; i++)
 	{
@@ -455,9 +467,8 @@ void SCR_ClosedCaptions(void)
 		if (music && !gamestopped && (closedcaptions[i].t < flashingtics) && (closedcaptions[i].t & 1))
 			continue;
 
-		flags = V_NOSCALESTART|V_ALLOWLOWERCASE;
-		y = vid.height-((i + 2)*10*vid.dupy);
-		dot = ' ';
+		flags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_ALLOWLOWERCASE;
+		y = basey-((i + 2)*10);
 
 		if (closedcaptions[i].b)
 			y -= (closedcaptions[i].b--)*vid.dupy;
@@ -469,8 +480,10 @@ void SCR_ClosedCaptions(void)
 			dot = '\x19';
 		else if (closedcaptions[i].c && closedcaptions[i].c->origin)
 			dot = '\x1E';
+		else
+			dot = ' ';
 
-		V_DrawRightAlignedString(vid.width-(20*vid.dupx), y,
-		flags, va("%c [%s]", dot, (closedcaptions[i].s->caption[0] ? closedcaptions[i].s->caption : closedcaptions[i].s->name)));
+		V_DrawRightAlignedString(BASEVIDWIDTH - 20, y, flags,
+			va("%c [%s]", dot, (closedcaptions[i].s->caption[0] ? closedcaptions[i].s->caption : closedcaptions[i].s->name)));
 	}
 }
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index f4686d2bf4f3e95626ba6a69322ce44b379ce8ec..05ac6450e2267c0c08182775cece05eeaefabeab 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -94,6 +94,7 @@ void *hwSym(const char *funcName,void *handle)
 #ifdef SHUFFLE
 	GETFUNC(PostImgRedraw);
 #endif //SHUFFLE
+	GETFUNC(FlushScreenTextures);
 	GETFUNC(StartScreenWipe);
 	GETFUNC(EndScreenWipe);
 	GETFUNC(DoScreenWipe);
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 1776ff45033a2ff6666c39d132c08f1dd1696b70..c8fcd080ebd42aa5f989c60875e0cd2f4bbe1aec 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2693,7 +2693,7 @@ const char *I_LocateWad(void)
 	return waddir;
 }
 
-#if defined(LINUX) || defined(LINUX64)
+#ifdef __linux__
 #define MEMINFO_FILE "/proc/meminfo"
 #define MEMTOTAL "MemTotal:"
 #define MEMFREE "MemFree:"
@@ -2713,20 +2713,23 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	};
 	if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm_open")) == NULL)
 	{
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 	if (kvm_nlist(kd, namelist) != 0)
 	{
 		kvm_close (kd);
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 	if (kvm_read(kd, namelist[X_SUM].n_value, &sum,
 		sizeof (sum)) != sizeof (sum))
 	{
 		kvm_close(kd);
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 	kvm_close(kd);
@@ -2747,7 +2750,7 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	if (total)
 		*total = (UINT32)info.dwTotalPhys;
 	return (UINT32)info.dwAvailPhys;
-#elif defined (LINUX) || defined (LINUX64)
+#elif defined (__linux__)
 	/* Linux */
 	char buf[1024];
 	char *memTag;
@@ -2763,25 +2766,28 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	if (n < 0)
 	{
 		// Error
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 
 	buf[n] = '\0';
-	if (NULL == (memTag = strstr(buf, MEMTOTAL)))
+	if ((memTag = strstr(buf, MEMTOTAL)) == NULL)
 	{
 		// Error
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 
 	memTag += sizeof (MEMTOTAL);
 	totalKBytes = atoi(memTag);
 
-	if (NULL == (memTag = strstr(buf, MEMFREE)))
+	if ((memTag = strstr(buf, MEMFREE)) == NULL)
 	{
 		// Error
-		*total = 0L;
+		if (total)
+			*total = 0L;
 		return 0;
 	}
 
@@ -2796,7 +2802,7 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	if (total)
 		*total = 48<<20;
 	return 48<<20;
-#endif /* LINUX */
+#endif
 }
 
 const CPUInfoFlags *I_CPUInfo(void)
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 365d70729f23c650d7fce7e65e6874c6de55573f..a1031209d958651ae26e0e11b08e2193b6ba732d 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -658,6 +658,14 @@ static void Impl_HandleMouseButtonEvent(SDL_MouseButtonEvent evt, Uint32 type)
 
 	SDL_memset(&event, 0, sizeof(event_t));
 
+	// Ignore the event if the mouse is not actually focused on the window.
+	// This can happen if you used the mouse to restore keyboard focus;
+	// this apparently makes a mouse button down event but not a mouse button up event,
+	// resulting in whatever key was pressed down getting "stuck" if we don't ignore it.
+	// -- Monster Iestyn (28/05/18)
+	if (SDL_GetMouseFocus() != window)
+		return;
+
 	/// \todo inputEvent.button.which
 	if (USE_MOUSEINPUT)
 	{
@@ -1446,6 +1454,7 @@ void I_StartupGraphics(void)
 #ifdef SHUFFLE
 		HWD.pfnPostImgRedraw    = hwSym("PostImgRedraw",NULL);
 #endif
+		HWD.pfnFlushScreenTextures=hwSym("FlushScreenTextures",NULL);
 		HWD.pfnStartScreenWipe  = hwSym("StartScreenWipe",NULL);
 		HWD.pfnEndScreenWipe    = hwSym("EndScreenWipe",NULL);
 		HWD.pfnDoScreenWipe     = hwSym("DoScreenWipe",NULL);
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index cd7ced7cab1eed7886af073f1c187b40ee18fd7e..4347b35b22b2e07682e5ced0e89bd76636366a04 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -214,8 +214,11 @@ void OglSdlFinishUpdate(boolean waitvbl)
 	HWR_DrawScreenFinalTexture(sdlw, sdlh);
 	SDL_GL_SwapWindow(window);
 
-	SetModelView(realwidth, realheight);
-	SetStates();
+	GClipRect(0, 0, realwidth, realheight, NZCLIP_PLANE);
+
+	// Sryder:	We need to draw the final screen texture again into the other buffer in the original position so that
+	//			effects that want to take the old screen can do so after this
+	HWR_DrawScreenFinalTexture(realwidth, realheight);
 }
 
 EXPORT void HWRAPI( OglSdlSetPalette) (RGBA_t *palette, RGBA_t *pgamma)
diff --git a/src/sdl/sdl_sound.c b/src/sdl/sdl_sound.c
index 6ca11c9ef46b9c6e30c6935d1b7d633c2e7c1147..63b51c625415b043c09dcc8b3b8937af46f5be9c 100644
--- a/src/sdl/sdl_sound.c
+++ b/src/sdl/sdl_sound.c
@@ -1180,12 +1180,6 @@ void I_StartupSound(void)
 	audio.callback = I_UpdateStream;
 	audio.userdata = &localdata;
 
-	if (dedicated)
-	{
-		nosound = nomidimusic = nodigimusic = true;
-		return;
-	}
-
 	// Configure sound device
 	CONS_Printf("I_StartupSound:\n");
 
@@ -1481,9 +1475,6 @@ void I_InitMusic(void)
 	I_AddExitFunc(I_ShutdownGMEMusic);
 #endif
 
-	if ((nomidimusic && nodigimusic) || dedicated)
-		return;
-
 #ifdef HAVE_MIXER
 	MIX_VERSION(&MIXcompiled)
 	MIXlinked = Mix_Linked_Version();
diff --git a/src/sounds.c b/src/sounds.c
index 475d65cd17383fcb56c050d98206e8df018a0d0a..293ce381d4867d33e9a8ae9d1f76c07a5d659fb6 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -85,14 +85,14 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"athun1", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thunder"},
   {"athun2", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thunder"},
 
-  {"amwtr1", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr2", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr3", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr4", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr5", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr6", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr7", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
-  {"amwtr8", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"amwtr1", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr2", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr3", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr4", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr5", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr6", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr7", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
+  {"amwtr8", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stream"},
   {"bubbl1", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
   {"bubbl2", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
   {"bubbl3", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
diff --git a/src/st_stuff.c b/src/st_stuff.c
index ddb3e2738464c951ffbd29c9101fd51fb6c07792..8d4e3242444064192123b932e3caec17e2c40fd3 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -27,6 +27,8 @@
 #include "i_system.h"
 #include "m_menu.h"
 #include "m_cheat.h"
+#include "m_misc.h" // moviemode
+#include "m_anigif.h" // cv_gif_downscale
 #include "p_setup.h" // NiGHTS grading
 
 //random index
@@ -100,6 +102,7 @@ static patch_t *invincibility;
 static patch_t *sneakers;
 static patch_t *gravboots;
 static patch_t *nonicon;
+static patch_t *nonicon2;
 static patch_t *bluestat;
 static patch_t *byelstat;
 static patch_t *orngstat;
@@ -124,42 +127,32 @@ static boolean facefreed[MAXPLAYERS];
 
 hudinfo_t hudinfo[NUMHUDITEMS] =
 {
-	{  34, 176}, // HUD_LIVESNAME
-	{  16, 176}, // HUD_LIVESPIC
-	{  74, 184}, // HUD_LIVESNUM
-	{  38, 186}, // HUD_LIVESX
-
-	{  16,  42}, // HUD_RINGS
-	{ 220,  10}, // HUD_RINGSSPLIT
-	{  96,  42}, // HUD_RINGSNUM
-	{ 296,  10}, // HUD_RINGSNUMSPLIT
-	{ 120,  42}, // HUD_RINGSNUMTICS
-
-	{  16,  10}, // HUD_SCORE
-	{ 120,  10}, // HUD_SCORENUM
-
-	{  16,  26}, // HUD_TIME
-	{ 128,  10}, // HUD_TIMESPLIT
-	{  72,  26}, // HUD_MINUTES
-	{ 188,  10}, // HUD_MINUTESSPLIT
-	{  72,  26}, // HUD_TIMECOLON
-	{ 188,  10}, // HUD_TIMECOLONSPLIT
-	{  96,  26}, // HUD_SECONDS
-	{ 212,  10}, // HUD_SECONDSSPLIT
-	{  96,  26}, // HUD_TIMETICCOLON
-	{ 120,  26}, // HUD_TICS
-
-	{ 120,  56}, // HUD_SS_TOTALRINGS
-	{ 296,  40}, // HUD_SS_TOTALRINGS_SPLIT
-
-	{ 110,  93}, // HUD_GETRINGS
-	{ 160,  93}, // HUD_GETRINGSNUM
-	{ 124, 160}, // HUD_TIMELEFT
-	{ 168, 176}, // HUD_TIMELEFTNUM
-	{ 130,  93}, // HUD_TIMEUP
-	{ 152, 168}, // HUD_HUNTPICS
-	{ 152,  24}, // HUD_GRAVBOOTSICO
-	{ 240, 160}, // HUD_LAP
+	{  16, 176, V_SNAPTOLEFT|V_SNAPTOBOTTOM}, // HUD_LIVES
+
+	{  16,  42, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_RINGS
+	{  96,  42, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_RINGSNUM
+	{ 120,  42, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_RINGSNUMTICS
+
+	{  16,  10, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_SCORE
+	{ 120,  10, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_SCORENUM
+
+	{  16,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_TIME
+	{  72,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_MINUTES
+	{  72,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_TIMECOLON
+	{  96,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_SECONDS
+	{  96,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_TIMETICCOLON
+	{ 120,  26, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_TICS
+
+	{   0,  56, V_SNAPTOLEFT|V_SNAPTOTOP}, // HUD_SS_TOTALRINGS
+
+	{ 110,  93, 0}, // HUD_GETRINGS
+	{ 160,  93, 0}, // HUD_GETRINGSNUM
+	{ 124, 160, 0}, // HUD_TIMELEFT
+	{ 168, 176, 0}, // HUD_TIMELEFTNUM
+	{ 130,  93, 0}, // HUD_TIMEUP
+	{ 152, 168, 0}, // HUD_HUNTPICS
+
+	{ 288, 176, V_SNAPTORIGHT|V_SNAPTOBOTTOM}, // HUD_POWERUPS
 };
 
 //
@@ -208,17 +201,17 @@ void ST_doPaletteStuff(void)
 	else
 		palette = 0;
 
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
+		palette = 0; // No flashpals here in OpenGL
+#endif
+
 	palette = min(max(palette, 0), 13);
 
 	if (palette != st_palette)
 	{
 		st_palette = palette;
 
-#ifdef HWRENDER
-		if (rendermode == render_opengl)
-			HWR_SetPaletteColor(0);
-		else
-#endif
 		if (rendermode != render_none)
 		{
 			V_SetPaletteLump(GetPalette()); // Reset the palette
@@ -284,18 +277,18 @@ void ST_LoadGraphics(void)
 	scatterring = W_CachePatchName("SCATIND", PU_HUDGFX);
 	grenadering = W_CachePatchName("GRENIND", PU_HUDGFX);
 	railring = W_CachePatchName("RAILIND", PU_HUDGFX);
-	jumpshield = W_CachePatchName("TVWWC0", PU_HUDGFX);
-	forceshield = W_CachePatchName("TVFOC0", PU_HUDGFX);
-	ringshield = W_CachePatchName("TVATC0", PU_HUDGFX);
-	watershield = W_CachePatchName("TVELC0", PU_HUDGFX);
-	bombshield = W_CachePatchName("TVARC0", PU_HUDGFX);
-	pityshield = W_CachePatchName("TVPIC0", PU_HUDGFX);
-	flameshield = W_CachePatchName("TVFLC0", PU_HUDGFX);
-	bubbleshield = W_CachePatchName("TVBBC0", PU_HUDGFX);
-	thundershield = W_CachePatchName("TVZPC0", PU_HUDGFX);
-	invincibility = W_CachePatchName("TVIVC0", PU_HUDGFX);
-	sneakers = W_CachePatchName("TVSSC0", PU_HUDGFX);
-	gravboots = W_CachePatchName("TVGVC0", PU_HUDGFX);
+	jumpshield = W_CachePatchName("TVWWICON", PU_HUDGFX);
+	forceshield = W_CachePatchName("TVFOICON", PU_HUDGFX);
+	ringshield = W_CachePatchName("TVATICON", PU_HUDGFX);
+	watershield = W_CachePatchName("TVELICON", PU_HUDGFX);
+	bombshield = W_CachePatchName("TVARICON", PU_HUDGFX);
+	pityshield = W_CachePatchName("TVPIICON", PU_HUDGFX);
+	flameshield = W_CachePatchName("TVFLICON", PU_HUDGFX);
+	bubbleshield = W_CachePatchName("TVBBICON", PU_HUDGFX);
+	thundershield = W_CachePatchName("TVZPICON", PU_HUDGFX);
+	invincibility = W_CachePatchName("TVIVICON", PU_HUDGFX);
+	sneakers = W_CachePatchName("TVSSICON", PU_HUDGFX);
+	gravboots = W_CachePatchName("TVGVICON", PU_HUDGFX);
 
 	tagico = W_CachePatchName("TAGICO", PU_HUDGFX);
 	rflagico = W_CachePatchName("RFLAGICO", PU_HUDGFX);
@@ -305,6 +298,7 @@ void ST_LoadGraphics(void)
 	gotrflag = W_CachePatchName("GOTRFLAG", PU_HUDGFX);
 	gotbflag = W_CachePatchName("GOTBFLAG", PU_HUDGFX);
 	nonicon = W_CachePatchName("NONICON", PU_HUDGFX);
+	nonicon2 = W_CachePatchName("NONICON2", PU_HUDGFX);
 
 	// NiGHTS HUD things
 	bluestat = W_CachePatchName("BLUESTAT", PU_HUDGFX);
@@ -417,89 +411,13 @@ void ST_changeDemoView(void)
 
 boolean st_overlay;
 
-static INT32 SCZ(INT32 z)
-{
-	return FixedInt(FixedMul(z<<FRACBITS, vid.fdupy));
-}
-
-static INT32 SCY(INT32 y)
-{
-	//31/10/99: fixed by Hurdler so it _works_ also in hardware mode
-	// do not scale to resolution for hardware accelerated
-	// because these modes always scale by default
-	y = SCZ(y); // scale to resolution
-	if (splitscreen)
-	{
-		y >>= 1;
-		if (stplyr != &players[displayplayer])
-			y += vid.height / 2;
-	}
-	return y;
-}
-
-static INT32 STRINGY(INT32 y)
-{
-	//31/10/99: fixed by Hurdler so it _works_ also in hardware mode
-	// do not scale to resolution for hardware accelerated
-	// because these modes always scale by default
-	if (splitscreen)
-	{
-		y >>= 1;
-		if (stplyr != &players[displayplayer])
-			y += BASEVIDHEIGHT / 2;
-	}
-	return y;
-}
-
-static INT32 SPLITFLAGS(INT32 f)
-{
-	// Pass this V_SNAPTO(TOP|BOTTOM) and it'll trim them to account for splitscreen! -Red
-	if (splitscreen)
-	{
-		if (stplyr != &players[displayplayer])
-			f &= ~V_SNAPTOTOP;
-		else
-			f &= ~V_SNAPTOBOTTOM;
-	}
-	return f;
-}
-
-static INT32 SCX(INT32 x)
-{
-	return FixedInt(FixedMul(x<<FRACBITS, vid.fdupx));
-}
-
-#if 0
-static INT32 SCR(INT32 r)
-{
-	fixed_t y;
-		//31/10/99: fixed by Hurdler so it _works_ also in hardware mode
-	// do not scale to resolution for hardware accelerated
-	// because these modes always scale by default
-	y = FixedMul(r*FRACUNIT, vid.fdupy); // scale to resolution
-	if (splitscreen)
-	{
-		y >>= 1;
-		if (stplyr != &players[displayplayer])
-			y += vid.height / 2;
-	}
-	return FixedInt(FixedDiv(y, vid.fdupy));
-}
-#endif
-
 // =========================================================================
 //                          INTERNAL DRAWING
 // =========================================================================
-#define ST_DrawOverlayNum(x,y,n)           V_DrawTallNum(x, y, V_NOSCALESTART|V_HUDTRANS, n)
-#define ST_DrawPaddedOverlayNum(x,y,n,d)   V_DrawPaddedTallNum(x, y, V_NOSCALESTART|V_HUDTRANS, n, d)
-#define ST_DrawOverlayPatch(x,y,p)         V_DrawScaledPatch(x, y, V_NOSCALESTART|V_HUDTRANS, p)
-#define ST_DrawMappedOverlayPatch(x,y,p,c) V_DrawMappedScaledPatch(x, y, V_NOSCALESTART|V_HUDTRANS, p, c)
-#define ST_DrawNumFromHud(h,n,f)        V_DrawTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|f, n)
-#define ST_DrawPadNumFromHud(h,n,q,f)   V_DrawPaddedTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|f, n, q)
-#define ST_DrawPatchFromHud(h,p,f)      V_DrawScaledPatch(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|f, p)
-#define ST_DrawNumFromHudWS(h,n,f)      V_DrawTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|f, n)
-#define ST_DrawPadNumFromHudWS(h,n,q,f) V_DrawPaddedTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|f, n, q)
-#define ST_DrawPatchFromHudWS(h,p,f)    V_DrawScaledPatch(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|f, p)
+#define ST_DrawTopLeftOverlayPatch(x,y,p)         V_DrawScaledPatch(x, y, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, p)
+#define ST_DrawNumFromHud(h,n,flags)        V_DrawTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f|V_PERPLAYER|flags, n)
+#define ST_DrawPadNumFromHud(h,n,q,flags)   V_DrawPaddedTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f|V_PERPLAYER|flags, n, q)
+#define ST_DrawPatchFromHud(h,p,flags)      V_DrawScaledPatch(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f|V_PERPLAYER|flags, p)
 
 // Draw a number, scaled, over the view, maybe with set translucency
 // Always draw the number completely since it's overlay
@@ -536,77 +454,145 @@ static void ST_DrawNightsOverlayNum(fixed_t x /* right border */, fixed_t y, fix
 // Devmode information
 static void ST_drawDebugInfo(void)
 {
-	INT32 height = 192;
-	INT32 dist = 8;
+	INT32 height = 0;
 
 	if (!(stplyr->mo && cv_debug))
 		return;
 
-	if (cv_ticrate.value)
-	{
-		height -= 12;
-		dist >>= 1;
-	}
+#define VFLAGS V_MONOSPACE|V_SNAPTOTOP|V_SNAPTORIGHT
 
-	if (cv_debug & DBG_BASIC)
+	if ((moviemode == MM_GIF && cv_gif_downscale.value) || vid.dupx == 1)
 	{
-		const fixed_t d = AngleFixed(stplyr->mo->angle);
-		V_DrawRightAlignedString(320, height - 24, V_MONOSPACE, va("X: %6d", stplyr->mo->x>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 16, V_MONOSPACE, va("Y: %6d", stplyr->mo->y>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 8,  V_MONOSPACE, va("Z: %6d", stplyr->mo->z>>FRACBITS));
-		V_DrawRightAlignedString(320, height,      V_MONOSPACE, va("A: %6d", FixedInt(d)));
+		if (cv_debug & DBG_BASIC)
+		{
+			const fixed_t d = AngleFixed(stplyr->mo->angle);
+			V_DrawRightAlignedString(320,  0, VFLAGS, va("X: %6d", stplyr->mo->x>>FRACBITS));
+			V_DrawRightAlignedString(320,  8, VFLAGS, va("Y: %6d", stplyr->mo->y>>FRACBITS));
+			V_DrawRightAlignedString(320, 16, VFLAGS, va("Z: %6d", stplyr->mo->z>>FRACBITS));
+			V_DrawRightAlignedString(320, 24, VFLAGS, va("A: %6d", FixedInt(d)));
 
-		height -= (32+dist);
-	}
+			height += 4*9;
+		}
 
-	if (cv_debug & DBG_DETAILED)
+		if (cv_debug & (DBG_MEMORY|DBG_RANDOMIZER|DBG_DETAILED))
+		{
+			V_DrawRightAlignedThinString(320,   height, VFLAGS|V_REDMAP, "INFO NOT AVAILABLE");
+			V_DrawRightAlignedThinString(320, 8+height, VFLAGS|V_REDMAP, "AT THIS RESOLUTION");
+		}
+	}
+	else
 	{
-		V_DrawRightAlignedString(320, height - 104, V_MONOSPACE, va("SHIELD: %5x", stplyr->powers[pw_shield]));
-		V_DrawRightAlignedString(320, height - 96,  V_MONOSPACE, va("SCALE: %5d%%", (stplyr->mo->scale*100)/FRACUNIT));
-		V_DrawRightAlignedString(320, height - 88,  V_MONOSPACE, va("CARRY: %5x", stplyr->powers[pw_carry]));
-		V_DrawRightAlignedString(320, height - 80,  V_MONOSPACE, va("AIR: %4d, %3d", stplyr->powers[pw_underwater], stplyr->powers[pw_spacetime]));
+#define h 4
+#define dist 2
+#define V_DrawDebugLine(str) V_DrawRightAlignedSmallString(320, height, VFLAGS, str);\
+								height += h
 
-		// Flags
-		V_DrawRightAlignedString(304-92, height - 72, V_MONOSPACE, "PF:");
-		V_DrawString(304-90,             height - 72, (stplyr->pflags & PF_STARTJUMP) ? V_GREENMAP : V_REDMAP, "SJ");
-		V_DrawString(304-72,             height - 72, (stplyr->pflags & PF_JUMPED) ? V_GREENMAP : V_REDMAP, "JD");
-		V_DrawString(304-54,             height - 72, (stplyr->pflags & PF_SPINNING) ? V_GREENMAP : V_REDMAP, "SP");
-		V_DrawString(304-36,             height - 72, (stplyr->pflags & PF_STARTDASH) ? V_GREENMAP : V_REDMAP, "ST");
-		V_DrawString(304-18,             height - 72, (stplyr->pflags & PF_THOKKED) ? V_GREENMAP : V_REDMAP, "TH");
-		V_DrawString(304,                height - 72, (stplyr->pflags & PF_SHIELDABILITY) ? V_GREENMAP : V_REDMAP, "SH");
+		if (cv_debug & DBG_MEMORY)
+		{
+			V_DrawDebugLine(va("Heap: %8sKB", sizeu1(Z_TotalUsage()>>10)));
 
-		V_DrawRightAlignedString(320, height - 64, V_MONOSPACE, va("CEILZ: %6d", stplyr->mo->ceilingz>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 56, V_MONOSPACE, va("FLOORZ: %6d", stplyr->mo->floorz>>FRACBITS));
+			height += dist;
+		}
 
-		V_DrawRightAlignedString(320, height - 48, V_MONOSPACE, va("CNVX: %6d", stplyr->cmomx>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 40, V_MONOSPACE, va("CNVY: %6d", stplyr->cmomy>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 32, V_MONOSPACE, va("PLTZ: %6d", stplyr->mo->pmomz>>FRACBITS));
+		if (cv_debug & DBG_RANDOMIZER) // randomizer testing
+		{
+			fixed_t peekres = P_RandomPeek();
+			peekres *= 10000;     // Change from fixed point
+			peekres >>= FRACBITS; // to displayable decimal
 
-		V_DrawRightAlignedString(320, height - 24, V_MONOSPACE, va("MOMX: %6d", stplyr->rmomx>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 16, V_MONOSPACE, va("MOMY: %6d", stplyr->rmomy>>FRACBITS));
-		V_DrawRightAlignedString(320, height - 8,  V_MONOSPACE, va("MOMZ: %6d", stplyr->mo->momz>>FRACBITS));
-		V_DrawRightAlignedString(320, height,      V_MONOSPACE, va("SPEED: %6d", stplyr->speed>>FRACBITS));
+			V_DrawDebugLine(va("Init: %08x", P_GetInitSeed()));
+			V_DrawDebugLine(va("Seed: %08x", P_GetRandSeed()));
+			V_DrawDebugLine(va("==  :    .%04d", peekres));
 
-		height -= (112+dist);
-	}
+			height += dist;
+		}
 
-	if (cv_debug & DBG_RANDOMIZER) // randomizer testing
-	{
-		fixed_t peekres = P_RandomPeek();
-		peekres *= 10000;     // Change from fixed point
-		peekres >>= FRACBITS; // to displayable decimal
+		if (cv_debug & DBG_DETAILED)
+		{
+#define V_DrawDebugFlag(f, str) V_DrawRightAlignedSmallString(w, height, VFLAGS|f, str);\
+								w -= 9
+			const fixed_t d = AngleFixed(stplyr->drawangle);
+			INT32 w = 320;
+
+			V_DrawDebugLine(va("SHIELD: %5x", stplyr->powers[pw_shield]));
+			V_DrawDebugLine(va("SCALE: %5d%%", (stplyr->mo->scale*100)>>FRACBITS));
+			V_DrawDebugLine(va("CARRY: %5x", stplyr->powers[pw_carry]));
+			V_DrawDebugLine(va("AIR: %4d, %3d", stplyr->powers[pw_underwater], stplyr->powers[pw_spacetime]));
+			V_DrawDebugLine(va("ABILITY: %3d, %3d", stplyr->charability, stplyr->charability2));
+			V_DrawDebugLine(va("ACTIONSPD: %5d", stplyr->actionspd>>FRACBITS));
+			V_DrawDebugLine(va("PEEL: %3d", stplyr->dashmode));
+			V_DrawDebugLine(va("SCOREADD: %3d", stplyr->scoreadd));
+
+			// Flags
+			V_DrawDebugFlag(((stplyr->pflags & PF_SHIELDABILITY)  ? V_GREENMAP : V_REDMAP), "SH");
+			V_DrawDebugFlag(((stplyr->pflags & PF_THOKKED)        ? V_GREENMAP : V_REDMAP), "TH");
+			V_DrawDebugFlag(((stplyr->pflags & PF_STARTDASH)      ? V_GREENMAP : V_REDMAP), "ST");
+			V_DrawDebugFlag(((stplyr->pflags & PF_SPINNING)       ? V_GREENMAP : V_REDMAP), "SP");
+			V_DrawDebugFlag(((stplyr->pflags & PF_NOJUMPDAMAGE)   ? V_GREENMAP : V_REDMAP), "ND");
+			V_DrawDebugFlag(((stplyr->pflags & PF_JUMPED)         ? V_GREENMAP : V_REDMAP), "JD");
+			V_DrawDebugFlag(((stplyr->pflags & PF_STARTJUMP)      ? V_GREENMAP : V_REDMAP), "SJ");
+			V_DrawDebugFlag(0, "PF/SF:");
+			height += h;
+			w = 320;
+			V_DrawDebugFlag(((stplyr->pflags & PF_INVIS)          ? V_GREENMAP : V_REDMAP), "*I");
+			V_DrawDebugFlag(((stplyr->pflags & PF_NOCLIP)         ? V_GREENMAP : V_REDMAP), "*C");
+			V_DrawDebugFlag(((stplyr->pflags & PF_GODMODE)        ? V_GREENMAP : V_REDMAP), "*G");
+			V_DrawDebugFlag(((stplyr->charflags & SF_SUPER)       ? V_GREENMAP : V_REDMAP), "SU");
+			V_DrawDebugFlag(((stplyr->pflags & PF_APPLYAUTOBRAKE) ? V_GREENMAP : V_REDMAP), "AA");
+			V_DrawDebugFlag(((stplyr->pflags & PF_SLIDING)        ? V_GREENMAP : V_REDMAP), "SL");
+			V_DrawDebugFlag(((stplyr->pflags & PF_BOUNCING)       ? V_GREENMAP : V_REDMAP), "BO");
+			V_DrawDebugFlag(((stplyr->pflags & PF_GLIDING)        ? V_GREENMAP : V_REDMAP), "GL");
+			height += h;
+
+			V_DrawDebugLine(va("CEILINGZ: %6d", stplyr->mo->ceilingz>>FRACBITS));
+			V_DrawDebugLine(va("FLOORZ: %6d", stplyr->mo->floorz>>FRACBITS));
+
+			V_DrawDebugLine(va("CMOMX: %6d", stplyr->cmomx>>FRACBITS));
+			V_DrawDebugLine(va("CMOMY: %6d", stplyr->cmomy>>FRACBITS));
+			V_DrawDebugLine(va("PMOMZ: %6d", stplyr->mo->pmomz>>FRACBITS));
+
+			w = 320;
+			V_DrawDebugFlag(((stplyr->mo->eflags & MFE_APPLYPMOMZ)      ? V_GREENMAP : V_REDMAP), "AP");
+			V_DrawDebugFlag(((stplyr->mo->eflags & MFE_SPRUNG)          ? V_GREENMAP : V_REDMAP), "SP");
+			//V_DrawDebugFlag(((stplyr->mo->eflags & MFE_PUSHED)          ? V_GREENMAP : V_REDMAP), "PU"); -- not relevant to players
+			V_DrawDebugFlag(((stplyr->mo->eflags & MFE_GOOWATER)        ? V_GREENMAP : V_REDMAP), "GW");
+			V_DrawDebugFlag(((stplyr->mo->eflags & MFE_VERTICALFLIP)    ? V_GREENMAP : V_REDMAP), "VF");
+			V_DrawDebugFlag(((stplyr->mo->eflags & MFE_JUSTSTEPPEDDOWN) ? V_GREENMAP : V_REDMAP), "JS");
+			V_DrawDebugFlag(((stplyr->mo->eflags & MFE_UNDERWATER)      ? V_GREENMAP : V_REDMAP), "UW");
+			V_DrawDebugFlag(((stplyr->mo->eflags & MFE_TOUCHWATER)      ? V_GREENMAP : V_REDMAP), "TW");
+			V_DrawDebugFlag(((stplyr->mo->eflags & MFE_JUSTHITFLOOR)    ? V_GREENMAP : V_REDMAP), "JH");
+			V_DrawDebugFlag(((stplyr->mo->eflags & MFE_ONGROUND)        ? V_GREENMAP : V_REDMAP), "OG");
+			V_DrawDebugFlag(0, "MFE:");
+			height += h;
+
+			V_DrawDebugLine(va("MOMX: %6d", stplyr->rmomx>>FRACBITS));
+			V_DrawDebugLine(va("MOMY: %6d", stplyr->rmomy>>FRACBITS));
+			V_DrawDebugLine(va("MOMZ: %6d", stplyr->mo->momz>>FRACBITS));
+
+			V_DrawDebugLine(va("SPEED: %6d", stplyr->speed>>FRACBITS));
+
+			V_DrawDebugLine(va("DRAWANGLE: %6d", FixedInt(d)));
+
+			height += dist;
+#undef V_DrawDebugFlag
+		}
 
-		V_DrawRightAlignedString(320, height - 16, V_MONOSPACE, va("Init: %08x", P_GetInitSeed()));
-		V_DrawRightAlignedString(320, height - 8,  V_MONOSPACE, va("Seed: %08x", P_GetRandSeed()));
-		V_DrawRightAlignedString(320, height,      V_MONOSPACE, va("==  :    .%04d", peekres));
+		if (cv_debug & DBG_BASIC)
+		{
+			const fixed_t d = AngleFixed(stplyr->mo->angle);
+			V_DrawDebugLine(va("X: %6d", stplyr->mo->x>>FRACBITS));
+			V_DrawDebugLine(va("Y: %6d", stplyr->mo->y>>FRACBITS));
+			V_DrawDebugLine(va("Z: %6d", stplyr->mo->z>>FRACBITS));
+			V_DrawDebugLine(va("A: %6d", FixedInt(d)));
 
-		height -= (24+dist);
+			//height += dist;
+		}
 	}
 
-	if (cv_debug & DBG_MEMORY)
-	{
-		V_DrawRightAlignedString(320, height,     V_MONOSPACE, va("Heap: %7sKB", sizeu1(Z_TagsUsage(0, INT32_MAX)>>10)));
-	}
+#undef V_DrawDebugLine
+#undef dist
+#undef h
+#undef VFLAGS
 }
 
 static void ST_drawScore(void)
@@ -616,20 +602,51 @@ static void ST_drawScore(void)
 	if (objectplacing)
 	{
 		if (op_displayflags > UINT16_MAX)
-			ST_DrawOverlayPatch(SCX(hudinfo[HUD_SCORENUM].x-tallminus->width), SCY(hudinfo[HUD_SCORENUM].y), tallminus);
+			ST_DrawTopLeftOverlayPatch((hudinfo[HUD_SCORENUM].x-tallminus->width), hudinfo[HUD_SCORENUM].y, tallminus);
 		else
 			ST_DrawNumFromHud(HUD_SCORENUM, op_displayflags, V_HUDTRANS);
 	}
 	else
-		ST_DrawNumFromHud(HUD_SCORENUM, stplyr->score,V_HUDTRANS);
+		ST_DrawNumFromHud(HUD_SCORENUM, stplyr->score, V_HUDTRANS);
+}
+
+static void ST_drawRaceNum(INT32 time)
+{
+	INT32 height, bounce;
+	patch_t *racenum;
+
+	time += TICRATE;
+	height = ((3*BASEVIDHEIGHT)>>2) - 8;
+	bounce = TICRATE - (1 + (time % TICRATE));
+
+	switch (time/TICRATE)
+	{
+		case 3:
+			racenum = race3;
+			break;
+		case 2:
+			racenum = race2;
+			break;
+		case 1:
+			racenum = race1;
+			break;
+		default:
+			racenum = racego;
+			break;
+	}
+	if (bounce < 3)
+	{
+		height -= (2 - bounce);
+		if (!(P_AutoPause() || paused) && !bounce)
+				S_StartSound(0, ((racenum == racego) ? sfx_s3kad : sfx_s3ka7));
+	}
+	V_DrawScaledPatch(((BASEVIDWIDTH - SHORT(racenum->width))/2), height, V_PERPLAYER, racenum);
 }
 
 static void ST_drawTime(void)
 {
 	INT32 seconds, minutes, tictrn, tics;
-
-	// TIME:
-	ST_DrawPatchFromHudWS(HUD_TIME, ((mapheaderinfo[gamemap-1]->countdown && countdowntimer < 11*TICRATE && leveltime/5 & 1) ? sboredtime : sbotime), V_HUDTRANS);
+	boolean downwards = false;
 
 	if (objectplacing)
 	{
@@ -640,21 +657,70 @@ static void ST_drawTime(void)
 	}
 	else
 	{
-		tics = (mapheaderinfo[gamemap-1]->countdown ? countdowntimer : stplyr->realtime);
-		seconds = G_TicsToSeconds(tics);
+		// Counting down the hidetime?
+		if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (leveltime <= (hidetime*TICRATE)))
+		{
+			tics = (hidetime*TICRATE - leveltime);
+			if (tics < 3*TICRATE)
+				ST_drawRaceNum(tics);
+			downwards = true;
+		}
+		else
+		{
+			// Hidetime finish!
+			if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (leveltime < ((hidetime+1)*TICRATE)))
+				ST_drawRaceNum(hidetime*TICRATE - leveltime);
+
+			// Time limit?
+			if (gametype != GT_RACE && gametype != GT_COMPETITION && gametype != GT_COOP && cv_timelimit.value && timelimitintics > 0)
+			{
+				if (timelimitintics >= leveltime)
+				{
+					tics = (timelimitintics - leveltime);
+					if (tics < 3*TICRATE)
+						ST_drawRaceNum(tics);
+				}
+				else // Overtime!
+					tics = 0;
+				downwards = true;
+			}
+			// Post-hidetime normal.
+			else if (gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+				tics = stplyr->realtime - hidetime*TICRATE;
+			// "Shadow! What are you doing? Hurry and get back here
+			// right now before the island blows up with you on it!"
+			// "Blows up??" *awkward silence* "I've got to get outta
+			// here and find Amy and Tails right away!"
+			else if (mapheaderinfo[gamemap-1]->countdown)
+			{
+				tics = countdowntimer;
+				downwards = true;
+			}
+			// Normal.
+			else
+				tics = stplyr->realtime;
+		}
+
 		minutes = G_TicsToMinutes(tics, true);
+		seconds = G_TicsToSeconds(tics);
 		tictrn  = G_TicsToCentiseconds(tics);
 	}
 
-	if (cv_timetic.value == 1) // Tics only -- how simple is this?
-		ST_DrawNumFromHudWS(HUD_SECONDS, tics, V_HUDTRANS);
+	// TIME:
+	ST_DrawPatchFromHud(HUD_TIME, ((downwards && (tics < 30*TICRATE) && (leveltime/5 & 1)) ? sboredtime : sbotime), V_HUDTRANS);
+
+	if (!tics && downwards && (leveltime/5 & 1)) // overtime!
+		return;
+
+	if (cv_timetic.value == 3) // Tics only -- how simple is this?
+		ST_DrawNumFromHud(HUD_SECONDS, tics, V_HUDTRANS);
 	else
 	{
-		ST_DrawNumFromHudWS(HUD_MINUTES, minutes, V_HUDTRANS); // Minutes
-		ST_DrawPatchFromHudWS(HUD_TIMECOLON, sbocolon, V_HUDTRANS); // Colon
-		ST_DrawPadNumFromHudWS(HUD_SECONDS, seconds, 2, V_HUDTRANS); // Seconds
+		ST_DrawNumFromHud(HUD_MINUTES, minutes, V_HUDTRANS); // Minutes
+		ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon, V_HUDTRANS); // Colon
+		ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2, V_HUDTRANS); // Seconds
 
-		if (!splitscreen && (cv_timetic.value == 2 || cv_timetic.value == 3 || modeattacking)) // there's not enough room for tics in splitscreen, don't even bother trying!
+		if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking) // there's not enough room for tics in splitscreen, don't even bother trying!
 		{
 			ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod, V_HUDTRANS); // Period
 			ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2, V_HUDTRANS); // Tics
@@ -666,7 +732,7 @@ static inline void ST_drawRings(void)
 {
 	INT32 ringnum;
 
-	ST_DrawPatchFromHudWS(HUD_RINGS, ((!stplyr->spectator && stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings), ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
+	ST_DrawPatchFromHud(HUD_RINGS, ((!stplyr->spectator && stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings), ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
 
 	if (objectplacing)
 		ringnum = op_currentdoomednum;
@@ -681,35 +747,41 @@ static inline void ST_drawRings(void)
 	else
 		ringnum = max(stplyr->rings, 0);
 
-	if (!splitscreen && cv_timetic.value == 3) // Yes, even in modeattacking
-		ST_DrawNumFromHud(HUD_RINGSNUMTICS, ringnum, ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
+	if (cv_timetic.value == 2) // Yes, even in modeattacking
+		ST_DrawNumFromHud(HUD_RINGSNUMTICS, ringnum, V_PERPLAYER|((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
 	else
-		ST_DrawNumFromHudWS(HUD_RINGSNUM, ringnum, ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
+		ST_DrawNumFromHud(HUD_RINGSNUM, ringnum, V_PERPLAYER|((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
 }
 
-static void ST_drawLives(void)
+static void ST_drawLivesArea(void)
 {
-	const INT32 v_splitflag = (splitscreen && stplyr == &players[displayplayer] ? V_SPLITSCREEN : 0);
-	INT32 livescount;
+	INT32 v_colmap = V_YELLOWMAP, livescount;
 	boolean notgreyedout;
 
 	if (!stplyr->skincolor)
 		return; // Just joined a server, skin isn't loaded yet!
 
 	// face background
-	V_DrawSmallScaledPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, livesback);
+	V_DrawSmallScaledPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+		hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, livesback);
 
 	// face
-	if (stplyr->mo && stplyr->mo->color)
+	if (stplyr->spectator)
+	{
+		// spectator face
+		UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, SKINCOLOR_CLOUDY, GTC_CACHE);
+		V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANSHALF, faceprefix[stplyr->skin], colormap);
+	}
+	else if (stplyr->mo && stplyr->mo->color)
 	{
 		// skincolor face/super
 		UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->mo->color, GTC_CACHE);
 		patch_t *face = faceprefix[stplyr->skin];
 		if (stplyr->powers[pw_super])
 			face = superprefix[stplyr->skin];
-		V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,face, colormap);
+		V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, face, colormap);
 		if (cv_translucenthud.value == 10 && stplyr->powers[pw_super] == 1 && stplyr->mo->tracer)
 		{
 			INT32 v_supertrans = (stplyr->mo->tracer->frame & FF_TRANSMASK) >> FF_TRANSSHIFT;
@@ -717,8 +789,8 @@ static void ST_drawLives(void)
 			{
 				v_supertrans <<= V_ALPHASHIFT;
 				colormap = R_GetTranslationColormap(stplyr->skin, stplyr->mo->tracer->color, GTC_CACHE);
-				V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-					V_SNAPTOLEFT|V_SNAPTOBOTTOM|v_supertrans|v_splitflag,face, colormap);
+				V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+					hudinfo[HUD_LIVES].f|V_PERPLAYER|v_supertrans, face, colormap);
 			}
 		}
 	}
@@ -726,94 +798,158 @@ static void ST_drawLives(void)
 	{
 		// skincolor face
 		UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE);
-		V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,faceprefix[stplyr->skin], colormap);
+		V_DrawSmallMappedPatch(hudinfo[HUD_LIVES].x, hudinfo[HUD_LIVES].y,
+			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, faceprefix[stplyr->skin], colormap);
 	}
 
-	// name
-	if (strlen(skins[stplyr->skin].hudname) > 8)
-		V_DrawThinString(hudinfo[HUD_LIVESNAME].x, hudinfo[HUD_LIVESNAME].y + (v_splitflag ? -12 : 0),
-			V_HUDTRANS|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_MONOSPACE|V_YELLOWMAP|v_splitflag, skins[stplyr->skin].hudname);
-	else
-		V_DrawString(hudinfo[HUD_LIVESNAME].x, hudinfo[HUD_LIVESNAME].y + (v_splitflag ? -12 : 0),
-			V_HUDTRANS|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_MONOSPACE|V_YELLOWMAP|v_splitflag, skins[stplyr->skin].hudname);
-	// x
-	V_DrawScaledPatch(hudinfo[HUD_LIVESX].x, hudinfo[HUD_LIVESX].y + (v_splitflag ? -4 : 0),
-		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, stlivex);
-
-	// lives number
-	if ((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 3)
+	// Lives number
+	if (G_GametypeUsesLives() || gametype == GT_RACE)
 	{
-		INT32 i;
-		livescount = 0;
-		notgreyedout = (stplyr->lives > 0);
-		for (i = 0; i < MAXPLAYERS; i++)
+		// x
+		V_DrawScaledPatch(hudinfo[HUD_LIVES].x+22, hudinfo[HUD_LIVES].y+10,
+			hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, stlivex);
+
+		// lives number
+		if (gametype == GT_RACE)
 		{
-			if (!playeringame[i])
-				continue;
+			livescount = 0x7f;
+			notgreyedout = true;
+		}
+		else if ((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 3)
+		{
+			INT32 i;
+			livescount = 0;
+			notgreyedout = (stplyr->lives > 0);
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
 
-			if (players[i].lives < 1)
-				continue;
+				if (players[i].lives < 1)
+					continue;
 
-			if (players[i].lives > 1)
-				notgreyedout = true;
+				if (players[i].lives > 1)
+					notgreyedout = true;
 
-			if (players[i].lives == 0x7f)
-			{
-				livescount = 0x7f;
-				break;
+				if (players[i].lives == 0x7f)
+				{
+					livescount = 0x7f;
+					break;
+				}
+				else if (livescount < 99)
+					livescount += (players[i].lives);
 			}
-			else if (livescount < 99)
-				livescount += (players[i].lives);
+		}
+		else
+		{
+			livescount = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? 0x7f : stplyr->lives);
+			notgreyedout = true;
+		}
+
+		if (livescount == 0x7f)
+			V_DrawCharacter(hudinfo[HUD_LIVES].x+50, hudinfo[HUD_LIVES].y+8,
+				'\x16' | 0x80 | hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, false);
+		else
+		{
+			if (livescount > 99)
+				livescount = 99;
+			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8,
+				hudinfo[HUD_LIVES].f|V_PERPLAYER|(notgreyedout ? V_HUDTRANS : V_HUDTRANSHALF), va("%d",livescount));
 		}
 	}
-	else
+	// Spectator
+	else if (stplyr->spectator)
+		v_colmap = V_GRAYMAP;
+	// Tag
+	else if (gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
 	{
-		livescount = stplyr->lives;
-		notgreyedout = true;
+		if (stplyr->pflags & PF_TAGIT)
+		{
+			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "IT!");
+			v_colmap = V_ORANGEMAP;
+		}
+	}
+	// Team name
+	else if (G_GametypeHasTeams())
+	{
+		if (stplyr->ctfteam == 1)
+		{
+			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "RED");
+			v_colmap = V_REDMAP;
+		}
+		else if (stplyr->ctfteam == 2)
+		{
+			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, "BLUE");
+			v_colmap = V_BLUEMAP;
+		}
 	}
 
-	if (livescount == 0x7f)
-		V_DrawCharacter(hudinfo[HUD_LIVESNUM].x - 8, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0), '\x16' | 0x80 | V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, false);
+	// name
+	v_colmap |= (V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER);
+	if (strlen(skins[stplyr->skin].hudname) <= 5)
+		V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname);
+	else if (V_ThinStringWidth(skins[stplyr->skin].hudname, v_colmap) <= 40)
+		V_DrawRightAlignedThinString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname);
 	else
+		V_DrawThinString(hudinfo[HUD_LIVES].x+18, hudinfo[HUD_LIVES].y, v_colmap, skins[stplyr->skin].hudname);
+
+	// Power Stones collected
+	if (G_RingSlingerGametype()
+#ifdef HAVE_BLUA
+	&& LUA_HudEnabled(hud_powerstones)
+#endif
+	)
 	{
-		if (livescount > 99)
-			livescount = 99;
-		V_DrawRightAlignedString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
-			V_SNAPTOLEFT|V_SNAPTOBOTTOM|(notgreyedout ? V_HUDTRANS : V_HUDTRANSHALF)|v_splitflag,
-			((livescount > 99) ? "!!" : va("%d",livescount)));
+		INT32 workx = hudinfo[HUD_LIVES].x+1, j;
+		if ((leveltime & 1) && stplyr->powers[pw_invulnerability] && (stplyr->powers[pw_sneakers] == stplyr->powers[pw_invulnerability])) // hack; extremely unlikely to be activated unintentionally
+		{
+			for (j = 0; j < 7; ++j) // "super" indicator
+			{
+				V_DrawScaledPatch(workx, hudinfo[HUD_LIVES].y-9, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, emeraldpics[1][j]);
+				workx += 8;
+			}
+		}
+		else
+		{
+			for (j = 0; j < 7; ++j) // powerstones
+			{
+				if (stplyr->powers[pw_emeralds] & (1 << j))
+					V_DrawScaledPatch(workx, hudinfo[HUD_LIVES].y-9, V_HUDTRANS|hudinfo[HUD_LIVES].f|V_PERPLAYER, emeraldpics[1][j]);
+				workx += 8;
+			}
+		}
 	}
 }
 
 static void ST_drawInput(void)
 {
-	//const INT32 v_splitflag = (splitscreen && stplyr == &players[displayplayer] ? V_SPLITSCREEN : 0); -- no splitscreen support - record attack only for base game
-	const UINT8 accent = (stplyr->skincolor ? Color_Index[stplyr->skincolor-1][4] : 0);
-	UINT8 col, offs;
+	const INT32 accent = V_SNAPTOLEFT|V_SNAPTOBOTTOM|(stplyr->skincolor ? Color_Index[stplyr->skincolor-1][4] : 0);
+	INT32 col;
+	UINT8 offs;
 
-	INT32 x = hudinfo[HUD_LIVESPIC].x, y = hudinfo[HUD_LIVESPIC].y;
+	INT32 x = hudinfo[HUD_LIVES].x, y = hudinfo[HUD_LIVES].y;
 
 	if (stplyr->powers[pw_carry] == CR_NIGHTSMODE)
 		y -= 16;
 
 	// O backing
-	V_DrawFill(x, y-1, 16, 16, 20);
-	V_DrawFill(x, y+15, 16, 1, 29);
+	V_DrawFill(x, y-1, 16, 16, hudinfo[HUD_LIVES].f|20);
+	V_DrawFill(x, y+15, 16, 1, hudinfo[HUD_LIVES].f|29);
 
 	if (cv_showinputjoy.value) // joystick render!
 	{
-		/*V_DrawFill(x   , y   , 16,  1, 16);
-		V_DrawFill(x   , y+15, 16,  1, 16);
-		V_DrawFill(x   , y+ 1,  1, 14, 16);
-		V_DrawFill(x+15, y+ 1,  1, 14, 16); -- red's outline*/
+		/*V_DrawFill(x   , y   , 16,  1, hudinfo[HUD_LIVES].f|16);
+		V_DrawFill(x   , y+15, 16,  1, hudinfo[HUD_LIVES].f|16);
+		V_DrawFill(x   , y+ 1,  1, 14, hudinfo[HUD_LIVES].f|16);
+		V_DrawFill(x+15, y+ 1,  1, 14, hudinfo[HUD_LIVES].f|16); -- red's outline*/
 		if (stplyr->cmd.sidemove || stplyr->cmd.forwardmove)
 		{
 			// joystick hole
-			V_DrawFill(x+5, y+4, 6, 6, 29);
+			V_DrawFill(x+5, y+4, 6, 6, hudinfo[HUD_LIVES].f|29);
 			// joystick top
 			V_DrawFill(x+3+stplyr->cmd.sidemove/12,
 				y+2-stplyr->cmd.forwardmove/12,
-				10, 10, 29);
+				10, 10, hudinfo[HUD_LIVES].f|29);
 			V_DrawFill(x+3+stplyr->cmd.sidemove/9,
 				y+1-stplyr->cmd.forwardmove/9,
 				10, 10, accent);
@@ -821,10 +957,10 @@ static void ST_drawInput(void)
 		else
 		{
 			// just a limited, greyed out joystick top
-			V_DrawFill(x+3, y+11, 10, 1, 29);
+			V_DrawFill(x+3, y+11, 10, 1, hudinfo[HUD_LIVES].f|29);
 			V_DrawFill(x+3,
 				y+1,
-				10, 10, 16);
+				10, 10, hudinfo[HUD_LIVES].f|16);
 		}
 	}
 	else // arrows!
@@ -838,10 +974,10 @@ static void ST_drawInput(void)
 		else
 		{
 			offs = 1;
-			col = 16;
-			V_DrawFill(x- 2, y+10,  6,  1, 29);
-			V_DrawFill(x+ 4, y+ 9,  1,  1, 29);
-			V_DrawFill(x+ 5, y+ 8,  1,  1, 29);
+			col = hudinfo[HUD_LIVES].f|16;
+			V_DrawFill(x- 2, y+10,  6,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 4, y+ 9,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 5, y+ 8,  1,  1, hudinfo[HUD_LIVES].f|29);
 		}
 		V_DrawFill(x- 2, y+ 5-offs,  6,  6, col);
 		V_DrawFill(x+ 4, y+ 6-offs,  1,  4, col);
@@ -856,12 +992,12 @@ static void ST_drawInput(void)
 		else
 		{
 			offs = 1;
-			col = 16;
-			V_DrawFill(x+ 5, y+ 3,  1,  1, 29);
-			V_DrawFill(x+ 6, y+ 4,  1,  1, 29);
-			V_DrawFill(x+ 7, y+ 5,  2,  1, 29);
-			V_DrawFill(x+ 9, y+ 4,  1,  1, 29);
-			V_DrawFill(x+10, y+ 3,  1,  1, 29);
+			col = hudinfo[HUD_LIVES].f|16;
+			V_DrawFill(x+ 5, y+ 3,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 6, y+ 4,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 7, y+ 5,  2,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+ 9, y+ 4,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+10, y+ 3,  1,  1, hudinfo[HUD_LIVES].f|29);
 		}
 		V_DrawFill(x+ 5, y- 2-offs,  6,  6, col);
 		V_DrawFill(x+ 6, y+ 4-offs,  4,  1, col);
@@ -876,10 +1012,10 @@ static void ST_drawInput(void)
 		else
 		{
 			offs = 1;
-			col = 16;
-			V_DrawFill(x+12, y+10,  6,  1, 29);
-			V_DrawFill(x+11, y+ 9,  1,  1, 29);
-			V_DrawFill(x+10, y+ 8,  1,  1, 29);
+			col = hudinfo[HUD_LIVES].f|16;
+			V_DrawFill(x+12, y+10,  6,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+11, y+ 9,  1,  1, hudinfo[HUD_LIVES].f|29);
+			V_DrawFill(x+10, y+ 8,  1,  1, hudinfo[HUD_LIVES].f|29);
 		}
 		V_DrawFill(x+12, y+ 5-offs,  6,  6, col);
 		V_DrawFill(x+11, y+ 6-offs,  1,  4, col);
@@ -894,8 +1030,8 @@ static void ST_drawInput(void)
 		else
 		{
 			offs = 1;
-			col = 16;
-			V_DrawFill(x+ 5, y+17,  6,  1, 29);
+			col = hudinfo[HUD_LIVES].f|16;
+			V_DrawFill(x+ 5, y+17,  6,  1, hudinfo[HUD_LIVES].f|29);
 		}
 		V_DrawFill(x+ 5, y+12-offs,  6,  6, col);
 		V_DrawFill(x+ 6, y+11-offs,  4,  1, col);
@@ -911,16 +1047,16 @@ static void ST_drawInput(void)
 	else\
 	{\
 		offs = 1;\
-		col = 16;\
-		V_DrawFill(x+16+(xoffs), y+9+(yoffs), 10, 1, 29);\
+		col = hudinfo[HUD_LIVES].f|16;\
+		V_DrawFill(x+16+(xoffs), y+9+(yoffs), 10, 1, hudinfo[HUD_LIVES].f|29);\
 	}\
 	V_DrawFill(x+16+(xoffs), y+(yoffs)-offs, 10, 10, col);\
-	V_DrawCharacter(x+16+1+(xoffs), y+1+(yoffs)-offs, symb, false)
+	V_DrawCharacter(x+16+1+(xoffs), y+1+(yoffs)-offs, hudinfo[HUD_LIVES].f|symb, false)
 
 	drawbutt( 4,-3, BT_JUMP, 'J');
 	drawbutt(15,-3, BT_USE,  'S');
 
-	V_DrawFill(x+16+4, y+8, 21, 10, 20); // sundial backing
+	V_DrawFill(x+16+4, y+8, 21, 10, hudinfo[HUD_LIVES].f|20); // sundial backing
 	if (stplyr->mo)
 	{
 		UINT8 i, precision;
@@ -940,7 +1076,7 @@ static void ST_drawInput(void)
 		{
 			V_DrawFill(x+16+14-(i*xcomp)/precision,
 				y+12-(i*ycomp)/precision,
-				1, 1, 16);
+				1, 1, hudinfo[HUD_LIVES].f|16);
 		}
 
 		if (ycomp <= 0)
@@ -957,6 +1093,7 @@ static void ST_drawInput(void)
 		if (stplyr->pflags & PF_AUTOBRAKE)
 		{
 			V_DrawThinString(x, y,
+				hudinfo[HUD_LIVES].f|
 				((!stplyr->powers[pw_carry]
 				&& (stplyr->pflags & PF_APPLYAUTOBRAKE)
 				&& !(stplyr->cmd.sidemove || stplyr->cmd.forwardmove)
@@ -967,12 +1104,12 @@ static void ST_drawInput(void)
 		}
 		if (stplyr->pflags & PF_ANALOGMODE)
 		{
-			V_DrawThinString(x, y, 0, "ANALOG");
+			V_DrawThinString(x, y, hudinfo[HUD_LIVES].f, "ANALOG");
 			y -= 8;
 		}
 	}
 	if (!demosynced) // should always be last, so it doesn't push anything else around
-		V_DrawThinString(x, y, ((leveltime & 4) ? V_YELLOWMAP : V_REDMAP), "BAD DEMO!!");
+		V_DrawThinString(x, y, hudinfo[HUD_LIVES].f|((leveltime & 4) ? V_YELLOWMAP : V_REDMAP), "BAD DEMO!!");
 }
 
 static void ST_drawLevelTitle(void)
@@ -980,13 +1117,8 @@ static void ST_drawLevelTitle(void)
 	char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl;
 	char *subttl = mapheaderinfo[gamemap-1]->subttl;
 	INT32 actnum = mapheaderinfo[gamemap-1]->actnum;
-	INT32 lvlttlxpos;
+	INT32 lvlttly, zoney, lvlttlxpos, ttlnumxpos, zonexpos;
 	INT32 subttlxpos = BASEVIDWIDTH/2;
-	INT32 ttlnumxpos;
-	INT32 zonexpos;
-
-	INT32 lvlttly;
-	INT32 zoney;
 
 	if (!(timeinmap > 2 && timeinmap-3 < 110))
 		return;
@@ -998,10 +1130,41 @@ static void ST_drawLevelTitle(void)
 
 	ttlnumxpos = lvlttlxpos + V_LevelNameWidth(lvlttl);
 	zonexpos = ttlnumxpos - V_LevelNameWidth(M_GetText("ZONE"));
+	ttlnumxpos++;
 
 	if (lvlttlxpos < 0)
 		lvlttlxpos = 0;
 
+#if 0 // toaster's experiment. srb2&toast.exe one day, maybe? Requires stuff below to be converted to fixed point.
+#define MIDTTLY 79
+#define MIDZONEY 105
+#define MIDDIFF 4
+
+	if (timeinmap < 10)
+	{
+		fixed_t z = ((timeinmap - 3)<<FRACBITS)/7;
+		INT32 ttlh = V_LevelNameHeight(lvlttl);
+		zoney = (200<<FRACBITS) - ((200 - (MIDZONEY + MIDDIFF))*z);
+		lvlttly = ((MIDTTLY + ttlh - MIDDIFF)*z) - (ttlh<<FRACBITS);
+	}
+	else if (timeinmap < 105)
+	{
+		fixed_t z = (((timeinmap - 10)*MIDDIFF)<<(FRACBITS+1))/95;
+		zoney = ((MIDZONEY + MIDDIFF)<<FRACBITS) - z;
+		lvlttly = ((MIDTTLY - MIDDIFF)<<FRACBITS) + z;
+	}
+	else
+	{
+		fixed_t z = ((timeinmap - 105)<<FRACBITS)/7;
+		INT32 zoneh = V_LevelNameHeight(M_GetText("ZONE"));
+		zoney = (MIDZONEY + zoneh - MIDDIFF)*(FRACUNIT - z) - (zoneh<<FRACBITS);
+		lvlttly = ((MIDTTLY + MIDDIFF)<<FRACBITS) + ((200 - (MIDTTLY + MIDDIFF))*z);
+	}
+
+#undef MIDTTLY
+#undef MIDZONEY
+#undef MIDDIFF
+#else
 	// There's no consistent algorithm that can accurately define the old positions
 	// so I just ended up resorting to a single switct statement to define them
 	switch (timeinmap-3)
@@ -1020,151 +1183,200 @@ static void ST_drawLevelTitle(void)
 		case 109: zoney =   0; lvlttly = 200; break;
 		default:  zoney = 104; lvlttly =  80; break;
 	}
+#endif
 
 	if (actnum)
-		V_DrawLevelActNum(ttlnumxpos, zoney, 0, actnum);
+		V_DrawLevelActNum(ttlnumxpos, zoney, V_PERPLAYER, actnum);
 
-	V_DrawLevelTitle(lvlttlxpos, lvlttly, 0, lvlttl);
+	V_DrawLevelTitle(lvlttlxpos, lvlttly, V_PERPLAYER, lvlttl);
 
 	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
-		V_DrawLevelTitle(zonexpos, zoney, 0, M_GetText("ZONE"));
+		V_DrawLevelTitle(zonexpos, zoney, V_PERPLAYER, M_GetText("ZONE"));
 
 	if (lvlttly+48 < 200)
-		V_DrawCenteredString(subttlxpos, lvlttly+48, V_ALLOWLOWERCASE, subttl);
+		V_DrawCenteredString(subttlxpos, lvlttly+48, V_PERPLAYER|V_ALLOWLOWERCASE, subttl);
 }
 
-static void ST_drawFirstPersonHUD(void)
+static void ST_drawPowerupHUD(void)
 {
-	player_t *player = stplyr;
 	patch_t *p = NULL;
 	UINT16 invulntime = 0;
+	INT32 offs = hudinfo[HUD_POWERUPS].x;
+	const UINT8 q = ((splitscreen && stplyr == &players[secondarydisplayplayer]) ? 1 : 0);
+	static INT32 flagoffs[2] = {0, 0}, shieldoffs[2] = {0, 0};
+#define ICONSEP (16+4) // matches weapon rings HUD
 
-	if (player->playerstate != PST_LIVE)
+	if (stplyr->spectator || stplyr->playerstate != PST_LIVE)
 		return;
 
 	// Graue 06-18-2004: no V_NOSCALESTART, no SCX, no SCY, snap to right
-	if ((player->powers[pw_shield] & SH_NOSTACK & ~SH_FORCEHP) == SH_FORCE)
+	if (stplyr->powers[pw_shield] & SH_NOSTACK)
 	{
-		UINT8 i, max = (player->powers[pw_shield] & SH_FORCEHP);
-		for (i = 0; i <= max; i++)
+		shieldoffs[q] = ICONSEP;
+
+		if ((stplyr->powers[pw_shield] & SH_NOSTACK & ~SH_FORCEHP) == SH_FORCE)
 		{
-			INT32 flags = (V_SNAPTORIGHT|V_SNAPTOTOP)|((i == max) ? V_HUDTRANS : V_HUDTRANSHALF);
-			if (splitscreen)
-				V_DrawSmallScaledPatch(312-(3*i), STRINGY(24)+(3*i), flags, forceshield);
-			else
-				V_DrawScaledPatch(304-(3*i), 24+(3*i), flags, forceshield);
+			UINT8 i, max = (stplyr->powers[pw_shield] & SH_FORCEHP);
+			for (i = 0; i <= max; i++)
+			{
+				V_DrawSmallScaledPatch(offs-(i<<1), hudinfo[HUD_POWERUPS].y-(i<<1), (V_PERPLAYER|hudinfo[HUD_POWERUPS].f)|((i == max) ? V_HUDTRANS : V_HUDTRANSHALF), forceshield);
+			}
+		}
+		else
+		{
+			switch (stplyr->powers[pw_shield] & SH_NOSTACK)
+			{
+				case SH_WHIRLWIND:   p = jumpshield;    break;
+				case SH_ELEMENTAL:   p = watershield;   break;
+				case SH_ARMAGEDDON:  p = bombshield;    break;
+				case SH_ATTRACT:     p = ringshield;    break;
+				case SH_PITY:        p = pityshield;    break;
+				case SH_FLAMEAURA:   p = flameshield;   break;
+				case SH_BUBBLEWRAP:  p = bubbleshield;  break;
+				case SH_THUNDERCOIN: p = thundershield; break;
+				default: break;
+			}
+
+			if (p)
+				V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, p);
 		}
 	}
-	else switch (player->powers[pw_shield] & SH_NOSTACK)
+	else if (shieldoffs[q])
 	{
-	case SH_WHIRLWIND:   p = jumpshield;    break;
-	case SH_ELEMENTAL:   p = watershield;   break;
-	case SH_ARMAGEDDON:  p = bombshield;    break;
-	case SH_ATTRACT:     p = ringshield;    break;
-	case SH_PITY:        p = pityshield;    break;
-	case SH_FLAMEAURA:   p = flameshield;   break;
-	case SH_BUBBLEWRAP:  p = bubbleshield;  break;
-	case SH_THUNDERCOIN: p = thundershield; break;
-	default: break;
+		if (shieldoffs[q] > 1)
+			shieldoffs[q] = 2*shieldoffs[q]/3;
+		else
+			shieldoffs[q] = 0;
 	}
 
-	if (p)
+	offs -= shieldoffs[q];
+
+	// YOU have a flag. Display a monitor-like icon for it.
+	if (stplyr->gotflag)
 	{
-		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24), V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
+		flagoffs[q] = ICONSEP;
+		p = (stplyr->gotflag & GF_REDFLAG) ? gotrflag : gotbflag;
+		V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, p);
+	}
+	else if (flagoffs[q])
+	{
+		if (flagoffs[q] > 1)
+			flagoffs[q] = 2*flagoffs[q]/3;
 		else
-			V_DrawScaledPatch(304, 24, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
+			flagoffs[q] = 0;
 	}
 
-	// pw_flashing just sets the icon to flash no matter what.
-	invulntime = player->powers[pw_flashing] ? 1 : player->powers[pw_invulnerability];
-	if (invulntime > 3*TICRATE || (invulntime && leveltime & 1))
+	offs -= flagoffs[q];
+
+	invulntime = stplyr->powers[pw_flashing] ? stplyr->powers[pw_flashing] : stplyr->powers[pw_invulnerability];
+	if (stplyr->powers[pw_invulnerability] > 3*TICRATE || (invulntime && leveltime & 1))
 	{
-		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24) + 14, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, invincibility);
-		else
-			V_DrawScaledPatch(304, 24 + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, invincibility);
+		V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, invincibility);
+		V_DrawRightAlignedThinString(offs + 16, hudinfo[HUD_POWERUPS].y + 8, V_PERPLAYER|hudinfo[HUD_POWERUPS].f, va("%d", invulntime/TICRATE));
 	}
 
-	if (player->powers[pw_sneakers] > 3*TICRATE || (player->powers[pw_sneakers] && leveltime & 1))
+	if (invulntime > 7)
+		offs -= ICONSEP;
+	else
 	{
-		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24) + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, sneakers);
-		else
-			V_DrawScaledPatch(304, 24 + 56, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, sneakers);
-	}
-
-	p = NULL;
-
-	{
-		UINT32 airtime;
-		UINT32 frame = 0;
-		spriteframe_t *sprframe;
-		// If both air timers are active, use the air timer with the least time left
-		if (player->powers[pw_underwater] && player->powers[pw_spacetime])
-			airtime = min(player->powers[pw_underwater], player->powers[pw_spacetime]);
-		else // Use whichever one is active otherwise
-			airtime = (player->powers[pw_spacetime]) ? player->powers[pw_spacetime] : player->powers[pw_underwater];
-
-		if (!airtime)
-			return; // No air timers are active, nothing would be drawn anyway
-
-		airtime--; // The original code was all n*TICRATE + 1, so let's remove 1 tic for simplicity
-
-		if (airtime > 11*TICRATE)
-			return; // Not time to draw any drown numbers yet
-		// Choose which frame to use based on time left
-		if (airtime <= 11*TICRATE && airtime >= 10*TICRATE)
-			frame = 5;
-		else if (airtime <= 9*TICRATE && airtime >= 8*TICRATE)
-			frame = 4;
-		else if (airtime <= 7*TICRATE && airtime >= 6*TICRATE)
-			frame = 3;
-		else if (airtime <= 5*TICRATE && airtime >= 4*TICRATE)
-			frame = 2;
-		else if (airtime <= 3*TICRATE && airtime >= 2*TICRATE)
-			frame = 1;
-		else if (airtime <= 1*TICRATE && airtime > 0)
-			frame = 0;
-		else
-			return; // Don't draw anything between numbers
+		UINT8 a = ICONSEP, b = 7-invulntime;
+		while (b--)
+			a = 2*a/3;
+		offs -= a;
+	}
 
-		if (player->charflags & SF_MACHINE)
-			frame += 6;  // Robots use different drown numbers
+	if (stplyr->powers[pw_sneakers] > 3*TICRATE || (stplyr->powers[pw_sneakers] && leveltime & 1))
+	{
+		V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, sneakers);
+		V_DrawRightAlignedThinString(offs + 16, hudinfo[HUD_POWERUPS].y + 8, V_PERPLAYER|hudinfo[HUD_POWERUPS].f, va("%d", stplyr->powers[pw_sneakers]/TICRATE));
+	}
 
-		// Get the front angle patch for the frame
-		sprframe = &sprites[SPR_DRWN].spriteframes[frame];
-		p = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+	if (stplyr->powers[pw_sneakers] > 7)
+		offs -= ICONSEP;
+	else
+	{
+		UINT8 a = ICONSEP, b = 7-stplyr->powers[pw_sneakers];
+		while (b--)
+			a = 2*a/3;
+		offs -= a;
+	}
+
+	if (stplyr->powers[pw_gravityboots] > 3*TICRATE || (stplyr->powers[pw_gravityboots] && leveltime & 1))
+	{
+		V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, gravboots);
+		V_DrawRightAlignedThinString(offs + 16, hudinfo[HUD_POWERUPS].y + 8, V_PERPLAYER|hudinfo[HUD_POWERUPS].f, va("%d", stplyr->powers[pw_gravityboots]/TICRATE));
 	}
 
+#undef ICONSEP
+}
+
+static void ST_drawFirstPersonHUD(void)
+{
+	patch_t *p = NULL;
+	UINT32 airtime;
+	spriteframe_t *sprframe;
+	// If both air timers are active, use the air timer with the least time left
+	if (stplyr->powers[pw_underwater] && stplyr->powers[pw_spacetime])
+		airtime = min(stplyr->powers[pw_underwater], stplyr->powers[pw_spacetime]);
+	else // Use whichever one is active otherwise
+		airtime = (stplyr->powers[pw_spacetime]) ? stplyr->powers[pw_spacetime] : stplyr->powers[pw_underwater];
+
+	if (airtime < 1)
+		return; // No air timers are active, nothing would be drawn anyway
+
+	airtime--; // The original code was all n*TICRATE + 1, so let's remove 1 tic for simplicity
+
+	if (airtime > 11*TICRATE)
+		return; // Not time to draw any drown numbers yet
+
+	if (!((airtime > 10*TICRATE - 5)
+	|| (airtime <= 9*TICRATE && airtime > 8*TICRATE - 5)
+	|| (airtime <= 7*TICRATE && airtime > 6*TICRATE - 5)
+	|| (airtime <= 5*TICRATE && airtime > 4*TICRATE - 5)
+	|| (airtime <= 3*TICRATE && airtime > 2*TICRATE - 5)
+	|| (airtime <= 1*TICRATE)))
+		return; // Don't draw anything between numbers
+
+	if (!((airtime % 10) < 5))
+		return; // Keep in line with the flashing thing from third person.
+
+	airtime /= (2*TICRATE); // To be strictly accurate it'd need to be ((airtime/TICRATE) - 1)/2, but integer division rounds down for us
+
+	if (stplyr->charflags & SF_MACHINE)
+		airtime += 6;  // Robots use different drown numbers
+
+	// Get the front angle patch for the frame
+	sprframe = &sprites[SPR_DRWN].spriteframes[airtime];
+	p = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+
 	// Display the countdown drown numbers!
 	if (p)
-		V_DrawScaledPatch(SCX((BASEVIDWIDTH/2) - (SHORT(p->width)/2) + SHORT(p->leftoffset)), SCY(60 - SHORT(p->topoffset)),
-			V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, p);
+		V_DrawScaledPatch((BASEVIDWIDTH/2) - (SHORT(p->width)/2) + SHORT(p->leftoffset), 60 - SHORT(p->topoffset),
+			V_PERPLAYER|V_PERPLAYER|V_TRANSLUCENT, p);
 }
 
 static void ST_drawNightsRecords(void)
 {
-	INT32 aflag = 0;
+	INT32 aflag = V_PERPLAYER;
 
 	if (!stplyr->texttimer)
 		return;
 
 	if (stplyr->texttimer < TICRATE/2)
-		aflag = (9 - 9*stplyr->texttimer/(TICRATE/2)) << V_ALPHASHIFT;
+		aflag |= (9 - 9*stplyr->texttimer/(TICRATE/2)) << V_ALPHASHIFT;
 
 	// A "Bonus Time Start" by any other name...
 	if (stplyr->textvar == 1)
 	{
-		V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(52), V_GREENMAP|aflag, M_GetText("GET TO THE GOAL!"));
-		V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(60),            aflag, M_GetText("SCORE MULTIPLIER START!"));
+		V_DrawCenteredString(BASEVIDWIDTH/2, 52, V_GREENMAP|aflag, M_GetText("GET TO THE GOAL!"));
+		V_DrawCenteredString(BASEVIDWIDTH/2, 60,            aflag, M_GetText("SCORE MULTIPLIER START!"));
 
 		if (stplyr->finishedtime)
 		{
-			V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(140), aflag, "TIME:");
-			V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(148), aflag, "BONUS:");
-			V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(140), V_ORANGEMAP|aflag, va("%d", (stplyr->startedtime - stplyr->finishedtime)/TICRATE));
-			V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(148), V_ORANGEMAP|aflag, va("%d", (stplyr->finishedtime/TICRATE) * 100));
+			V_DrawString(BASEVIDWIDTH/2 - 48, 140, aflag, "TIME:");
+			V_DrawString(BASEVIDWIDTH/2 - 48, 148, aflag, "BONUS:");
+			V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, 140, V_ORANGEMAP|aflag, va("%d", (stplyr->startedtime - stplyr->finishedtime)/TICRATE));
+			V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, 148, V_ORANGEMAP|aflag, va("%d", (stplyr->finishedtime/TICRATE) * 100));
 		}
 	}
 
@@ -1175,7 +1387,7 @@ static void ST_drawNightsRecords(void)
 			return;
 
 		// Yes, this string is an abomination.
-		V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(60), aflag,
+		V_DrawCenteredString(BASEVIDWIDTH/2, 60, aflag,
 		                     va(M_GetText("\x80GET\x82 %d\x80 %s%s%s!"), stplyr->capsule->health,
 		                        (stplyr->textvar == 3) ? M_GetText("MORE ") : "",
 		                        (G_IsSpecialStage(gamemap)) ? "SPHERE" : "RING",
@@ -1185,26 +1397,26 @@ static void ST_drawNightsRecords(void)
 	// End Bonus
 	else if (stplyr->textvar == 4)
 	{
-		V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(140), aflag, (G_IsSpecialStage(gamemap)) ? "ORBS:" : "RINGS:");
-		V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(148), aflag, "BONUS:");
-		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(140), V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings));
-		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(148), V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings * 50));
-		ST_DrawNightsOverlayNum((BASEVIDWIDTH/2 + 48)<<FRACBITS, STRINGY(160)<<FRACBITS, FRACUNIT, aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_AZURE);
+		V_DrawString(BASEVIDWIDTH/2 - 56, 140, aflag, (G_IsSpecialStage(gamemap)) ? "SPHERES:" : "RINGS:");
+		V_DrawString(BASEVIDWIDTH/2 - 56, 148, aflag, "BONUS:");
+		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 56, 140, V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings));
+		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 56, 140, V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings * 50));
+		ST_DrawNightsOverlayNum((BASEVIDWIDTH/2 + 56)<<FRACBITS, 160<<FRACBITS, FRACUNIT, aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_AZURE);
 
 		// If new record, say so!
 		if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1) <= stplyr->lastmarescore)
 		{
 			if (stplyr->texttimer & 16)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(184), V_YELLOWMAP|aflag, "* NEW RECORD *");
+				V_DrawCenteredString(BASEVIDWIDTH/2, 184, V_YELLOWMAP|aflag, "* NEW RECORD *");
 		}
 
 		if (P_HasGrades(gamemap, stplyr->lastmare + 1))
 		{
 			if (aflag)
-				V_DrawTranslucentPatch(BASEVIDWIDTH/2 + 60, STRINGY(160), aflag,
+				V_DrawTranslucentPatch(BASEVIDWIDTH/2 + 60, 160, aflag,
 				                       ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
 			else
-				V_DrawScaledPatch(BASEVIDWIDTH/2 + 60, STRINGY(160), 0,
+				V_DrawScaledPatch(BASEVIDWIDTH/2 + 60, 160, 0,
 				                  ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
 		}
 	}
@@ -1247,7 +1459,6 @@ static void ST_drawNiGHTSHUD(void)
 	INT32 origamount;
 	INT32 minlink = 1;
 	INT32 total_ringcount;
-	boolean nosshack = false;
 
 	// When debugging, show "0 Link".
 	if (cv_debug & DBG_NIGHTSBASIC)
@@ -1257,15 +1468,6 @@ static void ST_drawNiGHTSHUD(void)
 	if (stplyr->texttimer && stplyr->textvar == 4)
 		minlink = INT32_MAX;
 
-	if (G_IsSpecialStage(gamemap))
-	{ // Since special stages share score, time, rings, etc.
-		// disable splitscreen mode for its HUD.
-		if (stplyr != &players[displayplayer])
-			return;
-		nosshack = splitscreen;
-		splitscreen = false;
-	}
-
 	// Drill meter
 	if (
 #ifdef HAVE_BLUA
@@ -1273,67 +1475,42 @@ static void ST_drawNiGHTSHUD(void)
 #endif
 	stplyr->powers[pw_carry] == CR_NIGHTSMODE)
 	{
-		INT32 locx, locy;
+		INT32 locx = 16, locy = 180;
 		INT32 dfill;
 		UINT8 fillpatch;
 
-		if (splitscreen || nosshack)
-		{
-			locx = 110;
-			locy = 188;
-		}
-		else
-		{
-			locx = 16;
-			locy = 180;
-		}
-
 		// Use which patch?
 		if (stplyr->pflags & PF_DRILLING)
 			fillpatch = (stplyr->drillmeter & 1) + 1;
 		else
 			fillpatch = 0;
 
-		if (splitscreen)
-		{ // 11-5-14 Replaced the old hack with a slightly better hack. -Red
-			V_DrawScaledPatch(locx, STRINGY(locy)-3, SPLITFLAGS(V_SNAPTOBOTTOM)|V_HUDTRANS, drillbar);
-			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), SPLITFLAGS(V_SNAPTOBOTTOM)|V_HUDTRANS, drillfill[fillpatch]);
-		}
-		else if (nosshack)
-		{ // Even dirtier hack-of-a-hack to draw seperate drill meters in splitscreen special stages but nothing else.
-			splitscreen = true;
-			V_DrawScaledPatch(locx, STRINGY(locy)-3, V_HUDTRANS, drillbar);
-			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), V_HUDTRANS, drillfill[fillpatch]);
-			stplyr = &players[secondarydisplayplayer];
-			if (stplyr->pflags & PF_DRILLING)
-				fillpatch = (stplyr->drillmeter & 1) + 1;
-			else
-				fillpatch = 0;
-			V_DrawScaledPatch(locx, STRINGY(locy-3), V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
-			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, STRINGY(locy + 3), V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
-			stplyr = &players[displayplayer];
-			splitscreen = false;
-		}
-		else
-		{ // Draw normally. <:3
-			V_DrawScaledPatch(locx, locy, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
-			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, locy + 3, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
-		}
+		V_DrawScaledPatch(locx, locy, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
+		for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
+			V_DrawScaledPatch(locx + 2 + dfill, locy + 3, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
 
 		// Display actual drill amount and bumper time
-		if (cv_debug & DBG_NIGHTSBASIC)
+		if (!splitscreen && (cv_debug & DBG_NIGHTSBASIC))
 		{
 			if (stplyr->bumpertime)
-				V_DrawString(SCX(locx), SCY(locy - 8), V_NOSCALESTART|V_REDMAP|V_MONOSPACE, va("BUMPER: 0.%02d", G_TicsToCentiseconds(stplyr->bumpertime)));
+				V_DrawString(locx, locy - 8, V_REDMAP|V_MONOSPACE, va("BUMPER: 0.%02d", G_TicsToCentiseconds(stplyr->bumpertime)));
 			else
-				V_DrawString(SCX(locx), SCY(locy - 8), V_NOSCALESTART|V_MONOSPACE, va("Drill: %3d%%", (stplyr->drillmeter*100)/(96*20)));
+				V_DrawString(locx, locy - 8, V_MONOSPACE, va("Drill: %3d%%", (stplyr->drillmeter*100)/(96*20)));
 		}
 	}
 
+	/*if (G_IsSpecialStage(gamemap))
+	{ // Since special stages share score, time, rings, etc.
+		// disable splitscreen mode for its HUD.
+		// --------------------------------------
+		// NOPE! Consistency between different splitscreen stuffs
+		// now we've got the screen squashing instead. ~toast
+		if (stplyr != &players[displayplayer])
+			return;
+		nosshack = splitscreen;
+		splitscreen = false;
+	}*/
+
 	// Link drawing
 	if (
 #ifdef HAVE_BLUA
@@ -1341,44 +1518,39 @@ static void ST_drawNiGHTSHUD(void)
 #endif
 	stplyr->linkcount > minlink)
 	{
+		static INT32 prevsel[2] = {0, 0}, prevtime[2] = {0, 0};
+		const UINT8 q = ((splitscreen && stplyr == &players[secondarydisplayplayer]) ? 1 : 0);
+		INT32 sel = ((stplyr->linkcount-1) / 5) % NUMLINKCOLORS, aflag = V_PERPLAYER, mag = ((stplyr->linkcount-1 >= 300) ? 1 : 0);
 		skincolors_t colornum;
-		INT32 aflag;
 		fixed_t x, y, scale;
 
+		if (sel != prevsel[q])
+		{
+			prevsel[q] = sel;
+			prevtime[q] = 2 + mag;
+		}
+
 		if (stplyr->powers[pw_nights_linkfreeze] && (!(stplyr->powers[pw_nights_linkfreeze] & 2) || (stplyr->powers[pw_nights_linkfreeze] > flashingtics)))
 			colornum = SKINCOLOR_ICY;
 		else
-			colornum = linkColor[((stplyr->linkcount-1 >= 300) ? 1 : 0)][((stplyr->linkcount-1) / 5) % NUMLINKCOLORS];
+			colornum = linkColor[mag][sel];
 
-		aflag = ((stplyr->linktimer < 2*TICRATE/3)
+		aflag |= ((stplyr->linktimer < 2*TICRATE/3)
 		? (9 - 9*stplyr->linktimer/(2*TICRATE/3)) << V_ALPHASHIFT
 		: 0);
 
-		if (splitscreen)
-		{
-			x = (256+4)<<FRACBITS;
-			y = (STRINGY(152)+11)<<FRACBITS;
-			aflag |= SPLITFLAGS(V_SNAPTOBOTTOM)|V_SNAPTORIGHT;
-		}
-		else
-		{
-			x = (160+4)<<FRACBITS;
-			y = (160+11)<<FRACBITS;
-			aflag |= V_SNAPTOBOTTOM;
-		}
+		y = (160+11)<<FRACBITS;
+		aflag |= V_SNAPTOBOTTOM;
+
+		x = (160+4)<<FRACBITS;
 
-		switch (stplyr->linktimer)
+		if (prevtime[q])
 		{
-			case (2*TICRATE):
-				scale = (36*FRACUNIT)/32;
-				break;
-			case (2*TICRATE - 1):
-				scale = (34*FRACUNIT)/32;
-				break;
-			default:
-				scale = FRACUNIT;
-				break;
+			scale = ((32 + prevtime[q])<<FRACBITS)/32;
+			prevtime[q]--;
 		}
+		else
+			scale = FRACUNIT;
 
 		y -= (11*scale);
 
@@ -1403,11 +1575,11 @@ static void ST_drawNiGHTSHUD(void)
 	if (LUA_HudEnabled(hud_nightsrings))
 	{
 #endif
-	ST_DrawOverlayPatch(SCX(16), SCY(8), nbracket);
+	ST_DrawTopLeftOverlayPatch(16, 8, nbracket);
 	if (G_IsSpecialStage(gamemap))
-		ST_DrawOverlayPatch(SCX(24), SCY(8) + SCZ(8), nsshud);
+		ST_DrawTopLeftOverlayPatch(24, 16, nsshud);
 	else
-		ST_DrawOverlayPatch(SCX(24), SCY(8) + SCZ(8), nhud[(leveltime/2)%12]);
+		ST_DrawTopLeftOverlayPatch(24, 16, nhud[(leveltime/2)%12]);
 
 	if (G_IsSpecialStage(gamemap))
 	{
@@ -1428,8 +1600,8 @@ static void ST_drawNiGHTSHUD(void)
 		origamount = stplyr->capsule->spawnpoint->angle;
 		I_Assert(origamount > 0); // should not happen now
 
-		ST_DrawOverlayPatch(SCX(72), SCY(8), nbracket);
-		ST_DrawOverlayPatch(SCX(74), SCY(8) + SCZ(4), minicaps);
+		ST_DrawTopLeftOverlayPatch(72, 8, nbracket);
+		ST_DrawTopLeftOverlayPatch(74, 8 + 4, minicaps);
 
 		if (stplyr->capsule->reactiontime != 0)
 		{
@@ -1438,10 +1610,10 @@ static void ST_drawNiGHTSHUD(void)
 
 			for (r = 0; r < 5; r++)
 			{
-				ST_DrawOverlayPatch(SCX(230 - (7*r)), SCY(144), redstat);
-				ST_DrawOverlayPatch(SCX(188 - (7*r)), SCY(144), orngstat);
-				ST_DrawOverlayPatch(SCX(146 - (7*r)), SCY(144), yelstat);
-				ST_DrawOverlayPatch(SCX(104 - (7*r)), SCY(144), byelstat);
+				V_DrawScaledPatch(230 - (7*r), 144, V_PERPLAYER|V_HUDTRANS, redstat);
+				V_DrawScaledPatch(188 - (7*r), 144, V_PERPLAYER|V_HUDTRANS, orngstat);
+				V_DrawScaledPatch(146 - (7*r), 144, V_PERPLAYER|V_HUDTRANS, yelstat);
+				V_DrawScaledPatch(104 - (7*r), 144, V_PERPLAYER|V_HUDTRANS, byelstat);
 			}
 
 			amount = (origamount - stplyr->capsule->health);
@@ -1460,7 +1632,7 @@ static void ST_drawNiGHTSHUD(void)
 					if (r > 10) ++t;
 					if (r > 5)  ++t;
 
-					ST_DrawOverlayPatch(SCX(69 + (7*t)), SCY(144), bluestat);
+					V_DrawScaledPatch(69 + (7*t), 144, V_PERPLAYER|V_HUDTRANS, bluestat);
 				}
 			}
 		}
@@ -1469,27 +1641,27 @@ static void ST_drawNiGHTSHUD(void)
 			INT32 cfill;
 
 			// Lil' white box!
-			V_DrawScaledPatch(15, STRINGY(8) + 34, V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulebar);
+			V_DrawScaledPatch(15, 8 + 34, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulebar);
 
 			amount = (origamount - stplyr->capsule->health);
 			amount = (amount * length)/origamount;
 
 			for (cfill = 0; cfill < amount && cfill < 88; ++cfill)
-				V_DrawScaledPatch(15 + cfill + 1, STRINGY(8) + 35, V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulefill);
+				V_DrawScaledPatch(15 + cfill + 1, 8 + 35, V_PERPLAYER|V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulefill);
 		}
 
 		if (total_ringcount >= stplyr->capsule->health)
-			ST_DrawOverlayPatch(SCX(40), SCY(8) + SCZ(5), nredar[leveltime%8]);
+			ST_DrawTopLeftOverlayPatch(40, 8 + 5, nredar[leveltime%8]);
 		else
-			ST_DrawOverlayPatch(SCX(40), SCY(8) + SCZ(5), narrow[(leveltime/2)%8]);
+			ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[(leveltime/2)%8]);
 	}
 	else
-		ST_DrawOverlayPatch(SCX(40), SCY(8) + SCZ(5), narrow[8]);
+		ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[8]);
 
 	if (total_ringcount >= 100)
-		ST_DrawOverlayNum((total_ringcount >= 1000) ? SCX(76) : SCX(72), SCY(8) + SCZ(11), total_ringcount);
+		V_DrawTallNum((total_ringcount >= 1000) ? 76 : 72, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, total_ringcount);
 	else
-		ST_DrawOverlayNum(SCX(68), SCY(8) + SCZ(11), total_ringcount);
+		V_DrawTallNum(68, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, total_ringcount);
 #ifdef HAVE_BLUA
 	}
 #endif
@@ -1500,9 +1672,7 @@ static void ST_drawNiGHTSHUD(void)
 	&& LUA_HudEnabled(hud_nightsscore)
 #endif
 	)
-	{
-		ST_DrawNightsOverlayNum(304<<FRACBITS, STRINGY(16)<<FRACBITS, FRACUNIT, SPLITFLAGS(V_SNAPTOTOP)|V_SNAPTORIGHT, stplyr->marescore, nightsnum, SKINCOLOR_AZURE);
-	}
+		ST_DrawNightsOverlayNum(304<<FRACBITS, 14<<FRACBITS, FRACUNIT, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT, stplyr->marescore, nightsnum, SKINCOLOR_AZURE);
 
 	if (!stplyr->exiting
 #ifdef HAVE_BLUA
@@ -1514,22 +1684,21 @@ static void ST_drawNiGHTSHUD(void)
 		if (modeattacking == ATTACKING_NIGHTS)
 		{
 			INT32 maretime = max(stplyr->realtime - stplyr->marebegunat, 0);
-			fixed_t cornerx = vid.width, cornery = vid.height-SCZ(20);
 
-#define ASSISHHUDFIX(n) (n*vid.dupx)
-			ST_DrawOverlayPatch(cornerx-ASSISHHUDFIX(22), cornery, W_CachePatchName("NGRTIMER", PU_HUDGFX));
-			ST_DrawPaddedOverlayNum(cornerx-ASSISHHUDFIX(22), cornery, G_TicsToCentiseconds(maretime), 2);
-			ST_DrawOverlayPatch(cornerx-ASSISHHUDFIX(46), cornery, sboperiod);
+#define VFLAGS V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_PERPLAYER|V_HUDTRANS
+			V_DrawScaledPatch(BASEVIDWIDTH-22, BASEVIDHEIGHT-20, VFLAGS, W_CachePatchName("NGRTIMER", PU_HUDGFX));
+			V_DrawPaddedTallNum(BASEVIDWIDTH-22, BASEVIDHEIGHT-20, VFLAGS, G_TicsToCentiseconds(maretime), 2);
+			V_DrawScaledPatch(BASEVIDWIDTH-46, BASEVIDHEIGHT-20, VFLAGS, sboperiod);
 			if (maretime < 60*TICRATE)
-				ST_DrawOverlayNum(cornerx-ASSISHHUDFIX(46), cornery, G_TicsToSeconds(maretime));
+				V_DrawTallNum(BASEVIDWIDTH-46, BASEVIDHEIGHT-20, VFLAGS, G_TicsToSeconds(maretime));
 			else
 			{
-				ST_DrawPaddedOverlayNum(cornerx-ASSISHHUDFIX(46), cornery, G_TicsToSeconds(maretime), 2);
-				ST_DrawOverlayPatch(cornerx-ASSISHHUDFIX(70), cornery, sbocolon);
-				ST_DrawOverlayNum(cornerx-ASSISHHUDFIX(70), cornery, G_TicsToMinutes(maretime, true));
+				V_DrawPaddedTallNum(BASEVIDWIDTH-46, BASEVIDHEIGHT-20, VFLAGS, G_TicsToSeconds(maretime), 2);
+				V_DrawScaledPatch(BASEVIDWIDTH-70, BASEVIDHEIGHT-20, VFLAGS, sbocolon);
+				V_DrawTallNum(BASEVIDWIDTH-70, BASEVIDHEIGHT-20, VFLAGS, G_TicsToMinutes(maretime, true));
 			}
+#undef VFLAGS
 		}
-#undef ASSISHHUDFIX
 	}
 
 	// Ideya time remaining
@@ -1558,10 +1727,10 @@ static void ST_drawNiGHTSHUD(void)
 			if (flashingLeft < TICRATE/2) // Start fading out
 			{
 				UINT32 fadingFlag = (9 - 9*flashingLeft/(TICRATE/2)) << V_ALPHASHIFT;
-				V_DrawTranslucentPatch(SCX(160 - (minus5sec->width/2)), SCY(28), V_NOSCALESTART|fadingFlag, minus5sec);
+				V_DrawTranslucentPatch(160 - (minus5sec->width/2), 28, V_PERPLAYER|fadingFlag, minus5sec);
 			}
 			else
-				V_DrawScaledPatch(SCX(160 - (minus5sec->width/2)), SCY(28), V_NOSCALESTART, minus5sec);
+				V_DrawScaledPatch(160 - (minus5sec->width/2), 28, V_PERPLAYER, minus5sec);
 		}
 
 		if (realnightstime < 10)
@@ -1571,7 +1740,7 @@ static void ST_drawNiGHTSHUD(void)
 		else
 			numbersize = 48/2;
 
-		ST_DrawNightsOverlayNum((160 + numbersize)<<FRACBITS, STRINGY(12)<<FRACBITS, FRACUNIT, SPLITFLAGS(V_SNAPTOTOP), realnightstime, nightsnum,
+		ST_DrawNightsOverlayNum((160 + numbersize)<<FRACBITS, 14<<FRACBITS, FRACUNIT, V_PERPLAYER|V_SNAPTOTOP, realnightstime, nightsnum,
 			((realnightstime < 10) ? SKINCOLOR_RED : SKINCOLOR_SUPERGOLD4));
 
 		// Show exact time in debug
@@ -1587,22 +1756,22 @@ static void ST_drawNiGHTSHUD(void)
 		if (stplyr->powers[pw_nights_superloop])
 		{
 			pwr = stplyr->powers[pw_nights_superloop];
-			V_DrawSmallScaledPatch(SCX(110), SCY(44), V_NOSCALESTART, W_CachePatchName("NPRUA0",PU_CACHE));
-			V_DrawThinString(SCX(106), SCY(52), V_NOSCALESTART|V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			V_DrawSmallScaledPatch(110, 44, 0, W_CachePatchName("NPRUA0",PU_CACHE));
+			V_DrawThinString(106, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
 		}
 
 		if (stplyr->powers[pw_nights_helper])
 		{
 			pwr = stplyr->powers[pw_nights_helper];
-			V_DrawSmallScaledPatch(SCX(150), SCY(44), V_NOSCALESTART, W_CachePatchName("NPRUC0",PU_CACHE));
-			V_DrawThinString(SCX(146), SCY(52), V_NOSCALESTART|V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			V_DrawSmallScaledPatch(150, 44, 0, W_CachePatchName("NPRUC0",PU_CACHE));
+			V_DrawThinString(146, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
 		}
 
 		if (stplyr->powers[pw_nights_linkfreeze])
 		{
 			pwr = stplyr->powers[pw_nights_linkfreeze];
-			V_DrawSmallScaledPatch(SCX(190), SCY(44), V_NOSCALESTART, W_CachePatchName("NPRUE0",PU_CACHE));
-			V_DrawThinString(SCX(186), SCY(52), V_NOSCALESTART|V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
+			V_DrawSmallScaledPatch(190, 44, 0, W_CachePatchName("NPRUE0",PU_CACHE));
+			V_DrawThinString(186, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
 		}
 	}
 
@@ -1611,12 +1780,31 @@ static void ST_drawNiGHTSHUD(void)
 	if (LUA_HudEnabled(hud_nightsrecords))
 #endif
 	ST_drawNightsRecords();
+}
 
-	if (nosshack)
-		splitscreen = true;
+static inline void ST_drawWeaponSelect(INT32 xoffs, INT32 y)
+{
+	INT32 q = stplyr->weapondelay, del = 0, p = 16;
+	while (q)
+	{
+		if (q > p)
+		{
+			del += p;
+			q -= p;
+			q /= 2;
+			if (p > 1)
+				p /= 2;
+		}
+		else
+		{
+			del += q;
+			break;
+		}
+	}
+	V_DrawScaledPatch(6 + xoffs, y-2 - del/2, V_PERPLAYER|V_SNAPTOBOTTOM, curweapon);
 }
 
-static void ST_drawWeaponRing(powertype_t weapon, INT32 rwflag, INT32 wepflag, INT32 xoffs, patch_t *pat)
+static void ST_drawWeaponRing(powertype_t weapon, INT32 rwflag, INT32 wepflag, INT32 xoffs, INT32 y, patch_t *pat)
 {
 	INT32 txtflags = 0, patflags = 0;
 
@@ -1627,30 +1815,27 @@ static void ST_drawWeaponRing(powertype_t weapon, INT32 rwflag, INT32 wepflag, I
 
 		if (weapon == pw_infinityring
 		|| (stplyr->ringweapons & rwflag))
-			txtflags |= V_20TRANS;
+			; //txtflags |= V_20TRANS;
 		else
 		{
 			txtflags |= V_TRANSLUCENT;
 			patflags =  V_80TRANS;
 		}
 
-		V_DrawScaledPatch(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|patflags, pat);
-
-		if (stplyr->powers[weapon] > 99)
-			V_DrawThinString(8 + xoffs + 1, STRINGY(162), V_SNAPTOLEFT|txtflags, va("%d", stplyr->powers[weapon]));
-		else
-			V_DrawString(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|txtflags, va("%d", stplyr->powers[weapon]));
+		V_DrawScaledPatch(8 + xoffs, y, V_PERPLAYER|V_SNAPTOBOTTOM|patflags, pat);
+		V_DrawRightAlignedThinString(24 + xoffs, y + 8, V_PERPLAYER|V_SNAPTOBOTTOM|txtflags, va("%d", stplyr->powers[weapon]));
 
 		if (stplyr->currentweapon == wepflag)
-			V_DrawScaledPatch(6 + xoffs, STRINGY(162 - (splitscreen ? 4 : 2)), V_SNAPTOLEFT, curweapon);
+			ST_drawWeaponSelect(xoffs, y);
 	}
 	else if (stplyr->ringweapons & rwflag)
-		V_DrawScaledPatch(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|V_TRANSLUCENT, pat);
+		V_DrawScaledPatch(8 + xoffs, y, V_PERPLAYER|V_SNAPTOBOTTOM|V_TRANSLUCENT, pat);
 }
 
 static void ST_drawMatchHUD(void)
 {
-	INT32 offset = (BASEVIDWIDTH / 2) - (NUM_WEAPONS * 10);
+	const INT32 y = 176; // HUD_LIVES
+	INT32 offset = (BASEVIDWIDTH / 2) - (NUM_WEAPONS * 10) - 6;
 
 	if (!G_RingSlingerGametype())
 		return;
@@ -1658,239 +1843,240 @@ static void ST_drawMatchHUD(void)
 	if (G_TagGametype() && !(stplyr->pflags & PF_TAGIT))
 		return;
 
-#ifdef HAVE_BLUA
-	if (LUA_HudEnabled(hud_weaponrings)) {
-#endif
+	{
+		if (stplyr->powers[pw_infinityring])
+			ST_drawWeaponRing(pw_infinityring, 0, 0, offset, y, infinityring);
+		else
+		{
+			if (stplyr->rings > 0)
+				V_DrawScaledPatch(8 + offset, y, V_PERPLAYER|V_SNAPTOBOTTOM, normring);
+			else
+				V_DrawTranslucentPatch(8 + offset, y, V_PERPLAYER|V_SNAPTOBOTTOM|V_80TRANS, normring);
 
-	if (stplyr->powers[pw_infinityring])
-		ST_drawWeaponRing(pw_infinityring, 0, 0, offset, infinityring);
-	else if (stplyr->rings > 0)
-		V_DrawScaledPatch(8 + offset, STRINGY(162), V_SNAPTOLEFT, normring);
-	else
-		V_DrawTranslucentPatch(8 + offset, STRINGY(162), V_SNAPTOLEFT|V_80TRANS, normring);
-
-	if (!stplyr->currentweapon)
-		V_DrawScaledPatch(6 + offset, STRINGY(162 - (splitscreen ? 4 : 2)), V_SNAPTOLEFT, curweapon);
-
-	offset += 20;
-	ST_drawWeaponRing(pw_automaticring, RW_AUTO, WEP_AUTO, offset, autoring);
-	offset += 20;
-	ST_drawWeaponRing(pw_bouncering, RW_BOUNCE, WEP_BOUNCE, offset, bouncering);
-	offset += 20;
-	ST_drawWeaponRing(pw_scatterring, RW_SCATTER, WEP_SCATTER, offset, scatterring);
-	offset += 20;
-	ST_drawWeaponRing(pw_grenadering, RW_GRENADE, WEP_GRENADE, offset, grenadering);
-	offset += 20;
-	ST_drawWeaponRing(pw_explosionring, RW_EXPLODE, WEP_EXPLODE, offset, explosionring);
-	offset += 20;
-	ST_drawWeaponRing(pw_railring, RW_RAIL, WEP_RAIL, offset, railring);
+			if (!stplyr->currentweapon)
+				ST_drawWeaponSelect(offset, y);
+		}
 
-#ifdef HAVE_BLUA
+		offset += 20;
+		ST_drawWeaponRing(pw_automaticring, RW_AUTO, WEP_AUTO, offset, y, autoring);
+		offset += 20;
+		ST_drawWeaponRing(pw_bouncering, RW_BOUNCE, WEP_BOUNCE, offset, y, bouncering);
+		offset += 20;
+		ST_drawWeaponRing(pw_scatterring, RW_SCATTER, WEP_SCATTER, offset, y, scatterring);
+		offset += 20;
+		ST_drawWeaponRing(pw_grenadering, RW_GRENADE, WEP_GRENADE, offset, y, grenadering);
+		offset += 20;
+		ST_drawWeaponRing(pw_explosionring, RW_EXPLODE, WEP_EXPLODE, offset, y, explosionring);
+		offset += 20;
+		ST_drawWeaponRing(pw_railring, RW_RAIL, WEP_RAIL, offset, y, railring);
 	}
+}
 
-	if (LUA_HudEnabled(hud_powerstones)) {
-#endif
-
-	// Power Stones collected
-	offset = 136; // Used for Y now
-
-	if (stplyr->powers[pw_emeralds] & EMERALD1)
-		V_DrawScaledPatch(28, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[0]);
-
-	offset += 8;
-
-	if (stplyr->powers[pw_emeralds] & EMERALD2)
-		V_DrawScaledPatch(40, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[1]);
-
-	if (stplyr->powers[pw_emeralds] & EMERALD6)
-		V_DrawScaledPatch(16, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[5]);
-
-	offset += 16;
-
-	if (stplyr->powers[pw_emeralds] & EMERALD3)
-		V_DrawScaledPatch(40, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[2]);
-
-	if (stplyr->powers[pw_emeralds] & EMERALD5)
-		V_DrawScaledPatch(16, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[4]);
-
-	offset += 8;
-
-	if (stplyr->powers[pw_emeralds] & EMERALD4)
-		V_DrawScaledPatch(28, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[3]);
-
-	offset -= 16;
-
-	if (stplyr->powers[pw_emeralds] & EMERALD7)
-		V_DrawScaledPatch(28, STRINGY(offset), V_SNAPTOLEFT, tinyemeraldpics[6]);
+static void ST_drawTextHUD(void)
+{
+	INT32 y = 176 - 16; // HUD_LIVES
+	boolean dof12 = false, dospecheader = false;
 
-#ifdef HAVE_BLUA
-	}
-#endif
+#define textHUDdraw(str) \
+{\
+	V_DrawThinString(16, y, V_PERPLAYER|V_HUDTRANS|V_SNAPTOLEFT|V_SNAPTOBOTTOM, str);\
+	y -= 8;\
 }
 
-static inline void ST_drawRaceHUD(void)
-{
-	if (leveltime >= TICRATE && leveltime < 5*TICRATE)
+	if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (!stplyr->spectator))
 	{
-		INT32 height = ((3*BASEVIDHEIGHT)>>2) - 8;
-		INT32 bounce = (leveltime % TICRATE);
-		patch_t *racenum;
-		switch (leveltime/TICRATE)
+		if (leveltime < hidetime * TICRATE)
 		{
-			case 1:
-				racenum = race3;
-				break;
-			case 2:
-				racenum = race2;
-				break;
-			case 3:
-				racenum = race1;
-				break;
-			default:
-				racenum = racego;
-				break;
+			if (stplyr->pflags & PF_TAGIT)
+			{
+				textHUDdraw(M_GetText("Waiting for players to hide..."))
+				textHUDdraw(M_GetText("\x82""You are blindfolded!"))
+			}
+			else if (gametype == GT_HIDEANDSEEK)
+				textHUDdraw(M_GetText("Hide before time runs out!"))
+			else
+				textHUDdraw(M_GetText("Flee before you are hunted!"))
 		}
-		if (bounce < 3)
+		else if (gametype == GT_HIDEANDSEEK && !(stplyr->pflags & PF_TAGIT))
 		{
-			height -= (2 - bounce);
-			if (!bounce)
-					S_StartSound(0, ((racenum == racego) ? sfx_s3kad : sfx_s3ka7));
+			textHUDdraw(M_GetText("You cannot move while hiding."))
+			dof12 = true;
 		}
-		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT(racenum->width))/2), (INT32)(SCY(height)), V_NOSCALESTART, racenum);
 	}
 
-	if (circuitmap)
+	if (!stplyr->spectator && stplyr->exiting && cv_playersforexit.value && gametype == GT_COOP)
 	{
-		if (stplyr->exiting)
-			V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), V_YELLOWMAP, "FINISHED!");
-		else
-			V_DrawString(hudinfo[HUD_LAP].x, STRINGY(hudinfo[HUD_LAP].y), 0, va("Lap: %u/%d", stplyr->laps+1, cv_numlaps.value));
-	}
-}
+		INT32 i, total = 0, exiting = 0;
 
-static void ST_drawTagHUD(void)
-{
-	char pstime[33] = "";
-	char pstext[33] = "";
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (!playeringame[i] || players[i].spectator)
+				continue;
+			if (players[i].lives <= 0)
+				continue;
 
-	// Figure out what we're going to print.
-	if (leveltime < hidetime * TICRATE) //during the hide time, the seeker and hiders have different messages on their HUD.
-	{
-		if (hidetime)
-			sprintf(pstime, "%d", (hidetime - leveltime/TICRATE)); //hide time is in seconds, not tics.
+			total++;
+			if (players[i].exiting)
+				exiting++;
+		}
 
-		if (stplyr->pflags & PF_TAGIT && !stplyr->spectator)
-			sprintf(pstext, "%s", M_GetText("WAITING FOR PLAYERS TO HIDE..."));
-		else
+		if (cv_playersforexit.value != 4)
 		{
-			if (!stplyr->spectator) //spectators get a generic HUD message rather than a gametype specific one.
-			{
-				if (gametype == GT_HIDEANDSEEK) //hide and seek.
-					sprintf(pstext, "%s", M_GetText("HIDE BEFORE TIME RUNS OUT!"));
-				else //default
-					sprintf(pstext, "%s", M_GetText("FLEE BEFORE YOU ARE HUNTED!"));
-			}
-			else
-				sprintf(pstext, "%s", M_GetText("HIDE TIME REMAINING:"));
+			total *= cv_playersforexit.value;
+			if (total & 3)
+				total += 4; // round up
+			total /= 4;
 		}
-	}
-	else
-	{
-		if (cv_timelimit.value && timelimitintics >= leveltime)
-			sprintf(pstime, "%d", (timelimitintics-leveltime)/TICRATE);
 
-		if (stplyr->pflags & PF_TAGIT)
-			sprintf(pstext, "%s", M_GetText("YOU'RE IT!"));
-		else
+		if (exiting < total)
 		{
-			if (cv_timelimit.value)
-				sprintf(pstext, "%s", M_GetText("TIME REMAINING:"));
-			else //Since having no hud message in tag is not characteristic:
-				sprintf(pstext, "%s", M_GetText("NO TIME LIMIT"));
+			total -= exiting;
+			textHUDdraw(va(M_GetText("%d player%s remaining"), total, ((total == 1) ? "" : "s")))
+			dof12 = true;
 		}
 	}
+	else if (gametype != GT_COOP && (stplyr->exiting || (G_GametypeUsesLives() && stplyr->lives <= 0 && countdown != 1)))
+		dof12 = true;
+	else if (!G_PlatformGametype() && stplyr->playerstate == PST_DEAD && stplyr->lives) //Death overrides spectator text.
+	{
+		INT32 respawntime = cv_respawntime.value - stplyr->deadtimer/TICRATE;
 
-	// Print the stuff.
-	if (pstext[0])
+		if (respawntime > 0 && !stplyr->spectator)
+			textHUDdraw(va(M_GetText("Respawn in %d..."), respawntime))
+		else
+			textHUDdraw(M_GetText("\x82""JUMP:""\x80 Respawn"))
+	}
+	else if (stplyr->spectator && (gametype != GT_COOP || stplyr->playerstate == PST_LIVE))
 	{
-		if (splitscreen)
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(168), 0, pstext);
+		if (G_IsSpecialStage(gamemap) && useNightsSS)
+			textHUDdraw(M_GetText("\x82""Wait for the stage to end..."))
+		else if (gametype == GT_COOP)
+		{
+			if (stplyr->lives <= 0
+			&& cv_cooplives.value == 2
+			&& (netgame || multiplayer))
+			{
+				INT32 i;
+				for (i = 0; i < MAXPLAYERS; i++)
+				{
+					if (!playeringame[i])
+						continue;
+
+					if (&players[i] == stplyr)
+						continue;
+
+					if (players[i].lives > 1)
+						break;
+					}
+
+				if (i != MAXPLAYERS)
+					textHUDdraw(M_GetText("You'll steal a life on respawn..."))
+				else
+					textHUDdraw(M_GetText("Wait to respawn..."))
+			}
+			else
+				textHUDdraw(M_GetText("Wait to respawn..."))
+		}
 		else
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(184), 0, pstext);
+			textHUDdraw(M_GetText("\x82""FIRE:""\x80 Enter game"))
+
+		textHUDdraw(M_GetText("\x82""SPIN:""\x80 Lower"))
+		textHUDdraw(M_GetText("\x82""JUMP:""\x80 Rise"))
+
+		dof12 = true;
+		dospecheader = true;
 	}
-	if (pstime[0])
+
+	if (!splitscreen && dof12)
+		textHUDdraw(M_GetText("\x82""F12:""\x80 Switch view"))
+
+	if (circuitmap)
 	{
-		if (splitscreen)
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(184), 0, pstime);
+		if (stplyr->exiting)
+			textHUDdraw(M_GetText("\x82""FINISHED!"))
 		else
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(192), 0, pstime);
+			textHUDdraw(va("Lap:""\x82 %u/%d", stplyr->laps+1, cv_numlaps.value))
 	}
+
+	if (dospecheader)
+		textHUDdraw(M_GetText("\x86""Spectator mode:"))
+
+#undef textHUDdraw
+
 }
 
-static void ST_drawCTFHUD(void)
+static inline void ST_drawRaceHUD(void)
 {
-	INT32 i;
-	UINT16 whichflag = 0;
+	if (leveltime > TICRATE && leveltime <= 5*TICRATE)
+		ST_drawRaceNum(4*TICRATE - leveltime);
+}
 
-	// Draw the flags
-	V_DrawSmallScaledPatch(256, (splitscreen) ? STRINGY(160) : STRINGY(176), V_HUDTRANS, rflagico);
-	V_DrawSmallScaledPatch(288, (splitscreen) ? STRINGY(160) : STRINGY(176), V_HUDTRANS, bflagico);
+static void ST_drawTeamHUD(void)
+{
+	patch_t *p;
+#define SEP 20
 
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (players[i].gotflag & GF_REDFLAG) // Red flag isn't at base
-			V_DrawScaledPatch(256, (splitscreen) ? STRINGY(156) : STRINGY(174), V_HUDTRANS, nonicon);
-		else if (players[i].gotflag & GF_BLUEFLAG) // Blue flag isn't at base
-			V_DrawScaledPatch(288, (splitscreen) ? STRINGY(156) : STRINGY(174), V_HUDTRANS, nonicon);
+	if (gametype == GT_CTF)
+		p = bflagico;
+	else
+		p = bmatcico;
 
-		whichflag |= players[i].gotflag;
-		if ((whichflag & (GF_REDFLAG|GF_BLUEFLAG)) == (GF_REDFLAG|GF_BLUEFLAG))
-			break; // both flags were found, let's stop early
-	}
+	V_DrawSmallScaledPatch(BASEVIDWIDTH/2 - SEP - SHORT(p->width)/4, 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
 
-	// YOU have a flag. Display a monitor-like icon for it.
-	if (stplyr->gotflag)
-	{
-		patch_t *p = (stplyr->gotflag & GF_REDFLAG) ? gotrflag : gotbflag;
+	if (gametype == GT_CTF)
+		p = rflagico;
+	else
+		p = rmatcico;
 
-		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24) + 42, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
-		else
-			V_DrawScaledPatch(304, 24 + 84, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
-	}
+	V_DrawSmallScaledPatch(BASEVIDWIDTH/2 + SEP - SHORT(p->width)/4, 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
 
-	// Display a countdown timer showing how much time left until the flag your team dropped returns to base.
+	if (gametype != GT_CTF)
+		goto num;
 	{
-		char timeleft[33];
-		if (redflag && redflag->fuse > 1)
+		INT32 i;
+		UINT16 whichflag = 0;
+
+		// Show which flags aren't at base.
+		for (i = 0; i < MAXPLAYERS; i++)
 		{
-			sprintf(timeleft, "%u", (redflag->fuse / TICRATE));
-			V_DrawCenteredString(268, STRINGY(184), V_YELLOWMAP|V_HUDTRANS, timeleft);
+			if (players[i].gotflag & GF_BLUEFLAG) // Blue flag isn't at base
+				V_DrawScaledPatch(BASEVIDWIDTH/2 - SEP - SHORT(nonicon->width)/2, 0, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, nonicon);
+			if (players[i].gotflag & GF_REDFLAG) // Red flag isn't at base
+				V_DrawScaledPatch(BASEVIDWIDTH/2 + SEP - SHORT(nonicon2->width)/2, 0, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, nonicon2);
+
+			whichflag |= players[i].gotflag;
+			if ((whichflag & (GF_REDFLAG|GF_BLUEFLAG)) == (GF_REDFLAG|GF_BLUEFLAG))
+				break; // both flags were found, let's stop early
 		}
 
-		if (blueflag && blueflag->fuse > 1)
+		// Display a countdown timer showing how much time left until the flag returns to base.
 		{
-			sprintf(timeleft, "%u", (blueflag->fuse / TICRATE));
-			V_DrawCenteredString(300, STRINGY(184), V_YELLOWMAP|V_HUDTRANS, timeleft);
+			if (blueflag && blueflag->fuse > 1)
+				V_DrawCenteredString(BASEVIDWIDTH/2 - SEP, 8, V_YELLOWMAP|V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", (blueflag->fuse / TICRATE)));
+
+			if (redflag && redflag->fuse > 1)
+				V_DrawCenteredString(BASEVIDWIDTH/2 + SEP, 8, V_YELLOWMAP|V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", (redflag->fuse / TICRATE)));
 		}
 	}
-}
 
-// Draws "Red Team", "Blue Team", or "Spectator" for team gametypes.
-static inline void ST_drawTeamName(void)
-{
-	if (stplyr->ctfteam == 1)
-		V_DrawString(256, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "RED TEAM");
-	else if (stplyr->ctfteam == 2)
-		V_DrawString(248, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "BLUE TEAM");
-	else
-		V_DrawString(244, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "SPECTATOR");
+num:
+	V_DrawCenteredString(BASEVIDWIDTH/2 - SEP, 16, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", bluescore));
+	V_DrawCenteredString(BASEVIDWIDTH/2 + SEP, 16, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, va("%u", redscore));
+
+#undef SEP
 }
 
 static void ST_drawSpecialStageHUD(void)
 {
 	if (totalrings > 0)
-		ST_DrawNumFromHudWS(HUD_SS_TOTALRINGS, totalrings, V_HUDTRANS);
+	{
+		if (hudinfo[HUD_SS_TOTALRINGS].x)
+			ST_DrawNumFromHud(HUD_SS_TOTALRINGS, totalrings, V_HUDTRANS);
+		else if (cv_timetic.value == 2)
+			V_DrawTallNum(hudinfo[HUD_RINGSNUMTICS].x, hudinfo[HUD_SS_TOTALRINGS].y, hudinfo[HUD_RINGSNUMTICS].f|V_PERPLAYER|V_HUDTRANS, totalrings);
+		else
+			V_DrawTallNum(hudinfo[HUD_RINGSNUM].x, hudinfo[HUD_SS_TOTALRINGS].y, hudinfo[HUD_RINGSNUM].f|V_PERPLAYER|V_HUDTRANS, totalrings);
+	}
 
 	if (leveltime < 5*TICRATE && totalrings > 0)
 	{
@@ -1900,7 +2086,7 @@ static void ST_drawSpecialStageHUD(void)
 
 	if (sstimer)
 	{
-		V_DrawString(hudinfo[HUD_TIMELEFT].x, STRINGY(hudinfo[HUD_TIMELEFT].y), V_HUDTRANS, M_GetText("TIME LEFT"));
+		V_DrawString(hudinfo[HUD_TIMELEFT].x, hudinfo[HUD_TIMELEFT].y, hudinfo[HUD_TIMELEFT].f|V_PERPLAYER|V_HUDTRANS, M_GetText("TIME LEFT"));
 		ST_DrawNumFromHud(HUD_TIMELEFTNUM, sstimer/TICRATE, V_HUDTRANS);
 	}
 	else
@@ -1943,7 +2129,7 @@ static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offse
 		interval = 0;
 	}
 
-	V_DrawScaledPatch(hudinfo[HUD_HUNTPICS].x+offset, STRINGY(hudinfo[HUD_HUNTPICS].y), V_HUDTRANS, patches[i]);
+	V_DrawScaledPatch(hudinfo[HUD_HUNTPICS].x+offset, hudinfo[HUD_HUNTPICS].y, hudinfo[HUD_HUNTPICS].f|V_PERPLAYER|V_HUDTRANS, patches[i]);
 	return interval;
 }
 
@@ -2057,12 +2243,13 @@ static void ST_overlayDrawer(void)
 			if (LUA_HudEnabled(hud_rings))
 #endif
 			ST_drawRings();
-			if (G_GametypeUsesLives()
+
+			if (!modeattacking
 #ifdef HAVE_BLUA
 			&& LUA_HudEnabled(hud_lives)
 #endif
 			)
-				ST_drawLives();
+				ST_drawLivesArea();
 		}
 	}
 
@@ -2102,45 +2289,52 @@ static void ST_overlayDrawer(void)
 		}
 
 		if (p)
-			V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, STRINGY(BASEVIDHEIGHT/2 - (SHORT(p->height)/2)) - (splitscreen ? 4 : 0), (stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS), p);
+			V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, BASEVIDHEIGHT/2 - (SHORT(p->height)/2), V_PERPLAYER|(stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS), p);
 	}
 
+	if (G_GametypeHasTeams())
+		ST_drawTeamHUD();
 
 	if (!hu_showscores) // hide the following if TAB is held
 	{
 		// Countdown timer for Race Mode
-		if (countdown)
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(176), 0, va("%d", countdown/TICRATE));
-
-		// If you are in overtime, put a big honkin' flashin' message on the screen.
-		if (G_RingSlingerGametype() && cv_overtime.value
-			&& (leveltime > (timelimitintics + TICRATE/2)) && cv_timelimit.value && (leveltime/TICRATE % 2 == 0))
+		if (countdown > 1)
 		{
-			if (splitscreen)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(168), 0, M_GetText("OVERTIME!"));
+			tic_t time = countdown/TICRATE + 1;
+			if (time < 4)
+				ST_drawRaceNum(countdown);
 			else
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(184), 0, M_GetText("OVERTIME!"));
+			{
+				tic_t num = time;
+				INT32 sz = SHORT(tallnum[0]->width)/2, width = 0;
+				do
+				{
+					width += sz;
+					num /= 10;
+				} while (num);
+				V_DrawTallNum((BASEVIDWIDTH/2) + width, ((3*BASEVIDHEIGHT)>>2) - 7, V_PERPLAYER, time);
+				//V_DrawCenteredString(BASEVIDWIDTH/2, 176, V_PERPLAYER, va("%d", countdown/TICRATE + 1));
+			}
 		}
 
+		// If you are in overtime, put a big honkin' flashin' message on the screen.
+		if (G_RingSlingerGametype() && cv_overtime.value
+		&& (leveltime > (timelimitintics + TICRATE/2)) && cv_timelimit.value && (leveltime/TICRATE % 2 == 0))
+			V_DrawCenteredString(BASEVIDWIDTH/2, 184, V_PERPLAYER, M_GetText("OVERTIME!"));
+
 		// Draw Match-related stuff
 		//\note Match HUD is drawn no matter what gametype.
 		// ... just not if you're a spectator.
-		if (!stplyr->spectator)
+		if (!stplyr->spectator
+#ifdef HAVE_BLUA
+		&& (LUA_HudEnabled(hud_weaponrings))
+#endif
+		)
 			ST_drawMatchHUD();
 
 		// Race HUD Stuff
 		if (gametype == GT_RACE || gametype == GT_COMPETITION)
 			ST_drawRaceHUD();
-		// Tag HUD Stuff
-		else if (gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
-			ST_drawTagHUD();
-		// CTF HUD Stuff
-		else if (gametype == GT_CTF)
-			ST_drawCTFHUD();
-
-		// Team names for team gametypes
-		if (G_GametypeHasTeams())
-			ST_drawTeamName();
 
 		// Special Stage HUD
 		if (!useNightsSS && G_IsSpecialStage(gamemap) && stplyr == &players[displayplayer])
@@ -2152,9 +2346,6 @@ static void ST_overlayDrawer(void)
 		else
 			ST_doHuntIconsAndSound();
 
-		if (stplyr->powers[pw_gravityboots] > 3*TICRATE || (stplyr->powers[pw_gravityboots] && leveltime & 1))
-			V_DrawScaledPatch(hudinfo[HUD_GRAVBOOTSICO].x, STRINGY(hudinfo[HUD_GRAVBOOTSICO].y), V_SNAPTORIGHT, gravboots);
-
 		if(!P_IsLocalPlayer(stplyr))
 		{
 			char name[MAXPLAYERNAME+1];
@@ -2168,10 +2359,14 @@ static void ST_overlayDrawer(void)
 
 		// This is where we draw all the fun cheese if you have the chasecam off!
 		if ((stplyr == &players[displayplayer] && !camera.chase)
-			|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))
+		|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))
 		{
 			ST_drawFirstPersonHUD();
+			if (cv_powerupdisplay.value)
+				ST_drawPowerupHUD();
 		}
+		else if (cv_powerupdisplay.value == 2)
+			ST_drawPowerupHUD();
 	}
 
 #ifdef HAVE_BLUA
@@ -2187,102 +2382,12 @@ static void ST_overlayDrawer(void)
 	)
 		ST_drawLevelTitle();
 
-	if (!hu_showscores && (netgame || multiplayer) && displayplayer == consoleplayer)
-	{
-		if (!stplyr->spectator && stplyr->exiting && cv_playersforexit.value && gametype == GT_COOP)
-		{
-			INT32 i, total = 0, exiting = 0;
-
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i] || players[i].spectator)
-					continue;
-				if (players[i].lives <= 0)
-					continue;
-
-				total++;
-				if (players[i].exiting)
-					exiting++;
-			}
-
-			if (cv_playersforexit.value != 4)
-			{
-				total *= cv_playersforexit.value;
-				if (total % 4) total += 4; // round up
-				total /= 4;
-			}
-
-			if (exiting >= total)
-				;
-			else
-			{
-				total -= exiting;
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(124), 0, va(M_GetText("%d more player%s required to exit."), total, ((total == 1) ? "" : "s")));
-				if (!splitscreen)
-					V_DrawCenteredString(BASEVIDWIDTH/2, 132, 0, M_GetText("Press F12 to watch another player."));
-			}
-		}
-		else if (!splitscreen && gametype != GT_COOP && (stplyr->exiting || (G_GametypeUsesLives() && stplyr->lives <= 0 && countdown != 1)))
-			V_DrawCenteredString(BASEVIDWIDTH/2, 132, 0, M_GetText("Press F12 to watch another player."));
-		else if (gametype == GT_HIDEANDSEEK &&
-		 (!stplyr->spectator && !(stplyr->pflags & PF_TAGIT)) && (leveltime > hidetime * TICRATE))
-		{
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(116), 0, M_GetText("You cannot move while hiding."));
-			if (!splitscreen)
-				V_DrawCenteredString(BASEVIDWIDTH/2, 132, 0, M_GetText("Press F12 to watch another player."));
-		}
-		else if (!G_PlatformGametype() && stplyr->playerstate == PST_DEAD && stplyr->lives) //Death overrides spectator text.
-		{
-			INT32 respawntime = cv_respawntime.value - stplyr->deadtimer/TICRATE;
-			if (respawntime > 0 && !stplyr->spectator)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, va(M_GetText("Respawn in: %d second%s."), respawntime, respawntime == 1 ? "" : "s"));
-			else
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Jump to respawn."));
-		}
-		else if (stplyr->spectator && (gametype != GT_COOP || stplyr->playerstate == PST_LIVE)
+	if (!hu_showscores && (netgame || multiplayer)
 #ifdef HAVE_BLUA
 		&& LUA_HudEnabled(hud_textspectator)
 #endif
-		)
-		{
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(60), V_HUDTRANSHALF, M_GetText("You are a spectator."));
-			if (G_GametypeHasTeams())
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Fire to be assigned to a team."));
-			else if (G_IsSpecialStage(gamemap) && useNightsSS)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("You cannot play until the stage has ended."));
-			else if (gametype == GT_COOP && stplyr->lives <= 0)
-			{
-				if (cv_cooplives.value == 1
-				&& (netgame || multiplayer))
-				{
-					INT32 i;
-					for (i = 0; i < MAXPLAYERS; i++)
-					{
-						if (!playeringame[i])
-							continue;
-
-						if (&players[i] == stplyr)
-							continue;
-
-						if (players[i].lives > 1)
-							break;
-					}
-
-					if (i != MAXPLAYERS)
-						V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132)-(splitscreen ? 12 : 0), V_HUDTRANSHALF, M_GetText("You'll steal a life on respawn."));
-				}
-			}
-			else if (gametype != GT_COOP)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Fire to enter the game."));
-			if (!splitscreen)
-			{
-				V_DrawCenteredString(BASEVIDWIDTH/2, 148, V_HUDTRANSHALF, M_GetText("Press F12 to watch another player."));
-				V_DrawCenteredString(BASEVIDWIDTH/2, 164, V_HUDTRANSHALF, M_GetText("Press Jump to float and Spin to sink."));
-			}
-			else
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(136), V_HUDTRANSHALF, M_GetText("Press Jump to float and Spin to sink."));
-		}
-	}
+	)
+		ST_drawTextHUD();
 
 	if (modeattacking)
 		ST_drawInput();
@@ -2324,6 +2429,22 @@ void ST_Drawer(void)
 #endif
 		if (rendermode != render_none) ST_doPaletteStuff();
 
+	// Blindfold!
+	if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+	&& (leveltime < hidetime * TICRATE))
+	{
+		if (players[displayplayer].pflags & PF_TAGIT)
+		{
+			stplyr = &players[displayplayer];
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31|V_PERPLAYER);
+		}
+		else if (splitscreen && players[secondarydisplayplayer].pflags & PF_TAGIT)
+		{
+			stplyr = &players[secondarydisplayplayer];
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31|V_PERPLAYER);
+		}
+	}
+
 	if (st_overlay)
 	{
 		// No deadview!
diff --git a/src/st_stuff.h b/src/st_stuff.h
index 9be37b50287b2e9d001ac28a394c97b7a13439b6..df0adace3f4a451c2d2120f8410e4552edfb8b0c 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -72,38 +72,28 @@ extern patch_t *ngradeletters[7];
   */
 typedef struct
 {
-	INT32 x, y;
+	INT32 x, y, f;
 } hudinfo_t;
 
 typedef enum
 {
-	HUD_LIVESNAME,
-	HUD_LIVESPIC,
-	HUD_LIVESNUM,
-	HUD_LIVESX,
+	HUD_LIVES,
 
 	HUD_RINGS,
-	HUD_RINGSSPLIT,
 	HUD_RINGSNUM,
-	HUD_RINGSNUMSPLIT,
 	HUD_RINGSNUMTICS,
 
 	HUD_SCORE,
 	HUD_SCORENUM,
 
 	HUD_TIME,
-	HUD_TIMESPLIT,
 	HUD_MINUTES,
-	HUD_MINUTESSPLIT,
 	HUD_TIMECOLON,
-	HUD_TIMECOLONSPLIT,
 	HUD_SECONDS,
-	HUD_SECONDSSPLIT,
 	HUD_TIMETICCOLON,
 	HUD_TICS,
 
 	HUD_SS_TOTALRINGS,
-	HUD_SS_TOTALRINGS_SPLIT,
 
 	HUD_GETRINGS,
 	HUD_GETRINGSNUM,
@@ -111,8 +101,7 @@ typedef enum
 	HUD_TIMELEFTNUM,
 	HUD_TIMEUP,
 	HUD_HUNTPICS,
-	HUD_GRAVBOOTSICO,
-	HUD_LAP,
+	HUD_POWERUPS,
 
 	NUMHUDITEMS
 } hudnum_t;
diff --git a/src/v_video.c b/src/v_video.c
index 5d9b9e841aa909055a4a3c0fe68f0e64766229c3..a27415f1baab1f050fe4d9385c6980da6b697e31 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -15,6 +15,8 @@
 
 #include "doomdef.h"
 #include "r_local.h"
+#include "p_local.h" // stplyr
+#include "g_game.h" // players
 #include "v_video.h"
 #include "hu_stuff.h"
 #include "r_draw.h"
@@ -540,6 +542,8 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 	fixed_t pwidth; // patch width
 	fixed_t offx = 0; // x offset
 
+	UINT8 perplayershuffle = 0;
+
 	if (rendermode == render_none)
 		return;
 
@@ -624,8 +628,74 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 			x -= FixedMul(SHORT(patch->leftoffset)<<FRACBITS, pscale);
 	}
 
-	if (scrn & V_SPLITSCREEN)
-		y>>=1;
+	if (splitscreen && (scrn & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1);
+		fdup >>= 1;
+		rowfrac <<= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1));
+			colfrac <<= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 1;
+				scrn &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle = 2;
+				y += adjusty;
+				scrn &= ~V_SNAPTOTOP;
+			}
+		}
+	}
 
 	desttop = screens[scrn&V_PARAMMASK];
 
@@ -666,16 +736,22 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 					x += (vid.width - (BASEVIDWIDTH * dupx));
 				else if (!(scrn & V_SNAPTOLEFT))
 					x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+				if (perplayershuffle & 4)
+					x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+				else if (perplayershuffle & 8)
+					x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
 			}
 			if (vid.height != BASEVIDHEIGHT * dupy)
 			{
 				// same thing here
-				if ((scrn & (V_SPLITSCREEN|V_SNAPTOBOTTOM)) == (V_SPLITSCREEN|V_SNAPTOBOTTOM))
-					y += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy));
-				else if (scrn & V_SNAPTOBOTTOM)
+				if (scrn & V_SNAPTOBOTTOM)
 					y += (vid.height - (BASEVIDHEIGHT * dupy));
 				else if (!(scrn & V_SNAPTOTOP))
 					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+				if (perplayershuffle & 1)
+					y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+				else if (perplayershuffle & 2)
+					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
 			}
 		}
 
@@ -750,6 +826,8 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 	UINT8 *desttop, *dest;
 	const UINT8 *source, *deststop;
 
+	UINT8 perplayershuffle = 0;
+
 	if (rendermode == render_none)
 		return;
 
@@ -793,6 +871,84 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 	y -= FixedMul(SHORT(patch->topoffset)<<FRACBITS, pscale);
 	x -= FixedMul(SHORT(patch->leftoffset)<<FRACBITS, pscale);
 
+	if (splitscreen && (scrn & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1);
+		fdup >>= 1;
+		rowfrac <<= 1;
+		y >>= 1;
+		sy >>= 1;
+		h >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1));
+			colfrac <<= 1;
+			x >>= 1;
+			sx >>= 1;
+			w >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				sx += adjustx;
+				scrn &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				sy += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				sx += adjustx;
+				y += adjusty;
+				sy += adjusty;
+				scrn &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				scrn &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				sy += adjusty;
+				scrn &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
 	desttop = screens[scrn&V_PARAMMASK];
 
 	if (!desttop)
@@ -831,16 +987,22 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 					x += (vid.width - (BASEVIDWIDTH * dupx));
 				else if (!(scrn & V_SNAPTOLEFT))
 					x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+				if (perplayershuffle & 4)
+					x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+				else if (perplayershuffle & 8)
+					x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
 			}
 			if (vid.height != BASEVIDHEIGHT * dupy)
 			{
 				// same thing here
-				if ((scrn & (V_SPLITSCREEN|V_SNAPTOBOTTOM)) == (V_SPLITSCREEN|V_SNAPTOBOTTOM))
-					y += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy));
-				else if (scrn & V_SNAPTOBOTTOM)
+				if (scrn & V_SNAPTOBOTTOM)
 					y += (vid.height - (BASEVIDHEIGHT * dupy));
 				else if (!(scrn & V_SNAPTOTOP))
 					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+				if (perplayershuffle & 1)
+					y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+				else if (perplayershuffle & 2)
+					y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
 			}
 		}
 
@@ -987,6 +1149,8 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	UINT8 *dest;
 	const UINT8 *deststop;
 
+	UINT8 perplayershuffle = 0;
+
 	if (rendermode == render_none)
 		return;
 
@@ -998,6 +1162,74 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	}
 #endif
 
+	if (splitscreen && (c & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				c &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
 	if (!(c & V_NOSCALESTART))
 	{
 		INT32 dupx = vid.dupx, dupy = vid.dupy;
@@ -1022,6 +1254,10 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 				x += (vid.width - (BASEVIDWIDTH * dupx));
 			else if (!(c & V_SNAPTOLEFT))
 				x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
 		}
 		if (vid.height != BASEVIDHEIGHT * dupy)
 		{
@@ -1030,6 +1266,10 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 				y += (vid.height - (BASEVIDHEIGHT * dupy));
 			else if (!(c & V_SNAPTOTOP))
 				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
 		}
 	}
 
@@ -1165,14 +1405,6 @@ void V_DrawPatchFill(patch_t *pat)
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
 	INT32 x, y, pw = SHORT(pat->width) * dupz, ph = SHORT(pat->height) * dupz;
 
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-	{
-		pw = FixedMul(SHORT(pat->width)*FRACUNIT, vid.fdupx)>>FRACBITS;
-		ph = FixedMul(SHORT(pat->height)*FRACUNIT, vid.fdupy)>>FRACBITS;
-	}
-#endif
-
 	for (x = 0; x < vid.width; x += pw)
 	{
 		for (y = 0; y < vid.height; y += ph)
@@ -1183,25 +1415,34 @@ void V_DrawPatchFill(patch_t *pat)
 //
 // Fade all the screen buffer, so that the menu is more readable,
 // especially now that we use the small hufont in the menus...
+// If color is 0x00 to 0xFF, draw transtable (strength range 0-9).
+// Else, use COLORMAP lump (strength range 0-31).
+// IF YOU ARE NOT CAREFUL, THIS CAN AND WILL CRASH!
+// I have kept the safety checks out of this function;
+// the v.fadeScreen Lua interface handles those.
 //
-void V_DrawFadeScreen(void)
+void V_DrawFadeScreen(UINT16 color, UINT8 strength)
 {
-	const UINT8 *fadetable = (UINT8 *)colormaps + 16*256;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
-	UINT8 *buf = screens[0];
-
 #ifdef HWRENDER
 	if (rendermode != render_soft && rendermode != render_none)
 	{
-		HWR_FadeScreenMenuBack(0x01010160, 0); // hack, 0 means full height
+		HWR_FadeScreenMenuBack(color, strength);
 		return;
 	}
 #endif
 
-	// heavily simplified -- we don't need to know x or y
-	// position when we're doing a full screen fade
-	for (; buf < deststop; ++buf)
-		*buf = fadetable[*buf];
+	{
+		const UINT8 *fadetable = ((color & 0xFF00) // Color is not palette index?
+		? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
+		: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
+		const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+		UINT8 *buf = screens[0];
+
+		// heavily simplified -- we don't need to know x or y
+		// position when we're doing a full screen fade
+		for (; buf < deststop; ++buf)
+			*buf = fadetable[*buf];
+	}
 }
 
 // Simple translucency with one color, over a set number of lines starting from the top.
@@ -1245,20 +1486,36 @@ static const UINT8 *V_GetStringColormap(INT32 colorflags)
 {
 	switch ((colorflags & V_CHARCOLORMASK) >> V_CHARCOLORSHIFT)
 	{
-	case 1: // 0x81, purple
-		return purplemap;
-	case 2: // 0x82, yellow
+	case  1: // 0x81, magenta
+		return magentamap;
+	case  2: // 0x82, yellow
 		return yellowmap;
-	case 3: // 0x83, lgreen
+	case  3: // 0x83, lgreen
 		return lgreenmap;
-	case 4: // 0x84, blue
+	case  4: // 0x84, blue
 		return bluemap;
-	case 5: // 0x85, red
+	case  5: // 0x85, red
 		return redmap;
-	case 6: // 0x86, gray
+	case  6: // 0x86, gray
 		return graymap;
-	case 7: // 0x87, orange
+	case  7: // 0x87, orange
 		return orangemap;
+	case  8: // 0x88, sky
+		return skymap;
+	case  9: // 0x89, purple
+		return purplemap;
+	case 10: // 0x8A, aqua
+		return aquamap;
+	case 11: // 0x8B, peridot
+		return peridotmap;
+	case 12: // 0x8C, azure
+		return azuremap;
+	case 13: // 0x8D, brown
+		return brownmap;
+	case 14: // 0x8E, rosy
+		return rosymap;
+	case 15: // 0x8F, invert
+		return invertmap;
 	default: // reset
 		return NULL;
 	}
diff --git a/src/v_video.h b/src/v_video.h
index 50c927aefe47cdadfd8aefd441dabac638c6d3d9..6113d08ecdc9f1ad1c46868fc24149bfb80c6213 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -73,13 +73,21 @@ extern RGBA_t *pMasterPalette;
 #define V_CHARCOLORSHIFT     12
 #define V_CHARCOLORMASK      0x0000F000
 // for simplicity's sake, shortcuts to specific colors
-#define V_PURPLEMAP          0x00001000
+#define V_MAGENTAMAP         0x00001000
 #define V_YELLOWMAP          0x00002000
 #define V_GREENMAP           0x00003000
 #define V_BLUEMAP            0x00004000
 #define V_REDMAP             0x00005000
 #define V_GRAYMAP            0x00006000
 #define V_ORANGEMAP          0x00007000
+#define V_SKYMAP             0x00008000
+#define V_PURPLEMAP          0x00009000
+#define V_AQUAMAP            0x0000A000
+#define V_PERIDOTMAP         0x0000B000
+#define V_AZUREMAP           0x0000C000
+#define V_BROWNMAP           0x0000D000
+#define V_ROSYMAP            0x0000E000
+#define V_INVERTMAP          0x0000F000
 
 // use bits 17-20 for alpha transparency
 #define V_ALPHASHIFT         16
@@ -112,8 +120,8 @@ extern RGBA_t *pMasterPalette;
 #define V_WRAPX              0x10000000 // Don't clamp texture on X (for HW mode)
 #define V_WRAPY              0x20000000 // Don't clamp texture on Y (for HW mode)
 
-#define V_NOSCALESTART       0x40000000  // don't scale x, y, start coords
-#define V_SPLITSCREEN        0x80000000
+#define V_NOSCALESTART       0x40000000 // don't scale x, y, start coords
+#define V_PERPLAYER          0x80000000 // automatically adjust coordinates/scaling for splitscreen mode
 
 // defines for old functions
 #define V_DrawPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT, s|V_NOSCALESTART|V_NOSCALEPATCH, p, NULL)
@@ -147,7 +155,7 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c);
 void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum);
 
 // fade down the screen buffer before drawing the menu over
-void V_DrawFadeScreen(void);
+void V_DrawFadeScreen(UINT16 color, UINT8 strength);
 
 void V_DrawFadeConsBack(INT32 plines);
 
diff --git a/src/win32/win_dll.c b/src/win32/win_dll.c
index 8fa4d17f73c8857f49b4a3ab0ca9a8ae69ea9381..c9b3fba4eea522aec31a6e956387939cdc7665b0 100644
--- a/src/win32/win_dll.c
+++ b/src/win32/win_dll.c
@@ -117,6 +117,7 @@ static loadfunc_t hwdFuncTable[] = {
 #ifdef SHUFFLE
 	{"PostImgRedraw@4",     &hwdriver.pfnPostImgRedraw},
 #endif
+	{"FlushScreenTextures@0",&hwdriver.pfnFlushScreenTextures},
 	{"StartScreenWipe@0",   &hwdriver.pfnStartScreenWipe},
 	{"EndScreenWipe@0",     &hwdriver.pfnEndScreenWipe},
 	{"DoScreenWipe@4",      &hwdriver.pfnDoScreenWipe},
@@ -147,6 +148,7 @@ static loadfunc_t hwdFuncTable[] = {
 #ifdef SHUFFLE
 	{"PostImgRedraw",       &hwdriver.pfnPostImgRedraw},
 #endif
+	{"FlushScreenTextures"},&hwdriver.pfnFlushScreenTextures},
 	{"StartScreenWipe",     &hwdriver.pfnStartScreenWipe},
 	{"EndScreenWipe",       &hwdriver.pfnEndScreenWipe},
 	{"DoScreenWipe",        &hwdriver.pfnDoScreenWipe},
diff --git a/src/y_inter.c b/src/y_inter.c
index 10cf27369a1c12641a481d9c9064c071715f416a..954048d4ccf4eb44f9fbadc318581996e2d553b9 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -161,27 +161,25 @@ static void Y_FollowIntermission(void);
 static void Y_UnloadData(void);
 
 // Stuff copy+pasted from st_stuff.c
-static INT32 SCX(INT32 x)
-{
-	return FixedInt(FixedMul(x<<FRACBITS, vid.fdupx));
-}
-static INT32 SCY(INT32 z)
-{
-	return FixedInt(FixedMul(z<<FRACBITS, vid.fdupy));
-}
-
-#define ST_DrawNumFromHud(h,n)        V_DrawTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART, n)
-#define ST_DrawPadNumFromHud(h,n,q)   V_DrawPaddedTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART, n, q)
-#define ST_DrawPatchFromHud(h,p)      V_DrawScaledPatch(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART, p)
+#define ST_DrawNumFromHud(h,n)        V_DrawTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n)
+#define ST_DrawPadNumFromHud(h,n,q)   V_DrawPaddedTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n, q)
+#define ST_DrawPatchFromHud(h,p)      V_DrawScaledPatch(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, p)
 
 static void Y_IntermissionTokenDrawer(void)
 {
-	INT32 y;
-	INT32 offs = 0;
+	INT32 y, offs, lowy, calc;
 	UINT32 tokencount;
-	INT32 lowy = BASEVIDHEIGHT - 32;
-	INT16 temp = SHORT(tokenicon->height)/2;
-	INT32 calc;
+	INT16 temp;
+	UINT8 em;
+
+	offs = 0;
+	lowy = BASEVIDHEIGHT - 32 - 8;
+	temp = SHORT(tokenicon->height)/2;
+
+	em = 0;
+	while (emeralds & (1 << em))
+		if (++em == 7)
+			return;
 
 	if (tallydonetic != -1)
 	{
@@ -190,7 +188,7 @@ static void Y_IntermissionTokenDrawer(void)
 			offs = 8;
 	}
 
-	V_DrawFill(32, lowy-1, 16, 1, 31); // slot
+	V_DrawSmallScaledPatch(32, lowy-1, 0, emeraldpics[2][em]); // coinbox
 
 	y = (lowy + offs + 1) - (temp + (token + 1)*8);
 
@@ -255,31 +253,34 @@ void Y_IntermissionDrawer(void)
 		if (gottoken) // first to be behind everything else
 			Y_IntermissionTokenDrawer();
 
-		// draw score
-		ST_DrawPatchFromHud(HUD_SCORE, sboscore);
-		ST_DrawNumFromHud(HUD_SCORENUM, data.coop.score);
-
-		// draw time
-		ST_DrawPatchFromHud(HUD_TIME, sbotime);
-		if (cv_timetic.value == 1)
-			ST_DrawNumFromHud(HUD_SECONDS, data.coop.tics);
-		else
+		if (!splitscreen)
 		{
-			INT32 seconds, minutes, tictrn;
+			// draw score
+			ST_DrawPatchFromHud(HUD_SCORE, sboscore);
+			ST_DrawNumFromHud(HUD_SCORENUM, data.coop.score);
+
+			// draw time
+			ST_DrawPatchFromHud(HUD_TIME, sbotime);
+			if (cv_timetic.value == 1)
+				ST_DrawNumFromHud(HUD_SECONDS, data.coop.tics);
+			else
+			{
+				INT32 seconds, minutes, tictrn;
 
-			seconds = G_TicsToSeconds(data.coop.tics);
-			minutes = G_TicsToMinutes(data.coop.tics, true);
-			tictrn  = G_TicsToCentiseconds(data.coop.tics);
+				seconds = G_TicsToSeconds(data.coop.tics);
+				minutes = G_TicsToMinutes(data.coop.tics, true);
+				tictrn  = G_TicsToCentiseconds(data.coop.tics);
 
-			ST_DrawNumFromHud(HUD_MINUTES, minutes); // Minutes
-			ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
-			ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds
+				ST_DrawNumFromHud(HUD_MINUTES, minutes); // Minutes
+				ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
+				ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds
 
-			// we should show centiseconds on the intermission screen too, if the conditions are right.
-			if (modeattacking || cv_timetic.value == 2)
-			{
-				ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
-				ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
+				// we should show centiseconds on the intermission screen too, if the conditions are right.
+				if (modeattacking || cv_timetic.value == 2)
+				{
+					ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
+					ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
+				}
 			}
 		}
 
@@ -320,7 +321,7 @@ void Y_IntermissionDrawer(void)
 			Y_IntermissionTokenDrawer();
 
 		// draw the header
-		if (intertic <= TICRATE)
+		if (intertic <= 2*TICRATE)
 			animatetic = 0;
 		else if (!animatetic && data.spec.bonus.points == 0 && data.spec.passed3[0] != '\0')
 			animatetic = intertic;
@@ -370,14 +371,64 @@ void Y_IntermissionDrawer(void)
 		}
 
 		// draw the emeralds
-		if (intertic & 1)
+		//if (intertic & 1)
 		{
-			INT32 emeraldx = 80;
+			INT32 emeraldx = 152 - 3*28;
+			INT32 em = (gamemap - sstage_start);
+
 			for (i = 0; i < 7; ++i)
 			{
-				if (emeralds & (1 << i))
-					V_DrawScaledPatch(emeraldx, 74, 0, emeraldpics[i]);
-				emeraldx += 24;
+				if ((i != em) && !(intertic & 1) && (emeralds & (1 << i)))
+					V_DrawScaledPatch(emeraldx, 74, 0, emeraldpics[0][i]);
+				emeraldx += 28;
+			}
+
+			if (em < 7)
+			{
+				static UINT8 emeraldbounces = 0;
+				static INT32 emeraldmomy = 20;
+				static INT32 emeraldy = -40;
+
+				emeraldx = 152 + (em-3)*28;
+
+				if (intertic <= 1)
+				{
+					emeraldbounces = 0;
+					emeraldmomy = 20;
+					emeraldy = -40;
+				}
+				else
+				{
+					if (emeralds & (1 << em))
+					{
+						if (emeraldbounces < 3)
+						{
+							emeraldmomy += 1;
+							emeraldy += emeraldmomy;
+							if (emeraldy > 74)
+							{
+								S_StartSound(NULL, sfx_tink); // tink
+								emeraldbounces++;
+								emeraldmomy = -(emeraldmomy/2);
+								emeraldy = 74;
+							}
+						}
+					}
+					else
+					{
+						emeraldmomy += 1;
+						emeraldy += emeraldmomy;
+						emeraldx += intertic - 6;
+						if (emeraldbounces < 1 && emeraldy > 74)
+						{
+							S_StartSound(NULL, sfx_shldls); // nope
+							emeraldbounces++;
+							emeraldmomy = -(emeraldmomy/2);
+							emeraldy = 74;
+						}
+					}
+					V_DrawScaledPatch(emeraldx, emeraldy, 0, emeraldpics[0][em]);
+				}
 			}
 		}
 
@@ -774,7 +825,7 @@ void Y_Ticker(void)
 		{
 			tallydonetic = intertic;
 			endtic = intertic + 3*TICRATE; // 3 second pause after end of tally
-			S_StartSound(NULL, sfx_chchng); // cha-ching!
+			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
 
 			// Update when done with tally
 			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
@@ -807,7 +858,7 @@ void Y_Ticker(void)
 			tallydonetic = -1;
 		}
 
-		if (intertic < TICRATE) // one second pause before tally begins
+		if (intertic < 2*TICRATE) // TWO second pause before tally begins, thank you mazmazz
 			return;
 
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -819,7 +870,7 @@ void Y_Ticker(void)
 			if ((intertic - tallydonetic) > (3*TICRATE)/2)
 			{
 				endtic = intertic + 4*TICRATE; // 4 second pause after end of tally
-				S_StartSound(NULL, sfx_s3kac); // cha-ching!
+				S_StartSound(NULL, sfx_s3kac); // bingly-bingly-bing!
 			}
 			return;
 		}
@@ -839,7 +890,7 @@ void Y_Ticker(void)
 			if (!(data.spec.continues & 0x80)) // don't set endtic yet!
 				endtic = intertic + 4*TICRATE; // 4 second pause after end of tally
 
-			S_StartSound(NULL, sfx_chchng); // cha-ching!
+			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
 
 			// Update when done with tally
 			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && !demoplayback)
@@ -1767,13 +1818,13 @@ static void Y_AwardCoopBonuses(void)
 				players[i].score = MAXSCORE;
 		}
 
-		ptlives = (!ultimatemode && !modeattacking) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0;
+		ptlives = (!ultimatemode && !modeattacking && players[i].lives != 0x7f) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0;
 		if (ptlives)
 			P_GivePlayerLives(&players[i], ptlives);
 
 		if (i == consoleplayer)
 		{
-			data.coop.gotlife = ptlives;
+			data.coop.gotlife = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? 0 : ptlives);
 			M_Memcpy(&data.coop.bonuses, &localbonuses, sizeof(data.coop.bonuses));
 		}
 	}
@@ -1811,16 +1862,15 @@ static void Y_AwardSpecialStageBonus(void)
 			players[i].score = MAXSCORE;
 
 		// grant extra lives right away since tally is faked
-		ptlives = (!ultimatemode && !modeattacking) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0;
+		ptlives = (!ultimatemode && !modeattacking && players[i].lives != 0x7f) ? max((players[i].score/50000) - (oldscore/50000), 0) : 0;
 		if (ptlives)
 			P_GivePlayerLives(&players[i], ptlives);
 
 		if (i == consoleplayer)
 		{
+			data.spec.gotlife = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? 0 : ptlives);
 			M_Memcpy(&data.spec.bonus, &localbonus, sizeof(data.spec.bonus));
 
-			data.spec.gotlife = ptlives;
-
 			// Continues related
 			data.spec.continues = min(players[i].continues, 8);
 			if (players[i].gotcontinue)
diff --git a/src/z_zone.c b/src/z_zone.c
index a28ea87b035d3e0e7e5c7ca3ae7005aea541524e..b5799b583889f731bc5058699ba74d63bb462ce0 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -82,6 +82,59 @@ typedef struct memblock_s
 	struct memblock_s *next, *prev;
 } ATTRPACK memblock_t;
 
+// both the head and tail of the zone memory block list
+static memblock_t head;
+
+//
+// Function prototypes
+//
+static void Command_Memfree_f(void);
+#ifdef ZDEBUG
+static void Command_Memdump_f(void);
+#endif
+
+// --------------------------
+// Zone memory initialisation
+// --------------------------
+
+/** Initialises zone memory.
+  * Used at game startup.
+  *
+  * \sa I_GetFreeMem, Command_Memfree_f, Command_Memdump_f
+  */
+void Z_Init(void)
+{
+	UINT32 total, memfree;
+
+	memset(&head, 0x00, sizeof(head));
+
+	head.next = head.prev = &head;
+
+	memfree = I_GetFreeMem(&total)>>20;
+	CONS_Printf("System memory: %uMB - Free: %uMB\n", total>>20, memfree);
+
+	// Note: This allocates memory. Watch out.
+	COM_AddCommand("memfree", Command_Memfree_f);
+
+#ifdef ZDEBUG
+	COM_AddCommand("memdump", Command_Memdump_f);
+#endif
+}
+
+
+// ----------------------
+// Zone memory allocation
+// ----------------------
+
+/** Returns the corresponding memblock_t for a given memory block.
+  *
+  * \param ptr A pointer to allocated memory,
+  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
+  * \param func A string containing the name of the function that called this,
+  *              to be printed if the function I_Errors
+  * \return A pointer to the memblock_t for the given memory.
+  * \sa Z_Free, Z_ReallocAlign
+  */
 #ifdef ZDEBUG
 #define Ptr2Memblock(s, f) Ptr2Memblock2(s, f, __FILE__, __LINE__)
 static memblock_t *Ptr2Memblock2(void *ptr, const char* func, const char *file, INT32 line)
@@ -131,32 +184,12 @@ static memblock_t *Ptr2Memblock(void *ptr, const char* func)
 
 }
 
-static memblock_t head;
-
-static void Command_Memfree_f(void);
-#ifdef ZDEBUG
-static void Command_Memdump_f(void);
-#endif
-
-void Z_Init(void)
-{
-	UINT32 total, memfree;
-
-	memset(&head, 0x00, sizeof(head));
-
-	head.next = head.prev = &head;
-
-	memfree = I_GetFreeMem(&total)>>20;
-	CONS_Printf("System memory: %uMB - Free: %uMB\n", total>>20, memfree);
-
-	// Note: This allocates memory. Watch out.
-	COM_AddCommand("memfree", Command_Memfree_f);
-
-#ifdef ZDEBUG
-	COM_AddCommand("memdump", Command_Memdump_f);
-#endif
-}
-
+/** Frees allocated memory.
+  *
+  * \param ptr A pointer to allocated memory,
+  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
+  * \sa Z_FreeTags
+  */
 #ifdef ZDEBUG
 void Z_Free2(void *ptr, const char *file, INT32 line)
 #else
@@ -206,7 +239,11 @@ void Z_Free(void *ptr)
 #endif
 }
 
-// malloc() that doesn't accept failure.
+/** malloc() that doesn't accept failure.
+  *
+  * \param size Amount of memory to be allocated, in bytes.
+  * \return A pointer to the allocated memory.
+  */
 static void *xm(size_t size)
 {
 	const size_t padedsize = size+sizeof (size_t);
@@ -227,10 +264,18 @@ static void *xm(size_t size)
 	return p;
 }
 
-// Z_Malloc
-// You can pass Z_Malloc() a NULL user if the tag is less than
-// PU_PURGELEVEL.
-
+/** The Z_MallocAlign function.
+  * Allocates a block of memory, adds it to a linked list so we can keep track of it.
+  *
+  * \param size Amount of memory to be allocated, in bytes.
+  * \param tag Purge tag.
+  * \param user The address of a pointer to the memory to be allocated.
+  *             When the memory is freed by Z_Free later,
+  *             the pointer at this address will then be automatically set to NULL.
+  * \param alignbits The alignment of the memory to be allocated, in bits. Can be 0.
+  * \note You can pass Z_Malloc() a NULL user if the tag is less than PU_PURGELEVEL.
+  * \sa Z_CallocAlign, Z_ReallocAlign
+  */
 #ifdef ZDEBUG
 void *Z_Malloc2(size_t size, INT32 tag, void *user, INT32 alignbits,
 	const char *file, INT32 line)
@@ -307,6 +352,19 @@ void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 	return given;
 }
 
+/** The Z_CallocAlign function.
+  * Allocates a block of memory, adds it to a linked list so we can keep track of it.
+  * Unlike Z_MallocAlign, this also initialises the bytes to zero.
+  *
+  * \param size Amount of memory to be allocated, in bytes.
+  * \param tag Purge tag.
+  * \param user The address of a pointer to the memory to be allocated.
+  *             When the memory is freed by Z_Free later,
+  *             the pointer at this address will then be automatically set to NULL.
+  * \param alignbits The alignment of the memory to be allocated, in bits. Can be 0.
+  * \note You can pass Z_Calloc() a NULL user if the tag is less than PU_PURGELEVEL.
+  * \sa Z_MallocAlign, Z_ReallocAlign
+  */
 #ifdef ZDEBUG
 void *Z_Calloc2(size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line)
 #else
@@ -323,10 +381,26 @@ void *Z_CallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 #endif
 }
 
+/** The Z_ReallocAlign function.
+  * Reallocates a block of memory with a new size.
+  *
+  * \param ptr A pointer to allocated memory,
+  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
+  *             If NULL, this function instead acts as a wrapper for Z_CallocAlign.
+  * \param size New size of memory block, in bytes.
+  *             If zero, then the memory is freed and NULL is returned.
+  * \param tag New purge tag.
+  * \param user The address of a pointer to the memory to be reallocated.
+  *             This can be a different user to the one originally assigned to the memory block.
+  * \param alignbits The alignment of the memory to be allocated, in bits. Can be 0.
+  * \return A pointer to the reallocated memory. Can be NULL if memory was freed.
+  * \note You can pass Z_Realloc() a NULL user if the tag is less than PU_PURGELEVEL.
+  * \sa Z_MallocAlign, Z_CallocAlign
+  */
 #ifdef ZDEBUG
 void *Z_Realloc2(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line)
 #else
-void *Z_ReallocAlign(void *ptr, size_t size,INT32 tag, void *user,  INT32 alignbits)
+void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits)
 #endif
 {
 	void *rez;
@@ -393,6 +467,11 @@ void *Z_ReallocAlign(void *ptr, size_t size,INT32 tag, void *user,  INT32 alignb
 	return rez;
 }
 
+/** Frees all memory for a given set of tags.
+  *
+  * \param lowtag The lowest tag to consider.
+  * \param hightag The highest tag to consider.
+  */
 void Z_FreeTags(INT32 lowtag, INT32 hightag)
 {
 	memblock_t *block, *next;
@@ -407,22 +486,25 @@ void Z_FreeTags(INT32 lowtag, INT32 hightag)
 	}
 }
 
-//
-// Z_CheckMemCleanup
-//
-// TODO: Currently blocks >= PU_PURGELEVEL are freed every
-// CLEANUPCOUNT. It might be better to keep track of
-// the total size of all purgable memory and free it when the
-// size exceeds some value.
-//
-// This was in Z_Malloc, but was freeing data at
-// unsafe times. Now it is only called when it is safe
-// to cleanup memory.
+// -----------------
+// Utility functions
+// -----------------
 
+// starting value of nextcleanup
 #define CLEANUPCOUNT 2000
 
+// number of function calls left before next cleanup
 static INT32 nextcleanup = CLEANUPCOUNT;
 
+/** This was in Z_Malloc, but was freeing data at
+  * unsafe times. Now it is only called when it is safe
+  * to cleanup memory.
+  *
+  * \todo Currently blocks >= PU_PURGELEVEL are freed every
+  *       CLEANUPCOUNT. It might be better to keep track of
+  *       the total size of all purgable memory and free it when the
+  *       size exceeds some value.
+  */
 void Z_CheckMemCleanup(void)
 {
 	if (nextcleanup-- == 0)
@@ -538,10 +620,21 @@ void Z_CheckHeap(INT32 i)
 	}
 }
 
+// ------------------------
+// Zone memory modification
+// ------------------------
+
+/** Changes a memory block's purge tag.
+  *
+  * \param ptr A pointer to allocated memory,
+  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
+  * \param tag The new tag.
+  * \sa Z_SetUser
+  */
 #ifdef PARANOIA
 void Z_ChangeTag2(void *ptr, INT32 tag, const char *file, INT32 line)
 #else
-void Z_ChangeTag2(void *ptr, INT32 tag)
+void Z_ChangeTag(void *ptr, INT32 tag)
 #endif
 {
 	memblock_t *block;
@@ -583,12 +676,59 @@ void Z_ChangeTag2(void *ptr, INT32 tag)
 	block->tag = tag;
 }
 
+/** Changes a memory block's user.
+  *
+  * \param ptr A pointer to allocated memory,
+  *             assumed to have been allocated with Z_Malloc/Z_Calloc.
+  * \param newuser The new user for the memory block.
+  * \sa Z_ChangeTag
+  */
+#ifdef PARANOIA
+void Z_SetUser2(void *ptr, void **newuser, const char *file, INT32 line)
+#else
+void Z_SetUser(void *ptr, void **newuser)
+#endif
+{
+	memblock_t *block;
+	memhdr_t *hdr;
+
+	if (ptr == NULL)
+		return;
+
+	hdr = (memhdr_t *)((UINT8 *)ptr - sizeof *hdr);
+
+#ifdef VALGRIND_MAKE_MEM_DEFINED
+	VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
+#endif
+
+#ifdef PARANOIA
+	if (hdr->id != ZONEID) I_Error("Z_CT at %s:%d: wrong id", file, line);
+#endif
+
+	block = hdr->block;
+
+#ifdef VALGRIND_MAKE_MEM_NOACCESS
+	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
+#endif
+
+	if (block->tag >= PU_PURGELEVEL && newuser == NULL)
+		I_Error("Internal memory management error: "
+			"tried to make block purgable but it has no owner");
+
+	block->user = (void*)newuser;
+	*newuser = ptr;
+}
+
+// -----------------
+// Zone memory usage
+// -----------------
+
 /** Calculates memory usage for a given set of tags.
+  *
   * \param lowtag The lowest tag to consider.
   * \param hightag The highest tag to consider.
   * \return Number of bytes currently allocated in the heap for the
   *         given tags.
-  * \sa Z_TagUsage
   */
 size_t Z_TagsUsage(INT32 lowtag, INT32 hightag)
 {
@@ -605,18 +745,20 @@ size_t Z_TagsUsage(INT32 lowtag, INT32 hightag)
 	return cnt;
 }
 
-size_t Z_TagUsage(INT32 tagnum)
-{
-	return Z_TagsUsage(tagnum, tagnum);
-}
+// -----------------------
+// Miscellaneous functions
+// -----------------------
 
-void Command_Memfree_f(void)
+/** The function called by the "memfree" console command.
+  * Prints the memory being used by each part of the game to the console.
+  */
+static void Command_Memfree_f(void)
 {
 	UINT32 freebytes, totalbytes;
 
 	Z_CheckHeap(-1);
 	CONS_Printf("\x82%s", M_GetText("Memory Info\n"));
-	CONS_Printf(M_GetText("Total heap used   : %7s KB\n"), sizeu1(Z_TagsUsage(0, INT32_MAX)>>10));
+	CONS_Printf(M_GetText("Total heap used   : %7s KB\n"), sizeu1(Z_TotalUsage()>>10));
 	CONS_Printf(M_GetText("Static            : %7s KB\n"), sizeu1(Z_TagUsage(PU_STATIC)>>10));
 	CONS_Printf(M_GetText("Static (sound)    : %7s KB\n"), sizeu1(Z_TagUsage(PU_SOUND)>>10));
 	CONS_Printf(M_GetText("Static (music)    : %7s KB\n"), sizeu1(Z_TagUsage(PU_MUSIC)>>10));
@@ -644,6 +786,11 @@ void Command_Memfree_f(void)
 }
 
 #ifdef ZDEBUG
+/** The function called by the "memdump" console command.
+  * Prints zone memory debugging information (i.e. tag, size, location in code allocated).
+  * Can be all memory allocated in game, or between a set of tags (if -min/-max args used).
+  * This command is available only if ZDEBUG is enabled.
+  */
 static void Command_Memdump_f(void)
 {
 	memblock_t *block;
@@ -665,45 +812,12 @@ static void Command_Memdump_f(void)
 }
 #endif
 
-// Creates a copy of a string.
+/** Creates a copy of a string.
+  *
+  * \param s The string to be copied.
+  * \return A copy of the string, allocated in zone memory.
+  */
 char *Z_StrDup(const char *s)
 {
 	return strcpy(ZZ_Alloc(strlen(s) + 1), s);
 }
-
-
-#ifdef PARANOIA
-void Z_SetUser2(void *ptr, void **newuser, const char *file, INT32 line)
-#else
-void Z_SetUser2(void *ptr, void **newuser)
-#endif
-{
-	memblock_t *block;
-	memhdr_t *hdr;
-
-	if (ptr == NULL)
-		return;
-
-	hdr = (memhdr_t *)((UINT8 *)ptr - sizeof *hdr);
-
-#ifdef VALGRIND_MAKE_MEM_DEFINED
-	VALGRIND_MAKE_MEM_DEFINED(hdr, sizeof *hdr);
-#endif
-
-#ifdef PARANOIA
-	if (hdr->id != ZONEID) I_Error("Z_CT at %s:%d: wrong id", file, line);
-#endif
-
-	block = hdr->block;
-
-#ifdef VALGRIND_MAKE_MEM_NOACCESS
-	VALGRIND_MAKE_MEM_NOACCESS(hdr, sizeof *hdr);
-#endif
-
-	if (block->tag >= PU_PURGELEVEL && newuser == NULL)
-		I_Error("Internal memory management error: "
-			"tried to make block purgable but it has no owner");
-
-	block->user = (void*)newuser;
-	*newuser = ptr;
-}
diff --git a/src/z_zone.h b/src/z_zone.h
index 552fd87baeb8556611cf38e8c2eec4b24f44a188..205c9ed791060b8e312d71554817b324a521e107 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -30,92 +30,115 @@
 //#define ZDEBUG
 
 //
-// ZONE MEMORY
-// PU - purge tags.
-// Tags < PU_LEVEL are not purged until freed explicitly.
-#define PU_STATIC               1 // static entire execution time
-#define PU_LUA                  2 // static entire execution time -- used by lua so it doesn't get caught in loops forever
-
-#define PU_SOUND               11 // static while playing
-#define PU_MUSIC               12 // static while playing
-#define PU_HUDGFX              13 // static until WAD added
-
-#define PU_HWRPATCHINFO        21 // Hardware GLPatch_t struct for OpenGL texture cache
-#define PU_HWRPATCHCOLMIPMAP   22 // Hardware GLMipmap_t struct colromap variation of patch
-
-#define PU_HWRCACHE            48 // static until unlocked
-#define PU_CACHE               49 // static until unlocked
-
-// Tags s.t. PU_LEVEL <= tag < PU_PURGELEVEL are purged at level start
-#define PU_LEVEL               50 // static until level exited
-#define PU_LEVSPEC             51 // a special thinker in a level
-#define PU_HWRPLANE            52
-
-// Tags >= PU_PURGELEVEL are purgable whenever needed
-#define PU_PURGELEVEL         100
-#define PU_CACHE_UNLOCKED     101
-#define PU_HWRCACHE_UNLOCKED  102 // 'second-level' cache for graphics
-                                  // stored in hardware format and downloaded as needed
-#define PU_HWRPATCHINFO_UNLOCKED 103
+// Purge tags
+//
+// Now they are an enum! -- Monster Iestyn 15/02/18
+//
+enum
+{
+	// Tags < PU_LEVEL are not purged until freed explicitly.
+	PU_STATIC                = 1, // static entire execution time
+	PU_LUA                   = 2, // static entire execution time -- used by lua so it doesn't get caught in loops forever
+
+	PU_SOUND                 = 11, // static while playing
+	PU_MUSIC                 = 12, // static while playing
+	PU_HUDGFX                = 13, // static until WAD added
+
+	PU_HWRPATCHINFO          = 21, // Hardware GLPatch_t struct for OpenGL texture cache
+	PU_HWRPATCHCOLMIPMAP     = 22, // Hardware GLMipmap_t struct colormap variation of patch
+
+	PU_HWRCACHE              = 48, // static until unlocked
+	PU_CACHE                 = 49, // static until unlocked
+
+	// Tags s.t. PU_LEVEL <= tag < PU_PURGELEVEL are purged at level start
+	PU_LEVEL                 = 50, // static until level exited
+	PU_LEVSPEC               = 51, // a special thinker in a level
+	PU_HWRPLANE              = 52, // if ZPLANALLOC is enabled in hw_bsp.c, this is used to alloc polygons for OpenGL
+
+	// Tags >= PU_PURGELEVEL are purgable whenever needed
+	PU_PURGELEVEL            = 100, // Note: this is never actually used as a tag
+	PU_CACHE_UNLOCKED        = 101, // Note: unused
+	PU_HWRCACHE_UNLOCKED     = 102, // 'unlocked' PU_HWRCACHE memory:
+									// 'second-level' cache for graphics
+                                    // stored in hardware format and downloaded as needed
+	PU_HWRPATCHINFO_UNLOCKED = 103, // 'unlocked' PU_HWRPATCHINFO memory
+};
 
+//
+// Zone memory initialisation
+//
 void Z_Init(void);
-void Z_FreeTags(INT32 lowtag, INT32 hightag);
-void Z_CheckMemCleanup(void);
-void Z_CheckHeap(INT32 i);
-#ifdef PARANOIA
-void Z_ChangeTag2(void *ptr, INT32 tag, const char *file, INT32 line);
-#else
-void Z_ChangeTag2(void *ptr, INT32 tag);
-#endif
 
-#ifdef PARANOIA
-void Z_SetUser2(void *ptr, void **newuser, const char *file, INT32 line);
-#else
-void Z_SetUser2(void *ptr, void **newuser);
-#endif
+//
+// Zone memory allocation
+//
+// enable ZDEBUG to get the file + line the functions were called from
+// for ZZ_Alloc, see doomdef.h
+//
 
+// Z_Free and alloc with alignment
 #ifdef ZDEBUG
-#define Z_Free(p) Z_Free2(p, __FILE__, __LINE__)
+#define Z_Free(p)                 Z_Free2(p, __FILE__, __LINE__)
+#define Z_MallocAlign(s,t,u,a)    Z_Malloc2(s, t, u, a, __FILE__, __LINE__)
+#define Z_CallocAlign(s,t,u,a)    Z_Calloc2(s, t, u, a, __FILE__, __LINE__)
+#define Z_ReallocAlign(p,s,t,u,a) Z_Realloc2(p,s, t, u, a, __FILE__, __LINE__)
 void Z_Free2(void *ptr, const char *file, INT32 line);
-#define Z_Malloc(s,t,u) Z_Malloc2(s, t, u, 0, __FILE__, __LINE__)
-#define Z_MallocAlign(s,t,u,a) Z_Malloc2(s, t, u, a, __FILE__, __LINE__)
 void *Z_Malloc2(size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line) FUNCALLOC(1);
-#define Z_Calloc(s,t,u) Z_Calloc2(s, t, u, 0, __FILE__, __LINE__)
-#define Z_CallocAlign(s,t,u,a) Z_Calloc2(s, t, u, a, __FILE__, __LINE__)
 void *Z_Calloc2(size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line) FUNCALLOC(1);
-#define Z_Realloc(p,s,t,u) Z_Realloc2(p, s, t, u, 0, __FILE__, __LINE__)
-#define Z_ReallocAlign(p,s,t,u,a) Z_Realloc2(p,s, t, u, a, __FILE__, __LINE__)
 void *Z_Realloc2(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits, const char *file, INT32 line) FUNCALLOC(2);
 #else
 void Z_Free(void *ptr);
 void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALLOC(1);
-#define Z_Malloc(s,t,u) Z_MallocAlign(s, t, u, 0)
 void *Z_CallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALLOC(1);
-#define Z_Calloc(s,t,u) Z_CallocAlign(s, t, u, 0)
-void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALLOC(2) ;
-#define Z_Realloc(p, s,t,u) Z_ReallocAlign(p, s, t, u, 0)
+void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignbits) FUNCALLOC(2);
 #endif
 
-size_t Z_TagUsage(INT32 tagnum);
-size_t Z_TagsUsage(INT32 lowtag, INT32 hightag);
+// Alloc with no alignment
+#define Z_Malloc(s,t,u)    Z_MallocAlign(s, t, u, 0)
+#define Z_Calloc(s,t,u)    Z_CallocAlign(s, t, u, 0)
+#define Z_Realloc(p,s,t,u) Z_ReallocAlign(p, s, t, u, 0)
 
-char *Z_StrDup(const char *in);
+// Free all memory by tag
+// these don't give line numbers for ZDEBUG currently though
+// (perhaps this should be changed in future?)
+#define Z_FreeTag(tagnum) Z_FreeTags(tagnum, tagnum)
+void Z_FreeTags(INT32 lowtag, INT32 hightag);
 
-// This is used to get the local FILE : LINE info from CPP
-// prior to really call the function in question.
+//
+// Utility functions
+//
+void Z_CheckMemCleanup(void);
+void Z_CheckHeap(INT32 i);
+
+//
+// Zone memory modification
+//
+// enable PARANOIA to get the file + line the functions were called from
 //
 #ifdef PARANOIA
 #define Z_ChangeTag(p,t) Z_ChangeTag2(p, t, __FILE__, __LINE__)
+#define Z_SetUser(p,u)   Z_SetUser2(p, u, __FILE__, __LINE__)
+void Z_ChangeTag2(void *ptr, INT32 tag, const char *file, INT32 line);
+void Z_SetUser2(void *ptr, void **newuser, const char *file, INT32 line);
 #else
-#define Z_ChangeTag(p,t) Z_ChangeTag2(p, t)
+void Z_ChangeTag(void *ptr, INT32 tag);
+void Z_SetUser(void *ptr, void **newuser);
 #endif
 
-#ifdef PARANOIA
-#define Z_SetUser(p,u) Z_SetUser2(p, u, __FILE__, __LINE__)
-#else
-#define Z_SetUser(p,u) Z_SetUser2(p, u)
-#endif
+//
+// Zone memory usage
+//
+// Note: These give the memory used in bytes,
+// shift down by 10 to convert to KB
+//
+#define Z_TagUsage(tagnum) Z_TagsUsage(tagnum, tagnum)
+size_t Z_TagsUsage(INT32 lowtag, INT32 hightag);
+#define Z_TotalUsage() Z_TagsUsage(0, INT32_MAX)
 
-#define Z_Unlock(p) (void)p
+//
+// Miscellaneous functions
+//
+char *Z_StrDup(const char *in);
+#define Z_Unlock(p) (void)p // TODO: remove this now that NDS code has been removed
 
 #endif