From 7cb77075d178c810e31f0791dc388cd245ea88e6 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Tue, 26 Aug 2014 23:56:30 -0400
Subject: [PATCH] SRB2 2.1.11 release

---
 readme.txt                                    |   2 +-
 src/Makefile.cfg                              |   2 +-
 src/command.c                                 |   8 -
 src/d_clisrv.c                                |  21 +-
 src/d_clisrv.h                                |   1 -
 src/d_main.c                                  |   7 +-
 src/d_netcmd.c                                |  37 +-
 src/doomdef.h                                 |   6 +-
 src/f_wipe.c                                  | 123 ++++--
 src/hardware/hw_main.c                        |  49 ++-
 src/lua_hudlib.c                              |  11 +-
 src/lua_playerlib.c                           |   2 +-
 src/m_cond.c                                  |   6 +-
 src/m_menu.c                                  |  21 +-
 src/m_menu.h                                  |   2 +-
 src/p_inter.c                                 |   1 +
 src/p_map.c                                   |   6 +
 src/p_mobj.c                                  |   3 +
 src/p_setup.c                                 | 384 +++++++-----------
 src/p_setup.h                                 |   1 -
 src/p_spec.c                                  | 141 +++++--
 src/p_tick.c                                  |   3 +-
 src/p_user.c                                  |  45 +-
 .../macosx/Srb2mac.xcodeproj/project.pbxproj  |   4 +-
 .../macosx/Srb2mac.xcodeproj/project.pbxproj  |   4 +-
 src/v_video.c                                 |  13 +-
 26 files changed, 530 insertions(+), 373 deletions(-)

diff --git a/readme.txt b/readme.txt
index 2a34380bb..f96d3823c 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
-Here it is! SRB2 v2.1.10 source code!
+Here it is! SRB2 v2.1.11 source code!
 (why do we keep the version number up to date
 	when everything else in this file is hilariously old?
 	- Inuyasha)
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index 1ea96df92..e4f9290c6 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -386,7 +386,7 @@ OBJDUMP_OPTS?=--wide --source --line-numbers
 LD=$(CC)
 
 ifdef SDL
-	INTERFACE=sdl
+	INTERFACE=sdl2
 	OBJDIR:=$(OBJDIR)/SDL
 endif
 
diff --git a/src/command.c b/src/command.c
index 14c5faae8..baf97cbd1 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1320,14 +1320,6 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
 	if (!var || !var->string || !value || !stricmp(var->string, value))
 		return; // no changes
 
-	// Don't allow skin/color changes in single player
-	if ((var == &cv_skin || var == &cv_playercolor) &&
-		!(cv_debug || devparm) && !(multiplayer || netgame)
-		&& (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
-	{
-		return;
-	}
-
 	if (var->flags & CV_NETVAR)
 	{
 		// send the value of the variable
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 3adba9efd..659dac1d0 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -79,8 +79,6 @@ char motd[254], server_context[8]; // Message of the Day, Unique Context (even w
 
 // server specific vars
 UINT8 playernode[MAXPLAYERS];
-UINT8 consfailcount[MAXPLAYERS];
-UINT8 consfailstatus[MAXPLAYERS];
 
 #ifdef NEWPING
 UINT16 pingmeasurecount = 1;
@@ -944,6 +942,14 @@ static void SV_SendResynch(INT32 node)
 {
 	INT32 i, j;
 
+	if (!nodeingame[node])
+	{
+		// player left during resynch
+		// so obviously we don't need to do any of this anymore
+		resynch_inprogress[node] = false;
+		return;
+	}
+
 	// resynched?
 	if (!resynch_status[node])
 	{
@@ -2213,6 +2219,9 @@ static void CL_RemovePlayer(INT32 playernum)
 		playerpernode[node]--;
 		if (playerpernode[node] <= 0)
 		{
+			// If a resynch was in progress, well, it no longer needs to be.
+			SV_InitResynchVars(playernode[playernum]);
+
 			nodeingame[playernode[playernum]] = false;
 			Net_CloseConnection(playernode[playernum]);
 			ResetNode(node);
@@ -2270,9 +2279,6 @@ static void CL_RemovePlayer(INT32 playernum)
 	if (playernum == displayplayer)
 		displayplayer = consoleplayer; // don't look through someone's view who isn't there
 
-	consfailcount[playernum] = 0;
-	consfailstatus[playernum] = 0;
-
 #ifdef HAVE_BLUA
 	LUA_InvalidatePlayer(&players[playernum]);
 #endif
@@ -2753,8 +2759,13 @@ void SV_ResetServer(void)
 	tictoclear = maketic;
 
 	for (i = 0; i < MAXNETNODES; i++)
+	{
 		ResetNode(i);
 
+		// Make sure resynch status doesn't get carried over!
+		SV_InitResynchVars(i);
+	}
+
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 #ifdef HAVE_BLUA
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 700fb5f67..6bc06f13a 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -496,7 +496,6 @@ SINT8 nametonum(const char *name);
 
 extern char motd[254], server_context[8];
 extern UINT8 playernode[MAXPLAYERS];
-extern UINT8 consfailcount[MAXPLAYERS];
 
 INT32 D_NumPlayers(void);
 void D_ResetTiccmds(void);
diff --git a/src/d_main.c b/src/d_main.c
index 42799b7d3..bf1bc7330 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1091,10 +1091,10 @@ void D_SRB2Main(void)
 
 	// Check MD5s of autoloaded files
 	W_VerifyFileMD5(0, "ac309fb3c7d4b5b685e2cd26beccf0e8"); // srb2.srb/srb2.wad
-	W_VerifyFileMD5(1, "e956466eff2c79f7b1cdefad24761bce"); // zones.dta
-	W_VerifyFileMD5(2, "95a4cdbed287323dd361243f357a5fd2"); // player.dta
+	W_VerifyFileMD5(1, "f39b6c849295e3c81875726e8cc0e2c7"); // zones.dta
+	W_VerifyFileMD5(2, "cfca0f1c73023cbbd8f844f45480f799"); // player.dta
 	W_VerifyFileMD5(3, "85901ad4bf94637e5753d2ac2c03ea26"); // rings.dta
-	W_VerifyFileMD5(4, "01735733412bf68c42f4669e964fc952"); // patch.dta
+	W_VerifyFileMD5(4, "3d6cfc185fd7c195eb934ce593b0248f"); // patch.dta
 	// don't check music.dta because people like to modify it, and it doesn't matter if they do
 	// ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for.
 #endif
@@ -1146,7 +1146,6 @@ void D_SRB2Main(void)
 
 	wipegamestate = gamestate;
 
-	P_InitMapHeaders();
 	savedata.lives = 0; // flag this as not-used
 
 	//------------------------------------------------ COMMAND LINE PARAMS
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 13cf2fed9..5e81a5a55 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -3177,15 +3177,16 @@ static void Command_ListWADS_f(void)
 	INT32 i = numwadfiles;
 	char *tempname;
 	CONS_Printf(M_GetText("There are %d wads loaded:\n"),numwadfiles);
-	for (i--; i; i--)
+	for (i--; i >= 0; i--)
 	{
 		nameonly(tempname = va("%s", wadfiles[i]->filename));
-		if (i >= mainwads)
-			CONS_Printf("   %.2d: %s\n", i, tempname);
+		if (!i)
+			CONS_Printf("\x82 IWAD\x80: %s\n", tempname);
+		else if (i <= mainwads)
+			CONS_Printf("\x82 * %.2d\x80: %s\n", i, tempname);
 		else
-			CONS_Printf("*  %.2d: %s\n", i, tempname);
+			CONS_Printf("   %.2d: %s\n", i, tempname);
 	}
-	CONS_Printf("  IWAD: %s\n", wadfiles[0]->filename);
 }
 
 // =========================================================================
@@ -4026,6 +4027,16 @@ static void Name2_OnChange(void)
   */
 static void Skin_OnChange(void)
 {
+	if (!Playing())
+		return; // do whatever you want
+
+	if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player.
+		&& (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CONTINUING))
+	{
+		CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
+		return;
+	}
+
 	if (CanChangeSkin(consoleplayer) && !P_PlayerMoving(consoleplayer))
 		SendNameAndColor();
 	else
@@ -4042,6 +4053,9 @@ static void Skin_OnChange(void)
   */
 static void Skin2_OnChange(void)
 {
+	if (!Playing() || !splitscreen)
+		return; // do whatever you want
+
 	if (CanChangeSkin(secondarydisplayplayer) && !P_PlayerMoving(secondarydisplayplayer))
 		SendNameAndColor2();
 	else
@@ -4057,6 +4071,16 @@ static void Skin2_OnChange(void)
   */
 static void Color_OnChange(void)
 {
+	if (!Playing())
+		return; // do whatever you want
+
+	if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player.
+		&& (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CONTINUING))
+	{
+		CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
+		return;
+	}
+
 	if (!P_PlayerMoving(consoleplayer))
 	{
 		// Color change menu scrolling fix is no longer necessary
@@ -4076,6 +4100,9 @@ static void Color_OnChange(void)
   */
 static void Color2_OnChange(void)
 {
+	if (!Playing() || !splitscreen)
+		return; // do whatever you want
+
 	if (!P_PlayerMoving(secondarydisplayplayer))
 	{
 		// Color change menu scrolling fix is no longer necessary
diff --git a/src/doomdef.h b/src/doomdef.h
index 06f6ba121..a978e047b 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -144,8 +144,8 @@ extern FILE *logstream;
 #define VERSIONSTRING "Trunk"
 #else
 #define VERSION    201 // Game version
-#define SUBVERSION 10  // more precise version number
-#define VERSIONSTRING "v2.1.10"
+#define SUBVERSION 11  // more precise version number
+#define VERSIONSTRING "v2.1.11"
 #endif
 
 // Modification options
@@ -201,7 +201,7 @@ extern FILE *logstream;
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.1.0 is not version "1".
-#define MODVERSION 15
+#define MODVERSION 16
 
 
 
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 64d5cfeed..4b4ecd7e3 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -148,8 +148,8 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
 		*mask++ = FixedDiv((pcolor->s.red+1)<<FRACBITS, paldiv)>>FRACBITS;
 	}
 
-	fm.xscale = FixedDiv(fm.width<<FRACBITS, vid.width<<FRACBITS);
-	fm.yscale = FixedDiv(fm.height<<FRACBITS, vid.height<<FRACBITS);
+	fm.xscale = FixedDiv(vid.width<<FRACBITS, fm.width<<FRACBITS);
+	fm.yscale = FixedDiv(vid.height<<FRACBITS, fm.height<<FRACBITS);
 	return &fm;
 
 	// Landing point for freeing data -- do this instead of just returning NULL
@@ -172,13 +172,6 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
   */
 static void F_DoWipe(fademask_t *fademask)
 {
-	// wipe screen, start, end
-	UINT8 *w = wipe_scr;
-	const UINT8 *s = wipe_scr_start;
-	const UINT8 *e = wipe_scr_end;
-	UINT8 transval;
-	INT32 x = 0, y = 0;
-
 #ifdef HWRENDER
 	/// \todo Mask wipes for OpenGL
 	if(rendermode != render_soft)
@@ -187,29 +180,99 @@ static void F_DoWipe(fademask_t *fademask)
 		return;
 	}
 #endif
-	// Software mask wipe
-	do
+
+	// Software mask wipe -- optimized; though it might not look like it!
+	// Okay, to save you wondering *how* this is more optimized than the simpler
+	// version that came before it...
+	// ---
+	// The previous code did two FixedMul calls for every single pixel on the
+	// screen, of which there are hundreds of thousands -- if not millions -- of.
+	// This worked fine for smaller screen sizes, but with excessively large
+	// (1920x1200) screens that meant 4 million+ calls out to FixedMul, and that
+	// would take /just/ long enough that fades would start to noticably lag.
+	// ---
+	// This code iterates over the fade mask's pixels instead of the screen's,
+	// and deals with drawing over each rectangular area before it moves on to
+	// the next pixel in the fade mask.  As a result, it's more complex (and might
+	// look a little messy; sorry!) but it simultaneously runs at twice the speed.
+	// In addition, we precalculate all the X and Y positions that we need to draw
+	// from and to, so it uses a little extra memory, but again, helps it run faster.
 	{
-		if (*s != *e)
+		// wipe screen, start, end
+		UINT8       *w = wipe_scr;
+		const UINT8 *s = wipe_scr_start;
+		const UINT8 *e = wipe_scr_end;
+
+		// first pixel for each screen
+		UINT8       *w_base = w;
+		const UINT8 *s_base = s;
+		const UINT8 *e_base = e;
+
+		// mask data, end
+		UINT8       *transtbl;
+		const UINT8 *mask    = fademask->mask;
+		const UINT8 *maskend = mask + fademask->size;
+
+		// rectangle draw hints
+		UINT32 draw_linestart, draw_rowstart;
+		UINT32 draw_lineend,   draw_rowend;
+		UINT32 draw_linestogo, draw_rowstogo;
+
+		// rectangle coordinates, etc.
+		UINT16 scrxpos[fademask->width  + 1];
+		UINT16 scrypos[fademask->height + 1];
+		UINT16 maskx, masky;
+		UINT32 relativepos;
+
+		// ---
+		// Screw it, we do the fixed point math ourselves up front.
+		scrxpos[0] = 0;
+		for (relativepos = 0, maskx = 1; maskx < fademask->width; ++maskx)
+			scrxpos[maskx] = (relativepos += fademask->xscale)>>FRACBITS;
+		scrxpos[fademask->width] = vid.width;
+
+		scrypos[0] = 0;
+		for (relativepos = 0, masky = 1; masky < fademask->height; ++masky)
+			scrypos[masky] = (relativepos += fademask->yscale)>>FRACBITS;
+		scrypos[fademask->height] = vid.height;
+		// ---
+
+		maskx = masky = 0;
+		do
 		{
-			transval = fademask->mask[ // y*width + x
-				(FixedMul(y<<FRACBITS,fademask->yscale)>>FRACBITS)*fademask->width
-				+ (FixedMul(x<<FRACBITS,fademask->xscale)>>FRACBITS)
-			];
-
-			if (transval == 0)
-				*w = *s;
-			else if (transval == 10)
-				*w = *e;
-			else
-				*w = transtables[(*e<<8) + *s + ((9 - transval)<<FF_TRANSSHIFT)];
-		}
-		if (++x >= vid.width)
-		{
-			x = 0;
-			++y;
-		}
-	} while (++w && ++s && ++e && w < wipe_scr + vid.width*vid.height);
+			// pointer to transtable that this mask would use
+			transtbl = transtables + ((9 - *mask)<<FF_TRANSSHIFT);
+			// (ignore that it goes out of bounds if *mask is 0 or 10 --
+			//  it wouldn't be used in those cases anyway)
+
+			draw_rowstart = scrxpos[maskx];
+			draw_rowend   = scrxpos[maskx + 1];
+			draw_linestart = scrypos[masky];
+			draw_lineend   = scrypos[masky + 1];
+
+			// DRAWING LOOP
+			relativepos = (draw_linestart * vid.width) + draw_rowstart;
+			draw_linestogo = draw_lineend - draw_linestart;
+			while (draw_linestogo--)
+			{
+				w = w_base + relativepos;
+				s = s_base + relativepos;
+				e = e_base + relativepos;
+				draw_rowstogo = draw_rowend - draw_rowstart;
+				while (draw_rowstogo--)
+				{
+					if (*s != *e)
+						*w = ((*mask == 0) ? *s : (*mask == 10) ? *e : transtbl[(*e<<8) + *s]);
+					++w, ++s, ++e;
+				}
+				relativepos += vid.width;
+			}
+			// END DRAWING LOOP
+
+			if (++maskx >= fademask->width)
+				++masky, maskx = 0;
+		} while (++mask < maskend);
+	}
 }
 #endif
 
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 9cc4f5625..b9977968e 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -3376,7 +3376,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 		sSurf.FlatColor.s.blue = 0x00;
 		sSurf.FlatColor.s.green = 0x00;
 
-		if (spr->mobj->frame & FF_TRANSMASK || spr->mobj->flags2 & MF2_SHADOW)
+		/*if (spr->mobj->frame & FF_TRANSMASK || spr->mobj->flags2 & MF2_SHADOW)
 		{
 			sector_t *sector = spr->mobj->subsector->sector;
 			UINT8 lightlevel = sector->lightlevel;
@@ -3406,15 +3406,25 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 				sSurf.FlatColor.rgba = HWR_Lighting(lightlevel/2, colormap->rgba, colormap->fadergba, false, true);
 			else
 				sSurf.FlatColor.rgba = HWR_Lighting(lightlevel/2, NORMALFOG, FADEFOG, false, true);
+		}*/
+
+		// shadow is always half as translucent as the sprite itself
+		if (spr->mobj->flags2 & MF2_SHADOW)
+			sSurf.FlatColor.s.alpha = 0x20;
+		else if (spr->mobj->frame & FF_TRANSMASK)
+		{
+			HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &sSurf);
+			sSurf.FlatColor.s.alpha /= 2; //cut alpha in half!
 		}
-			Surf.FlatColor.rgba = NORMALFOG;
+		else
+			sSurf.FlatColor.s.alpha = 0x80; // default
 
 		/// \todo do the test earlier
 		if (!cv_grmd2.value || (md2_models[spr->mobj->sprite].scale < 0.0f) || (md2_models[spr->mobj->sprite].notfound = true) || (md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f) || (md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound = true))
 		{
-			if (0x80 > floorheight/4)
+			if (sSurf.FlatColor.s.alpha > floorheight/4)
 			{
-				sSurf.FlatColor.s.alpha = (UINT8)(0x80 - floorheight/4);
+				sSurf.FlatColor.s.alpha = (UINT8)(sSurf.FlatColor.s.alpha - floorheight/4);
 				HWD.pfnDrawPolygon(&sSurf, swallVerts, 4, PF_Translucent|PF_Modulated|PF_Clip);
 			}
 		}
@@ -4460,6 +4470,12 @@ 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])
+		type = &postimgtype2;
+	else
+		type = &postimgtype;
 
 	{
 		// do we really need to save player (is it not the same)?
@@ -4508,7 +4524,7 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	atransform.anglex = (float)(aimingangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
 	atransform.angley = (float)(viewangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
 
-	if (postimgtype == postimg_flip)
+	if (*type == postimg_flip)
 		atransform.flip = true;
 	else
 		atransform.flip = false;
@@ -4527,7 +4543,7 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	stransform.anglex = 0.0f;
 	stransform.angley = -270.0f;
 
-	if (postimgtype == postimg_flip)
+	if (*type == postimg_flip)
 		stransform.flip = true;
 	else
 		stransform.flip = false;
@@ -4669,11 +4685,17 @@ 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
 
 	FRGBAFloat ClearColor;
 
+	if (splitscreen && player == &players[secondarydisplayplayer])
+		type = &postimgtype2;
+	else
+		type = &postimgtype;
+
 	ClearColor.red = 0.0f;
 	ClearColor.green = 0.0f;
 	ClearColor.blue = 0.0f;
@@ -4732,7 +4754,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	atransform.anglex = (float)(aimingangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
 	atransform.angley = (float)(viewangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
 
-	if (postimgtype == postimg_flip)
+	if (*type == postimg_flip)
 		atransform.flip = true;
 	else
 		atransform.flip = false;
@@ -4751,7 +4773,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	stransform.anglex = 0.0f;
 	stransform.angley = -270.0f;
 
-	if (postimgtype == postimg_flip)
+	if (*type == postimg_flip)
 		stransform.flip = true;
 	else
 		stransform.flip = false;
@@ -5298,6 +5320,13 @@ INT32 HWR_GetTextureUsed(void)
 
 void HWR_DoPostProcessor(player_t *player)
 {
+	postimg_t *type;
+
+	if (splitscreen && player == &players[secondarydisplayplayer])
+		type = &postimgtype2;
+	else
+		type = &postimgtype;
+
 	// Armageddon Blast Flash!
 	// Could this even be considered postprocessor?
 	if (player->flashcount)
@@ -5332,7 +5361,7 @@ void HWR_DoPostProcessor(player_t *player)
 
 #ifdef SHUFFLE
 	// Drunken vision! WooOOooo~
-	if (postimgtype == postimg_water || postimgtype == postimg_heat)
+	if (*type == postimg_water || *type == postimg_heat)
 	{
 		// 10 by 10 grid. 2 coordinates (xy)
 		float v[SCREENVERTS][SCREENVERTS][2];
@@ -5343,7 +5372,7 @@ void HWR_DoPostProcessor(player_t *player)
 		INT32 FREQUENCY;
 
 		// Modifies the wave.
-		if (postimgtype == postimg_water)
+		if (*type == postimg_water)
 		{
 			WAVELENGTH = 20; // Lower is longer
 			AMPLITUDE = 20; // Lower is bigger
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index f388a1ff4..6800a2f98 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -165,12 +165,11 @@ static const char *const camera_opt[] = {
 
 static int lib_getHudInfo(lua_State *L)
 {
-	// arg 1 is empty table with this metatable set
-	lua_Integer mindex = luaL_checkinteger(L,2);
-	hudinfo_t **userdata = lua_newuserdata(L, sizeof(hudinfo_t *));
-	luaL_getmetatable(L, META_HUDINFO);
-	lua_setmetatable(L, -2);
-	*userdata = &hudinfo[mindex];
+	UINT32 i;
+	lua_remove(L, 1);
+
+	i = luaL_checkinteger(L, 1);
+	LUA_PushUserdata(L, &hudinfo[i], META_HUDINFO);
 	return 1;
 }
 
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 2686aed97..7f64fff62 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -635,7 +635,7 @@ static int power_set(lua_State *L)
 	if (hud_running)
 		return luaL_error(L, "Do not alter player_t in HUD rendering code!");
 	powers[p] = i;
-	return 1;
+	return 0;
 }
 
 // #powers -> NUMPOWERS
diff --git a/src/m_cond.c b/src/m_cond.c
index 40f39d663..17f755120 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -512,9 +512,9 @@ emblem_t emblemlocations[MAXEMBLEMS] =
 
 	// FLOODED COVE
 	// ---
-	{0, -1888, -1440, 2448, 52, 'N', SKINCOLOR_ROSEWOOD, 0, "", 0},
-	{ET_NGRADE, 0,0,0,      52, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
-	{ET_NTIME,  0,0,0,      52, 'T', SKINCOLOR_GREY,  90*TICRATE, "", 0},
+	{0, 1824, -1888, 2448, 52, 'N', SKINCOLOR_ROSEWOOD, 0, "", 0},
+	{ET_NGRADE, 0,0,0,     52, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NTIME,  0,0,0,     52, 'T', SKINCOLOR_GREY,  90*TICRATE, "", 0},
 
 
 	// CAVERN FORTRESS
diff --git a/src/m_menu.c b/src/m_menu.c
index 605096465..830b26443 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4452,8 +4452,6 @@ static void M_ReadSavegameInfo(UINT32 slot)
 	savegameinfo[slot].skincolor = READUINT8(save_p);
 	CHECKPOS
 	savegameinfo[slot].skinnum = READUINT8(save_p);
-	strcpy(savegameinfo[slot].playername,
-		skins[savegameinfo[slot].skinnum].name);
 
 	CHECKPOS
 	(void)READINT32(save_p); // Score
@@ -4463,18 +4461,27 @@ static void M_ReadSavegameInfo(UINT32 slot)
 	CHECKPOS
 	savegameinfo[slot].continues = READINT32(save_p); // continues
 
-	if (fake & (1<<10)) {
+	if (fake & (1<<10))
+	{
 		CHECKPOS
 		savegameinfo[slot].botskin = READUINT8(save_p);
-		if (savegameinfo[slot].botskin-1 < numskins)
-			snprintf(savegameinfo[slot].playername, 24, "%s & %s", savegameinfo[slot].playername, skins[savegameinfo[slot].botskin-1].name);
-		else
+		if (savegameinfo[slot].botskin-1 >= numskins)
 			savegameinfo[slot].botskin = 0;
 		CHECKPOS
 		savegameinfo[slot].botcolor = READUINT8(save_p); // because why not.
-	} else
+	}
+	else
 		savegameinfo[slot].botskin = 0;
 
+	if (savegameinfo[slot].botskin)
+		snprintf(savegameinfo[slot].playername, 32, "%s & %s",
+			skins[savegameinfo[slot].skinnum].realname,
+			skins[savegameinfo[slot].botskin-1].realname);
+	else
+		strcpy(savegameinfo[slot].playername, skins[savegameinfo[slot].skinnum].realname);
+
+	savegameinfo[slot].playername[31] = 0;
+
 	// File end marker check
 	CHECKPOS
 	if (READUINT8(save_p) != 0x1d) BADSAVE;
diff --git a/src/m_menu.h b/src/m_menu.h
index 302a7fade..ccb1c1102 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -190,7 +190,7 @@ typedef struct
 // savegame struct for save game menu
 typedef struct
 {
-	char playername[24];
+	char playername[32];
 	char levelname[32];
 	UINT8 actnum;
 	UINT8 skincolor;
diff --git a/src/p_inter.c b/src/p_inter.c
index f66bf12f3..5044b5b81 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2118,6 +2118,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source)
 				case MT_JETTGUNNER:
 				case MT_CRAWLACOMMANDER:
 				case MT_REDBUZZ:
+				case MT_DETON:
 					item = MT_MOUSE;
 					break;
 
diff --git a/src/p_map.c b/src/p_map.c
index fa8b9ed7d..be59af0a8 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -888,6 +888,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			if (tmthing->z + tmthing->height + tmthing->momz >= thing->z
 				&& tmthing->z + tmthing->height + tmthing->momz < thing->z + thing->height
 				&& P_IsObjectOnGround(thing)
+				&& !P_IsObjectOnGround(tmthing) // Don't crush if the monitor is on the ground...
 				&& (tmthing->flags & MF_SOLID))
 			{
 				if (tmthing->flags & (MF_MONITOR|MF_PUSHABLE))
@@ -941,6 +942,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			if (tmthing->z + tmthing->momz <= thing->z + thing->height
 				&& tmthing->z + tmthing->momz > thing->z
 				&& P_IsObjectOnGround(thing)
+				&& !P_IsObjectOnGround(tmthing) // Don't crush if the monitor is on the ground...
 				&& (tmthing->flags & MF_SOLID))
 			{
 				if (tmthing->flags & (MF_MONITOR|MF_PUSHABLE))
@@ -1084,6 +1086,10 @@ static boolean PIT_CheckThing(mobj_t *thing)
 					*momz = -*momz; // Therefore, you should be thrust in the opposite direction, vertically.
 				return false;
 			}
+/*
+			else if ((thing->flags & (MF_SOLID|MF_NOCLIP|MF_PUSHABLE)) == MF_SOLID)
+				return false; // this fixes both monitors and non-pushable solids being walked through on bobbing FOFs... for now!
+*/
 		}
 	}
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 9241e559f..1ee30fbc7 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -672,6 +672,9 @@ void P_ExplodeMissile(mobj_t *mo)
 		explodemo->momx -= (P_Random() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
 		explodemo->momy -= (P_Random() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
 		S_StartSound(explodemo, sfx_cybdth);
+
+		// Hack: Release an animal.
+		P_DamageMobj(mo, NULL, NULL, 10000);
 	}
 
 	mo->flags &= ~MF_MISSILE;
diff --git a/src/p_setup.c b/src/p_setup.c
index bd2b0a478..b3e85966a 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -161,7 +161,7 @@ FUNCNORETURN static ATTRNORETURN void CorruptMapError(const char *msg)
 /** Clears the data from a single map header.
   *
   * \param i Map number to clear header for.
-  * \sa P_ClearMapHeaderInfo, P_LoadMapHeader
+  * \sa P_ClearMapHeaderInfo
   */
 static void P_ClearSingleMapHeaderInfo(INT16 i)
 {
@@ -241,62 +241,6 @@ void P_AllocMapHeader(INT16 i)
 	P_ClearSingleMapHeaderInfo(i + 1);
 }
 
-/** Initializes the map headers.
-  * Only new, Dehacked format map headers (MAPxxD) are loaded here. Old map
-  * headers (MAPxxN) are no longer supported.
-  *
-  * \sa P_ClearMapHeaderInfo, P_LoadMapHeader
-  */
-void P_InitMapHeaders(void)
-{
-	char mapheader[7];
-	lumpnum_t lumpnum;
-	INT32 mapnum;
-
-	// Make sure that the map header is valid for the
-	// initial value of gamemap
-	if(!mapheaderinfo[gamemap-1])
-		P_AllocMapHeader(gamemap-1);
-
-	for (mapnum = 1; mapnum <= NUMMAPS; mapnum++)
-	{
-		strncpy(mapheader, G_BuildMapName(mapnum), 5);
-
-		mapheader[5] = 'D'; // New header
-		mapheader[6] = '\0';
-
-		lumpnum = W_CheckNumForName(mapheader);
-
-		if (!(lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0))
-			DEH_LoadDehackedLump(lumpnum);
-	}
-}
-
-/** Sets up the data in a single map header.
-  *
-  * \param mapnum Map number to load header for.
-  * \sa P_ClearSingleMapHeaderInfo, P_InitMapHeaders
-  */
-static inline void P_LoadMapHeader(INT16 mapnum)
-{
-	char mapheader[7];
-	lumpnum_t lumpnum;
-
-	strncpy(mapheader, G_BuildMapName(mapnum), 5);
-
-	mapheader[5] = 'D'; // New header
-	mapheader[6] = '\0';
-
-	lumpnum = W_CheckNumForName(mapheader);
-
-	if (!(lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0))
-	{
-		DEH_LoadDehackedLump(lumpnum);
-		return;
-	}
-}
-
-
 /** NiGHTS Grades are a special structure,
   * we initialize them here.
   *
@@ -1418,53 +1362,38 @@ static void P_LoadSideDefs2(lumpnum_t lumpnum)
 						{
 							col = msd->toptexture;
 
+							sec->extra_colormap->rgba =
+								(HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0) +
+								(HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8) +
+								(HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16);
+
+							// alpha
 							if (msd->toptexture[7])
-							{
-								sec->extra_colormap->rgba =
-									(HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0) +
-									(HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8) +
-									(HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16) +
-									(ALPHA2INT(col[7]) << 24);
-							}
+								sec->extra_colormap->rgba += (ALPHA2INT(col[7]) << 24);
 							else
-							{
-								sec->extra_colormap->rgba =
-									(HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0) +
-									(HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8) +
-									(HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16) +
-									(25 << 24);
-							}
+								sec->extra_colormap->rgba += (25 << 24);
 						}
 						else
-						{
 							sec->extra_colormap->rgba = 0;
-						}
 
 						if (msd->bottomtexture[0] == '#' && msd->bottomtexture[1] && msd->bottomtexture[2] && msd->bottomtexture[3] && msd->bottomtexture[4] && msd->bottomtexture[5] && msd->bottomtexture[6])
 						{
 							col = msd->bottomtexture;
 
+
+							sec->extra_colormap->fadergba =
+								(HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0) +
+								(HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8) +
+								(HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16);
+
+							// alpha
 							if (msd->bottomtexture[7])
-							{
-								sec->extra_colormap->fadergba =
-									(HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0) +
-									(HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8) +
-									(HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16) +
-									(ALPHA2INT(col[7]) << 24);
-							}
+								sec->extra_colormap->fadergba += (ALPHA2INT(col[7]) << 24);
 							else
-							{
-								sec->extra_colormap->fadergba =
-									(HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0) +
-									(HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8) +
-									(HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16) +
-									(25 << 24);
-							}
+								sec->extra_colormap->fadergba += (25 << 24);
 						}
 						else
-						{
 							sec->extra_colormap->fadergba = 0x19000000;
-						}
 #undef ALPHA2INT
 #undef HEX2INT
 					}
@@ -2278,6 +2207,140 @@ static void P_MakeMapMD5(lumpnum_t maplumpnum, void *dest)
 	M_Memcpy(dest, &resmd5, 16);
 }
 
+static void P_RunLevelScript(const char *scriptname)
+{
+	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_SCRIPTISFILE))
+	{
+		lumpnum_t lumpnum;
+		char newname[9];
+
+		strncpy(newname, scriptname, 8);
+
+		newname[8] = '\0';
+
+		lumpnum = W_CheckNumForName(newname);
+
+		if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0)
+		{
+			CONS_Debug(DBG_SETUP, "SOC Error: script lump %s not found/not valid.\n", newname);
+			return;
+		}
+
+		COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
+	}
+	else
+	{
+		COM_BufAddText(va("exec %s\n", scriptname));
+	}
+	COM_BufExecute(); // Run it!
+}
+
+static void P_ForceCharacter(const char *forcecharskin)
+{
+	if (netgame)
+	{
+		char skincmd[33];
+		if (splitscreen)
+		{
+			sprintf(skincmd, "skin2 %s\n", forcecharskin);
+			CV_Set(&cv_skin2, forcecharskin);
+		}
+
+		sprintf(skincmd, "skin %s\n", forcecharskin);
+		COM_BufAddText(skincmd);
+	}
+	else
+	{
+		if (splitscreen)
+		{
+			SetPlayerSkin(secondarydisplayplayer, forcecharskin);
+			if ((unsigned)cv_playercolor2.value != skins[players[secondarydisplayplayer].skin].prefcolor)
+			{
+				CV_StealthSetValue(&cv_playercolor2, skins[players[secondarydisplayplayer].skin].prefcolor);
+				players[secondarydisplayplayer].skincolor = skins[players[secondarydisplayplayer].skin].prefcolor;
+			}
+		}
+
+		SetPlayerSkin(consoleplayer, forcecharskin);
+		// normal player colors in single player
+		if ((unsigned)cv_playercolor.value != skins[players[consoleplayer].skin].prefcolor)
+		{
+			CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin].prefcolor);
+			players[consoleplayer].skincolor = skins[players[consoleplayer].skin].prefcolor;
+		}
+	}
+}
+
+static void P_LoadRecordGhosts(void)
+{
+	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+	char *gpath = malloc(glen);
+	INT32 i;
+
+	if (!gpath)
+		return;
+
+	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+
+	// Best Score ghost
+	if (cv_ghost_bestscore.value)
+	{
+		for (i = 0; i < numskins; ++i)
+		{
+			if (cv_ghost_bestscore.value == 1 && players[consoleplayer].skin != i)
+				continue;
+
+			if (FIL_FileExists(va("%s-%s-score-best.lmp", gpath, skins[i].name)))
+				G_AddGhost(va("%s-%s-score-best.lmp", gpath, skins[i].name));
+		}
+	}
+
+	// Best Time ghost
+	if (cv_ghost_besttime.value)
+	{
+		for (i = 0; i < numskins; ++i)
+		{
+			if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i)
+				continue;
+
+			if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name)))
+				G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name));
+		}
+	}
+
+	// Best Rings ghost
+	if (cv_ghost_bestrings.value)
+	{
+		for (i = 0; i < numskins; ++i)
+		{
+			if (cv_ghost_bestrings.value == 1 && players[consoleplayer].skin != i)
+				continue;
+
+			if (FIL_FileExists(va("%s-%s-rings-best.lmp", gpath, skins[i].name)))
+				G_AddGhost(va("%s-%s-rings-best.lmp", gpath, skins[i].name));
+		}
+	}
+
+	// Last ghost
+	if (cv_ghost_last.value)
+	{
+		for (i = 0; i < numskins; ++i)
+		{
+			if (cv_ghost_last.value == 1 && players[consoleplayer].skin != i)
+				continue;
+
+			if (FIL_FileExists(va("%s-%s-last.lmp", gpath, skins[i].name)))
+				G_AddGhost(va("%s-%s-last.lmp", gpath, skins[i].name));
+		}
+	}
+
+	// Guest ghost
+	if (cv_ghost_guest.value && FIL_FileExists(va("%s-guest.lmp", gpath)))
+		G_AddGhost(va("%s-guest.lmp", gpath));
+
+	free(gpath);
+}
+
 /** Loads a level from a lump or external wad.
   *
   * \param skipprecip If true, don't spawn precipitation.
@@ -2329,33 +2392,7 @@ boolean P_SetupLevel(boolean skipprecip)
 		P_RunSOC(mapheaderinfo[gamemap-1]->runsoc);
 
 	if (cv_runscripts.value && mapheaderinfo[gamemap-1]->scriptname[0] != '#')
-	{
-		if (!(mapheaderinfo[gamemap-1]->levelflags & LF_SCRIPTISFILE))
-		{
-			lumpnum_t lumpnum;
-			char newname[9];
-
-			strncpy(newname, mapheaderinfo[gamemap-1]->scriptname, 8);
-
-			newname[8] = '\0';
-
-			lumpnum = W_CheckNumForName(newname);
-
-			if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0)
-			{
-				CONS_Debug(DBG_SETUP, "SOC Error: script lump %s not found/not valid.\n", newname);
-				goto noscript;
-			}
-
-			COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
-		}
-		else
-		{
-			COM_BufAddText(va("exec %s\n", mapheaderinfo[gamemap-1]->scriptname));
-		}
-		COM_BufExecute(); // Run it!
-	}
-noscript:
+		P_RunLevelScript(mapheaderinfo[gamemap-1]->scriptname);
 
 	P_LevelInitStuff();
 
@@ -2363,40 +2400,7 @@ noscript:
 
 	if (mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0'
 	&& atoi(mapheaderinfo[gamemap-1]->forcecharacter) != 255)
-	{
-		if (netgame)
-		{
-			char skincmd[33];
-			if (splitscreen)
-			{
-				sprintf(skincmd, "skin2 %s\n", mapheaderinfo[gamemap-1]->forcecharacter);
-				CV_Set(&cv_skin2, mapheaderinfo[gamemap-1]->forcecharacter);
-			}
-
-			sprintf(skincmd, "skin %s\n", mapheaderinfo[gamemap-1]->forcecharacter);
-			COM_BufAddText(skincmd);
-		}
-		else
-		{
-			if (splitscreen)
-			{
-				SetPlayerSkin(secondarydisplayplayer, mapheaderinfo[gamemap-1]->forcecharacter);
-				if ((unsigned)cv_playercolor2.value != skins[players[secondarydisplayplayer].skin].prefcolor)
-				{
-					CV_StealthSetValue(&cv_playercolor2, skins[players[secondarydisplayplayer].skin].prefcolor);
-					players[secondarydisplayplayer].skincolor = skins[players[secondarydisplayplayer].skin].prefcolor;
-				}
-			}
-
-			SetPlayerSkin(consoleplayer, mapheaderinfo[gamemap-1]->forcecharacter);
-			// normal player colors in single player
-			if ((unsigned)cv_playercolor.value != skins[players[consoleplayer].skin].prefcolor)
-			{
-				CV_StealthSetValue(&cv_playercolor, skins[players[consoleplayer].skin].prefcolor);
-				players[consoleplayer].skincolor = skins[players[consoleplayer].skin].prefcolor;
-			}
-		}
-	}
+		P_ForceCharacter(mapheaderinfo[gamemap-1]->forcecharacter);
 
 	// chasecam on in chaos, race, coop
 	// chasecam off in match, tag, capture the flag
@@ -2408,11 +2412,7 @@ noscript:
 
 	if (!dedicated)
 	{
-		if (maptol & TOL_2D)
-		{
-			CV_SetValue(&cv_cam_speed, 0);
-		}
-		else if (!cv_cam_speed.changed && !(maptol & TOL_2D))
+		if (!cv_cam_speed.changed)
 			CV_Set(&cv_cam_speed, cv_cam_speed.defaultvalue);
 
 		if (!cv_chasecam.changed)
@@ -2601,72 +2601,7 @@ noscript:
 		}
 
 	if (modeattacking == ATTACKING_RECORD && !demoplayback)
-	{
-		const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
-		char *gpath = malloc(glen);
-		if (gpath)
-		{
-			sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
-
-			// Best Score ghost
-			if (cv_ghost_bestscore.value)
-			{
-				for (i = 0; i < numskins; ++i)
-				{
-					if (cv_ghost_bestscore.value == 1 && players[consoleplayer].skin != i)
-						continue;
-
-					if (FIL_FileExists(va("%s-%s-score-best.lmp", gpath, skins[i].name)))
-						G_AddGhost(va("%s-%s-score-best.lmp", gpath, skins[i].name));
-				}
-			}
-
-			// Best Time ghost
-			if (cv_ghost_besttime.value)
-			{
-				for (i = 0; i < numskins; ++i)
-				{
-					if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i)
-						continue;
-
-					if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name)))
-						G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name));
-				}
-			}
-
-			// Best Rings ghost
-			if (cv_ghost_bestrings.value)
-			{
-				for (i = 0; i < numskins; ++i)
-				{
-					if (cv_ghost_bestrings.value == 1 && players[consoleplayer].skin != i)
-						continue;
-
-					if (FIL_FileExists(va("%s-%s-rings-best.lmp", gpath, skins[i].name)))
-						G_AddGhost(va("%s-%s-rings-best.lmp", gpath, skins[i].name));
-				}
-			}
-
-			// Last ghost
-			if (cv_ghost_last.value)
-			{
-				for (i = 0; i < numskins; ++i)
-				{
-					if (cv_ghost_last.value == 1 && players[consoleplayer].skin != i)
-						continue;
-
-					if (FIL_FileExists(va("%s-%s-last.lmp", gpath, skins[i].name)))
-						G_AddGhost(va("%s-%s-last.lmp", gpath, skins[i].name));
-				}
-			}
-
-			// Guest ghost
-			if (cv_ghost_guest.value && FIL_FileExists(va("%s-guest.lmp", gpath)))
-				G_AddGhost(va("%s-guest.lmp", gpath));
-
-			free(gpath);
-		}
-	}
+		P_LoadRecordGhosts();
 
 	if (G_TagGametype())
 	{
@@ -2988,17 +2923,14 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 
 		if (name[0] == 'M' && name[1] == 'A' && name[2] == 'P') // Ignore the headers
 		{
+			if (name[5]!='\0')
+				continue;
 			num = (INT16)M_MapNumber(name[3], name[4]);
 
 			//If you replaced the map you're on, end the level when done.
 			if (num == gamemap)
 				replacedcurrentmap = true;
 
-			if (name[5] == 'D')
-				P_LoadMapHeader(num);
-			else if (name[5]!='\0')
-				continue;
-
 			CONS_Printf("%s\n", name);
 		}
 
diff --git a/src/p_setup.h b/src/p_setup.h
index 9cb323360..49df2fc5b 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -64,7 +64,6 @@ boolean P_DelWadFile(void);
 boolean P_RunSOC(const char *socfilename);
 void P_WriteThings(lumpnum_t lump);
 size_t P_PrecacheLevelFlats(void);
-void P_InitMapHeaders(void);
 void P_AllocMapHeader(INT16 i);
 
 // Needed for NiGHTS
diff --git a/src/p_spec.c b/src/p_spec.c
index ad0b397e0..3c9af246d 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -1852,18 +1852,17 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 		}
 	}
 
-	// Special type 308, 307, 302, 304, 315, 318, and 320 only work once
-	if (specialtype == 302 || specialtype == 304 || specialtype == 307 || specialtype == 308 || specialtype == 315 || specialtype == 318 || specialtype == 320)
-	{
+	// These special types work only once
+	if (specialtype == 302  // Once
+	 || specialtype == 304  // Ring count - Once
+	 || specialtype == 307  // Character ability - Once
+	 || specialtype == 308  // Race only - Once
+	 || specialtype == 315  // No of pushables - Once
+	 || specialtype == 318  // Unlockable trigger - Once
+	 || specialtype == 320  // Unlockable - Once
+	 || specialtype == 399) // Level Load
 		triggerline->special = 0; // Clear it out
 
-		// Hmm, I'm thinking that we shouldn't touch the sector special, incase
-		// we have continuous executors associated with it, too?
-		/*
-		if (caller && (GETSECSPECIAL(caller->special, 2) >= 1 && GETSECSPECIAL(caller->special, 2) <= 7))
-			caller->special = (UINT16)(caller->special-(GETSECSPECIAL(caller->special, 2) << 4)); // Only remove the relevant section
-			*/
-	}
 	return true;
 }
 
@@ -1893,13 +1892,14 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
 		if (lines[masterline].tag != tag)
 			continue;
 
-		// "No More Enemies" takes care of itself.
+		// "No More Enemies" and "Level Load" take care of themselves.
 		if (lines[masterline].special == 313
-			// Each-time exectors handle themselves, too
-			|| lines[masterline].special == 301
-			|| lines[masterline].special == 306
-			|| lines[masterline].special == 310
-			|| lines[masterline].special == 312)
+		 || lines[masterline].special == 399
+		 // Each-time executors handle themselves, too
+		 || lines[masterline].special == 301 // Each time
+		 || lines[masterline].special == 306 // Character ability - Each time
+		 || lines[masterline].special == 310 // CTF Red team - Each time
+		 || lines[masterline].special == 312) // CTF Blue team - Each time
 			continue;
 
 		if (lines[masterline].special < 300
@@ -3323,12 +3323,79 @@ sector_t *P_PlayerTouchingSectorSpecial(player_t *player, INT32 section, INT32 n
 }
 
 //
-// P_ShouldObjectBeDamaged
+// P_ThingIsOnThe3DFloor
 //
-// Checks for conditions that a mobj should be damaged by
-// floor touch/anywhere-in-FOF damage specials.
+// This checks whether the mobj is on/in the FOF we want it to be at
+// Needed for the "All players" trigger sector specials only
 //
-#define P_ShouldObjectBeDamaged(mo,roversector) (roversector || P_IsObjectOnGround(mo))
+static boolean P_ThingIsOnThe3DFloor(mobj_t *mo, sector_t *sector, sector_t *targetsec)
+{
+	ffloor_t *rover;
+
+	if (!mo->player) // should NEVER happen
+		return false;
+
+	if (!targetsec->ffloors) // also should NEVER happen
+		return false;
+
+	for (rover = targetsec->ffloors; rover; rover = rover->next)
+	{
+		if (rover->master->frontsector != sector)
+			continue;
+
+		// we're assuming the FOF existed when the first player touched it
+		//if (!(rover->flags & FF_EXISTS))
+		//	return false;
+
+		// Check the 3D floor's type...
+		if (rover->flags & FF_BLOCKPLAYER)
+		{
+			// Thing must be on top of the floor to be affected...
+			if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
+				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
+			{
+				if ((mo->eflags & MFE_VERTICALFLIP) || mo->z != *rover->topheight)
+					return false;
+			}
+			else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
+				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
+			{
+				if (!(mo->eflags & MFE_VERTICALFLIP)
+					|| mo->z + mo->height != *rover->bottomheight)
+					return false;
+			}
+			else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
+			{
+				if (!((mo->eflags & MFE_VERTICALFLIP && mo->z + mo->height == *rover->bottomheight)
+					|| (!(mo->eflags & MFE_VERTICALFLIP) && mo->z == *rover->topheight)))
+					return false;
+			}
+		}
+		else
+		{
+			// Water and intangible FOFs
+			if (mo->z > *rover->topheight || (mo->z + mo->height) < *rover->bottomheight)
+				return false;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+//
+// P_MobjReadyToTrigger
+//
+// Is player standing on the sector's "ground"?
+//
+static inline boolean P_MobjReadyToTrigger(mobj_t *mo, sector_t *sec)
+{
+	if (mo->eflags & MFE_VERTICALFLIP)
+		return (mo->z+mo->height == sec->ceilingheight);
+	else
+		return (mo->z == sec->floorheight);
+}
 
 /** Applies a sector special to a player.
   *
@@ -3370,19 +3437,19 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 	switch (special)
 	{
 		case 1: // Damage (Generic)
-			if (P_ShouldObjectBeDamaged(player->mo, roversector))
+			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
 				P_DamageMobj(player->mo, NULL, NULL, 1);
 			break;
 		case 2: // Damage (Water)
-			if (P_ShouldObjectBeDamaged(player->mo, roversector) && (player->powers[pw_underwater] || player->pflags & PF_NIGHTSMODE) && (player->powers[pw_shield] & SH_NOSTACK) != SH_ELEMENTAL)
+			if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_underwater] || player->pflags & PF_NIGHTSMODE) && (player->powers[pw_shield] & SH_NOSTACK) != SH_ELEMENTAL)
 				P_DamageMobj(player->mo, NULL, NULL, 1);
 			break;
 		case 3: // Damage (Fire)
-			if (P_ShouldObjectBeDamaged(player->mo, roversector) && (player->powers[pw_shield] & SH_NOSTACK) != SH_ELEMENTAL)
+			if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_shield] & SH_NOSTACK) != SH_ELEMENTAL)
 				P_DamageMobj(player->mo, NULL, NULL, 1);
 			break;
 		case 4: // Damage (Electrical)
-			if (P_ShouldObjectBeDamaged(player->mo, roversector) && (player->powers[pw_shield] & SH_NOSTACK) != SH_ATTRACT)
+			if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_shield] & SH_NOSTACK) != SH_ATTRACT)
 				P_DamageMobj(player->mo, NULL, NULL, 1);
 			break;
 		case 5: // Spikes
@@ -3390,7 +3457,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			break;
 		case 6: // Death Pit (Camera Mod)
 		case 7: // Death Pit (No Camera Mod)
-			if (P_ShouldObjectBeDamaged(player->mo, roversector))
+			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
 				P_DamageMobj(player->mo, NULL, NULL, 10000);
 			break;
 		case 8: // Instant Kill
@@ -3451,10 +3518,26 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			break;
 		case 2: // Linedef executor requires all players present+doesn't require touching floor
 		case 3: // Linedef executor requires all players present
-			/// \todo take FOFs into account (+continues for proper splitscreen support?)
+			/// \todo check continues for proper splitscreen support?
 			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i] && !players[i].bot && players[i].mo && (gametype != GT_COOP || players[i].lives > 0) && !(P_PlayerTouchingSectorSpecial(&players[i], 2, 3) == sector || P_PlayerTouchingSectorSpecial(&players[i], 2, 2) == sector))
-					goto DoneSection2;
+				if (playeringame[i] && !players[i].bot && players[i].mo && (gametype != GT_COOP || players[i].lives > 0))
+				{
+					if (roversector)
+					{
+						if (players[i].mo->subsector->sector != roversector)
+							goto DoneSection2;
+						if (!P_ThingIsOnThe3DFloor(players[i].mo, sector, roversector))
+							goto DoneSection2;
+					}
+					else
+					{
+						if (players[i].mo->subsector->sector != sector)
+							goto DoneSection2;
+
+						if (special == 3 && !P_MobjReadyToTrigger(players[i].mo, sector))
+							goto DoneSection2;
+					}
+				}
 		case 4: // Linedef executor that doesn't require touching floor
 		case 5: // Linedef executor
 		case 6: // Linedef executor (7 Emeralds)
@@ -5280,7 +5363,7 @@ static void P_RunLevelLoadExecutors(void)
 	for (i = 0; i < numlines; i++)
 	{
 		if (lines[i].special == 399)
-			P_LinedefExecute(lines[i].tag, NULL, NULL);
+			P_RunTriggerLinedef(&lines[i], NULL, NULL);
 	}
 }
 
diff --git a/src/p_tick.c b/src/p_tick.c
index c2610680d..9a0f80165 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -168,7 +168,8 @@ void Command_CountMobjs_f(void)
 				count++;
 		}
 
-		CONS_Printf(" * %d: %d\n", i, count);
+		if (count > 0) // Don't bother displaying if there are none of this type!
+			CONS_Printf(" * %d: %d\n", i, count);
 	}
 }
 
diff --git a/src/p_user.c b/src/p_user.c
index a8bc63522..a31666c9f 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -988,8 +988,8 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 	{
 		player->powers[pw_extralife] = 0;
 		player->powers[pw_invulnerability] = 0;
+		player->powers[pw_sneakers] = 0;
 	}
-	player->powers[pw_sneakers] = 0;
 
 	if (gametype != GT_COOP)
 	{
@@ -4009,7 +4009,7 @@ static boolean P_AnalogMove(player_t *player)
 //
 // Determines if the player is pressing in the direction they are moving
 //
-// 0 = no controls pressed
+// 0 = no controls pressed/no movement
 // 1 = pressing in the direction of movement
 // 2 = pressing in the opposite direction of movement
 //
@@ -4030,7 +4030,19 @@ INT32 P_GetPlayerControlDirection(player_t *player)
 	if (!cmd->forwardmove && !cmd->sidemove)
 		return 0;
 
-	if (P_AnalogMove(player) && thiscam->chase)
+	if (!player->mo->momx && !player->mo->momy)
+		return 0;
+
+	if (twodlevel || player->mo->flags2 & MF2_TWOD)
+	{
+		if (!cmd->sidemove)
+			return 0;
+		if (!player->mo->momx)
+			return 0;
+		origtempangle = tempangle = 0; // relative to the axis rather than the player!
+		controlplayerdirection = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
+	}
+	else if (P_AnalogMove(player) && thiscam->chase)
 	{
 		if (player->awayviewtics)
 			origtempangle = tempangle = player->awayviewmobj->angle;
@@ -4047,11 +4059,14 @@ INT32 P_GetPlayerControlDirection(player_t *player)
 	// Calculate the angle at which the controls are pointing
 	// to figure out the proper mforward and mbackward.
 	tempangle >>= ANGLETOFINESHIFT;
-	tempx += FixedMul(cmd->forwardmove*FRACUNIT,FINECOSINE(tempangle));
-	tempy += FixedMul(cmd->forwardmove*FRACUNIT,FINESINE(tempangle));
+	if (!(twodlevel || player->mo->flags2 & MF2_TWOD)) // in 2d mode, sidemove is treated as the forwards/backwards direction
+	{
+		tempx += FixedMul(cmd->forwardmove*FRACUNIT,FINECOSINE(tempangle));
+		tempy += FixedMul(cmd->forwardmove*FRACUNIT,FINESINE(tempangle));
 
-	tempangle = origtempangle-ANGLE_90;
-	tempangle >>= ANGLETOFINESHIFT;
+		tempangle = origtempangle-ANGLE_90;
+		tempangle >>= ANGLETOFINESHIFT;
+	}
 	tempx += FixedMul(cmd->sidemove*FRACUNIT,FINECOSINE(tempangle));
 	tempy += FixedMul(cmd->sidemove*FRACUNIT,FINESINE(tempangle));
 
@@ -8725,10 +8740,7 @@ void P_PlayerThink(player_t *player)
 	if (!(player->pflags & PF_NIGHTSMODE))
 	{
 		if (cmd->buttons & BT_USE)
-		{
-			if (!(player->pflags & PF_USEDOWN))
-				player->pflags |= PF_USEDOWN;
-		}
+			player->pflags |= PF_USEDOWN;
 		else
 			player->pflags &= ~PF_USEDOWN;
 	}
@@ -9026,8 +9038,7 @@ void P_PlayerAfterThink(player_t *player)
 		}
 	}
 
-	if (!(cmd->buttons & BT_WEAPONNEXT) && !(cmd->buttons & BT_WEAPONPREV)
-		&& !(cmd->buttons & BT_WEAPONMASK))
+	if (!(cmd->buttons & (BT_WEAPONNEXT|BT_WEAPONPREV|BT_WEAPONMASK)))
 		player->pflags &= ~PF_WPNDOWN;
 
 	// Weapon cycling if out of ammo for a certain weapon
@@ -9064,7 +9075,7 @@ void P_PlayerAfterThink(player_t *player)
 	&& player->charability2 == CA2_SPINDASH)
 		P_SetPlayerMobjState(player->mo, S_PLAY_ATK1);
 
-	if ((player->pflags & PF_CARRIED) && player->mo->tracer)
+	if (player->pflags & PF_CARRIED && player->mo->tracer)
 	{
 		player->mo->height = FixedDiv(P_GetPlayerHeight(player), FixedDiv(14*FRACUNIT,10*FRACUNIT));
 
@@ -9153,7 +9164,7 @@ void P_PlayerAfterThink(player_t *player)
 			}
 		}
 	}
-	else if ((player->pflags & PF_MACESPIN) && player->mo->tracer && player->mo->tracer->target)
+	else if (player->pflags & PF_MACESPIN && player->mo->tracer && player->mo->tracer->target)
 	{
 		player->mo->height = P_GetPlayerSpinHeight(player);
 		// tracer is what you're hanging onto....
@@ -9183,9 +9194,9 @@ void P_PlayerAfterThink(player_t *player)
 		}
 	}
 
-	if (((splitscreen && player == &players[secondarydisplayplayer]) || player == &players[displayplayer]))
+	if ((splitscreen && player == &players[secondarydisplayplayer]) || player == &players[displayplayer])
 	{
-		if (!(thiscam->chase)) // bob view only if looking through the player's eyes
+		if (!thiscam->chase) // bob view only if looking through the player's eyes
 		{
 			P_CalcHeight(player);
 			P_CalcPostImg(player);
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index 93da46bc1..c34690b3b 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.10;
+				CURRENT_PROJECT_VERSION = 2.1.11;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1226,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.10;
+				CURRENT_PROJECT_VERSION = 2.1.11;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
index 93da46bc1..c34690b3b 100644
--- a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.10;
+				CURRENT_PROJECT_VERSION = 2.1.11;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1226,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.10;
+				CURRENT_PROJECT_VERSION = 2.1.11;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/v_video.c b/src/v_video.c
index 52fefe4c6..926107ff2 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -479,14 +479,11 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 	deststart = desttop;
 	destend = desttop + SHORT(patch->width) * dupx;
 
-	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, desttop++)
+	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, ++x, desttop++)
 	{
 		INT32 topdelta, prevdelta = -1;
-		if (x < 0)
-		{ // don't draw off the left of the screen (WRAP PREVENTION)
-			x++;
+		if (x < 0) // don't draw off the left of the screen (WRAP PREVENTION)
 			continue;
-		}
 		if (x > vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
 			break;
 		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
@@ -594,13 +591,11 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 		}
 	}
 
-	for (col = sx<<FRACBITS; (col>>FRACBITS) < SHORT(patch->width) && (col>>FRACBITS) < w; col += colfrac, desttop++)
+	for (col = sx<<FRACBITS; (col>>FRACBITS) < SHORT(patch->width) && (col>>FRACBITS) < w; col += colfrac, ++x, desttop++)
 	{
 		INT32 topdelta, prevdelta = -1;
-		if (x < 0) { // don't draw off the left of the screen (WRAP PREVENTION)
-			x++;
+		if (x < 0) // don't draw off the left of the screen (WRAP PREVENTION)
 			continue;
-		}
 		if (x > vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
 			break;
 		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
-- 
GitLab