From bf8a39f7eedcf8011e3f19015d22bdde9b3e7589 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Sat, 11 Jan 2020 15:40:18 -0800
Subject: [PATCH 001/129] Only exit if base files fail to load

---
 src/w_wad.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/w_wad.c b/src/w_wad.c
index bbb30d3fa..cfd2db50e 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -822,13 +822,11 @@ UINT16 W_InitFile(const char *filename, boolean mainfile)
   * backwards, so a later file overrides all earlier ones.
   *
   * \param filenames A null-terminated list of files to use.
-  * \return 1 if all files were loaded, 0 if at least one was missing or
+  * \return 1 if base files were loaded, 0 if at least one was missing or
   *           invalid.
   */
 INT32 W_InitMultipleFiles(char **filenames, UINT16 mainfiles)
 {
-	INT32 rc = 1;
-
 	// open all the files, load headers, and count lumps
 	numwadfiles = 0;
 
@@ -836,13 +834,15 @@ INT32 W_InitMultipleFiles(char **filenames, UINT16 mainfiles)
 	for (; *filenames; filenames++)
 	{
 		//CONS_Debug(DBG_SETUP, "Loading %s\n", *filenames);
-		rc &= (W_InitFile(*filenames, numwadfiles < mainfiles) != INT16_MAX) ? 1 : 0;
+		if (W_InitFile(*filenames, numwadfiles < mainfiles) == INT16_MAX)
+		{
+			CONS_Printf(M_GetText("Errors occurred while loading %s; not added.\n"), *filenames);
+			if (numwadfiles < mainfiles)
+				return 0;
+		}
 	}
 
-	if (!numwadfiles)
-		I_Error("W_InitMultipleFiles: no files found");
-
-	return rc;
+	return 1;
 }
 
 /** Make sure a lump number is valid.

From 1e2a4c2cce3b4b8490b3cc287eeb84d2c09c02c7 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sat, 11 Jan 2020 22:26:20 -0600
Subject: [PATCH 002/129] Allow starpost Parameter to dictate order

---
 src/p_inter.c |  4 ++--
 src/p_mobj.c  | 11 ++++++++++-
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index 71740822e..4aa84ba84 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1441,8 +1441,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				return;
 			}
 
-			// We could technically have 91.1 Star Posts. 90 is cleaner.
-			if (special->health > 90)
+			// With the parameter + angle setup, we can go up to 1365 star posts. Who needs that many?
+			if (special->health > 1365)
 			{
 				CONS_Debug(DBG_GAMELOGIC, "Bad Starpost Number!\n");
 				return;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index a4231fa74..93a750997 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -12911,7 +12911,16 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		thinker_t* th;
 		mobj_t* mo2;
 		boolean foundanother = false;
-		mobj->health = (mthing->angle/360) + 1;
+
+		if (mthing->extrainfo)
+			// Allow thing Parameter to define star post num too!
+			// For starposts above param 15 (the 16th), add 360 to the angle like before and start parameter from 1 (NOT 0)!
+			// So the 16th starpost is angle=0 param=15, the 17th would be angle=360 param=1.
+			// This seems more intuitive for mappers to use until UDMF is ready, since most SP maps won't have over 16 consecutive star posts.
+			mobj->health = mthing->extrainfo + (mthing->angle/360)*15 + 1;
+		else
+			// Old behavior if Parameter is 0; add 360 to the angle for each consecutive star post.
+			mobj->health = (mthing->angle/360) + 1;
 
 		// See if other starposts exist in this level that have the same value.
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)

From e60ba92a9211371ad89b7530959e556385ab65e2 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sat, 11 Jan 2020 22:30:07 -0600
Subject: [PATCH 003/129] Checkpoint players at star post exact location with
 Ambush flag

---
 src/p_inter.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index 4aa84ba84..d6a47c78f 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1453,6 +1453,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 			if (cv_coopstarposts.value && G_GametypeUsesCoopStarposts() && (netgame || multiplayer))
 			{
+				mobj_t *checkbase = (special->spawnpoint && (special->spawnpoint->options & MTF_AMBUSH)) ? special : toucher;
 				for (i = 0; i < MAXPLAYERS; i++)
 				{
 					if (playeringame[i])
@@ -1461,8 +1462,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 							continue;
 
 						players[i].starposttime = leveltime;
-						players[i].starpostx = player->mo->x>>FRACBITS;
-						players[i].starposty = player->mo->y>>FRACBITS;
+						players[i].starpostx = checkbase->x>>FRACBITS;
+						players[i].starposty = checkbase->y>>FRACBITS;
 						players[i].starpostz = special->z>>FRACBITS;
 						players[i].starpostangle = special->angle;
 						players[i].starpostscale = player->mo->destscale;
@@ -1483,8 +1484,16 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			{
 				// Save the player's time and position.
 				player->starposttime = leveltime;
-				player->starpostx = toucher->x>>FRACBITS;
-				player->starposty = toucher->y>>FRACBITS;
+				if (special->spawnpoint && (special->spawnpoint->options & MTF_AMBUSH))
+				{
+					player->starpostx = special->x>>FRACBITS;
+					player->starposty = special->y>>FRACBITS;
+				}
+				else
+				{
+					player->starpostx = toucher->x>>FRACBITS;
+					player->starposty = toucher->y>>FRACBITS;
+				}
 				player->starpostz = special->z>>FRACBITS;
 				player->starpostangle = special->angle;
 				player->starpostscale = player->mo->destscale;

From 8a6173a3f2f6980d64473966769eceec11e733a7 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sun, 12 Jan 2020 09:11:57 -0600
Subject: [PATCH 004/129] Declare checkbase for both sides of the if statement

---
 src/p_inter.c | 79 ++++++++++++++++++++++++---------------------------
 1 file changed, 37 insertions(+), 42 deletions(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index d6a47c78f..9f53a46a0 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1451,59 +1451,54 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (player->starpostnum >= special->health)
 				return; // Already hit this post
 
-			if (cv_coopstarposts.value && G_GametypeUsesCoopStarposts() && (netgame || multiplayer))
 			{
 				mobj_t *checkbase = (special->spawnpoint && (special->spawnpoint->options & MTF_AMBUSH)) ? special : toucher;
-				for (i = 0; i < MAXPLAYERS; i++)
+
+				if (cv_coopstarposts.value && G_GametypeUsesCoopStarposts() && (netgame || multiplayer))
 				{
-					if (playeringame[i])
+					for (i = 0; i < MAXPLAYERS; i++)
 					{
-						if (players[i].bot) // ignore dumb, stupid tails
-							continue;
-
-						players[i].starposttime = leveltime;
-						players[i].starpostx = checkbase->x>>FRACBITS;
-						players[i].starposty = checkbase->y>>FRACBITS;
-						players[i].starpostz = special->z>>FRACBITS;
-						players[i].starpostangle = special->angle;
-						players[i].starpostscale = player->mo->destscale;
-						if (special->flags2 & MF2_OBJECTFLIP)
+						if (playeringame[i])
 						{
-							players[i].starpostscale *= -1;
-							players[i].starpostz += special->height>>FRACBITS;
-						}
-						players[i].starpostnum = special->health;
+							if (players[i].bot) // ignore dumb, stupid tails
+								continue;
 
-						if (cv_coopstarposts.value == 2 && (players[i].playerstate == PST_DEAD || players[i].spectator) && P_GetLives(&players[i]))
-							P_SpectatorJoinGame(&players[i]); //players[i].playerstate = PST_REBORN;
+							players[i].starposttime = leveltime;
+							players[i].starpostx = checkbase->x>>FRACBITS;
+							players[i].starposty = checkbase->y>>FRACBITS;
+							players[i].starpostz = special->z>>FRACBITS;
+							players[i].starpostangle = special->angle;
+							players[i].starpostscale = player->mo->destscale;
+							if (special->flags2 & MF2_OBJECTFLIP)
+							{
+								players[i].starpostscale *= -1;
+								players[i].starpostz += special->height>>FRACBITS;
+							}
+							players[i].starpostnum = special->health;
+
+							if (cv_coopstarposts.value == 2 && (players[i].playerstate == PST_DEAD || players[i].spectator) && P_GetLives(&players[i]))
+								P_SpectatorJoinGame(&players[i]); //players[i].playerstate = PST_REBORN;
+						}
 					}
-				}
-				S_StartSound(NULL, special->info->painsound);
-			}
-			else
-			{
-				// Save the player's time and position.
-				player->starposttime = leveltime;
-				if (special->spawnpoint && (special->spawnpoint->options & MTF_AMBUSH))
-				{
-					player->starpostx = special->x>>FRACBITS;
-					player->starposty = special->y>>FRACBITS;
+					S_StartSound(NULL, special->info->painsound);
 				}
 				else
 				{
-					player->starpostx = toucher->x>>FRACBITS;
-					player->starposty = toucher->y>>FRACBITS;
+					// Save the player's time and position.
+					player->starposttime = leveltime;
+					player->starpostx = checkbase->x>>FRACBITS;
+					player->starposty = checkbase->y>>FRACBITS;
+					player->starpostz = special->z>>FRACBITS;
+					player->starpostangle = special->angle;
+					player->starpostscale = player->mo->destscale;
+					if (special->flags2 & MF2_OBJECTFLIP)
+					{
+						player->starpostscale *= -1;
+						player->starpostz += special->height>>FRACBITS;
+					}
+					player->starpostnum = special->health;
+					S_StartSound(toucher, special->info->painsound);
 				}
-				player->starpostz = special->z>>FRACBITS;
-				player->starpostangle = special->angle;
-				player->starpostscale = player->mo->destscale;
-				if (special->flags2 & MF2_OBJECTFLIP)
-				{
-					player->starpostscale *= -1;
-					player->starpostz += special->height>>FRACBITS;
-				}
-				player->starpostnum = special->health;
-				S_StartSound(toucher, special->info->painsound);
 			}
 
 			P_ClearStarPost(special->health);

From c25e9696760afa898f24acab9ff43378e4156499 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Tue, 14 Jan 2020 23:29:56 -0600
Subject: [PATCH 005/129] wip viewroll stuff

---
 src/d_main.c        |   6 ++
 src/d_player.h      |   2 +
 src/lua_playerlib.c |   4 ++
 src/p_user.c        |   2 +
 src/r_main.c        | 141 +++++++++++++++++++++++++++++++++++++++++++-
 src/r_main.h        |   3 +
 6 files changed, 157 insertions(+), 1 deletion(-)

diff --git a/src/d_main.c b/src/d_main.c
index 8a7c446bb..763814a5a 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -266,6 +266,9 @@ static void D_Display(void)
 #endif
 	}
 
+	if (rendermode == render_soft && !splitscreen)
+		R_CheckViewMorph();
+
 	// change the view size if needed
 	if (setsizeneeded || setrenderstillneeded)
 	{
@@ -446,6 +449,9 @@ static void D_Display(void)
 				// Image postprocessing effect
 				if (rendermode == render_soft)
 				{
+					if (!splitscreen)
+						R_ApplyViewMorph();
+
 					if (postimgtype)
 						V_DoPostProcessor(0, postimgtype, postimgparam);
 					if (postimgtype2)
diff --git a/src/d_player.h b/src/d_player.h
index 62f38193f..6a1750113 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -324,6 +324,8 @@ typedef struct player_s
 	// bounded/scaled total momentum.
 	fixed_t bob;
 
+	angle_t viewrollangle;
+
 	// Mouse aiming, where the guy is looking at!
 	// It is updated with cmd->aiming.
 	angle_t aiming;
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index c501fbbb2..8b9397663 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -120,6 +120,8 @@ static int player_get(lua_State *L)
 		lua_pushfixed(L, plr->deltaviewheight);
 	else if (fastcmp(field,"bob"))
 		lua_pushfixed(L, plr->bob);
+	else if (fastcmp(field,"viewrollangle"))
+		lua_pushangle(L, plr->viewrollangle);
 	else if (fastcmp(field,"aiming"))
 		lua_pushangle(L, plr->aiming);
 	else if (fastcmp(field,"drawangle"))
@@ -415,6 +417,8 @@ static int player_set(lua_State *L)
 		plr->deltaviewheight = luaL_checkfixed(L, 3);
 	else if (fastcmp(field,"bob"))
 		plr->bob = luaL_checkfixed(L, 3);
+	else if (fastcmp(field,"viewrollangle"))
+		plr->viewrollangle = luaL_checkangle(L, 3);
 	else if (fastcmp(field,"aiming")) {
 		plr->aiming = luaL_checkangle(L, 3);
 		if (plr == &players[consoleplayer])
diff --git a/src/p_user.c b/src/p_user.c
index 0c4d25554..5d7383c4b 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -7432,6 +7432,8 @@ static void P_NiGHTSMovement(player_t *player)
 		else // AngleFixed(R_PointToAngle2()) results in slight inaccuracy! Don't use it unless movement is on both axises.
 			newangle = (INT16)FixedInt(AngleFixed(R_PointToAngle2(0,0, cmd->sidemove*FRACUNIT, cmd->forwardmove*FRACUNIT)));
 
+		newangle -= player->viewrollangle / ANG1;
+
 		if (newangle < 0 && moved)
 			newangle = (INT16)(360+newangle);
 	}
diff --git a/src/r_main.c b/src/r_main.c
index 3c6aaf6a6..fbb3c7047 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -551,6 +551,145 @@ static inline void R_InitLightTables(void)
 	}
 }
 
+static struct {
+	angle_t rollangle; // pre-shifted by fineshift
+	fixed_t fisheye;
+
+	fixed_t zoomneeded;
+	INT32 *scrmap;
+	size_t scrmapsize;
+	boolean use;
+} viewmorph = {0, 0, FRACUNIT, NULL, 0, false};
+
+void R_CheckViewMorph(void)
+{
+	float zoomfactor, rollcos, rollsin;
+	float x1, y1, x2, y2;
+	fixed_t temp;
+	size_t end, vx, vy, pos, usedpos;
+	INT32 usedx, usedy, halfwidth = vid.width/2, halfheight = vid.height/2;
+
+	angle_t rollangle = players[displayplayer].viewrollangle;
+	//fixed_t fisheye = players[displayplayer].viewfisheye;
+
+	// temp values
+	//angle_t rollangle = leveltime << (ANGLETOFINESHIFT);
+	fixed_t fisheye = 0;
+
+	rollangle >>= ANGLETOFINESHIFT;
+	rollangle = (((rollangle+1)/2)*2) & FINEMASK; // Limit the distinct number of angles to reduce recalcs from angles changing a lot.
+
+	fisheye &= ~0xFF; // Same limiter logic for fisheye
+
+	if (rollangle == viewmorph.rollangle && fisheye == viewmorph.fisheye)
+		return; // No change
+
+	viewmorph.rollangle = rollangle;
+	viewmorph.fisheye = fisheye;
+
+	if (viewmorph.rollangle == 0 && viewmorph.fisheye == 0)
+	{
+		viewmorph.use = false;
+		if (viewmorph.zoomneeded != FRACUNIT)
+			R_SetViewSize();
+		viewmorph.zoomneeded = FRACUNIT;
+
+		return;
+	}
+
+	if (viewmorph.scrmapsize != vid.width*vid.height)
+	{
+		if (viewmorph.scrmap)
+			free(viewmorph.scrmap);
+		viewmorph.scrmap = malloc(vid.width*vid.height * sizeof(INT32));
+		viewmorph.scrmapsize = vid.width*vid.height;
+	}
+
+	temp = FINECOSINE(rollangle);
+	rollcos = FIXED_TO_FLOAT(temp);
+	temp = FINESINE(rollangle);
+	rollsin = FIXED_TO_FLOAT(temp);
+
+	// Calculate maximum zoom needed
+	x1 = (vid.width*fabsf(rollcos) + vid.height*fabsf(rollsin)) / vid.width;
+	y1 = (vid.height*fabsf(rollcos) + vid.width*fabsf(rollsin)) / vid.height;
+
+	temp = max(x1, y1)*FRACUNIT;
+	if (temp < FRACUNIT)
+		temp = FRACUNIT;
+	else
+		temp |= 0xFFF; // Limit how many times the viewport needs to be recalculated
+
+	//CONS_Printf("Setting zoom to %f\n", FIXED_TO_FLOAT(temp));
+
+	if (temp != viewmorph.zoomneeded)
+	{
+		viewmorph.zoomneeded = temp;
+		R_SetViewSize();
+	}
+
+	zoomfactor = FIXED_TO_FLOAT(viewmorph.zoomneeded);
+
+	end = vid.width * vid.height - 1;
+
+	pos = 0;
+
+	// Pre-multiply rollcos and rollsin to use for positional stuff
+	rollcos /= zoomfactor;
+	rollsin /= zoomfactor;
+
+	x1 = -(halfwidth * rollcos - halfheight * rollsin);
+	y1 = -(halfheight * rollcos + halfwidth * rollsin);
+
+	//CONS_Printf("Top left corner is %f %f\n", x1, y1);
+
+	for (vy = 0; vy < halfheight; vy++)
+	{
+		x2 = x1;
+		y2 = y1;
+		x1 -= rollsin;
+		y1 += rollcos;
+
+		for (vx = 0; vx < vid.width; vx++)
+		{
+			usedx = halfwidth+x2;
+			usedy = halfheight+y2;
+
+			if (usedx < 0) usedx = 0;
+			else if (usedx >= vid.width) usedx = vid.width-1;
+			if (usedy < 0) usedy = 0;
+			else if (usedy >= vid.height) usedy = vid.height-1;
+
+			usedpos = usedx + usedy*vid.width;
+
+			viewmorph.scrmap[pos] = usedpos;
+			viewmorph.scrmap[end-pos] = end-usedpos;
+
+			x2 += rollcos;
+			y2 += rollsin;
+			pos++;
+		}
+	}
+
+	viewmorph.use = true;
+}
+
+void R_ApplyViewMorph(void)
+{
+	UINT8 *tmpscr = screens[4];
+	UINT8 *srcscr = screens[0];
+	INT32 p, end = vid.width * vid.height;
+
+	if (!viewmorph.use)
+		return;
+
+	for (p = 0; p < end; p++)
+		tmpscr[p] = srcscr[viewmorph.scrmap[p]];
+
+	VID_BlitLinearScreen(tmpscr, screens[0],
+			vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.width);
+}
+
 
 //
 // R_SetViewSize
@@ -599,7 +738,7 @@ void R_ExecuteSetViewSize(void)
 	centeryfrac = centery<<FRACBITS;
 
 	fov = FixedAngle(cv_fov.value/2) + ANGLE_90;
-	fovtan = FINETANGENT(fov >> ANGLETOFINESHIFT);
+	fovtan = FixedMul(FINETANGENT(fov >> ANGLETOFINESHIFT), viewmorph.zoomneeded);
 	if (splitscreen == 1) // Splitscreen FOV should be adjusted to maintain expected vertical view
 		fovtan = 17*fovtan/10;
 
diff --git a/src/r_main.h b/src/r_main.h
index 998bb50ef..92876ce25 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -95,6 +95,9 @@ void R_InitHardwareMode(void);
 #endif
 void R_ReloadHUDGraphics(void);
 
+void R_CheckViewMorph(void);
+void R_ApplyViewMorph(void);
+
 // just sets setsizeneeded true
 extern boolean setsizeneeded;
 void R_SetViewSize(void);

From cd957d84f7332fc71ebd1924aaa2b533684cc07d Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Tue, 14 Jan 2020 23:30:19 -0600
Subject: [PATCH 006/129] LMAO fisheye

---
 src/r_main.c | 29 ++++++++++++++++++++++++-----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/src/r_main.c b/src/r_main.c
index fbb3c7047..6efc7d99b 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -564,7 +564,7 @@ static struct {
 void R_CheckViewMorph(void)
 {
 	float zoomfactor, rollcos, rollsin;
-	float x1, y1, x2, y2;
+	float x1, y1, x2, y2, fisheyef;
 	fixed_t temp;
 	size_t end, vx, vy, pos, usedpos;
 	INT32 usedx, usedy, halfwidth = vid.width/2, halfheight = vid.height/2;
@@ -574,14 +574,14 @@ void R_CheckViewMorph(void)
 
 	// temp values
 	//angle_t rollangle = leveltime << (ANGLETOFINESHIFT);
-	fixed_t fisheye = 0;
+	fixed_t fisheye = FRACUNIT;
 
 	rollangle >>= ANGLETOFINESHIFT;
 	rollangle = (((rollangle+1)/2)*2) & FINEMASK; // Limit the distinct number of angles to reduce recalcs from angles changing a lot.
 
 	fisheye &= ~0xFF; // Same limiter logic for fisheye
 
-	if (rollangle == viewmorph.rollangle && fisheye == viewmorph.fisheye)
+	if (rollangle == viewmorph.rollangle && fisheye == viewmorph.fisheye && viewmorph.scrmapsize == vid.width*vid.height)
 		return; // No change
 
 	viewmorph.rollangle = rollangle;
@@ -614,6 +614,13 @@ void R_CheckViewMorph(void)
 	x1 = (vid.width*fabsf(rollcos) + vid.height*fabsf(rollsin)) / vid.width;
 	y1 = (vid.height*fabsf(rollcos) + vid.width*fabsf(rollsin)) / vid.height;
 
+	if (fisheye)
+	{
+		float dist = powf(2, (fisheyef = FIXED_TO_FLOAT(fisheye))/2);
+		x1 *= dist;
+		y1 *= dist;
+	}
+
 	temp = max(x1, y1)*FRACUNIT;
 	if (temp < FRACUNIT)
 		temp = FRACUNIT;
@@ -652,8 +659,20 @@ void R_CheckViewMorph(void)
 
 		for (vx = 0; vx < vid.width; vx++)
 		{
-			usedx = halfwidth+x2;
-			usedy = halfheight+y2;
+			if (fisheye)
+			{
+				float dist = sqrtf(x2*x2 + y2*y2) * zoomfactor / sqrtf(halfwidth*halfwidth + halfheight*halfheight);
+				dist += (1 - sqrtf(1 - dist*dist)) / dist;
+				dist = powf(dist, fisheyef);
+				usedx = halfwidth+x2*dist;
+				usedy = halfheight+y2*dist;
+
+			}
+			else
+			{
+				usedx = halfwidth+x2;
+				usedy = halfheight+y2;
+			}
 
 			if (usedx < 0) usedx = 0;
 			else if (usedx >= vid.width) usedx = vid.width-1;

From cdbea0be26067a5774778ba51a18df7aa661ce80 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Tue, 14 Jan 2020 23:30:39 -0600
Subject: [PATCH 007/129] Revert "LMAO fisheye"

This reverts commit cd957d84f7332fc71ebd1924aaa2b533684cc07d.
---
 src/r_main.c | 29 +++++------------------------
 1 file changed, 5 insertions(+), 24 deletions(-)

diff --git a/src/r_main.c b/src/r_main.c
index 6efc7d99b..fbb3c7047 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -564,7 +564,7 @@ static struct {
 void R_CheckViewMorph(void)
 {
 	float zoomfactor, rollcos, rollsin;
-	float x1, y1, x2, y2, fisheyef;
+	float x1, y1, x2, y2;
 	fixed_t temp;
 	size_t end, vx, vy, pos, usedpos;
 	INT32 usedx, usedy, halfwidth = vid.width/2, halfheight = vid.height/2;
@@ -574,14 +574,14 @@ void R_CheckViewMorph(void)
 
 	// temp values
 	//angle_t rollangle = leveltime << (ANGLETOFINESHIFT);
-	fixed_t fisheye = FRACUNIT;
+	fixed_t fisheye = 0;
 
 	rollangle >>= ANGLETOFINESHIFT;
 	rollangle = (((rollangle+1)/2)*2) & FINEMASK; // Limit the distinct number of angles to reduce recalcs from angles changing a lot.
 
 	fisheye &= ~0xFF; // Same limiter logic for fisheye
 
-	if (rollangle == viewmorph.rollangle && fisheye == viewmorph.fisheye && viewmorph.scrmapsize == vid.width*vid.height)
+	if (rollangle == viewmorph.rollangle && fisheye == viewmorph.fisheye)
 		return; // No change
 
 	viewmorph.rollangle = rollangle;
@@ -614,13 +614,6 @@ void R_CheckViewMorph(void)
 	x1 = (vid.width*fabsf(rollcos) + vid.height*fabsf(rollsin)) / vid.width;
 	y1 = (vid.height*fabsf(rollcos) + vid.width*fabsf(rollsin)) / vid.height;
 
-	if (fisheye)
-	{
-		float dist = powf(2, (fisheyef = FIXED_TO_FLOAT(fisheye))/2);
-		x1 *= dist;
-		y1 *= dist;
-	}
-
 	temp = max(x1, y1)*FRACUNIT;
 	if (temp < FRACUNIT)
 		temp = FRACUNIT;
@@ -659,20 +652,8 @@ void R_CheckViewMorph(void)
 
 		for (vx = 0; vx < vid.width; vx++)
 		{
-			if (fisheye)
-			{
-				float dist = sqrtf(x2*x2 + y2*y2) * zoomfactor / sqrtf(halfwidth*halfwidth + halfheight*halfheight);
-				dist += (1 - sqrtf(1 - dist*dist)) / dist;
-				dist = powf(dist, fisheyef);
-				usedx = halfwidth+x2*dist;
-				usedy = halfheight+y2*dist;
-
-			}
-			else
-			{
-				usedx = halfwidth+x2;
-				usedy = halfheight+y2;
-			}
+			usedx = halfwidth+x2;
+			usedy = halfheight+y2;
 
 			if (usedx < 0) usedx = 0;
 			else if (usedx >= vid.width) usedx = vid.width-1;

From e23f632cdbc687039ec25297ae0105142135e1a1 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Tue, 14 Jan 2020 23:44:21 -0600
Subject: [PATCH 008/129] Clean up fisheye and fix crashes

---
 src/r_main.c | 20 +++-----------------
 1 file changed, 3 insertions(+), 17 deletions(-)

diff --git a/src/r_main.c b/src/r_main.c
index fbb3c7047..628ba078f 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -553,13 +553,12 @@ static inline void R_InitLightTables(void)
 
 static struct {
 	angle_t rollangle; // pre-shifted by fineshift
-	fixed_t fisheye;
 
 	fixed_t zoomneeded;
 	INT32 *scrmap;
 	size_t scrmapsize;
 	boolean use;
-} viewmorph = {0, 0, FRACUNIT, NULL, 0, false};
+} viewmorph = {0, FRACUNIT, NULL, 0, false};
 
 void R_CheckViewMorph(void)
 {
@@ -570,24 +569,16 @@ void R_CheckViewMorph(void)
 	INT32 usedx, usedy, halfwidth = vid.width/2, halfheight = vid.height/2;
 
 	angle_t rollangle = players[displayplayer].viewrollangle;
-	//fixed_t fisheye = players[displayplayer].viewfisheye;
-
-	// temp values
-	//angle_t rollangle = leveltime << (ANGLETOFINESHIFT);
-	fixed_t fisheye = 0;
 
 	rollangle >>= ANGLETOFINESHIFT;
 	rollangle = (((rollangle+1)/2)*2) & FINEMASK; // Limit the distinct number of angles to reduce recalcs from angles changing a lot.
 
-	fisheye &= ~0xFF; // Same limiter logic for fisheye
-
-	if (rollangle == viewmorph.rollangle && fisheye == viewmorph.fisheye)
+	if (rollangle == viewmorph.rollangle && viewmorph.scrmapsize == vid.width*vid.height)
 		return; // No change
 
 	viewmorph.rollangle = rollangle;
-	viewmorph.fisheye = fisheye;
 
-	if (viewmorph.rollangle == 0 && viewmorph.fisheye == 0)
+	if (viewmorph.rollangle == 0)
 	{
 		viewmorph.use = false;
 		if (viewmorph.zoomneeded != FRACUNIT)
@@ -655,11 +646,6 @@ void R_CheckViewMorph(void)
 			usedx = halfwidth+x2;
 			usedy = halfheight+y2;
 
-			if (usedx < 0) usedx = 0;
-			else if (usedx >= vid.width) usedx = vid.width-1;
-			if (usedy < 0) usedy = 0;
-			else if (usedy >= vid.height) usedy = vid.height-1;
-
 			usedpos = usedx + usedy*vid.width;
 
 			viewmorph.scrmap[pos] = usedpos;

From bbbe76d2ca4a12745bb54e35be48fd1b3714cfe0 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 16 Jan 2020 03:09:02 -0800
Subject: [PATCH 009/129] Expose viewmobj as r_viewmobj

---
 src/r_main.c  | 55 +++++++++++++++++++++++++--------------------------
 src/r_state.h |  1 +
 2 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/src/r_main.c b/src/r_main.c
index 4cbf101b7..f25ea96fd 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -71,6 +71,7 @@ angle_t viewangle, aimingangle;
 fixed_t viewcos, viewsin;
 sector_t *viewsector;
 player_t *viewplayer;
+mobj_t *r_viewmobj;
 
 //
 // precalculated math tables
@@ -741,8 +742,6 @@ subsector_t *R_IsPointInSubsector(fixed_t x, fixed_t y)
 // R_SetupFrame
 //
 
-static mobj_t *viewmobj;
-
 // WARNING: a should be unsigned but to add with 2048, it isn't!
 #define AIMINGTODY(a) FixedDiv((FINETANGENT((2048+(((INT32)a)>>ANGLETOFINESHIFT)) & FINEMASK)*160)>>FRACBITS, fovtan)
 
@@ -799,16 +798,16 @@ void R_SetupFrame(player_t *player)
 	if (player->awayviewtics)
 	{
 		// cut-away view stuff
-		viewmobj = player->awayviewmobj; // should be a MT_ALTVIEWMAN
-		I_Assert(viewmobj != NULL);
-		viewz = viewmobj->z + 20*FRACUNIT;
+		r_viewmobj = player->awayviewmobj; // should be a MT_ALTVIEWMAN
+		I_Assert(r_viewmobj != NULL);
+		viewz = r_viewmobj->z + 20*FRACUNIT;
 		aimingangle = player->awayviewaiming;
-		viewangle = viewmobj->angle;
+		viewangle = r_viewmobj->angle;
 	}
 	else if (!player->spectator && chasecam)
 	// use outside cam view
 	{
-		viewmobj = NULL;
+		r_viewmobj = NULL;
 		viewz = thiscam->z + (thiscam->height>>1);
 		aimingangle = thiscam->aiming;
 		viewangle = thiscam->angle;
@@ -818,11 +817,11 @@ void R_SetupFrame(player_t *player)
 	{
 		viewz = player->viewz;
 
-		viewmobj = player->mo;
-		I_Assert(viewmobj != NULL);
+		r_viewmobj = player->mo;
+		I_Assert(r_viewmobj != NULL);
 
 		aimingangle = player->aiming;
-		viewangle = viewmobj->angle;
+		viewangle = r_viewmobj->angle;
 
 		if (!demoplayback && player->playerstate != PST_DEAD)
 		{
@@ -856,13 +855,13 @@ void R_SetupFrame(player_t *player)
 	}
 	else
 	{
-		viewx = viewmobj->x;
-		viewy = viewmobj->y;
+		viewx = r_viewmobj->x;
+		viewy = r_viewmobj->y;
 		viewx += quake.x;
 		viewy += quake.y;
 
-		if (viewmobj->subsector)
-			viewsector = viewmobj->subsector->sector;
+		if (r_viewmobj->subsector)
+			viewsector = r_viewmobj->subsector->sector;
 		else
 			viewsector = R_PointInSubsector(viewx, viewy)->sector;
 	}
@@ -884,12 +883,12 @@ void R_SkyboxFrame(player_t *player)
 		thiscam = &camera;
 
 	// cut-away view stuff
-	viewmobj = skyboxmo[0];
+	r_viewmobj = skyboxmo[0];
 #ifdef PARANOIA
-	if (!viewmobj)
+	if (!r_viewmobj)
 	{
 		const size_t playeri = (size_t)(player - players);
-		I_Error("R_SkyboxFrame: viewmobj null (player %s)", sizeu1(playeri));
+		I_Error("R_SkyboxFrame: r_viewmobj null (player %s)", sizeu1(playeri));
 	}
 #endif
 	if (player->awayviewtics)
@@ -920,13 +919,13 @@ void R_SkyboxFrame(player_t *player)
 			}
 		}
 	}
-	viewangle += viewmobj->angle;
+	viewangle += r_viewmobj->angle;
 
 	viewplayer = player;
 
-	viewx = viewmobj->x;
-	viewy = viewmobj->y;
-	viewz = viewmobj->z; // 26/04/17: use actual Z position instead of spawnpoint angle!
+	viewx = r_viewmobj->x;
+	viewy = r_viewmobj->y;
+	viewz = r_viewmobj->z; // 26/04/17: use actual Z position instead of spawnpoint angle!
 
 	if (mapheaderinfo[gamemap-1])
 	{
@@ -966,29 +965,29 @@ void R_SkyboxFrame(player_t *player)
 			else if (mh->skybox_scaley < 0)
 				y = (campos.y - skyboxmo[1]->y) * -mh->skybox_scaley;
 
-			if (viewmobj->angle == 0)
+			if (r_viewmobj->angle == 0)
 			{
 				viewx += x;
 				viewy += y;
 			}
-			else if (viewmobj->angle == ANGLE_90)
+			else if (r_viewmobj->angle == ANGLE_90)
 			{
 				viewx -= y;
 				viewy += x;
 			}
-			else if (viewmobj->angle == ANGLE_180)
+			else if (r_viewmobj->angle == ANGLE_180)
 			{
 				viewx -= x;
 				viewy -= y;
 			}
-			else if (viewmobj->angle == ANGLE_270)
+			else if (r_viewmobj->angle == ANGLE_270)
 			{
 				viewx += y;
 				viewy -= x;
 			}
 			else
 			{
-				angle_t ang = viewmobj->angle>>ANGLETOFINESHIFT;
+				angle_t ang = r_viewmobj->angle>>ANGLETOFINESHIFT;
 				viewx += FixedMul(x,FINECOSINE(ang)) - FixedMul(y,  FINESINE(ang));
 				viewy += FixedMul(x,  FINESINE(ang)) + FixedMul(y,FINECOSINE(ang));
 			}
@@ -999,8 +998,8 @@ void R_SkyboxFrame(player_t *player)
 			viewz += campos.z * -mh->skybox_scalez;
 	}
 
-	if (viewmobj->subsector)
-		viewsector = viewmobj->subsector->sector;
+	if (r_viewmobj->subsector)
+		viewsector = r_viewmobj->subsector->sector;
 	else
 		viewsector = R_PointInSubsector(viewx, viewy)->sector;
 
diff --git a/src/r_state.h b/src/r_state.h
index 75566923b..e7838e9fb 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -83,6 +83,7 @@ extern fixed_t viewx, viewy, viewz;
 extern angle_t viewangle, aimingangle;
 extern sector_t *viewsector;
 extern player_t *viewplayer;
+extern mobj_t *r_viewmobj;
 
 extern consvar_t cv_allowmlook;
 extern consvar_t cv_maxportals;

From 542e38e717e2a8cf38a9ce5b9fa2affe56424781 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 16 Jan 2020 03:09:16 -0800
Subject: [PATCH 010/129] Don't draw player mobj in first person

This solves that annoying albeit slightly amusing bug
where your sprite clips into your view during a quake.

For OpenGL, this also solves the player's model
rendering while in first person. So you'll no
longer be looking through Sonic's body!
---
 src/hardware/hw_main.c | 6 ++++--
 src/r_things.c         | 6 ++++--
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index f5f3edfc1..e658051a6 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5422,7 +5422,8 @@ static void HWR_AddSprites(sector_t *sec)
 	{
 		for (thing = sec->thinglist; thing; thing = thing->snext)
 		{
-			if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW)
+			if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW ||
+					thing == r_viewmobj)
 				continue;
 
 			approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
@@ -5445,7 +5446,8 @@ static void HWR_AddSprites(sector_t *sec)
 	{
 		// Draw everything in sector, no checks
 		for (thing = sec->thinglist; thing; thing = thing->snext)
-			if (!(thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW))
+			if (!(thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW ||
+						thing == r_viewmobj))
 				HWR_ProjectSprite(thing);
 	}
 
diff --git a/src/r_things.c b/src/r_things.c
index ef496335e..2a18c06a4 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1845,7 +1845,8 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 	{
 		for (thing = sec->thinglist; thing; thing = thing->snext)
 		{
-			if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW)
+			if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW ||
+					thing == r_viewmobj)
 				continue;
 
 			approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
@@ -1868,7 +1869,8 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 	{
 		// Draw everything in sector, no checks
 		for (thing = sec->thinglist; thing; thing = thing->snext)
-			if (!(thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW))
+			if (!(thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW ||
+						thing == r_viewmobj))
 				R_ProjectSprite(thing);
 	}
 

From 28c466a0a5fe36172026b0376c7cc6868e7dbdd0 Mon Sep 17 00:00:00 2001
From: lachwright <lachlanwright17@gmail.com>
Date: Thu, 16 Jan 2020 22:56:01 +0800
Subject: [PATCH 011/129] Reset pmomz after transfer to momz

---
 src/p_mobj.c | 4 ++++
 src/p_user.c | 6 ++----
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index a4231fa74..4ed19a9ff 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2350,6 +2350,7 @@ static void P_RingZMovement(mobj_t *mo)
 	if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
 	{
 		mo->momz += mo->pmomz;
+		mo->pmomz = 0;
 		mo->eflags &= ~MFE_APPLYPMOMZ;
 	}
 	mo->z += mo->momz;
@@ -2419,6 +2420,7 @@ static boolean P_ZMovement(mobj_t *mo)
 	if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
 	{
 		mo->momz += mo->pmomz;
+		mo->pmomz = 0;
 		mo->eflags &= ~MFE_APPLYPMOMZ;
 	}
 	mo->z += mo->momz;
@@ -2907,6 +2909,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 	if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
 	{
 		mo->momz += mo->pmomz;
+		mo->pmomz = 0;
 		mo->eflags &= ~MFE_APPLYPMOMZ;
 	}
 
@@ -3157,6 +3160,7 @@ static boolean P_SceneryZMovement(mobj_t *mo)
 	if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
 	{
 		mo->momz += mo->pmomz;
+		mo->pmomz = 0;
 		mo->eflags &= ~MFE_APPLYPMOMZ;
 	}
 	mo->z += mo->momz;
diff --git a/src/p_user.c b/src/p_user.c
index 0c4d25554..fb59efab7 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -4530,16 +4530,14 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		player->mo->z--;
 		if (player->mo->pmomz < 0)
 			player->mo->momz += player->mo->pmomz; // Add the platform's momentum to your jump.
-		else
-			player->mo->pmomz = 0;
+		player->mo->pmomz = 0;
 	}
 	else
 	{
 		player->mo->z++;
 		if (player->mo->pmomz > 0)
 			player->mo->momz += player->mo->pmomz; // Add the platform's momentum to your jump.
-		else
-			player->mo->pmomz = 0;
+		player->mo->pmomz = 0;
 	}
 	player->mo->eflags &= ~MFE_APPLYPMOMZ;
 

From 7cf563eadd0c2a4e1977c7024ef40975159e5aff Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Fri, 17 Jan 2020 18:32:13 -0600
Subject: [PATCH 012/129] Un/archive viewrollangle in netsaves

---
 src/p_saveg.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/p_saveg.c b/src/p_saveg.c
index 2b6a474bf..e0b6d9579 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -116,6 +116,7 @@ static void P_NetArchivePlayers(void)
 
 		WRITEANGLE(save_p, players[i].aiming);
 		WRITEANGLE(save_p, players[i].drawangle);
+		WRITEANGLE(save_p, players[i].viewrollangle);
 		WRITEANGLE(save_p, players[i].awayviewaiming);
 		WRITEINT32(save_p, players[i].awayviewtics);
 		WRITEINT16(save_p, players[i].rings);
@@ -325,6 +326,7 @@ static void P_NetUnArchivePlayers(void)
 
 		players[i].aiming = READANGLE(save_p);
 		players[i].drawangle = READANGLE(save_p);
+		players[i].viewrollangle = READANGLE(save_p);
 		players[i].awayviewaiming = READANGLE(save_p);
 		players[i].awayviewtics = READINT32(save_p);
 		players[i].rings = READINT16(save_p);

From 84329fcd2677a42860f986e06b901434bc44966e Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Fri, 17 Jan 2020 19:01:45 -0600
Subject: [PATCH 013/129] OGL can have little a viewroll

---
 src/hardware/hw_main.c           | 6 ++++++
 src/hardware/r_opengl/r_opengl.c | 2 ++
 2 files changed, 8 insertions(+)

diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 74be53db5..e1b5d23ff 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5969,6 +5969,8 @@ static void HWR_DrawSkyBackground(player_t *player)
 		dometransform.scalez = 1;
 		dometransform.fovxangle = fpov; // Tails
 		dometransform.fovyangle = fpov; // Tails
+		dometransform.roll = (player->viewrollangle != 0);
+		dometransform.rollangle = FIXED_TO_FLOAT(AngleFixed(player->viewrollangle));
 		dometransform.splitscreen = splitscreen;
 
 		HWR_GetTexture(texturetranslation[skytexture]);
@@ -6192,6 +6194,8 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	atransform.scalez = 1;
 	atransform.fovxangle = fpov; // Tails
 	atransform.fovyangle = fpov; // Tails
+	atransform.roll = (player->viewrollangle != 0);
+	atransform.rollangle = FIXED_TO_FLOAT(AngleFixed(player->viewrollangle));
 	atransform.splitscreen = splitscreen;
 
 	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
@@ -6412,6 +6416,8 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	atransform.scalez = 1;
 	atransform.fovxangle = fpov; // Tails
 	atransform.fovyangle = fpov; // Tails
+	atransform.roll = (player->viewrollangle != 0);
+	atransform.rollangle = FIXED_TO_FLOAT(AngleFixed(player->viewrollangle));
 	atransform.splitscreen = splitscreen;
 
 	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index becce9fa3..a3ed3c8d2 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -2238,6 +2238,8 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 		else
 			pglScalef(stransform->scalex, stransform->scaley, -stransform->scalez);
 
+		if (stransform->roll)
+			pglRotatef(stransform->rollangle, 0.0f, 0.0f, 1.0f);
 		pglRotatef(stransform->anglex       , 1.0f, 0.0f, 0.0f);
 		pglRotatef(stransform->angley+270.0f, 0.0f, 1.0f, 0.0f);
 		pglTranslatef(-stransform->x, -stransform->z, -stransform->y);

From 2b7d75126e8e8641a4b413c74b3c65da87e0d309 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Fri, 17 Jan 2020 20:39:15 -0600
Subject: [PATCH 014/129] Add DBG_VIEWMORPH to view pre-transformed view

---
 src/doomdef.h |  1 +
 src/r_main.c  | 30 ++++++++++++++++++++++++++++--
 2 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/src/doomdef.h b/src/doomdef.h
index 3d02871e4..565d1aadf 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -490,6 +490,7 @@ extern INT32 cv_debug;
 #define DBG_SETUP       0x0400
 #define DBG_LUA         0x0800
 #define DBG_RANDOMIZER  0x1000
+#define DBG_VIEWMORPH   0x2000
 
 // =======================
 // Misc stuff for later...
diff --git a/src/r_main.c b/src/r_main.c
index d4d05ad44..91e1b3fe7 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -667,8 +667,34 @@ void R_ApplyViewMorph(void)
 	if (!viewmorph.use)
 		return;
 
-	for (p = 0; p < end; p++)
-		tmpscr[p] = srcscr[viewmorph.scrmap[p]];
+	if (cv_debug & DBG_VIEWMORPH)
+	{
+		UINT8 border = 32;
+		UINT8 grid = 160;
+		INT32 ws = vid.width / 4;
+		INT32 hs = vid.width * (vid.height / 4);
+
+		memcpy(tmpscr, srcscr, vid.width*vid.height);
+		for (p = 0; p < vid.width; p++)
+		{
+			tmpscr[viewmorph.scrmap[p]] = border;
+			tmpscr[viewmorph.scrmap[p + hs]] = grid;
+			tmpscr[viewmorph.scrmap[p + hs*2]] = grid;
+			tmpscr[viewmorph.scrmap[p + hs*3]] = grid;
+			tmpscr[viewmorph.scrmap[end - 1 - p]] = border;
+		}
+		for (p = vid.width; p < end; p += vid.width)
+		{
+			tmpscr[viewmorph.scrmap[p]] = border;
+			tmpscr[viewmorph.scrmap[p + ws]] = grid;
+			tmpscr[viewmorph.scrmap[p + ws*2]] = grid;
+			tmpscr[viewmorph.scrmap[p + ws*3]] = grid;
+			tmpscr[viewmorph.scrmap[end - 1 - p]] = border;
+		}
+	}
+	else
+		for (p = 0; p < end; p++)
+			tmpscr[p] = srcscr[viewmorph.scrmap[p]];
 
 	VID_BlitLinearScreen(tmpscr, screens[0],
 			vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.width);

From 64c4a4c02c6a59264446e9a43593e7397b774baa Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Fri, 17 Jan 2020 20:39:38 -0600
Subject: [PATCH 015/129] Fisheye lens experiments

---
 src/r_main.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 83 insertions(+), 3 deletions(-)

diff --git a/src/r_main.c b/src/r_main.c
index 91e1b3fe7..689f18f2b 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -549,14 +549,29 @@ static inline void R_InitLightTables(void)
 	}
 }
 
+//#define WOUGHMP_WOUGHMP // I got a fish-eye lens - I'll make a rap video with a couple of friends
+// it's kinda laggy sometimes
+
 static struct {
 	angle_t rollangle; // pre-shifted by fineshift
+#ifdef WOUGHMP_WOUGHMP
+	fixed_t fisheye;
+#endif
 
 	fixed_t zoomneeded;
 	INT32 *scrmap;
 	size_t scrmapsize;
 	boolean use;
-} viewmorph = {0, FRACUNIT, NULL, 0, false};
+} viewmorph = {
+	0,
+#ifdef WOUGHMP_WOUGHMP
+	0,
+#endif
+	FRACUNIT,
+	NULL,
+	0,
+	false
+};
 
 void R_CheckViewMorph(void)
 {
@@ -565,18 +580,39 @@ void R_CheckViewMorph(void)
 	fixed_t temp;
 	size_t end, vx, vy, pos, usedpos;
 	INT32 usedx, usedy, halfwidth = vid.width/2, halfheight = vid.height/2;
+#ifdef WOUGHMP_WOUGHMP
+	float fisheyemap[MAXVIDWIDTH/2 + 1];
+#endif
 
 	angle_t rollangle = players[displayplayer].viewrollangle;
+#ifdef WOUGHMP_WOUGHMP
+	fixed_t fisheye = cv_cam2_turnmultiplier.value; // temporary test value
+#endif
 
 	rollangle >>= ANGLETOFINESHIFT;
 	rollangle = (((rollangle+1)/2)*2) & FINEMASK; // Limit the distinct number of angles to reduce recalcs from angles changing a lot.
 
-	if (rollangle == viewmorph.rollangle && viewmorph.scrmapsize == vid.width*vid.height)
+#ifdef WOUGHMP_WOUGHMP
+	fisheye &= ~0x7FF; // Same
+#endif
+
+	if (rollangle == viewmorph.rollangle &&
+#ifdef WOUGHMP_WOUGHMP
+		fisheye == viewmorph.fisheye &&
+#endif
+		viewmorph.scrmapsize == vid.width*vid.height)
 		return; // No change
 
 	viewmorph.rollangle = rollangle;
+#ifdef WOUGHMP_WOUGHMP
+	viewmorph.fisheye = fisheye;
+#endif
 
-	if (viewmorph.rollangle == 0)
+	if (viewmorph.rollangle == 0
+#ifdef WOUGHMP_WOUGHMP
+		 && viewmorph.fisheye == 0
+#endif
+	 )
 	{
 		viewmorph.use = false;
 		if (viewmorph.zoomneeded != FRACUNIT)
@@ -603,6 +639,22 @@ void R_CheckViewMorph(void)
 	x1 = (vid.width*fabsf(rollcos) + vid.height*fabsf(rollsin)) / vid.width;
 	y1 = (vid.height*fabsf(rollcos) + vid.width*fabsf(rollsin)) / vid.height;
 
+#ifdef WOUGHMP_WOUGHMP
+	if (fisheye)
+	{
+		float f = FIXED_TO_FLOAT(fisheye);
+		for (vx = 0; vx <= halfwidth; vx++)
+			fisheyemap[vx] = 1.0f / cos(atan(vx * f / halfwidth));
+
+		f = cos(atan(f));
+		if (f < 1.0f)
+		{
+			x1 /= f;
+			y1 /= f;
+		}
+	}
+#endif
+
 	temp = max(x1, y1)*FRACUNIT;
 	if (temp < FRACUNIT)
 		temp = FRACUNIT;
@@ -632,6 +684,34 @@ void R_CheckViewMorph(void)
 
 	//CONS_Printf("Top left corner is %f %f\n", x1, y1);
 
+#ifdef WOUGHMP_WOUGHMP
+	if (fisheye)
+	{
+		for (vy = 0; vy < halfheight; vy++)
+		{
+			x2 = x1;
+			y2 = y1;
+			x1 -= rollsin;
+			y1 += rollcos;
+
+			for (vx = 0; vx < vid.width; vx++)
+			{
+				usedx = halfwidth + x2*fisheyemap[(int) floorf(fabsf(y2*zoomfactor))];
+				usedy = halfheight + y2*fisheyemap[(int) floorf(fabsf(x2*zoomfactor))];
+
+				usedpos = usedx + usedy*vid.width;
+
+				viewmorph.scrmap[pos] = usedpos;
+				viewmorph.scrmap[end-pos] = end-usedpos;
+
+				x2 += rollcos;
+				y2 += rollsin;
+				pos++;
+			}
+		}
+	}
+	else
+#endif
 	for (vy = 0; vy < halfheight; vy++)
 	{
 		x2 = x1;

From 7bd9344dd01cf541d86d3e7965e7037fb85ca494 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Fri, 17 Jan 2020 21:55:16 -0600
Subject: [PATCH 016/129] I think this fixes the compile errors

---
 src/hardware/hw_main.c | 24 ++++++++++++++++++------
 src/r_main.c           |  4 ++--
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index e1b5d23ff..cf78c0cc0 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5969,8 +5969,12 @@ static void HWR_DrawSkyBackground(player_t *player)
 		dometransform.scalez = 1;
 		dometransform.fovxangle = fpov; // Tails
 		dometransform.fovyangle = fpov; // Tails
-		dometransform.roll = (player->viewrollangle != 0);
-		dometransform.rollangle = FIXED_TO_FLOAT(AngleFixed(player->viewrollangle));
+		if (player->viewrollangle != 0)
+		{
+			fixed_t rol = AngleFixed(player->viewrollangle);
+			dometransform.rollangle = FIXED_TO_FLOAT(rol);
+			dometransform.roll = true;
+		}
 		dometransform.splitscreen = splitscreen;
 
 		HWR_GetTexture(texturetranslation[skytexture]);
@@ -6194,8 +6198,12 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	atransform.scalez = 1;
 	atransform.fovxangle = fpov; // Tails
 	atransform.fovyangle = fpov; // Tails
-	atransform.roll = (player->viewrollangle != 0);
-	atransform.rollangle = FIXED_TO_FLOAT(AngleFixed(player->viewrollangle));
+	if (player->viewrollangle != 0)
+	{
+		fixed_t rol = AngleFixed(player->viewrollangle);
+		atransform.rollangle = FIXED_TO_FLOAT(rol);
+		atransform.roll = true;
+	}
 	atransform.splitscreen = splitscreen;
 
 	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
@@ -6416,8 +6424,12 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	atransform.scalez = 1;
 	atransform.fovxangle = fpov; // Tails
 	atransform.fovyangle = fpov; // Tails
-	atransform.roll = (player->viewrollangle != 0);
-	atransform.rollangle = FIXED_TO_FLOAT(AngleFixed(player->viewrollangle));
+	if (player->viewrollangle != 0)
+	{
+		fixed_t rol = AngleFixed(player->viewrollangle);
+		atransform.rollangle = FIXED_TO_FLOAT(rol);
+		atransform.roll = true;
+	}
 	atransform.splitscreen = splitscreen;
 
 	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
diff --git a/src/r_main.c b/src/r_main.c
index 689f18f2b..27c444d88 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -560,7 +560,7 @@ static struct {
 
 	fixed_t zoomneeded;
 	INT32 *scrmap;
-	size_t scrmapsize;
+	INT32 scrmapsize;
 	boolean use;
 } viewmorph = {
 	0,
@@ -578,7 +578,7 @@ void R_CheckViewMorph(void)
 	float zoomfactor, rollcos, rollsin;
 	float x1, y1, x2, y2;
 	fixed_t temp;
-	size_t end, vx, vy, pos, usedpos;
+	INT32 end, vx, vy, pos, usedpos;
 	INT32 usedx, usedy, halfwidth = vid.width/2, halfheight = vid.height/2;
 #ifdef WOUGHMP_WOUGHMP
 	float fisheyemap[MAXVIDWIDTH/2 + 1];

From 21ccefe4eb96e3b3f3216025f40d6271faaac206 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Fri, 17 Jan 2020 22:03:16 -0600
Subject: [PATCH 017/129] Fix sky texture scaling wrong with fov changes

---
 src/r_main.h | 1 +
 src/r_sky.c  | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/r_main.h b/src/r_main.h
index 2378661cc..0042015f2 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -26,6 +26,7 @@ extern INT32 centerx, centery;
 
 extern fixed_t centerxfrac, centeryfrac;
 extern fixed_t projection, projectiony;
+extern fixed_t fovtan; // field of view
 
 extern size_t validcount, linecount, loopcount, framecount;
 
diff --git a/src/r_sky.c b/src/r_sky.c
index c9d28cebc..da36eb937 100644
--- a/src/r_sky.c
+++ b/src/r_sky.c
@@ -76,5 +76,5 @@ void R_SetupSkyDraw(void)
 void R_SetSkyScale(void)
 {
 	fixed_t difference = vid.fdupx-(vid.dupx<<FRACBITS);
-	skyscale = FixedDiv(FRACUNIT, vid.fdupx+difference);
+	skyscale = FixedDiv(fovtan, vid.fdupx+difference);
 }

From 762223db7ceca9ed428da723c84f121c6292bf4c Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Fri, 17 Jan 2020 20:45:55 -0800
Subject: [PATCH 018/129] Duplicated code is gone, so sad

---
 src/hardware/hw_main.c | 44 +++-----------------
 src/r_things.c         | 93 ++++++++++++++++++++++++------------------
 src/r_things.h         |  9 ++++
 3 files changed, 68 insertions(+), 78 deletions(-)

diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index e658051a6..7ab0929da 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5399,7 +5399,7 @@ static void HWR_AddSprites(sector_t *sec)
 #ifdef HWPRECIP
 	precipmobj_t *precipthing;
 #endif
-	fixed_t approx_dist, limit_dist, hoop_limit_dist;
+	fixed_t limit_dist, hoop_limit_dist;
 
 	// BSP is traversed by subsector.
 	// A sector might have been split into several
@@ -5418,37 +5418,10 @@ static void HWR_AddSprites(sector_t *sec)
 	// If a limit exists, handle things a tiny bit different.
 	limit_dist = (fixed_t)(cv_drawdist.value) << FRACBITS;
 	hoop_limit_dist = (fixed_t)(cv_drawdist_nights.value) << FRACBITS;
-	if (limit_dist || hoop_limit_dist)
+	for (thing = sec->thinglist; thing; thing = thing->snext)
 	{
-		for (thing = sec->thinglist; thing; thing = thing->snext)
-		{
-			if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW ||
-					thing == r_viewmobj)
-				continue;
-
-			approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
-
-			if (thing->sprite == SPR_HOOP)
-			{
-				if (hoop_limit_dist && approx_dist > hoop_limit_dist)
-					continue;
-			}
-			else
-			{
-				if (limit_dist && approx_dist > limit_dist)
-					continue;
-			}
-
+		if (R_ThingVisibleWithinDist(thing, limit_dist, hoop_limit_dist))
 			HWR_ProjectSprite(thing);
-		}
-	}
-	else
-	{
-		// Draw everything in sector, no checks
-		for (thing = sec->thinglist; thing; thing = thing->snext)
-			if (!(thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW ||
-						thing == r_viewmobj))
-				HWR_ProjectSprite(thing);
 	}
 
 #ifdef HWPRECIP
@@ -5457,15 +5430,8 @@ static void HWR_AddSprites(sector_t *sec)
 	{
 		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
 		{
-			if (precipthing->precipflags & PCF_INVISIBLE)
-				continue;
-
-			approx_dist = P_AproxDistance(viewx-precipthing->x, viewy-precipthing->y);
-
-			if (approx_dist > limit_dist)
-				continue;
-
-			HWR_ProjectPrecipitationSprite(precipthing);
+			if (R_PrecipThingVisible(precipthing, limit_dist))
+				HWR_ProjectPrecipitationSprite(precipthing);
 		}
 	}
 #endif
diff --git a/src/r_things.c b/src/r_things.c
index 2a18c06a4..2a2872a34 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1808,7 +1808,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 	mobj_t *thing;
 	precipmobj_t *precipthing; // Tails 08-25-2002
 	INT32 lightnum;
-	fixed_t approx_dist, limit_dist, hoop_limit_dist;
+	fixed_t limit_dist, hoop_limit_dist;
 
 	if (rendermode != render_soft)
 		return;
@@ -1841,37 +1841,10 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 	// If a limit exists, handle things a tiny bit different.
 	limit_dist = (fixed_t)(cv_drawdist.value) << FRACBITS;
 	hoop_limit_dist = (fixed_t)(cv_drawdist_nights.value) << FRACBITS;
-	if (limit_dist || hoop_limit_dist)
+	for (thing = sec->thinglist; thing; thing = thing->snext)
 	{
-		for (thing = sec->thinglist; thing; thing = thing->snext)
-		{
-			if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW ||
-					thing == r_viewmobj)
-				continue;
-
-			approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
-
-			if (thing->sprite == SPR_HOOP)
-			{
-				if (hoop_limit_dist && approx_dist > hoop_limit_dist)
-					continue;
-			}
-			else
-			{
-				if (limit_dist && approx_dist > limit_dist)
-					continue;
-			}
-
+		if (R_ThingVisibleWithinDist(thing, limit_dist, hoop_limit_dist))
 			R_ProjectSprite(thing);
-		}
-	}
-	else
-	{
-		// Draw everything in sector, no checks
-		for (thing = sec->thinglist; thing; thing = thing->snext)
-			if (!(thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW ||
-						thing == r_viewmobj))
-				R_ProjectSprite(thing);
 	}
 
 	// no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off
@@ -1879,15 +1852,8 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 	{
 		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
 		{
-			if (precipthing->precipflags & PCF_INVISIBLE)
-				continue;
-
-			approx_dist = P_AproxDistance(viewx-precipthing->x, viewy-precipthing->y);
-
-			if (approx_dist > limit_dist)
-				continue;
-
-			R_ProjectPrecipitationSprite(precipthing);
+			if (R_PrecipThingVisible(precipthing, limit_dist))
+				R_ProjectPrecipitationSprite(precipthing);
 		}
 	}
 }
@@ -2582,6 +2548,55 @@ void R_ClipSprites(drawseg_t* dsstart, portal_t* portal)
 	}
 }
 
+/* Check if thing may be drawn from our current view. */
+boolean R_ThingVisible (mobj_t *thing)
+{
+	return (!(
+				thing->sprite == SPR_NULL ||
+				( thing->flags2 & (MF2_DONTDRAW) ) ||
+				thing == r_viewmobj
+	));
+}
+
+boolean R_ThingVisibleWithinDist (mobj_t *thing,
+		fixed_t      limit_dist,
+		fixed_t hoop_limit_dist)
+{
+	fixed_t approx_dist;
+
+	if (! R_ThingVisible(thing))
+		return false;
+
+	approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
+
+	if (thing->sprite == SPR_HOOP)
+	{
+		if (hoop_limit_dist && approx_dist > hoop_limit_dist)
+			return false;
+	}
+	else
+	{
+		if (limit_dist && approx_dist > limit_dist)
+			return false;
+	}
+
+	return true;
+}
+
+/* Check if precipitation may be drawn from our current view. */
+boolean R_PrecipThingVisible (precipmobj_t *precipthing,
+		fixed_t limit_dist)
+{
+	fixed_t approx_dist;
+
+	if (( precipthing->precipflags & PCF_INVISIBLE ))
+		return false;
+
+	approx_dist = P_AproxDistance(viewx-precipthing->x, viewy-precipthing->y);
+
+	return ( approx_dist <= limit_dist );
+}
+
 //
 // R_DrawMasked
 //
diff --git a/src/r_things.h b/src/r_things.h
index 1b74dd74e..76587868d 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -61,6 +61,15 @@ void R_InitSprites(void);
 void R_ClearSprites(void);
 void R_ClipSprites(drawseg_t* dsstart, portal_t* portal);
 
+boolean R_ThingVisible (mobj_t *thing);
+
+boolean R_ThingVisibleWithinDist (mobj_t *thing,
+		fixed_t        draw_dist,
+		fixed_t nights_draw_dist);
+
+boolean R_PrecipThingVisible (precipmobj_t *precipthing,
+		fixed_t precip_draw_dist);
+
 /** Used to count the amount of masked elements
  * per portal to later group them in separate
  * drawnode lists.

From 94a2f0bb4fd40562262f2e37a7fc392b2aa42e75 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Fri, 17 Jan 2020 20:56:32 -0800
Subject: [PATCH 019/129] Don't draw Tails' tails in first person
 (MF2_LINKDRAW)

---
 src/hardware/hw_main.c | 2 +-
 src/r_things.c         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 7ab0929da..d4682fc9d 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5695,7 +5695,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if ((thing->flags2 & MF2_LINKDRAW) && thing->tracer)
 	{
 		// bodge support - not nearly as comprehensive as r_things.c, but better than nothing
-		if (thing->tracer->sprite == SPR_NULL || thing->tracer->flags2 & MF2_DONTDRAW)
+		if (! R_ThingVisible(thing->tracer))
 			return;
 	}
 
diff --git a/src/r_things.c b/src/r_things.c
index 2a2872a34..cba080448 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1413,7 +1413,7 @@ static void R_ProjectSprite(mobj_t *thing)
 
 		thing = thing->tracer;
 
-		if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW)
+		if (! R_ThingVisible(thing))
 			return;
 
 		tr_x = thing->x - viewx;

From 1d221a453a183d3933b9176436e91efca9632011 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Fri, 17 Jan 2020 23:21:11 -0600
Subject: [PATCH 020/129] Avoid rendering unused left/right edges of screen
 while rolling

---
 src/r_main.c   | 24 +++++++++++++++++++++++-
 src/r_things.c | 39 ++++++---------------------------------
 2 files changed, 29 insertions(+), 34 deletions(-)

diff --git a/src/r_main.c b/src/r_main.c
index 27c444d88..a49b0519f 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -561,6 +561,7 @@ static struct {
 	fixed_t zoomneeded;
 	INT32 *scrmap;
 	INT32 scrmapsize;
+	INT32 x1; // clip rendering horizontally for efficiency
 	boolean use;
 } viewmorph = {
 	0,
@@ -570,6 +571,7 @@ static struct {
 	FRACUNIT,
 	NULL,
 	0,
+	0,
 	false
 };
 
@@ -615,6 +617,7 @@ void R_CheckViewMorph(void)
 	 )
 	{
 		viewmorph.use = false;
+		viewmorph.x1 = 0;
 		if (viewmorph.zoomneeded != FRACUNIT)
 			R_SetViewSize();
 		viewmorph.zoomneeded = FRACUNIT;
@@ -682,6 +685,14 @@ void R_CheckViewMorph(void)
 	x1 = -(halfwidth * rollcos - halfheight * rollsin);
 	y1 = -(halfheight * rollcos + halfwidth * rollsin);
 
+#ifdef WOUGHMP_WOUGHMP
+	if (fisheye)
+		viewmorph.x1 = (INT32)(halfwidth - (halfwidth * fabsf(rollcos) + halfheight * fabsf(rollsin)) * fisheyemap[halfwidth]);
+	else
+#endif
+	viewmorph.x1 = (INT32)(halfwidth - (halfwidth * fabsf(rollcos) + halfheight * fabsf(rollsin)));
+	//CONS_Printf("saving %d cols\n", viewmorph.x1);
+
 	//CONS_Printf("Top left corner is %f %f\n", x1, y1);
 
 #ifdef WOUGHMP_WOUGHMP
@@ -1316,7 +1327,18 @@ void R_RenderPlayerView(player_t *player)
 	validcount++;
 
 	// Clear buffers.
-	R_ClearClipSegs();
+	if (viewmorph.use)
+	{
+		portalclipstart = viewmorph.x1;
+		portalclipend = viewwidth-viewmorph.x1-1;
+		R_PortalClearClipSegs(portalclipstart, portalclipend);
+	}
+	else
+	{
+		portalclipstart = 0;
+		portalclipend = viewwidth-1;
+		R_ClearClipSegs();
+	}
 	R_ClearDrawSegs();
 	R_ClearPlanes();
 	R_ClearSprites();
diff --git a/src/r_things.c b/src/r_things.c
index 8fa0f2d0e..bb98d7f15 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1307,17 +1307,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 
 	shadow->mobj = thing; // Easy access! Tails 06-07-2002
 
-	shadow->x1 = x1 < 0 ? 0 : x1;
-	shadow->x2 = x2 >= viewwidth ? viewwidth-1 : x2;
-
-	// PORTAL SEMI-CLIPPING
-	if (portalrender)
-	{
-		if (shadow->x1 < portalclipstart)
-			shadow->x1 = portalclipstart;
-		if (shadow->x2 >= portalclipend)
-			shadow->x2 = portalclipend-1;
-	}
+	shadow->x1 = x1 < portalclipstart ? portalclipstart : x1;
+	shadow->x2 = x2 >= portalclipend ? portalclipend-1 : x2;
 
 	shadow->xscale = FixedMul(xscale, shadowxscale); //SoM: 4/17/2000
 	shadow->scale = FixedMul(yscale, shadowyscale);
@@ -1815,17 +1806,8 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	vis->mobj = thing; // Easy access! Tails 06-07-2002
 
-	vis->x1 = x1 < 0 ? 0 : x1;
-	vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2;
-
-	// PORTAL SEMI-CLIPPING
-	if (portalrender)
-	{
-		if (vis->x1 < portalclipstart)
-			vis->x1 = portalclipstart;
-		if (vis->x2 >= portalclipend)
-			vis->x2 = portalclipend-1;
-	}
+	vis->x1 = x1 < portalclipstart ? portalclipstart : x1;
+	vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2;
 
 	vis->xscale = xscale; //SoM: 4/17/2000
 	vis->sector = thing->subsector->sector;
@@ -2034,17 +2016,8 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 	vis->shear.tan = 0;
 	vis->shear.offset = 0;
 
-	vis->x1 = x1 < 0 ? 0 : x1;
-	vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2;
-
-	// PORTAL SEMI-CLIPPING
-	if (portalrender)
-	{
-		if (vis->x1 < portalclipstart)
-			vis->x1 = portalclipstart;
-		if (vis->x2 >= portalclipend)
-			vis->x2 = portalclipend-1;
-	}
+	vis->x1 = x1 < portalclipstart ? portalclipstart : x1;
+	vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2;
 
 	vis->xscale = xscale; //SoM: 4/17/2000
 	vis->sector = thing->subsector->sector;

From 8868fc4d8abce1b6393303155e758921f7ef9e4f Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sat, 18 Jan 2020 00:16:18 -0600
Subject: [PATCH 021/129] Fix lighting discrepancies between different FOVs

---
 src/r_draw8.c      |  9 +++++----
 src/r_draw8_npo2.c | 11 +++++++----
 src/r_main.h       |  2 ++
 src/r_segs.c       | 14 +++++++-------
 src/r_things.c     |  4 ++--
 5 files changed, 23 insertions(+), 17 deletions(-)

diff --git a/src/r_draw8.c b/src/r_draw8.c
index 015dac2a7..2f6bdcfa4 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -644,6 +644,7 @@ void R_CalcTiltedLighting(fixed_t start, fixed_t end)
 	}
 }
 
+#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f * FIXED_TO_FLOAT(fovtan))
 
 /**	\brief The R_DrawTiltedSpan_8 function
 	Draw slopes! Holy sheit!
@@ -669,7 +670,7 @@ void R_DrawTiltedSpan_8(void)
 
 	// Lighting is simple. It's just linear interpolation from start to end
 	{
-		float planelightfloat = BASEVIDWIDTH*BASEVIDWIDTH/vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f;
+		float planelightfloat = PLANELIGHTFLOAT;
 		float lightstart, lightend;
 
 		lightend = (iz + ds_szp->x*width) * planelightfloat;
@@ -805,7 +806,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 
 	// Lighting is simple. It's just linear interpolation from start to end
 	{
-		float planelightfloat = BASEVIDWIDTH*BASEVIDWIDTH/vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f;
+		float planelightfloat = PLANELIGHTFLOAT;
 		float lightstart, lightend;
 
 		lightend = (iz + ds_szp->x*width) * planelightfloat;
@@ -942,7 +943,7 @@ void R_DrawTiltedTranslucentWaterSpan_8(void)
 
 	// Lighting is simple. It's just linear interpolation from start to end
 	{
-		float planelightfloat = BASEVIDWIDTH*BASEVIDWIDTH/vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f;
+		float planelightfloat = PLANELIGHTFLOAT;
 		float lightstart, lightend;
 
 		lightend = (iz + ds_szp->x*width) * planelightfloat;
@@ -1078,7 +1079,7 @@ void R_DrawTiltedSplat_8(void)
 
 	// Lighting is simple. It's just linear interpolation from start to end
 	{
-		float planelightfloat = BASEVIDWIDTH*BASEVIDWIDTH/vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f;
+		float planelightfloat = PLANELIGHTFLOAT;
 		float lightstart, lightend;
 
 		lightend = (iz + ds_szp->x*width) * planelightfloat;
diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c
index 748ca195c..aa38ee2d9 100644
--- a/src/r_draw8_npo2.c
+++ b/src/r_draw8_npo2.c
@@ -62,6 +62,9 @@ void R_DrawSpan_NPO2_8 (void)
 }
 
 #ifdef ESLOPE
+
+#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f * FIXED_TO_FLOAT(fovtan))
+
 /**	\brief The R_DrawTiltedSpan_NPO2_8 function
 	Draw slopes! Holy sheit!
 */
@@ -86,7 +89,7 @@ void R_DrawTiltedSpan_NPO2_8(void)
 
 	// Lighting is simple. It's just linear interpolation from start to end
 	{
-		float planelightfloat = BASEVIDWIDTH*BASEVIDWIDTH/vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f;
+		float planelightfloat = PLANELIGHTFLOAT;
 		float lightstart, lightend;
 
 		lightend = (iz + ds_szp->x*width) * planelightfloat;
@@ -282,7 +285,7 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void)
 
 	// Lighting is simple. It's just linear interpolation from start to end
 	{
-		float planelightfloat = BASEVIDWIDTH*BASEVIDWIDTH/vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f;
+		float planelightfloat = PLANELIGHTFLOAT;
 		float lightstart, lightend;
 
 		lightend = (iz + ds_szp->x*width) * planelightfloat;
@@ -476,7 +479,7 @@ void R_DrawTiltedSplat_NPO2_8(void)
 
 	// Lighting is simple. It's just linear interpolation from start to end
 	{
-		float planelightfloat = BASEVIDWIDTH*BASEVIDWIDTH/vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f;
+		float planelightfloat = PLANELIGHTFLOAT;
 		float lightstart, lightend;
 
 		lightend = (iz + ds_szp->x*width) * planelightfloat;
@@ -869,7 +872,7 @@ void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void)
 
 	// Lighting is simple. It's just linear interpolation from start to end
 	{
-		float planelightfloat = BASEVIDWIDTH*BASEVIDWIDTH/vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f;
+		float planelightfloat = PLANELIGHTFLOAT;
 		float lightstart, lightend;
 
 		lightend = (iz + ds_szp->x*width) * planelightfloat;
diff --git a/src/r_main.h b/src/r_main.h
index 0042015f2..678d3a0a7 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -46,6 +46,8 @@ extern size_t validcount, linecount, loopcount, framecount;
 #define MAXLIGHTZ 128
 #define LIGHTZSHIFT 20
 
+#define LIGHTRESOLUTIONFIX (640*fovtan/vid.width)
+
 extern lighttable_t *scalelight[LIGHTLEVELS][MAXLIGHTSCALE];
 extern lighttable_t *scalelightfixed[MAXLIGHTSCALE];
 extern lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ];
diff --git a/src/r_segs.c b/src/r_segs.c
index dcb5fc160..fb4238448 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -199,7 +199,7 @@ static void R_DrawWallSplats(void)
 		// draw the columns
 		for (dc_x = x1; dc_x <= x2; dc_x++, spryscale += rw_scalestep)
 		{
-			pindex = FixedMul(spryscale, FixedDiv(640, vid.width))>>LIGHTSCALESHIFT;
+			pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 			if (pindex >= MAXLIGHTSCALE)
 				pindex = MAXLIGHTSCALE - 1;
 			dc_colormap = walllights[pindex];
@@ -599,7 +599,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 						else
 							xwalllights = scalelight[rlight->lightnum];
 
-						pindex = FixedMul(spryscale, FixedDiv(640, vid.width))>>LIGHTSCALESHIFT;
+						pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
 						if (pindex >= MAXLIGHTSCALE)
 							pindex = MAXLIGHTSCALE - 1;
@@ -644,7 +644,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 				}
 
 				// calculate lighting
-				pindex = FixedMul(spryscale, FixedDiv(640, vid.width))>>LIGHTSCALESHIFT;
+				pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
 				if (pindex >= MAXLIGHTSCALE)
 					pindex = MAXLIGHTSCALE - 1;
@@ -1188,7 +1188,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 						else
 							xwalllights = scalelight[lightnum];
 
-						pindex = FixedMul(spryscale, FixedDiv(640, vid.width))>>LIGHTSCALESHIFT;
+						pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
 						if (pindex >= MAXLIGHTSCALE)
 							pindex = MAXLIGHTSCALE-1;
@@ -1281,7 +1281,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 			}
 
 			// calculate lighting
-			pindex = FixedMul(spryscale, FixedDiv(640, vid.width))>>LIGHTSCALESHIFT;
+			pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
 			if (pindex >= MAXLIGHTSCALE)
 				pindex = MAXLIGHTSCALE - 1;
@@ -1486,7 +1486,7 @@ static void R_RenderSegLoop (void)
 		if (segtextured)
 		{
 			// calculate lighting
-			pindex = FixedMul(rw_scale, FixedDiv(640, vid.width))>>LIGHTSCALESHIFT;
+			pindex = FixedMul(rw_scale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
 			if (pindex >=  MAXLIGHTSCALE)
 				pindex = MAXLIGHTSCALE-1;
@@ -1521,7 +1521,7 @@ static void R_RenderSegLoop (void)
 				else
 					xwalllights = scalelight[lightnum];
 
-				pindex = FixedMul(rw_scale, FixedDiv(640, vid.width))>>LIGHTSCALESHIFT;
+				pindex = FixedMul(rw_scale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
 				if (pindex >=  MAXLIGHTSCALE)
 					pindex = MAXLIGHTSCALE-1;
diff --git a/src/r_things.c b/src/r_things.c
index bb98d7f15..cb9d78464 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1097,7 +1097,7 @@ static void R_SplitSprite(vissprite_t *sprite)
 			if (!((newsprite->cut & SC_FULLBRIGHT)
 				&& (!newsprite->extra_colormap || !(newsprite->extra_colormap->fog & 1))))
 			{
-				lindex = FixedMul(sprite->xscale, FixedDiv(640, vid.width))>>(LIGHTSCALESHIFT);
+				lindex = FixedMul(sprite->xscale, LIGHTRESOLUTIONFIX)>>(LIGHTSCALESHIFT);
 
 				if (lindex >= MAXLIGHTSCALE)
 					lindex = MAXLIGHTSCALE-1;
@@ -1872,7 +1872,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	else
 	{
 		// diminished light
-		lindex = FixedMul(xscale, FixedDiv(640, vid.width))>>(LIGHTSCALESHIFT);
+		lindex = FixedMul(xscale, LIGHTRESOLUTIONFIX)>>(LIGHTSCALESHIFT);
 
 		if (lindex >= MAXLIGHTSCALE)
 			lindex = MAXLIGHTSCALE-1;

From af4479924ad616ddf50daee4d1886a59f159607a Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sat, 18 Jan 2020 10:53:00 -0600
Subject: [PATCH 022/129] Fully clip drawing to roll-used screen bounds

---
 src/r_main.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 51 insertions(+), 1 deletion(-)

diff --git a/src/r_main.c b/src/r_main.c
index a49b0519f..7def18b40 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -561,17 +561,24 @@ static struct {
 	fixed_t zoomneeded;
 	INT32 *scrmap;
 	INT32 scrmapsize;
+
 	INT32 x1; // clip rendering horizontally for efficiency
+	INT16 ceilingclip[MAXVIDWIDTH], floorclip[MAXVIDWIDTH];
+
 	boolean use;
 } viewmorph = {
 	0,
 #ifdef WOUGHMP_WOUGHMP
 	0,
 #endif
+
 	FRACUNIT,
 	NULL,
 	0,
+
 	0,
+	{}, {},
+
 	false
 };
 
@@ -693,6 +700,47 @@ void R_CheckViewMorph(void)
 	viewmorph.x1 = (INT32)(halfwidth - (halfwidth * fabsf(rollcos) + halfheight * fabsf(rollsin)));
 	//CONS_Printf("saving %d cols\n", viewmorph.x1);
 
+	// Set ceilingclip and floorclip
+	for (vx = 0; vx < vid.width; vx++)
+	{
+		viewmorph.ceilingclip[vx] = vid.height;
+		viewmorph.floorclip[vx] = -1;
+	}
+	x2 = x1;
+	y2 = y1;
+	for (vx = 0; vx < vid.width; vx++)
+	{
+		INT16 xa, ya, xb, yb;
+		xa = x2+halfwidth;
+		ya = y2+halfheight;
+		xb = vid.width-1-xa;
+		yb = vid.height-1-ya;
+
+		viewmorph.ceilingclip[xa] = min(viewmorph.ceilingclip[xa], ya);
+		viewmorph.floorclip[xa] = max(viewmorph.floorclip[xa], ya);
+		viewmorph.ceilingclip[xb] = min(viewmorph.ceilingclip[xb], yb);
+		viewmorph.floorclip[xb] = max(viewmorph.floorclip[xb], yb);
+		x2 += rollcos;
+		y2 += rollsin;
+	}
+	x2 = x1;
+	y2 = y1;
+	for (vy = 0; vy < vid.height; vy++)
+	{
+		INT16 xa, ya, xb, yb;
+		xa = x2+halfwidth;
+		ya = y2+halfheight;
+		xb = vid.width-1-xa;
+		yb = vid.height-1-ya;
+
+		viewmorph.ceilingclip[xa] = min(viewmorph.ceilingclip[xa], ya);
+		viewmorph.floorclip[xa] = max(viewmorph.floorclip[xa], ya);
+		viewmorph.ceilingclip[xb] = min(viewmorph.ceilingclip[xb], yb);
+		viewmorph.floorclip[xb] = max(viewmorph.floorclip[xb], yb);
+		x2 -= rollsin;
+		y2 += rollcos;
+	}
+
 	//CONS_Printf("Top left corner is %f %f\n", x1, y1);
 
 #ifdef WOUGHMP_WOUGHMP
@@ -1327,11 +1375,14 @@ void R_RenderPlayerView(player_t *player)
 	validcount++;
 
 	// Clear buffers.
+	R_ClearPlanes();
 	if (viewmorph.use)
 	{
 		portalclipstart = viewmorph.x1;
 		portalclipend = viewwidth-viewmorph.x1-1;
 		R_PortalClearClipSegs(portalclipstart, portalclipend);
+		memcpy(ceilingclip, viewmorph.ceilingclip, sizeof(INT16)*vid.width);
+		memcpy(floorclip, viewmorph.floorclip, sizeof(INT16)*vid.width);
 	}
 	else
 	{
@@ -1340,7 +1391,6 @@ void R_RenderPlayerView(player_t *player)
 		R_ClearClipSegs();
 	}
 	R_ClearDrawSegs();
-	R_ClearPlanes();
 	R_ClearSprites();
 #ifdef FLOORSPLATS
 	R_ClearVisibleFloorSplats();

From 8679606ebb56975a93b7140184b45170b82bc3fe Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sat, 18 Jan 2020 10:53:23 -0600
Subject: [PATCH 023/129] Remove a couple adds from each pixel of morph mapping

---
 src/r_main.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/r_main.c b/src/r_main.c
index 7def18b40..9259c6902 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -770,7 +770,11 @@ void R_CheckViewMorph(void)
 		}
 	}
 	else
+	{
 #endif
+	x1 += halfwidth;
+	y1 += halfheight;
+
 	for (vy = 0; vy < halfheight; vy++)
 	{
 		x2 = x1;
@@ -780,8 +784,8 @@ void R_CheckViewMorph(void)
 
 		for (vx = 0; vx < vid.width; vx++)
 		{
-			usedx = halfwidth+x2;
-			usedy = halfheight+y2;
+			usedx = x2;
+			usedy = y2;
 
 			usedpos = usedx + usedy*vid.width;
 
@@ -793,6 +797,9 @@ void R_CheckViewMorph(void)
 			pos++;
 		}
 	}
+#ifdef WOUGHMP_WOUGHMP
+	}
+#endif
 
 	viewmorph.use = true;
 }

From 5757f24342f9dfdebe7bedc226f237bd0aea5cca Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sat, 18 Jan 2020 10:53:45 -0600
Subject: [PATCH 024/129] Reduce the number of distinct roll angles

---
 src/r_main.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/r_main.c b/src/r_main.c
index 9259c6902..1592c9ab2 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -599,7 +599,7 @@ void R_CheckViewMorph(void)
 #endif
 
 	rollangle >>= ANGLETOFINESHIFT;
-	rollangle = (((rollangle+1)/2)*2) & FINEMASK; // Limit the distinct number of angles to reduce recalcs from angles changing a lot.
+	rollangle = ((rollangle+2) & ~3) & FINEMASK; // Limit the distinct number of angles to reduce recalcs from angles changing a lot.
 
 #ifdef WOUGHMP_WOUGHMP
 	fisheye &= ~0x7FF; // Same

From f6efe19fca206f972ebe567051648b3b11f900c2 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sat, 18 Jan 2020 11:15:36 -0600
Subject: [PATCH 025/129] Use generally higher zooms and fix fuzzy edges

---
 src/r_main.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/r_main.c b/src/r_main.c
index 1592c9ab2..548f37f57 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -669,7 +669,7 @@ void R_CheckViewMorph(void)
 	if (temp < FRACUNIT)
 		temp = FRACUNIT;
 	else
-		temp |= 0xFFF; // Limit how many times the viewport needs to be recalculated
+		temp |= 0x3FFF; // Limit how many times the viewport needs to be recalculated
 
 	//CONS_Printf("Setting zoom to %f\n", FIXED_TO_FLOAT(temp));
 
@@ -712,7 +712,7 @@ void R_CheckViewMorph(void)
 	{
 		INT16 xa, ya, xb, yb;
 		xa = x2+halfwidth;
-		ya = y2+halfheight;
+		ya = y2+halfheight-1;
 		xb = vid.width-1-xa;
 		yb = vid.height-1-ya;
 

From ec89015662e12dada02877feb9309b565a07b2a4 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sat, 18 Jan 2020 11:19:59 -0600
Subject: [PATCH 026/129] Fix loss of aimingtody precision at high FOV

---
 src/r_main.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/r_main.c b/src/r_main.c
index 548f37f57..e43e3a1c3 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1045,7 +1045,7 @@ subsector_t *R_IsPointInSubsector(fixed_t x, fixed_t y)
 static mobj_t *viewmobj;
 
 // WARNING: a should be unsigned but to add with 2048, it isn't!
-#define AIMINGTODY(a) FixedDiv((FINETANGENT((2048+(((INT32)a)>>ANGLETOFINESHIFT)) & FINEMASK)*160)>>FRACBITS, fovtan)
+#define AIMINGTODY(a) ((FINETANGENT((2048+(((INT32)a)>>ANGLETOFINESHIFT)) & FINEMASK)*160)/fovtan)
 
 // recalc necessary stuff for mouseaiming
 // slopes are already calculated for the full possible view (which is 4*viewheight).

From 512435c59f2d31c8b892a5838a83df3db620c56d Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Sat, 18 Jan 2020 20:18:20 +0100
Subject: [PATCH 027/129] Fix mouse in controls setup menu

---
 src/m_menu.c      |  6 ++++++
 src/m_menu.h      |  3 +++
 src/sdl/i_video.c | 22 ++++++++++++++++------
 3 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 0d19a6a43..f8c14dd69 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3645,6 +3645,12 @@ void M_SetupNextMenu(menu_t *menudef)
 	hidetitlemap = false;
 }
 
+// Guess I'll put this here, idk
+boolean M_MouseNeeded(void)
+{
+	return (currentMenu == &MessageDef && currentMenu->prevMenu == &OP_ChangeControlsDef);
+}
+
 //
 // M_Ticker
 //
diff --git a/src/m_menu.h b/src/m_menu.h
index 3504868c9..3d7fb3b1c 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -320,6 +320,9 @@ typedef struct menu_s
 void M_SetupNextMenu(menu_t *menudef);
 void M_ClearMenus(boolean callexitmenufunc);
 
+// Maybe this goes here????? Who knows.
+boolean M_MouseNeeded(void);
+
 extern menu_t *currentMenu;
 
 extern menu_t MainDef;
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 2b8633e5b..13e2423c4 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -110,7 +110,6 @@ static SDL_bool disable_fullscreen = SDL_FALSE;
 #define USE_FULLSCREEN (disable_fullscreen||!allow_fullscreen)?0:cv_fullscreen.value
 static SDL_bool disable_mouse = SDL_FALSE;
 #define USE_MOUSEINPUT (!disable_mouse && cv_usemouse.value && havefocus)
-#define IGNORE_MOUSE (!cv_alwaysgrabmouse.value && (menuactive || paused || con_destlines || chat_on || gamestate != GS_LEVEL))
 #define MOUSE_MENU false //(!disable_mouse && cv_usemouse.value && menuactive && !USE_FULLSCREEN)
 #define MOUSEBUTTONS_MAX MOUSEBUTTONS
 
@@ -362,6 +361,17 @@ static INT32 Impl_SDL_Scancode_To_Keycode(SDL_Scancode code)
 	return 0;
 }
 
+static boolean IgnoreMouse(void)
+{
+	if (cv_alwaysgrabmouse.value)
+		return false;
+	if (menuactive)
+		return !M_MouseNeeded();
+	if (paused || con_destlines || chat_on || gamestate != GS_LEVEL)
+		return true;
+	return false;
+}
+
 static void SDLdoGrabMouse(void)
 {
 	SDL_ShowCursor(SDL_DISABLE);
@@ -388,7 +398,7 @@ void I_UpdateMouseGrab(void)
 {
 	if (SDL_WasInit(SDL_INIT_VIDEO) == SDL_INIT_VIDEO && window != NULL
 	&& SDL_GetMouseFocus() == window && SDL_GetKeyboardFocus() == window
-	&& USE_MOUSEINPUT && !IGNORE_MOUSE)
+	&& USE_MOUSEINPUT && !IgnoreMouse())
 		SDLdoGrabMouse();
 }
 
@@ -596,7 +606,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt)
 		}
 		//else firsttimeonmouse = SDL_FALSE;
 
-		if (USE_MOUSEINPUT && !IGNORE_MOUSE)
+		if (USE_MOUSEINPUT && !IgnoreMouse())
 			SDLdoGrabMouse();
 	}
 	else if (!mousefocus && !kbfocus)
@@ -647,7 +657,7 @@ static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt)
 
 	if (USE_MOUSEINPUT)
 	{
-		if ((SDL_GetMouseFocus() != window && SDL_GetKeyboardFocus() != window) || (IGNORE_MOUSE && !firstmove))
+		if ((SDL_GetMouseFocus() != window && SDL_GetKeyboardFocus() != window) || (IgnoreMouse() && !firstmove))
 		{
 			SDLdoUngrabMouse();
 			firstmove = false;
@@ -700,7 +710,7 @@ static void Impl_HandleMouseButtonEvent(SDL_MouseButtonEvent evt, Uint32 type)
 	// 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 || IGNORE_MOUSE)
+	if (SDL_GetMouseFocus() != window || IgnoreMouse())
 		return;
 
 	/// \todo inputEvent.button.which
@@ -1082,7 +1092,7 @@ void I_StartupMouse(void)
 	}
 	else
 		firsttimeonmouse = SDL_FALSE;
-	if (cv_usemouse.value && !IGNORE_MOUSE)
+	if (cv_usemouse.value && !IgnoreMouse())
 		SDLdoGrabMouse();
 	else
 		SDLdoUngrabMouse();

From 1317dba3e42a6f700e38f930d296f6f4128e97bf Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Sat, 18 Jan 2020 22:13:34 -0800
Subject: [PATCH 028/129] Refactor MUSICDEF parsing, actually count lines

If you use strtok for (CR)LF, it'll skip the empty lines bruh.
---
 src/s_sound.c | 305 ++++++++++++++++++++++++--------------------------
 1 file changed, 147 insertions(+), 158 deletions(-)

diff --git a/src/s_sound.c b/src/s_sound.c
index c4c92ebf5..848fd671a 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1472,188 +1472,112 @@ static UINT16 W_CheckForMusicDefInPwad(UINT16 wadid)
 	return INT16_MAX; // not found
 }
 
-void S_LoadMusicDefs(UINT16 wadnum)
+static boolean
+ReadMusicDefFields (UINT16 wadnum, int line, char *stoken, musicdef_t **defp)
 {
-	UINT16 lumpnum;
-	char *lump, *buf;
-	char *musdeftext;
-	char *stoken;
+	musicdef_t *def;
+
 	char *value;
 	char *textline;
-	size_t size;
-	INT32 i;
-	musicdef_t *def = NULL;
-	UINT16 line = 1; // for better error msgs
+	int i;
 
-	lumpnum = W_CheckForMusicDefInPwad(wadnum);
-	if (lumpnum == INT16_MAX)
-		return;
-
-	lump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-	size = W_LumpLengthPwad(wadnum, lumpnum);
-
-	// Null-terminated MUSICDEF lump.
-	musdeftext = malloc(size+1);
-	if (!musdeftext)
-		I_Error("S_LoadMusicDefs: No more free memory for the parser\n");
-	M_Memcpy(musdeftext, lump, size);
-	musdeftext[size] = '\0';
-
-	// for strtok
-	buf = malloc(size+1);
-	if (!buf)
-		I_Error("S_LoadMusicDefs: No more free memory for the parser\n");
-	M_Memcpy(buf, musdeftext, size+1);
-
-	stoken = strtok(buf, "\r\n ");
-	// Find music def
-	while (stoken)
+	if (!stricmp(stoken, "lump"))
 	{
-		/*if ((stoken[0] == '/' && stoken[1] == '/')
-			|| (stoken[0] == '#')) // skip comments
+		value = strtok(NULL, " ");
+		if (!value)
 		{
-			stoken = strtok(NULL, "\r\n"); // skip end of line
-			if (def)
-				stoken = strtok(NULL, "\r\n= ");
-			else
-				stoken = strtok(NULL, "\r\n ");
-			line++;
-		}
-		else*/ if (!stricmp(stoken, "lump"))
-		{
-			value = strtok(NULL, "\r\n ");
-
-			if (!value)
-			{
-				CONS_Alert(CONS_WARNING, "MUSICDEF: Lump '%s' is missing name. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line);
-				stoken = strtok(NULL, "\r\n"); // skip end of line
-				goto skip_lump;
-			}
-
-			// No existing musicdefs
-			/*if (!musicdefstart)
-			{
-				musicdefstart = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL);
-				STRBUFCPY(musicdefstart->name, value);
-				strlwr(musicdefstart->name);
-				def = musicdefstart;
-				//CONS_Printf("S_LoadMusicDefs: Initialized musicdef w/ song '%s'\n", def->name);
-			}
-			else*/
-			{
-				musicdef_t *prev = NULL;
-				def = musicdefstart;
-
-				// Search if this is a replacement
-				//CONS_Printf("S_LoadMusicDefs: Searching for song replacement...\n");
-				while (def)
-				{
-					if (!stricmp(def->name, value))
-					{
-						//CONS_Printf("S_LoadMusicDefs: Found song replacement '%s'\n", def->name);
-						break;
-					}
-
-					prev = def;
-					def = def->next;
-				}
-
-				// Nothing found, add to the end.
-				if (!def)
-				{
-					def = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL);
-					STRBUFCPY(def->name, value);
-					strlwr(def->name);
-					def->bpm = TICRATE<<(FRACBITS-1); // FixedDiv((60*TICRATE)<<FRACBITS, 120<<FRACBITS)
-					if (prev != NULL)
-						prev->next = def;
-					//CONS_Printf("S_LoadMusicDefs: Added song '%s'\n", def->name);
-				}
-			}
-
-skip_lump:
-			stoken = strtok(NULL, "\r\n ");
-			line++;
+			CONS_Alert(CONS_WARNING,
+					"MUSICDEF: Field '%s' is missing name. (file %s, line %d)\n",
+					stoken, wadfiles[wadnum]->filename, line);
+			return false;
 		}
 		else
 		{
-			// If this is set true, the line was invalid.
-			boolean brokenline = false;
+			musicdef_t *prev = NULL;
+			def = musicdefstart;
 
-			// Delimit only by line break.
-			value = strtok(NULL, "\r\n");
+			// Search if this is a replacement
+			//CONS_Printf("S_LoadMusicDefs: Searching for song replacement...\n");
+			while (def)
+			{
+				if (!stricmp(def->name, value))
+				{
+					//CONS_Printf("S_LoadMusicDefs: Found song replacement '%s'\n", def->name);
+					break;
+				}
 
+				prev = def;
+				def = def->next;
+			}
+
+			// Nothing found, add to the end.
+			if (!def)
+			{
+				def = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL);
+				STRBUFCPY(def->name, value);
+				strlwr(def->name);
+				def->bpm = TICRATE<<(FRACBITS-1); // FixedDiv((60*TICRATE)<<FRACBITS, 120<<FRACBITS)
+				if (prev != NULL)
+					prev->next = def;
+				//CONS_Printf("S_LoadMusicDefs: Added song '%s'\n", def->name);
+			}
+
+			(*defp) = def;
+		}
+	}
+	else
+	{
+		value = strtok(NULL, "");
+
+		if (value)
+		{
 			// Find the equals sign.
 			value = strchr(value, '=');
+		}
 
-			// It's not there?!
-			if (!value)
-				brokenline = true;
-			else
-			{
-				// Skip the equals sign.
-				value++;
-
-				// Now skip funny whitespace.
-				if (value[0] == '\0') // :NOTHING:
-					brokenline = true;
-				else
-					value += strspn(value, "\t ");
-			}
-
-			// If the line is valid, copy the text line from the lump data.
-			if (!brokenline)
-			{
-				// strtok returns memory that already belongs to the input string.
-				value = musdeftext + (value - buf);
-
-				// Find the length of the line.
-				size = strcspn(value, "\r\n");
-
-				// Copy the line.
-				textline = malloc(size+1);
-				if (!textline)
-					I_Error("S_LoadMusicDefs: No more free memory for text line\n");
-				M_Memcpy(textline, value, size);
-				textline[size] = '\0';
-			}
-			else
-			{
-				CONS_Alert(CONS_WARNING, "MUSICDEF: Field '%s' is missing value. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line);
-				stoken = strtok(NULL, "\r\n"); // skip end of line
-				goto skip_field;
-			}
+		if (!value)
+		{
+			CONS_Alert(CONS_WARNING,
+					"MUSICDEF: Field '%s' is missing value. (file %s, line %d)\n",
+					stoken, wadfiles[wadnum]->filename, line);
+			return false;
+		}
+		else
+		{
+			def = (*defp);
 
 			if (!def)
 			{
-				CONS_Alert(CONS_ERROR, "MUSICDEF: No music definition before field '%s'. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line);
-				free(textline);
-				free(buf);
-				free(musdeftext);
-				return;
+				CONS_Alert(CONS_ERROR,
+						"MUSICDEF: No music definition before field '%s'. (file %s, line %d)\n",
+						stoken, wadfiles[wadnum]->filename, line);
+				return false;
 			}
 
-			i = atoi(textline);
+			// Skip the equals sign.
+			value++;
 
+			// Now skip funny whitespace.
+			value += strspn(value, "\t ");
+
+			textline = value;
+			i = atoi(value);
+
+			/* based ignored lumps */
 			if (!stricmp(stoken, "usage")) {
 #if 0 // Ignore for now
 				STRBUFCPY(def->usage, textline);
-				//CONS_Printf("S_LoadMusicDefs: Set usage to '%s'\n", def->usage);
 #endif
 			} else if (!stricmp(stoken, "source")) {
 #if 0 // Ignore for now
 				STRBUFCPY(def->source, textline);
-				//CONS_Printf("S_LoadMusicDefs: Set source to '%s'\n", def->usage);
 #endif
 			} else if (!stricmp(stoken, "title")) {
 				STRBUFCPY(def->title, textline);
-				//CONS_Printf("S_LoadMusicDefs: Set title to '%s'\n", def->source);
 			} else if (!stricmp(stoken, "alttitle")) {
 				STRBUFCPY(def->alttitle, textline);
-				//CONS_Printf("S_LoadMusicDefs: Set alttitle to '%s'\n", def->source);
 			} else if (!stricmp(stoken, "authors")) {
 				STRBUFCPY(def->authors, textline);
-				//CONS_Printf("S_LoadMusicDefs: Set authors to '%s'\n", def->source);
 			} else if (!stricmp(stoken, "soundtestpage")) {
 				def->soundtestpage = (UINT8)i;
 			} else if (!stricmp(stoken, "soundtestcond")) {
@@ -1670,21 +1594,86 @@ skip_lump:
 				if (bpmf > 0)
 					def->bpm = FixedDiv((60*TICRATE)<<FRACBITS, bpmf);
 			} else {
-				CONS_Alert(CONS_WARNING, "MUSICDEF: Invalid field '%s'. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line);
+				CONS_Alert(CONS_WARNING,
+						"MUSICDEF: Invalid field '%s'. (file %s, line %d)\n",
+						stoken, wadfiles[wadnum]->filename, line);
 			}
-
-			// Free the temporary text line from memory.
-			free(textline);
-
-skip_field:
-			stoken = strtok(NULL, "\r\n= ");
-			line++;
 		}
 	}
 
-	free(buf);
+	return true;
+}
+
+void S_LoadMusicDefs(UINT16 wadnum)
+{
+	UINT16 lumpnum;
+	char *lump;
+	char *musdeftext;
+	size_t size;
+
+	char *lf;
+	char *stoken;
+
+	size_t nlf;
+	size_t ncr;
+
+	musicdef_t *def = NULL;
+	int line = 1; // for better error msgs
+
+	lumpnum = W_CheckForMusicDefInPwad(wadnum);
+	if (lumpnum == INT16_MAX)
+		return;
+
+	lump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+	size = W_LumpLengthPwad(wadnum, lumpnum);
+
+	// Null-terminated MUSICDEF lump.
+	musdeftext = malloc(size+1);
+	if (!musdeftext)
+		I_Error("S_LoadMusicDefs: No more free memory for the parser\n");
+	M_Memcpy(musdeftext, lump, size);
+	musdeftext[size] = '\0';
+
+	// Find music def
+	stoken = musdeftext;
+	for (;;)
+	{
+		lf = strpbrk(stoken, "\r\n");
+		if (lf)
+		{
+			if (*lf == '\n')
+				nlf = 1;
+			else
+				nlf = 0;
+			*lf++ = '\0';/* now we can delimit to here */
+		}
+
+		stoken = strtok(stoken, " ");
+		if (stoken)
+		{
+			if (! ReadMusicDefFields(wadnum, line, stoken, &def))
+				break;
+		}
+
+		if (lf)
+		{
+			do
+			{
+				line += nlf;
+				ncr = strspn(lf, "\r");/* skip CR */
+				lf += ncr;
+				nlf = strspn(lf, "\n");
+				lf += nlf;
+			}
+			while (nlf || ncr) ;
+
+			stoken = lf;/* now the next nonempty line */
+		}
+		else
+			break;/* EOF */
+	}
+
 	free(musdeftext);
-	return;
 }
 
 //

From 9dbc54284ecdc46080f0e00a5f35040ba94922fc Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Sat, 18 Jan 2020 23:12:30 -0800
Subject: [PATCH 029/129] Opt into new MUSICDEF format (2.2.0 compatibility)

The "VERSION" directive enables features available in a certain version of
SRB2. It may be used as "VERSION 2.2.0".
---
 src/s_sound.c | 92 +++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 79 insertions(+), 13 deletions(-)

diff --git a/src/s_sound.c b/src/s_sound.c
index 848fd671a..d11fc4321 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1441,6 +1441,12 @@ static tic_t     pause_starttic;
 /// Music Definitions
 /// ------------------------
 
+enum
+{
+	MUSICDEF_220,
+	MUSICDEF_221,
+};
+
 musicdef_t soundtestsfx = {
 	"_STSFX", // prevents exactly one valid track name from being used on the sound test
 	"Sound Effects",
@@ -1472,10 +1478,27 @@ static UINT16 W_CheckForMusicDefInPwad(UINT16 wadid)
 	return INT16_MAX; // not found
 }
 
+static void
+MusicDefStrcpy (char *p, const char *s, size_t n, int version)
+{
+	strlcpy(p, s, n);
+	if (version == MUSICDEF_220)
+	{
+		while (( p = strchr(p, '_') ))
+		{
+			n = strspn(p, "_");
+			memset(p, ' ', n); // turn _ into spaces.
+			p += n;
+		}
+	}
+}
+
 static boolean
-ReadMusicDefFields (UINT16 wadnum, int line, char *stoken, musicdef_t **defp)
+ReadMusicDefFields (UINT16 wadnum, int line, boolean fields, char *stoken,
+		musicdef_t **defp, int *versionp)
 {
 	musicdef_t *def;
+	int version;
 
 	char *value;
 	char *textline;
@@ -1525,14 +1548,47 @@ ReadMusicDefFields (UINT16 wadnum, int line, char *stoken, musicdef_t **defp)
 			(*defp) = def;
 		}
 	}
+	else if (!stricmp(stoken, "version"))
+	{
+		if (fields)/* is this not the first field? */
+		{
+			CONS_Alert(CONS_WARNING,
+					"MUSICDEF: Field '%s' must come first. (file %s, line %d)\n",
+					stoken, wadfiles[wadnum]->filename, line);
+			return false;
+		}
+		else
+		{
+			value = strtok(NULL, " ");
+			if (!value)
+			{
+				CONS_Alert(CONS_WARNING,
+						"MUSICDEF: Field '%s' is missing version. (file %s, line %d)\n",
+						stoken, wadfiles[wadnum]->filename, line);
+				return false;
+			}
+			else
+			{
+				if (strcasecmp(value, "2.2.0"))
+					(*versionp) = MUSICDEF_221;
+			}
+		}
+	}
 	else
 	{
-		value = strtok(NULL, "");
+		version = (*versionp);
 
-		if (value)
+		if (version == MUSICDEF_220)
+			value = strtok(NULL, " =");
+		else
 		{
-			// Find the equals sign.
-			value = strchr(value, '=');
+			value = strtok(NULL, "");
+
+			if (value)
+			{
+				// Find the equals sign.
+				value = strchr(value, '=');
+			}
 		}
 
 		if (!value)
@@ -1554,11 +1610,14 @@ ReadMusicDefFields (UINT16 wadnum, int line, char *stoken, musicdef_t **defp)
 				return false;
 			}
 
-			// Skip the equals sign.
-			value++;
+			if (version != MUSICDEF_220)
+			{
+				// Skip the equals sign.
+				value++;
 
-			// Now skip funny whitespace.
-			value += strspn(value, "\t ");
+				// Now skip funny whitespace.
+				value += strspn(value, "\t ");
+			}
 
 			textline = value;
 			i = atoi(value);
@@ -1573,11 +1632,14 @@ ReadMusicDefFields (UINT16 wadnum, int line, char *stoken, musicdef_t **defp)
 				STRBUFCPY(def->source, textline);
 #endif
 			} else if (!stricmp(stoken, "title")) {
-				STRBUFCPY(def->title, textline);
+				MusicDefStrcpy(def->title, textline,
+						sizeof def->title, version);
 			} else if (!stricmp(stoken, "alttitle")) {
-				STRBUFCPY(def->alttitle, textline);
+				MusicDefStrcpy(def->alttitle, textline,
+						sizeof def->alttitle, version);
 			} else if (!stricmp(stoken, "authors")) {
-				STRBUFCPY(def->authors, textline);
+				MusicDefStrcpy(def->authors, textline,
+						sizeof def->authors, version);
 			} else if (!stricmp(stoken, "soundtestpage")) {
 				def->soundtestpage = (UINT8)i;
 			} else if (!stricmp(stoken, "soundtestcond")) {
@@ -1618,7 +1680,9 @@ void S_LoadMusicDefs(UINT16 wadnum)
 	size_t ncr;
 
 	musicdef_t *def = NULL;
+	int version = MUSICDEF_220;
 	int line = 1; // for better error msgs
+	boolean fields = false;
 
 	lumpnum = W_CheckForMusicDefInPwad(wadnum);
 	if (lumpnum == INT16_MAX)
@@ -1651,8 +1715,10 @@ void S_LoadMusicDefs(UINT16 wadnum)
 		stoken = strtok(stoken, " ");
 		if (stoken)
 		{
-			if (! ReadMusicDefFields(wadnum, line, stoken, &def))
+			if (! ReadMusicDefFields(wadnum, line, fields, stoken,
+						&def, &version))
 				break;
+			fields = true;
 		}
 
 		if (lf)

From c1097813f47f717a63b687a670f5e090308b6b52 Mon Sep 17 00:00:00 2001
From: Zwip-Zwap Zapony <ZwipZwapZapony@gmail.com>
Date: Mon, 20 Jan 2020 16:14:20 +0100
Subject: [PATCH 030/129] Fix "cam2_turnmultiplier"

In splitscreen stuff, player 2's "cam2_turnmultiplier" implementation
was wrong compared to player 1's "cam_turnmultiplier"
This commit makes player 2's multiplier work just like player 1's does
---
 src/g_game.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index a80d0f40a..0bec68d6e 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1497,9 +1497,9 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	else
 	{
 		if (turnright)
-			cmd->angleturn = (INT16)(cmd->angleturn - (((angleturn[tspeed]<<FRACBITS) * cv_cam2_turnmultiplier.value)>>FRACBITS));
+			cmd->angleturn = (INT16)(cmd->angleturn - ((angleturn[tspeed] * cv_cam2_turnmultiplier.value)>>FRACBITS));
 		else if (turnleft)
-			cmd->angleturn = (INT16)(cmd->angleturn + (((angleturn[tspeed]<<FRACBITS) * cv_cam2_turnmultiplier.value)>>FRACBITS));
+			cmd->angleturn = (INT16)(cmd->angleturn + ((angleturn[tspeed] * cv_cam2_turnmultiplier.value)>>FRACBITS));
 
 		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
 		{

From 2d8ea7125c24e1e3c6e8b56bdaabfeaa54354e25 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Mon, 20 Jan 2020 15:36:27 -0800
Subject: [PATCH 031/129] Remove unnecessary optimization

---
 src/s_sound.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/s_sound.c b/src/s_sound.c
index d11fc4321..d84e20ab4 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1485,11 +1485,7 @@ MusicDefStrcpy (char *p, const char *s, size_t n, int version)
 	if (version == MUSICDEF_220)
 	{
 		while (( p = strchr(p, '_') ))
-		{
-			n = strspn(p, "_");
-			memset(p, ' ', n); // turn _ into spaces.
-			p += n;
-		}
+			*p++ = ' '; // turn _ into spaces.
 	}
 }
 

From 49a4c7d5f3db21808e1d4875b6f5d5b6989a5ac7 Mon Sep 17 00:00:00 2001
From: Zwip-Zwap Zapony <ZwipZwapZapony@gmail.com>
Date: Tue, 21 Jan 2020 14:53:05 +0100
Subject: [PATCH 032/129] Separate "turnmultiplier"s for splitscreen players

This fixes player 2 using player 1's "cam_turnmultiplier"
instead of player 2's "cam2_turnmultiplier"
---
 src/g_game.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index 646147e73..f1b9b0b5c 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1140,7 +1140,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	INT32 *myaiming = (ssplayer == 1 ? &localaiming : &localaiming2);
 
 	angle_t drawangleoffset = (player->powers[pw_carry] == CR_ROLLOUT) ? ANGLE_180 : 0;
-	INT32 chasecam, chasefreelook, alwaysfreelook, usejoystick, invertmouse, mousemove;
+	INT32 chasecam, chasefreelook, alwaysfreelook, usejoystick, invertmouse, turnmultiplier, mousemove;
 	controlstyle_e controlstyle = G_ControlStyle(ssplayer);
 	INT32 *mx; INT32 *my; INT32 *mly;
 
@@ -1163,6 +1163,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 		alwaysfreelook = cv_alwaysfreelook.value;
 		usejoystick = cv_usejoystick.value;
 		invertmouse = cv_invertmouse.value;
+		turnmultiplier = cv_cam_turnmultiplier.value;
 		mousemove = cv_mousemove.value;
 		mx = &mousex;
 		my = &mousey;
@@ -1176,6 +1177,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 		alwaysfreelook = cv_alwaysfreelook2.value;
 		usejoystick = cv_usejoystick2.value;
 		invertmouse = cv_invertmouse2.value;
+		turnmultiplier = cv_cam2_turnmultiplier.value;
 		mousemove = cv_mousemove2.value;
 		mx = &mouse2x;
 		my = &mouse2y;
@@ -1293,14 +1295,14 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	{
 		if (turnright && turnleft);
 		else if (turnright)
-			cmd->angleturn = (INT16)(cmd->angleturn - ((angleturn[tspeed] * cv_cam_turnmultiplier.value)>>FRACBITS));
+			cmd->angleturn = (INT16)(cmd->angleturn - ((angleturn[tspeed] * turnmultiplier)>>FRACBITS));
 		else if (turnleft)
-			cmd->angleturn = (INT16)(cmd->angleturn + ((angleturn[tspeed] * cv_cam_turnmultiplier.value)>>FRACBITS));
+			cmd->angleturn = (INT16)(cmd->angleturn + ((angleturn[tspeed] * turnmultiplier)>>FRACBITS));
 
 		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
 		{
 			// JOYAXISRANGE should be 1023 (divide by 1024)
-			cmd->angleturn = (INT16)(cmd->angleturn - ((((lookjoystickvector.xaxis * angleturn[1]) >> 10) * cv_cam_turnmultiplier.value)>>FRACBITS)); // ANALOG!
+			cmd->angleturn = (INT16)(cmd->angleturn - ((((lookjoystickvector.xaxis * angleturn[1]) >> 10) * turnmultiplier)>>FRACBITS)); // ANALOG!
 		}
 
 		if (turnright || turnleft || abs(cmd->angleturn) > angleturn[2])

From dfcd058c80db463c4564e1bc7db746e25cc7819e Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Tue, 21 Jan 2020 02:29:29 -0800
Subject: [PATCH 033/129] (BRUH MOMENT) activettscale was -1, so do recache
 after it's set

---
 src/f_finale.c | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/f_finale.c b/src/f_finale.c
index bc904d8f2..e37eab764 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -2693,8 +2693,18 @@ static void F_FigureActiveTtScale(void)
 	SINT8 newttscale = max(1, min(6, vid.dupx));
 	SINT8 oldttscale = activettscale;
 
-	if (newttscale == testttscale)
-		return;
+	if (needpatchrecache)
+		ttloaded[0] = ttloaded[1] = ttloaded[2] = ttloaded[3] = ttloaded[4] = ttloaded[5] = 0;
+	else
+	{
+		if (newttscale == testttscale)
+			return;
+
+		// We have a new ttscale, so load gfx
+		if(oldttscale > 0)
+			F_UnloadAlacroixGraphics(oldttscale);
+	}
+
 	testttscale = newttscale;
 
 	// If ttscale is unavailable: look for lower scales, then higher scales.
@@ -2712,10 +2722,6 @@ static void F_FigureActiveTtScale(void)
 
 	activettscale = (newttscale >= 1 && newttscale <= 6) ? newttscale : 0;
 
-	// We have a new ttscale, so load gfx
-	if(oldttscale > 0)
-		F_UnloadAlacroixGraphics(oldttscale);
-
 	if(activettscale > 0)
 		F_LoadAlacroixGraphics(activettscale);
 }
@@ -2757,12 +2763,6 @@ void F_TitleScreenDrawer(void)
 		return;
 #endif
 
-	if (needpatchrecache && (curttmode == TTMODE_ALACROIX))
-	{
-		ttloaded[0] = ttloaded[1] = ttloaded[2] = ttloaded[3] = ttloaded[4] = ttloaded[5] = 0;
-		F_LoadAlacroixGraphics(activettscale);
-	}
-
 	switch(curttmode)
 	{
 		case TTMODE_OLD:

From c347254e8005b7d9aba5bea4290dded68b5a8a93 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Tue, 21 Jan 2020 03:20:50 -0800
Subject: [PATCH 034/129] Reorder Sonic's ass so the OpenGL wipe doesn't see
 garbage

---
 src/d_main.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/d_main.c b/src/d_main.c
index 2432c5e9e..74547f9f7 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -651,8 +651,12 @@ void D_SRB2Loop(void)
 	// hack to start on a nice clear console screen.
 	COM_ImmedExecute("cls;version");
 
-	V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(W_GetNumForName("CONSBACK"), PU_CACHE));
 	I_FinishUpdate(); // page flip or blit buffer
+	/*
+	LMFAO this was showing garbage under OpenGL
+	because I_FinishUpdate was called afterward
+	*/
+	V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(W_GetNumForName("CONSBACK"), PU_CACHE));
 
 	for (;;)
 	{

From f297f4ff5636cbf081b7991875b563dc06eef3d1 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Tue, 21 Jan 2020 03:28:33 -0800
Subject: [PATCH 035/129] Sonic is dead again

---
 src/d_main.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/d_main.c b/src/d_main.c
index 74547f9f7..32972c151 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -656,7 +656,9 @@ void D_SRB2Loop(void)
 	LMFAO this was showing garbage under OpenGL
 	because I_FinishUpdate was called afterward
 	*/
-	V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(W_GetNumForName("CONSBACK"), PU_CACHE));
+	/* Smells like a hack... Don't fade Sonic's ass into the title screen. */
+	if (gamestate != GS_TITLESCREEN)
+		V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(W_GetNumForName("CONSBACK"), PU_CACHE));
 
 	for (;;)
 	{

From 9cd101c68ddb9618e215e2f4c6dff90c7d1b6a9d Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Tue, 21 Jan 2020 14:55:50 -0800
Subject: [PATCH 036/129] Revert "Fix Ploadflat closing the game with "Too many
 flats in level" error message"

This reverts commit ea129f701c86f87daa7355e6f031d0633951cdfa.
---
 src/p_setup.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_setup.c b/src/p_setup.c
index 42a6438a0..9a270d2f3 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -672,7 +672,7 @@ INT32 P_AddLevelFlat(const char *flatname, levelflat_t *levelflat)
 //
 INT32 P_AddLevelFlatRuntime(const char *flatname)
 {
-	return Ploadflat(levelflats, flatname);
+	return Ploadflat(0, flatname);
 }
 
 // help function for $$$.sav checking

From d90c0b8fbd0e122073b5a8adba09db832a39daf7 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Tue, 21 Jan 2020 15:11:16 -0800
Subject: [PATCH 037/129] Don't check "Too many flats in level" with
 P_AddLevelFlatRuntime

Also moved the debug down in case anyone uses that.
---
 src/p_setup.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/p_setup.c b/src/p_setup.c
index 9a270d2f3..9b5b7e5c2 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -585,15 +585,11 @@ Ploadflat (levelflat_t *levelflat, const char *flatname)
 			if (strnicmp(levelflat[i].name, flatname, 8) == 0)
 				return i;
 		}
+
+		if (numlevelflats >= MAXLEVELFLATS)
+			I_Error("Too many flats in level\n");
 	}
 
-#ifndef ZDEBUG
-	CONS_Debug(DBG_SETUP, "flat #%03d: %s\n", atoi(sizeu1(numlevelflats)), levelflat->name);
-#endif
-
-	if (numlevelflats >= MAXLEVELFLATS)
-		I_Error("Too many flats in level\n");
-
 	if (levelflat)
 		levelflat += numlevelflats;
 	else
@@ -656,6 +652,10 @@ flatfound:
 		levelflat->u.flat.baselumpnum = LUMPERROR;
 	}
 
+#ifndef ZDEBUG
+	CONS_Debug(DBG_SETUP, "flat #%03d: %s\n", atoi(sizeu1(numlevelflats)), levelflat->name);
+#endif
+
 	return ( numlevelflats++ );
 }
 

From 2373a0aa8308816e2e6037fea736bf58a116b4dc Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Tue, 21 Jan 2020 16:47:47 -0800
Subject: [PATCH 038/129] Actually actually match the old behavior and check
 existing levelflats in P_AddLevelFlatRuntime BRUH

---
 src/p_setup.c | 31 +++++++++++++++----------------
 1 file changed, 15 insertions(+), 16 deletions(-)

diff --git a/src/p_setup.c b/src/p_setup.c
index 9b5b7e5c2..132684163 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -566,7 +566,7 @@ levelflat refers to an array of level flats,
 or NULL if we want to allocate it now.
 */
 static INT32
-Ploadflat (levelflat_t *levelflat, const char *flatname)
+Ploadflat (levelflat_t *levelflat, const char *flatname, boolean resize)
 {
 #ifndef NO_PNG_LUMPS
 	UINT8         buffer[8];
@@ -577,27 +577,26 @@ Ploadflat (levelflat_t *levelflat, const char *flatname)
 
 	size_t i;
 
-	if (levelflat)
+	// Scan through the already found flats, return if it matches.
+	for (i = 0; i < numlevelflats; i++)
 	{
-		// Scan through the already found flats, return if it matches.
-		for (i = 0; i < numlevelflats; i++)
-		{
-			if (strnicmp(levelflat[i].name, flatname, 8) == 0)
-				return i;
-		}
-
-		if (numlevelflats >= MAXLEVELFLATS)
-			I_Error("Too many flats in level\n");
+		if (strnicmp(levelflat[i].name, flatname, 8) == 0)
+			return i;
 	}
 
-	if (levelflat)
-		levelflat += numlevelflats;
-	else
+	if (resize)
 	{
 		// allocate new flat memory
 		levelflats = Z_Realloc(levelflats, (numlevelflats + 1) * sizeof(*levelflats), PU_LEVEL, NULL);
 		levelflat  = levelflats + numlevelflats;
 	}
+	else
+	{
+		if (numlevelflats >= MAXLEVELFLATS)
+			I_Error("Too many flats in level\n");
+
+		levelflat += numlevelflats;
+	}
 
 	// Store the name.
 	strlcpy(levelflat->name, flatname, sizeof (levelflat->name));
@@ -663,7 +662,7 @@ flatfound:
 // allocate an id for it, and set the levelflat (to speedup search)
 INT32 P_AddLevelFlat(const char *flatname, levelflat_t *levelflat)
 {
-	return Ploadflat(levelflat, flatname);
+	return Ploadflat(levelflat, flatname, false);
 }
 
 // help function for Lua and $$$.sav reading
@@ -672,7 +671,7 @@ INT32 P_AddLevelFlat(const char *flatname, levelflat_t *levelflat)
 //
 INT32 P_AddLevelFlatRuntime(const char *flatname)
 {
-	return Ploadflat(0, flatname);
+	return Ploadflat(levelflats, flatname, true);
 }
 
 // help function for $$$.sav checking

From d374bf4f9ba239d86b04f93878a496c21280eaf0 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Wed, 22 Jan 2020 03:05:08 +0100
Subject: [PATCH 039/129] Let clients rejoin the server without losing their
 status

This is accomplished by simply preserving
the player's body after disconnecting.

Bodies will despawn after the number of minutes
specified by the "rejointimeout" console variable (float).
A value of 0 disables the feature completely.

Clients rejoining are identified by their IP address,
and may rejoin even if the server is full or joins are disabled,
for as long as their body remains.

From a technical standpoint, when the user disconnects,
the player they were controlling does not leave,
the underlying player_t just keeps working normally,
except it does not receive any input anymore.
When the user reconnects, they are simply "relinked"
to their player_t.

Those "soulless" players can be identified through
their "quittime" field, which is the number of tics
elapsed since the user disconnected, or zero
if still connected. "quittime" is exposed to Lua.
---
 src/command.c        |   2 +-
 src/d_clisrv.c       | 316 +++++++++++++++++++++++++++----------------
 src/d_clisrv.h       |   5 +-
 src/d_netcmd.c       |  35 ++---
 src/d_player.h       |   1 +
 src/g_game.c         |   4 +-
 src/hu_stuff.c       |  14 +-
 src/lua_consolelib.c |   2 +-
 src/lua_hook.h       |   2 +-
 src/lua_hooklib.c    |   2 +-
 src/lua_playerlib.c  |   4 +
 src/p_saveg.c        |   2 +
 src/p_tick.c         |  13 +-
 13 files changed, 252 insertions(+), 150 deletions(-)

diff --git a/src/command.c b/src/command.c
index 31c9b50ad..8a0fc3f4e 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1407,7 +1407,7 @@ static void Got_NetVar(UINT8 **p, INT32 playernum)
 		// not from server or remote admin, must be hacked/buggy client
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal netvar command received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 	netid = READUINT16(*p);
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index e85664d38..d40027a3d 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -21,6 +21,7 @@
 #include "d_net.h"
 #include "d_main.h"
 #include "g_game.h"
+#include "st_stuff.h"
 #include "hu_stuff.h"
 #include "keys.h"
 #include "g_input.h" // JOY1
@@ -391,7 +392,7 @@ static void ExtraDataTicker(void)
 					{
 						if (server)
 						{
-							SendKick(i, KICK_MSG_CON_FAIL);
+							SendKick(i, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 							DEBFILE(va("player %d kicked [gametic=%u] reason as follows:\n", i, gametic));
 						}
 						CONS_Alert(CONS_WARNING, M_GetText("Got unknown net command [%s]=%d (max %d)\n"), sizeu1(curpos - bufferstart), *curpos, bufferstart[0]);
@@ -437,6 +438,9 @@ void SendKick(UINT8 playernum, UINT8 msg)
 {
 	UINT8 buf[2];
 
+	if (!(server && cv_rejointimeout.value))
+		msg &= ~KICK_MSG_KEEP_BODY;
+
 	buf[0] = playernum;
 	buf[1] = msg;
 	SendNetXCmd(XD_KICK, &buf, 2);
@@ -1064,7 +1068,7 @@ static void SV_SendResynch(INT32 node)
 
 	if (resynch_score[node] > (unsigned)cv_resynchattempts.value*250)
 	{
-		SendKick(nodetoplayer[node], KICK_MSG_CON_FAIL);
+		SendKick(nodetoplayer[node], KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		resynch_score[node] = 0;
 	}
 }
@@ -2403,6 +2407,7 @@ void CL_ClearPlayer(INT32 playernum)
 	if (players[playernum].mo)
 		P_RemoveMobj(players[playernum].mo);
 	memset(&players[playernum], 0, sizeof (player_t));
+	memset(playeraddress[playernum], 0, sizeof(*playeraddress));
 }
 
 //
@@ -2410,7 +2415,7 @@ void CL_ClearPlayer(INT32 playernum)
 //
 // Removes a player from the current game
 //
-static void CL_RemovePlayer(INT32 playernum, INT32 reason)
+static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 {
 	// Sanity check: exceptional cases (i.e. c-fails) can cause multiple
 	// kick commands to be issued for the same player.
@@ -2423,9 +2428,6 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
 		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);
@@ -2812,9 +2814,12 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 	char buf[3 + MAX_REASONLENGTH];
 	char *reason = buf;
 	kickreason_t kickreason = KR_KICK;
+	boolean keepbody;
 
 	pnum = READUINT8(*p);
 	msg = READUINT8(*p);
+	keepbody = (msg & KICK_MSG_KEEP_BODY) != 0;
+	msg &= ~KICK_MSG_KEEP_BODY;
 
 	if (pnum == serverplayer && IsPlayerAdmin(playernum))
 	{
@@ -2874,6 +2879,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 */
 		pnum = playernum;
 		msg = KICK_MSG_CON_FAIL;
+		keepbody = true;
 	}
 
 	//CONS_Printf("\x82%s ", player_names[pnum]);
@@ -2942,7 +2948,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			kickreason = KR_TIMEOUT;
 			break;
 		case KICK_MSG_PLAYER_QUIT:
-			if (netgame) // not splitscreen/bots
+			if (netgame && !players[pnum].quittime) // not splitscreen/bots or soulless body
 				HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false);
 			kickreason = KR_LEAVE;
 			break;
@@ -2983,6 +2989,24 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		else
 			M_StartMessage(M_GetText("You have been kicked by the server\n\nPress ESC\n"), NULL, MM_NOTHING);
 	}
+	else if (keepbody)
+	{
+		if (server && !demoplayback)
+		{
+			INT32 node = playernode[pnum];
+			playerpernode[node]--;
+			if (playerpernode[node] <= 0)
+			{
+				nodeingame[node] = false;
+				Net_CloseConnection(node);
+				ResetNode(node);
+			}
+		}
+
+		playernode[pnum] = UINT8_MAX;
+
+		players[pnum].quittime = 1;
+	}
 	else
 		CL_RemovePlayer(pnum, kickreason);
 }
@@ -2991,6 +3015,9 @@ consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, N
 consvar_t cv_joinnextround = {"joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
 static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
 consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}};
+consvar_t cv_rejointimeout = {"rejointimeout", "Off", CV_SAVE|CV_FLOAT, rejointimeout_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}};
 consvar_t cv_resynchattempts = {"resynchattempts", "10", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL	};
 consvar_t cv_blamecfail = {"blamecfail", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
@@ -3060,6 +3087,7 @@ static void ResetNode(INT32 node)
 	nodewaiting[node] = 0;
 	playerpernode[node] = 0;
 	sendingsavegame[node] = false;
+	SV_InitResynchVars(node);
 }
 
 void SV_ResetServer(void)
@@ -3075,13 +3103,8 @@ 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
@@ -3089,6 +3112,7 @@ void SV_ResetServer(void)
 #endif
 		playeringame[i] = false;
 		playernode[i] = UINT8_MAX;
+		memset(playeraddress[i], 0, sizeof(*playeraddress));
 		sprintf(player_names[i], "Player %d", i + 1);
 		adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
 	}
@@ -3179,6 +3203,37 @@ void D_QuitNetGame(void)
 #endif
 }
 
+static INT32 FindRejoinerNum(SINT8 node)
+{
+	char strippednodeaddress[64];
+	const char *nodeaddress;
+	char *port;
+	INT32 i;
+
+	// Make sure there is no dead dress before proceeding to the stripping
+	if (!I_GetNodeAddress)
+		return -1;
+	nodeaddress = I_GetNodeAddress(node);
+	if (!nodeaddress)
+		return -1;
+
+	// Strip the address of its port
+	strcpy(strippednodeaddress, nodeaddress);
+	port = strchr(strippednodeaddress, ':');
+	if (port)
+		*port = '\0';
+
+	// Check if any player matches the stripped address
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playeringame[i] && playeraddress[i][0] && playernode[i] == UINT8_MAX
+		&& !strcmp(playeraddress[i], strippednodeaddress))
+			return i;
+	}
+
+	return -1;
+}
+
 // Adds a node to the game (player will follow at map change or at savegame....)
 static inline void SV_AddNode(INT32 node)
 {
@@ -3195,13 +3250,16 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 {
 	INT16 node, newplayernum;
 	boolean splitscreenplayer;
+	boolean rejoined;
+	player_t *newplayer;
+	char *port;
 
 	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
 		// protect against hacked/buggy client
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -3210,15 +3268,34 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 	splitscreenplayer = newplayernum & 0x80;
 	newplayernum &= ~0x80;
 
-	// Clear player before joining, lest some things get set incorrectly
-	// HACK: don't do this for splitscreen, it relies on preset values
-	if (!splitscreen && !botingame)
-		CL_ClearPlayer(newplayernum);
-	playeringame[newplayernum] = true;
+	rejoined = playeringame[newplayernum];
+
+	if (!rejoined)
+	{
+		// Clear player before joining, lest some things get set incorrectly
+		// HACK: don't do this for splitscreen, it relies on preset values
+		if (!splitscreen && !botingame)
+			CL_ClearPlayer(newplayernum);
+		playeringame[newplayernum] = true;
+		G_AddPlayer(newplayernum);
+		if (newplayernum+1 > doomcom->numslots)
+			doomcom->numslots = (INT16)(newplayernum+1);
+
+		if (server && I_GetNodeAddress)
+		{
+			strcpy(playeraddress[newplayernum], I_GetNodeAddress(node));
+			port = strchr(playeraddress[newplayernum], ':');
+			if (port)
+				*port = '\0';
+		}
+	}
+
+	newplayer = &players[newplayernum];
+
+	newplayer->jointime = 0;
+	newplayer->quittime = 0;
+
 	READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
-	G_AddPlayer(newplayernum);
-	if (newplayernum+1 > doomcom->numslots)
-		doomcom->numslots = (INT16)(newplayernum+1);
 
 	// the server is creating my player
 	if (node == mynode)
@@ -3236,29 +3313,67 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 			secondarydisplayplayer = newplayernum;
 			DEBFILE("spawning my brother\n");
 			if (botingame)
-				players[newplayernum].bot = 1;
+				newplayer->bot = 1;
 		}
 		D_SendPlayerConfig();
 		addedtogame = true;
+
+		if (rejoined)
+		{
+			if (newplayer->mo)
+			{
+				if (!splitscreenplayer)
+					localangle = newplayer->mo->angle;
+				else
+					localangle2 = newplayer->mo->angle;
+
+				newplayer->viewheight = 41*newplayer->height/48;
+
+				if (newplayer->mo->eflags & MFE_VERTICALFLIP)
+					newplayer->viewz = newplayer->mo->z + newplayer->mo->height - newplayer->viewheight;
+				else
+					newplayer->viewz = newplayer->mo->z + newplayer->viewheight;
+			}
+
+			// wake up the status bar
+			ST_Start();
+			// wake up the heads up text
+			HU_Start();
+
+			if (camera.chase && !splitscreenplayer)
+				P_ResetCamera(newplayer, &camera);
+			if (camera2.chase && splitscreenplayer)
+				P_ResetCamera(newplayer, &camera2);
+		}
 	}
 
 	if (netgame)
 	{
-		if (server && cv_showjoinaddress.value)
-		{
-			const char *address;
-			if (I_GetNodeAddress && (address = I_GetNodeAddress(node)) != NULL)
-				HU_AddChatText(va("\x82*%s has joined the game (player %d) (%s)", player_names[newplayernum], newplayernum, address), false);	// merge join notification + IP to avoid clogging console/chat.
-		}
+		char joinmsg[256];
+
+		if (rejoined)
+			strcpy(joinmsg, M_GetText("\x82*%s has rejoined the game (player %d)"));
 		else
-			HU_AddChatText(va("\x82*%s has joined the game (player %d)", player_names[newplayernum], newplayernum), false);	// if you don't wanna see the join address.
+			strcpy(joinmsg, M_GetText("\x82*%s has joined the game (player %d)"));
+		strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum));
+
+		// Merge join notification + IP to avoid clogging console/chat
+		if (server && cv_showjoinaddress.value && I_GetNodeAddress)
+		{
+			const char *address = I_GetNodeAddress(node);
+			if (address)
+				strcat(joinmsg, va(" (%s)", address));
+		}
+
+		HU_AddChatText(joinmsg, false);
 	}
 
 	if (server && multiplayer && motd[0] != '\0')
 		COM_BufAddText(va("sayto %d %s\n", newplayernum, motd));
 
 #ifdef HAVE_BLUA
-	LUAh_PlayerJoin(newplayernum);
+	if (!rejoined)
+		LUAh_PlayerJoin(newplayernum);
 #endif
 }
 
@@ -3267,11 +3382,7 @@ static boolean SV_AddWaitingPlayers(const char *name, const char *name2)
 	INT32 node, n, newplayer = false;
 	UINT8 buf[2 + MAXPLAYERNAME];
 	UINT8 *p;
-	UINT8 newplayernum = 0;
-
-	// What is the reason for this? Why can't newplayernum always be 0?
-	if (dedicated)
-		newplayernum = 1;
+	INT32 newplayernum;
 
 	for (node = 0; node < MAXNETNODES; node++)
 	{
@@ -3280,68 +3391,22 @@ static boolean SV_AddWaitingPlayers(const char *name, const char *name2)
 		{
 			newplayer = true;
 
-			if (netgame)
-				// !!!!!!!!! EXTREMELY SUPER MEGA GIGA ULTRA ULTIMATELY TERRIBLY IMPORTANT !!!!!!!!!
-				//
-				// The line just after that comment is an awful, horrible, terrible, TERRIBLE hack.
-				//
-				// Basically, the fix I did in order to fix the download freezes happens
-				// to cause situations in which a player number does not match
-				// the node number associated to that player.
-				// That is totally normal, there is absolutely *nothing* wrong with that.
-				// Really. Player 7 being tied to node 29, for instance, is totally fine.
-				//
-				// HOWEVER. A few (broken) parts of the netcode do the TERRIBLE mistake
-				// of mixing up the concepts of node and player, resulting in
-				// incorrect handling of cases where a player is tied to a node that has
-				// a different number (which is a totally normal case, or at least should be).
-				// This incorrect handling can go as far as literally
-				// anyone from joining your server at all, forever.
-				//
-				// Given those two facts, there are two options available
-				// in order to let this download freeze fix be:
-				//  1) Fix the broken parts that assume a node is a player or similar bullshit.
-				//  2) Change the part this comment is located at, so that any player who joins
-				//     is given the same number as their associated node.
-				//
-				// No need to say, 1) is by far the obvious best, whereas 2) is a terrible hack.
-				// Unfortunately, after trying 1), I most likely didn't manage to find all
-				// of those broken parts, and thus 2) has become the only safe option that remains.
-				//
-				// So I did this hack.
-				//
-				// If it isn't clear enough, in order to get rid of this ugly hack,
-				// you will have to fix all parts of the netcode that
-				// make a confusion between nodes and players.
-				//
-				// And if it STILL isn't clear enough, a node and a player
-				// is NOT the same thing. Never. NEVER. *NEVER*.
-				//
-				// And if someday you make the terrible mistake of
-				// daring to have the unforgivable idea to try thinking
-				// that a node might possibly be the same as a player,
-				// or that a player should have the same number as its node,
-				// be sure that I will somehow know about it and
-				// hunt you down tirelessly and make you regret it,
-				// even if you live on the other side of the world.
-				//
-				// TODO:            vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
-				// \todo >>>>>>>>>> Remove this horrible hack as soon as possible <<<<<<<<<<
-				// TODO:            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-				//
-				// !!!!!!!!! EXTREMELY SUPER MEGA GIGA ULTRA ULTIMATELY TERRIBLY IMPORTANT !!!!!!!!!
-				newplayernum = node; // OMFG SAY WELCOME TO TEH NEW HACK FOR FIX FIL DOWNLOAD!!1!
-			else // Don't use the hack if we don't have to
+			newplayernum = FindRejoinerNum(node);
+			if (newplayernum == -1)
+			{
 				// search for a free playernum
 				// we can't use playeringame since it is not updated here
-				for (; newplayernum < MAXPLAYERS; newplayernum++)
+				for (newplayernum = dedicated ? 1 : 0; newplayernum < MAXPLAYERS; newplayernum++)
 				{
+					if (playeringame[newplayernum])
+						continue;
 					for (n = 0; n < MAXNETNODES; n++)
 						if (nodetoplayer[n] == newplayernum || nodetoplayer2[n] == newplayernum)
 							break;
 					if (n == MAXNETNODES)
 						break;
 				}
+			}
 
 			// should never happen since we check the playernum
 			// before accepting the join
@@ -3368,8 +3433,6 @@ static boolean SV_AddWaitingPlayers(const char *name, const char *name2)
 			SendNetXCmd(XD_ADDPLAYER, &buf, p - buf);
 
 			DEBFILE(va("Server added player %d node %d\n", newplayernum, node));
-			// use the next free slot (we can't put playeringame[newplayernum] = true here)
-			newplayernum++;
 		}
 	}
 
@@ -3495,16 +3558,19 @@ static size_t TotalTextCmdPerTic(tic_t tic)
 static void HandleConnect(SINT8 node)
 {
 	char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1];
+	INT32 rejoinernum;
 	INT32 i;
 
+	rejoinernum = FindRejoinerNum(node);
+
 	if (bannednode && bannednode[node])
 		SV_SendRefuse(node, M_GetText("You have been banned\nfrom the server"));
 	else if (netbuffer->u.clientcfg.version != VERSION
 		|| netbuffer->u.clientcfg.subversion != SUBVERSION)
 		SV_SendRefuse(node, va(M_GetText("Different SRB2 versions cannot\nplay a netgame!\n(server version %d.%d.%d)"), VERSION/100, VERSION%100, SUBVERSION));
-	else if (!cv_allownewplayer.value && node)
+	else if (!cv_allownewplayer.value && node && rejoinernum == -1)
 		SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment"));
-	else if (D_NumPlayers() >= cv_maxplayers.value)
+	else if (D_NumPlayers() >= cv_maxplayers.value && rejoinernum == -1)
 		SV_SendRefuse(node, va(M_GetText("Maximum players reached: %d"), cv_maxplayers.value));
 	else if (netgame && netbuffer->u.clientcfg.localplayers > 1) // Hacked client?
 		SV_SendRefuse(node, M_GetText("Too many players from\nthis node."));
@@ -3519,7 +3585,7 @@ static void HandleConnect(SINT8 node)
 		for (i = 0; i < netbuffer->u.clientcfg.localplayers - playerpernode[node]; i++)
 		{
 			strlcpy(names[i], netbuffer->u.clientcfg.names[i], MAXPLAYERNAME + 1);
-			if (!EnsurePlayerNameIsGood(names[i], -1))
+			if (!EnsurePlayerNameIsGood(names[i], rejoinernum))
 			{
 				SV_SendRefuse(node, "Bad player name");
 				return;
@@ -3956,7 +4022,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 				}
 				else
 				{
-					SendKick(netconsole, KICK_MSG_CON_FAIL);
+					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 					DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
 						netconsole, realstart, consistancy[realstart%BACKUPTICS],
 						SHORT(netbuffer->u.clientpak.consistancy)));
@@ -4075,6 +4141,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 					kickmsg = KICK_MSG_TIMEOUT;
 				else
 					kickmsg = KICK_MSG_PLAYER_QUIT;
+				kickmsg |= KICK_MSG_KEEP_BODY;
 
 				SendKick(netconsole, kickmsg);
 				nodetoplayer[node] = -1;
@@ -4096,7 +4163,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 			{
 				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHEND", node);
 				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL);
+					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 				break;
 			}
 			resynch_local_inprogress = false;
@@ -4114,7 +4181,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 			{
 				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node);
 				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL);
+					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 				break;
 			}
 
@@ -4174,7 +4241,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 			{
 				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHING", node);
 				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL);
+					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 				break;
 			}
 			resynch_local_inprogress = true;
@@ -4186,7 +4253,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 			{
 				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node);
 				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL);
+					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 				break;
 			}
 
@@ -4210,7 +4277,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 			{
 				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node);
 				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL);
+					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 				break;
 			}
 			if (client)
@@ -4406,7 +4473,7 @@ static void CL_SendClientCmd(void)
 		packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
 		HSendPacket(servernode, false, 0, packetsize);
 	}
-	else if (gamestate != GS_NULL)
+	else if (gamestate != GS_NULL && addedtogame)
 	{
 		G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
 		netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
@@ -4582,6 +4649,11 @@ static void Local_Maketic(INT32 realtics)
 	localcmds.angleturn |= TICCMD_RECEIVED;
 }
 
+// This function is utter bullshit and is responsible for
+// the random desynch that happens when a player spawns.
+// This is because ticcmds are resent to clients if a packet
+// was dropped, and thus modifying them can lead to several
+// clients having their ticcmds set to different values.
 void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle)
 {
 	tic_t tic;
@@ -4615,28 +4687,36 @@ void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle)
 // create missed tic
 static void SV_Maketic(void)
 {
-	INT32 j;
+	INT32 i;
 
-	for (j = 0; j < MAXNETNODES; j++)
-		if (playerpernode[j])
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+
+		// We didn't receive this tic
+		if ((netcmds[maketic % BACKUPTICS][i].angleturn & TICCMD_RECEIVED) == 0)
 		{
-			INT32 player = nodetoplayer[j];
-			if ((netcmds[maketic%BACKUPTICS][player].angleturn & TICCMD_RECEIVED) == 0)
-			{ // we didn't receive this tic
-				INT32 i;
+			ticcmd_t *    ticcmd = &netcmds[(maketic    ) % BACKUPTICS][i];
+			ticcmd_t *prevticcmd = &netcmds[(maketic - 1) % BACKUPTICS][i];
 
-				DEBFILE(va("MISS tic%4d for node %d\n", maketic, j));
-#if defined(PARANOIA) && 0
-				CONS_Debug(DBG_NETPLAY, "Client Misstic %d\n", maketic);
-#endif
-				// copy the old tic
-				for (i = 0; i < playerpernode[j]; i++, player = nodetoplayer2[j])
-				{
-					netcmds[maketic%BACKUPTICS][player] = netcmds[(maketic-1)%BACKUPTICS][player];
-					netcmds[maketic%BACKUPTICS][player].angleturn &= ~TICCMD_RECEIVED;
-				}
+			if (players[i].quittime)
+			{
+				// Copy the angle/aiming from the previous tic
+				// and empty the other inputs
+				memset(ticcmd, 0, sizeof(netcmds[0][0]));
+				ticcmd->angleturn = prevticcmd->angleturn | TICCMD_RECEIVED;
+				ticcmd->aiming = prevticcmd->aiming;
+			}
+			else
+			{
+				DEBFILE(va("MISS tic%4d for player %d\n", maketic, i));
+				// Copy the input from the previous tic
+				*ticcmd = *prevticcmd;
+				ticcmd->angleturn &= ~TICCMD_RECEIVED;
 			}
 		}
+	}
 
 	// all tic are now proceed make the next
 	maketic++;
@@ -4758,7 +4838,7 @@ static inline void PingUpdate(void)
 // ok your net has been bad for too long, you deserve to die.
 					{
 						pingtimeout[i] = 0;
-						SendKick(i, KICK_MSG_PING_HIGH);
+						SendKick(i, KICK_MSG_PING_HIGH | KICK_MSG_KEEP_BODY);
 					}
 				}
 				/*
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 99fae32fb..5ee11056a 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -463,6 +463,7 @@ extern consvar_t cv_playbackspeed;
 #define KICK_MSG_PING_HIGH   6
 #define KICK_MSG_CUSTOM_KICK 7
 #define KICK_MSG_CUSTOM_BAN  8
+#define KICK_MSG_KEEP_BODY   0x80
 
 typedef enum
 {
@@ -490,7 +491,9 @@ extern UINT32 realpingtable[MAXPLAYERS];
 extern UINT32 playerpingtable[MAXPLAYERS];
 extern tic_t servermaxping;
 
-extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend, cv_noticedownload, cv_downloadspeed;
+extern consvar_t cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_rejointimeout;
+extern consvar_t cv_resynchattempts, cv_blamecfail;
+extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed;
 
 // Used in d_net, the only dependence
 tic_t ExpandTics(INT32 low);
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index cfcc1b2c5..af1bb8948 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -572,6 +572,7 @@ void D_RegisterServerCommands(void)
 
 	// d_clisrv
 	CV_RegisterVar(&cv_maxplayers);
+	CV_RegisterVar(&cv_rejointimeout);
 	CV_RegisterVar(&cv_resynchattempts);
 	CV_RegisterVar(&cv_maxsend);
 	CV_RegisterVar(&cv_noticedownload);
@@ -1091,7 +1092,7 @@ static void SetPlayerName(INT32 playernum, char *newname)
 	{
 		CONS_Printf(M_GetText("Player %d sent a bad name change\n"), playernum+1);
 		if (server && netgame)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 	}
 }
 
@@ -1449,7 +1450,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 		if (kick)
 		{
 			CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s (team: %d), color: %d)\n"), player_names[playernum], p->ctfteam, p->skincolor);
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 			return;
 		}
 	}
@@ -2032,7 +2033,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal map change received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -2142,7 +2143,7 @@ static void Got_Pause(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal pause command received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -2215,7 +2216,7 @@ static void Got_Suicide(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -2278,7 +2279,7 @@ static void Got_Clearscores(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal clear scores command received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -2625,7 +2626,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 		// this should never happen unless the client is hacked/buggy
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 	}
 
 	if (NetPacket.packet.verification) // Special marker that the server sent the request
@@ -2634,7 +2635,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 		{
 			CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
 			if (server)
-				SendKick(playernum, KICK_MSG_CON_FAIL);
+				SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 			return;
 		}
 		playernum = NetPacket.packet.playernum;
@@ -2667,7 +2668,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 		{
 			CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
 			if (server)
-				SendKick(playernum, KICK_MSG_CON_FAIL);
+				SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		}
 		return;
 	}
@@ -2716,7 +2717,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 	if (server && ((NetPacket.packet.newteam < 0 || NetPacket.packet.newteam > 3) || error))
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
-		SendKick(playernum, KICK_MSG_CON_FAIL);
+		SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 	}
 
 	//Safety first!
@@ -3000,7 +3001,7 @@ static void Got_Verification(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal verification received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -3050,7 +3051,7 @@ static void Got_Removal(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal demotion received from %s (serverplayer is %s)\n"), player_names[playernum], player_names[serverplayer]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -3124,7 +3125,7 @@ static void Got_MotD_f(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal motd change received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		Z_Free(mymotd);
 		return;
 	}
@@ -3180,7 +3181,7 @@ static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal runsoc command received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -3338,7 +3339,7 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 	if ((playernum != serverplayer && !IsPlayerAdmin(playernum)) || kick)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]);
-		SendKick(playernum, KICK_MSG_CON_FAIL);
+		SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -3387,7 +3388,7 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal addfile command received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -4170,7 +4171,7 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal exitlevel command received from %s\n"), player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
diff --git a/src/d_player.h b/src/d_player.h
index 62f38193f..db55a9913 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -510,6 +510,7 @@ typedef struct player_s
 	UINT8 bot;
 
 	tic_t jointime; // Timer when player joins game to change skin/color
+	tic_t quittime; // Time elapsed since user disconnected, zero if connected
 #ifdef HWRENDER
 	fixed_t fovadd; // adjust FOV for hw rendering
 #endif
diff --git a/src/g_game.c b/src/g_game.c
index 2a12dd298..956751bb9 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2307,6 +2307,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
 	INT32 skin;
 	UINT32 availabilities;
 	tic_t jointime;
+	tic_t quittime;
 	boolean spectator;
 	boolean outofcoop;
 	INT16 bot;
@@ -2320,6 +2321,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
 	ctfteam = players[player].ctfteam;
 	exiting = players[player].exiting;
 	jointime = players[player].jointime;
+	quittime = players[player].quittime;
 	spectator = players[player].spectator;
 	outofcoop = players[player].outofcoop;
 	pflags = (players[player].pflags & (PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE|PF_TAGIT|PF_GAMETYPEOVER));
@@ -2391,6 +2393,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
 	p->pflags = pflags;
 	p->ctfteam = ctfteam;
 	p->jointime = jointime;
+	p->quittime = quittime;
 	p->spectator = spectator;
 	p->outofcoop = outofcoop;
 
@@ -2974,7 +2977,6 @@ void G_AddPlayer(INT32 playernum)
 		}
 	}
 
-	p->jointime = 0;
 	p->playerstate = PST_REBORN;
 
 	p->height = mobjinfo[MT_PLAYER].height;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 1ad04f6ff..66ed8b11a 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -659,7 +659,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 			M_GetText("Illegal say command received from %s while muted\n") : M_GetText("Illegal csay command received from non-admin %s\n"),
 			player_names[playernum]);
 		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL);
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
@@ -673,7 +673,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 			{
 				CONS_Alert(CONS_WARNING, M_GetText("Illegal say command received from %s containing invalid characters\n"), player_names[playernum]);
 				if (server)
-					SendKick(playernum, KICK_MSG_CON_FAIL);
+					SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 				return;
 			}
 		}
@@ -2367,7 +2367,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 
 		if (!splitscreen) // don't draw it on splitscreen,
 		{
-			if (!(tab[i].num == serverplayer))
+			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
 				HU_drawPing(x+ 253, y, playerpingtable[tab[i].num], false, 0);
 			//else
 			//	V_DrawSmallString(x+ 246, y+4, V_YELLOWMAP, "SERVER");
@@ -2566,7 +2566,7 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
 		if (!splitscreen)
 		{
-			if (!(tab[i].num == serverplayer))
+			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
 				HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0);
 		//else
 			//V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
@@ -2690,7 +2690,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 		if (!splitscreen)
 		{
-			if (!(tab[i].num == serverplayer))
+			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
 				HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0);
 		//else
 		//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
@@ -2721,7 +2721,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		supercheck = supercheckdef;
 
 		strlcpy(name, tab[i].name, 7);
-		if (!(tab[i].num == serverplayer))
+		if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
 			HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0);
 		//else
 		//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
@@ -2829,7 +2829,7 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
 		strlcpy(name, tab[i].name, 7);
 		if (!splitscreen) // don't draw it on splitscreen,
 		{
-			if (!(tab[i].num == serverplayer))
+			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
 				HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0);
 		//else
 		//	V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 4d23f73b2..90ea85382 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -87,7 +87,7 @@ deny:
 
 	CONS_Alert(CONS_WARNING, M_GetText("Illegal lua command received from %s\n"), player_names[playernum]);
 	if (server)
-		SendKick(playernum, KICK_MSG_CON_FAIL);
+		SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 }
 
 // Wrapper for COM_AddCommand commands
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 6617bca93..c092c0a94 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -91,7 +91,7 @@ boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8
 boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnMapThing by mobj type
 boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj); // Hook for P_PlayerAfterThink Smiles mobj-following
 UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj); // Hook for P_PlayerCanDamage
-void LUAh_PlayerQuit(player_t *plr, int reason); // Hook for player quitting
+void LUAh_PlayerQuit(player_t *plr, kickreason_t reason); // Hook for player quitting
 void LUAh_IntermissionThinker(void); // Hook for Y_Ticker
 
 #endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index ef87d0b6f..acc82a66a 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -1295,7 +1295,7 @@ UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj)
 	return shouldCollide;
 }
 
-void LUAh_PlayerQuit(player_t *plr, int reason)
+void LUAh_PlayerQuit(player_t *plr, kickreason_t reason)
 {
 	hook_p hookp;
 	if (!gL || !(hooksAvailable[hook_PlayerQuit/8] & (1<<(hook_PlayerQuit%8))))
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index c501fbbb2..1dd4c45b5 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -362,6 +362,8 @@ static int player_get(lua_State *L)
 		lua_pushinteger(L, plr->bot);
 	else if (fastcmp(field,"jointime"))
 		lua_pushinteger(L, plr->jointime);
+	else if (fastcmp(field,"quittime"))
+		lua_pushinteger(L, plr->quittime);
 #ifdef HWRENDER
 	else if (fastcmp(field,"fovadd"))
 		lua_pushfixed(L, plr->fovadd);
@@ -701,6 +703,8 @@ static int player_set(lua_State *L)
 		return NOSET;
 	else if (fastcmp(field,"jointime"))
 		plr->jointime = (tic_t)luaL_checkinteger(L, 3);
+	else if (fastcmp(field,"quittime"))
+		plr->quittime = (tic_t)luaL_checkinteger(L, 3);
 #ifdef HWRENDER
 	else if (fastcmp(field,"fovadd"))
 		plr->fovadd = luaL_checkfixed(L, 3);
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 89447db80..1db1e893e 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -255,6 +255,7 @@ static void P_NetArchivePlayers(void)
 		WRITEINT32(save_p, players[i].onconveyor);
 
 		WRITEUINT32(save_p, players[i].jointime);
+		WRITEUINT32(save_p, players[i].quittime);
 
 		WRITEUINT16(save_p, flags);
 
@@ -446,6 +447,7 @@ static void P_NetUnArchivePlayers(void)
 		players[i].onconveyor = READINT32(save_p);
 
 		players[i].jointime = READUINT32(save_p);
+		players[i].quittime = READUINT32(save_p);
 
 		flags = READUINT16(save_p);
 
diff --git a/src/p_tick.c b/src/p_tick.c
index e0f60bd22..6f28d0de0 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -590,10 +590,19 @@ void P_Ticker(boolean run)
 {
 	INT32 i;
 
-	//Increment jointime even if paused.
+	// Increment jointime and quittime even if paused
 	for (i = 0; i < MAXPLAYERS; i++)
 		if (playeringame[i])
-			++players[i].jointime;
+		{
+			players[i].jointime++;
+
+			if (players[i].quittime)
+			{
+				players[i].quittime++;
+				if (server && players[i].quittime >= FixedMul(cv_rejointimeout.value, 60 * TICRATE))
+					SendKick(i, KICK_MSG_PLAYER_QUIT);
+			}
+		}
 
 	if (objectplacing)
 	{

From 73d0549fa43da04aa8954c53766aa727b2c71204 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Wed, 22 Jan 2020 03:11:05 +0100
Subject: [PATCH 040/129] Ignore players towards exit count 30 seconds after
 their disconnection

---
 src/g_game.c | 2 ++
 src/p_user.c | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/src/g_game.c b/src/g_game.c
index 956751bb9..30519b379 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2999,6 +2999,8 @@ boolean G_EnoughPlayersFinished(void)
 	{
 		if (!playeringame[i] || players[i].spectator || players[i].bot)
 			continue;
+		if (players[i].quittime > 30 * TICRATE)
+			continue;
 		if (players[i].lives <= 0)
 			continue;
 
diff --git a/src/p_user.c b/src/p_user.c
index ea42a2c36..4044428b8 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -11479,6 +11479,8 @@ void P_PlayerThink(player_t *player)
 			{
 				if (!playeringame[i] || players[i].spectator || players[i].bot)
 					continue;
+				if (players[i].quittime > 30 * TICRATE)
+					continue;
 				if (players[i].lives <= 0)
 					continue;
 

From d03c928baa35e133683df7395bbb775aa29cd5a7 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Wed, 22 Jan 2020 03:14:44 +0100
Subject: [PATCH 041/129] Give flashing tics to disconnected players and
 prevent drowning

---
 src/p_enemy.c | 3 +++
 src/p_user.c  | 8 ++++++--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/p_enemy.c b/src/p_enemy.c
index 74a11fe67..186b0854d 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -748,6 +748,9 @@ boolean P_LookForPlayers(mobj_t *actor, boolean allaround, boolean tracer, fixed
 		if (player->bot)
 			continue; // ignore bots
 
+		if (player->quittime)
+			continue; // Ignore uncontrolled bodies
+
 		if (dist > 0
 			&& P_AproxDistance(P_AproxDistance(player->mo->x - actor->x, player->mo->y - actor->y), player->mo->z - actor->z) > dist)
 			continue; // Too far away
diff --git a/src/p_user.c b/src/p_user.c
index 4044428b8..2d87c910b 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -11921,6 +11921,10 @@ void P_PlayerThink(player_t *player)
 			player->pflags &= ~PF_USEDOWN;
 	}
 
+	// IF PLAYER NOT HERE THEN FLASH END IF
+	if (player->quittime && player->powers[pw_flashing] < flashingtics - 1)
+		player->powers[pw_flashing] = flashingtics - 1;
+
 	// Counters, time dependent power ups.
 	// Time Bonus & Ring Bonus count settings
 
@@ -11964,12 +11968,12 @@ void P_PlayerThink(player_t *player)
 		else
 			player->powers[pw_underwater] = 0;
 	}
-	else if (player->powers[pw_underwater] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && player->spectator)) // underwater timer
+	else if (player->powers[pw_underwater] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && (player->spectator || player->quittime))) // underwater timer
 		player->powers[pw_underwater]--;
 
 	if (player->powers[pw_spacetime] && (player->pflags & PF_GODMODE || (player->powers[pw_shield] & SH_PROTECTWATER)))
 		player->powers[pw_spacetime] = 0;
-	else if (player->powers[pw_spacetime] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && player->spectator)) // underwater timer
+	else if (player->powers[pw_spacetime] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && (player->spectator || player->quittime))) // underwater timer
 		player->powers[pw_spacetime]--;
 
 	if (player->powers[pw_gravityboots] && player->powers[pw_gravityboots] < UINT16_MAX)

From 412ba38a81b79909383bdeb94f12baf381b791d0 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Wed, 22 Jan 2020 03:19:15 +0100
Subject: [PATCH 042/129] Refactor player spawning code a little

---
 src/g_game.c  | 126 ++++++++++++++++++++++++++------------------------
 src/g_game.h  |   4 +-
 src/p_setup.c |   6 +--
 3 files changed, 71 insertions(+), 65 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index 30519b379..ce28766ec 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2547,74 +2547,24 @@ static boolean G_CheckSpot(INT32 playernum, mapthing_t *mthing)
 // or a not-so-appropriate spot, if it initially fails
 // due to a lack of starts open or something.
 //
-void G_SpawnPlayer(INT32 playernum, boolean starpost)
+void G_SpawnPlayer(INT32 playernum)
 {
-	mapthing_t *spawnpoint;
-
 	if (!playeringame[playernum])
 		return;
 
 	P_SpawnPlayer(playernum);
-
-	if (starpost) //Don't even bother with looking for a place to spawn.
-	{
-		P_MovePlayerToStarpost(playernum);
-#ifdef HAVE_BLUA
-		LUAh_PlayerSpawn(&players[playernum]); // Lua hook for player spawning :)
-#endif
-		return;
-	}
-
-	// -- CTF --
-	// Order: CTF->DM->Coop
-	if (gametype == GT_CTF && players[playernum].ctfteam)
-	{
-		if (!(spawnpoint = G_FindCTFStart(playernum)) // find a CTF start
-		&& !(spawnpoint = G_FindMatchStart(playernum))) // find a DM start
-			spawnpoint = G_FindCoopStart(playernum); // fallback
-	}
-
-	// -- DM/Tag/CTF-spectator/etc --
-	// Order: DM->CTF->Coop
-	else if (gametype == GT_MATCH || gametype == GT_TEAMMATCH || gametype == GT_CTF
-	 || ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && !(players[playernum].pflags & PF_TAGIT)))
-	{
-		if (!(spawnpoint = G_FindMatchStart(playernum)) // find a DM start
-		&& !(spawnpoint = G_FindCTFStart(playernum))) // find a CTF start
-			spawnpoint = G_FindCoopStart(playernum); // fallback
-	}
-
-	// -- Other game modes --
-	// Order: Coop->DM->CTF
-	else
-	{
-		if (!(spawnpoint = G_FindCoopStart(playernum)) // find a Co-op start
-		&& !(spawnpoint = G_FindMatchStart(playernum))) // find a DM start
-			spawnpoint = G_FindCTFStart(playernum); // fallback
-	}
-
-	//No spawns found.  ANYWHERE.
-	if (!spawnpoint)
-	{
-		if (nummapthings)
-		{
-			if (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer))
-				CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the first mapthing!\n"));
-			spawnpoint = &mapthings[0];
-		}
-		else
-		{
-			if (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer))
-				CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the origin!\n"));
-			//P_MovePlayerToSpawn handles this fine if the spawnpoint is NULL.
-		}
-	}
-	P_MovePlayerToSpawn(playernum, spawnpoint);
-
+	G_MovePlayerToSpawnOrStarpost(playernum);
 #ifdef HAVE_BLUA
 	LUAh_PlayerSpawn(&players[playernum]); // Lua hook for player spawning :)
 #endif
+}
 
+void G_MovePlayerToSpawnOrStarpost(INT32 playernum)
+{
+	if (players[playernum].starposttime)
+		P_MovePlayerToStarpost(playernum);
+	else
+		P_MovePlayerToSpawn(playernum, G_FindMapStart(playernum));
 }
 
 mapthing_t *G_FindCTFStart(INT32 playernum)
@@ -2711,6 +2661,60 @@ mapthing_t *G_FindCoopStart(INT32 playernum)
 	return NULL;
 }
 
+mapthing_t *G_FindMapStart(INT32 playernum)
+{
+	mapthing_t *spawnpoint;
+
+	if (!playeringame[playernum])
+		return NULL;
+
+	// -- CTF --
+	// Order: CTF->DM->Coop
+	if (gametype == GT_CTF && players[playernum].ctfteam)
+	{
+		if (!(spawnpoint = G_FindCTFStart(playernum)) // find a CTF start
+		&& !(spawnpoint = G_FindMatchStart(playernum))) // find a DM start
+			spawnpoint = G_FindCoopStart(playernum); // fallback
+	}
+
+	// -- DM/Tag/CTF-spectator/etc --
+	// Order: DM->CTF->Coop
+	else if (gametype == GT_MATCH || gametype == GT_TEAMMATCH || gametype == GT_CTF
+	 || ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && !(players[playernum].pflags & PF_TAGIT)))
+	{
+		if (!(spawnpoint = G_FindMatchStart(playernum)) // find a DM start
+		&& !(spawnpoint = G_FindCTFStart(playernum))) // find a CTF start
+			spawnpoint = G_FindCoopStart(playernum); // fallback
+	}
+
+	// -- Other game modes --
+	// Order: Coop->DM->CTF
+	else
+	{
+		if (!(spawnpoint = G_FindCoopStart(playernum)) // find a Co-op start
+		&& !(spawnpoint = G_FindMatchStart(playernum))) // find a DM start
+			spawnpoint = G_FindCTFStart(playernum); // fallback
+	}
+
+	//No spawns found. ANYWHERE.
+	if (!spawnpoint)
+	{
+		if (nummapthings)
+		{
+			if (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer))
+				CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the first mapthing!\n"));
+			spawnpoint = &mapthings[0];
+		}
+		else
+		{
+			if (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer))
+				CONS_Alert(CONS_ERROR, M_GetText("No player spawns found, spawning at the origin!\n"));
+		}
+	}
+
+	return spawnpoint;
+}
+
 // Go back through all the projectiles and remove all references to the old
 // player mobj, replacing them with the new one.
 void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo)
@@ -2889,7 +2893,7 @@ void G_DoReborn(INT32 playernum)
 			{
 				if (!playeringame[i])
 					continue;
-				G_SpawnPlayer(i, (players[i].starposttime));
+				G_SpawnPlayer(i);
 			}
 
 			// restore time in netgame (see also p_setup.c)
@@ -2935,7 +2939,7 @@ void G_DoReborn(INT32 playernum)
 			P_RemoveMobj(player->mo);
 		}
 
-		G_SpawnPlayer(playernum, (player->starposttime));
+		G_SpawnPlayer(playernum);
 		if (oldmo)
 			G_ChangePlayerReferences(oldmo, players[playernum].mo);
 	}
diff --git a/src/g_game.h b/src/g_game.h
index e7f4a4677..7c4bd2161 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -133,7 +133,9 @@ void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc);
 mapthing_t *G_FindCTFStart(INT32 playernum);
 mapthing_t *G_FindMatchStart(INT32 playernum);
 mapthing_t *G_FindCoopStart(INT32 playernum);
-void G_SpawnPlayer(INT32 playernum, boolean starpost);
+mapthing_t *G_FindMapStart(INT32 playernum);
+void G_MovePlayerToSpawnOrStarpost(INT32 playernum);
+void G_SpawnPlayer(INT32 playernum);
 
 // Can be called by the startup code or M_Responder.
 // A normal game starts at map 1, but a warp test can start elsewhere
diff --git a/src/p_setup.c b/src/p_setup.c
index 673a9024e..b45d3bd80 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -3057,11 +3057,11 @@ boolean P_SetupLevel(boolean skipprecip)
 
 				if (players[i].starposttime)
 				{
-					G_SpawnPlayer(i, true);
+					G_SpawnPlayer(i);
 					P_ClearStarPost(players[i].starpostnum);
 				}
 				else
-					G_SpawnPlayer(i, false);
+					G_SpawnPlayer(i);
 			}
 		}
 
@@ -3133,7 +3133,7 @@ boolean P_SetupLevel(boolean skipprecip)
 			if (players[playersactive[i]].mo)
 				P_RemoveMobj(players[playersactive[i]].mo);
 
-			G_SpawnPlayer(playersactive[i], false); //respawn the lucky player in his dedicated spawn location.
+			G_SpawnPlayer(playersactive[i]); //respawn the lucky player in his dedicated spawn location.
 		}
 		else
 			CONS_Printf(M_GetText("No player currently available to become IT. Awaiting available players.\n"));

From 2527b6c3892ea616fa17b689a9f1a04b67dd0df5 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Wed, 22 Jan 2020 03:20:27 +0100
Subject: [PATCH 043/129] Teleport disconnected players to starpost if they
 fall in a pit

---
 src/p_spec.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/p_spec.c b/src/p_spec.c
index dba4e17b5..7547f604f 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4415,10 +4415,18 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 		case 6: // Death Pit (Camera Mod)
 		case 7: // Death Pit (No Camera Mod)
 			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
-				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_DEATHPIT);
+			{
+				if (player->quittime)
+					G_MovePlayerToSpawnOrStarpost(player - players);
+				else
+					P_DamageMobj(player->mo, NULL, NULL, 1, DMG_DEATHPIT);
+			}
 			break;
 		case 8: // Instant Kill
-			P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
+			if (player->quittime)
+				G_MovePlayerToSpawnOrStarpost(player - players);
+			else
+				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
 			break;
 		case 9: // Ring Drainer (Floor Touch)
 		case 10: // Ring Drainer (No Floor Touch)

From cfec8609f943c2b7520027e06cd5fa0839e02754 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Wed, 22 Jan 2020 04:01:06 +0100
Subject: [PATCH 044/129] Fix missing declaration

---
 src/d_clisrv.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index fc50177ef..0106d559c 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -77,6 +77,7 @@ char motd[254], server_context[8]; // Message of the Day, Unique Context (even w
 
 // Server specific vars
 UINT8 playernode[MAXPLAYERS];
+char playeraddress[MAXPLAYERS][64];
 
 // Minimum timeout for sending the savegame
 // The actual timeout will be longer depending on the savegame length

From fb28ede66049396d531c1d910646882a037569c4 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Wed, 22 Jan 2020 16:58:57 +0100
Subject: [PATCH 045/129] Rename R_IsPointInSubsector to
 R_PointInSubsectorOrNull/Nil

The old name made it really easy to accidentally read
R_IsPointInSubsector as R_PointInSubsector, and anyway it
didn't even make sense...
---
 src/lua_baselib.c | 6 +++---
 src/m_cheat.c     | 8 ++++----
 src/p_mobj.c      | 2 +-
 src/p_user.c      | 4 ++--
 src/r_main.c      | 4 ++--
 src/r_main.h      | 2 +-
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 7a16f3c69..66bd30e32 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -2193,11 +2193,11 @@ static int lib_rPointInSubsector(lua_State *L)
 	return 1;
 }
 
-static int lib_rIsPointInSubsector(lua_State *L)
+static int lib_rPointInSubsectorOrNil(lua_State *L)
 {
 	fixed_t x = luaL_checkfixed(L, 1);
 	fixed_t y = luaL_checkfixed(L, 2);
-	subsector_t *sub = R_IsPointInSubsector(x, y);
+	subsector_t *sub = R_PointInSubsectorOrNull(x, y);
 	//HUDSAFE
 	INLEVEL
 	if (sub)
@@ -3141,7 +3141,7 @@ static luaL_Reg lib[] = {
 	{"R_PointToDist",lib_rPointToDist},
 	{"R_PointToDist2",lib_rPointToDist2},
 	{"R_PointInSubsector",lib_rPointInSubsector},
-	{"R_IsPointInSubsector",lib_rIsPointInSubsector},
+	{"R_PointInSubsectorOrNil",lib_rPointInSubsectorOrNil},
 
 	// r_things (sprite)
 	{"R_Char2Frame",lib_rChar2Frame},
diff --git a/src/m_cheat.c b/src/m_cheat.c
index c284acf3e..980d9fc21 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -452,7 +452,7 @@ void Command_RTeleport_f(void)
 	else
 		inty = 0;
 
-	ss = R_IsPointInSubsector(p->mo->x + intx*FRACUNIT, p->mo->y + inty*FRACUNIT);
+	ss = R_PointInSubsectorOrNull(p->mo->x + intx*FRACUNIT, p->mo->y + inty*FRACUNIT);
 	if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("Not a valid location.\n"));
@@ -530,7 +530,7 @@ void Command_Teleport_f(void)
 			inty = mt->y<<FRACBITS;
 			offset = mt->z<<FRACBITS;
 
-			ss = R_IsPointInSubsector(intx, inty);
+			ss = R_PointInSubsectorOrNull(intx, inty);
 			if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
 			{
 				CONS_Alert(CONS_NOTICE, M_GetText("Spawnpoint not in a valid location.\n"));
@@ -597,7 +597,7 @@ void Command_Teleport_f(void)
 				return;
 			}
 
-			ss = R_IsPointInSubsector(mo2->x, mo2->y);
+			ss = R_PointInSubsectorOrNull(mo2->x, mo2->y);
 			if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
 			{
 				CONS_Alert(CONS_NOTICE, M_GetText("Starpost not in a valid location.\n"));
@@ -653,7 +653,7 @@ void Command_Teleport_f(void)
 			}
 		}
 
-		ss = R_IsPointInSubsector(intx, inty);
+		ss = R_PointInSubsectorOrNull(intx, inty);
 		if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
 		{
 			CONS_Alert(CONS_NOTICE, M_GetText("Not a valid location.\n"));
diff --git a/src/p_mobj.c b/src/p_mobj.c
index a8599ceb5..c4ba9c14f 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -11114,7 +11114,7 @@ void P_SpawnPrecipitation(void)
 		x = basex + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
 		y = basey + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
 
-		precipsector = R_IsPointInSubsector(x, y);
+		precipsector = R_PointInSubsectorOrNull(x, y);
 
 		// No sector? Stop wasting time,
 		// move on to the next entry in the blockmap
diff --git a/src/p_user.c b/src/p_user.c
index a1df8a58b..74349e185 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -3160,7 +3160,7 @@ static void P_DoClimbing(player_t *player)
 	platx = P_ReturnThrustX(player->mo, player->mo->angle, player->mo->radius + FixedMul(8*FRACUNIT, player->mo->scale));
 	platy = P_ReturnThrustY(player->mo, player->mo->angle, player->mo->radius + FixedMul(8*FRACUNIT, player->mo->scale));
 
-	glidesector = R_IsPointInSubsector(player->mo->x + platx, player->mo->y + platy);
+	glidesector = R_PointInSubsectorOrNull(player->mo->x + platx, player->mo->y + platy);
 
 	{
 		boolean floorclimb = false;
@@ -10235,7 +10235,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 
 	// move camera down to move under lower ceilings
-	newsubsec = R_IsPointInSubsector(((mo->x>>FRACBITS) + (thiscam->x>>FRACBITS))<<(FRACBITS-1), ((mo->y>>FRACBITS) + (thiscam->y>>FRACBITS))<<(FRACBITS-1));
+	newsubsec = R_PointInSubsectorOrNull(((mo->x>>FRACBITS) + (thiscam->x>>FRACBITS))<<(FRACBITS-1), ((mo->y>>FRACBITS) + (thiscam->y>>FRACBITS))<<(FRACBITS-1));
 
 	if (!newsubsec)
 		newsubsec = thiscam->subsector;
diff --git a/src/r_main.c b/src/r_main.c
index a93cd6f49..f4daac834 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -701,9 +701,9 @@ subsector_t *R_PointInSubsector(fixed_t x, fixed_t y)
 }
 
 //
-// R_IsPointInSubsector, same as above but returns 0 if not in subsector
+// R_PointInSubsectorOrNull, same as above but returns 0 if not in subsector
 //
-subsector_t *R_IsPointInSubsector(fixed_t x, fixed_t y)
+subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y)
 {
 	node_t *node;
 	INT32 side, i;
diff --git a/src/r_main.h b/src/r_main.h
index 3e575fbd7..d72e94973 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -64,7 +64,7 @@ fixed_t R_PointToDist2(fixed_t px2, fixed_t py2, fixed_t px1, fixed_t py1);
 
 fixed_t R_ScaleFromGlobalAngle(angle_t visangle);
 subsector_t *R_PointInSubsector(fixed_t x, fixed_t y);
-subsector_t *R_IsPointInSubsector(fixed_t x, fixed_t y);
+subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y);
 
 boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixed_t bottomh, fixed_t toph);
 

From aa05581de2959c2d61b9675c3ce47c900442a283 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bartu=20=C4=B0nce?= <bartuince97@gmail.com>
Date: Wed, 22 Jan 2020 18:53:17 +0100
Subject: [PATCH 046/129] Added support for 10+ emblem hints

---
 src/m_menu.c | 166 +++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 162 insertions(+), 4 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 62bea7ae0..0a39148ec 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -231,6 +231,8 @@ static void M_Credits(INT32 choice);
 static void M_SoundTest(INT32 choice);
 static void M_PandorasBox(INT32 choice);
 static void M_EmblemHints(INT32 choice);
+static void M_EmblemHintsFull(INT32 choice);
+static void M_HandleEmblemHints(INT32 choice);
 static void M_HandleChecklist(INT32 choice);
 menu_t SR_MainDef, SR_UnlockChecklistDef;
 
@@ -342,6 +344,7 @@ static void M_DrawAddons(void);
 static void M_DrawChecklist(void);
 static void M_DrawSoundTest(void);
 static void M_DrawEmblemHints(void);
+static void M_DrawEmblemHintsFull(void);
 static void M_DrawPauseMenu(void);
 static void M_DrawServerMenu(void);
 static void M_DrawLevelPlatterMenu(void);
@@ -727,8 +730,14 @@ static menuitem_t SR_SoundTestMenu[] =
 
 static menuitem_t SR_EmblemHintMenu[] =
 {
-	{IT_STRING|IT_CVAR,         NULL, "Emblem Radar", &cv_itemfinder, 10},
-	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",         &SPauseDef,     20}
+	{IT_STRING | IT_CALL,       NULL, "Check All Hints", M_EmblemHintsFull, 10},
+	{IT_STRING|IT_CVAR,         NULL, "Emblem Radar", &cv_itemfinder, 20},
+	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",         &SPauseDef,     30}
+};
+
+static menuitem_t SR_EmblemHintFullMenu[] =
+{
+	{IT_KEYHANDLER | IT_STRING, NULL, "", M_HandleEmblemHints, 0},
 };
 
 // --------------------------------
@@ -1738,6 +1747,19 @@ menu_t SR_EmblemHintDef =
 	NULL
 };
 
+menu_t SR_EmblemHintFullDef =
+{
+	MN_SR_MAIN + (MN_SR_EMBLEMHINT << 6),
+	NULL,
+	sizeof (SR_EmblemHintFullMenu)/sizeof (menuitem_t),
+	&SR_EmblemHintDef,
+	SR_EmblemHintFullMenu,
+	M_DrawEmblemHintsFull,
+	0, 150,
+	0,
+	NULL
+};
+
 // Single Player
 menu_t SP_MainDef = //CENTERMENUSTYLE(NULL, SP_MainMenu, &MainDef, 72);
 {
@@ -7227,10 +7249,23 @@ finishchecklist:
 #define NUMHINTS 5
 static void M_EmblemHints(INT32 choice)
 {
+	INT32 i;
+	UINT32 local = 0;
+	emblem_t *emblem;
+	for (i = 0; i < numemblems; i++)
+	{
+		emblem = &emblemlocations[i];
+		if (emblem->level != gamemap || emblem->type > ET_SKIN)
+			continue;
+		if (++local > NUMHINTS*2)
+			break;
+	}
+
 	(void)choice;
-	SR_EmblemHintMenu[0].status = (M_SecretUnlocked(SECRET_ITEMFINDER)) ? (IT_CVAR|IT_STRING) : (IT_SECRET);
+	SR_EmblemHintMenu[0].status = (local > NUMHINTS*2) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+	SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER)) ? (IT_CVAR|IT_STRING) : (IT_SECRET);
 	M_SetupNextMenu(&SR_EmblemHintDef);
-	itemOn = 1; // always start on back.
+	itemOn = 2; // always start on back.
 }
 
 static void M_DrawEmblemHints(void)
@@ -7301,6 +7336,129 @@ static void M_DrawEmblemHints(void)
 	M_DrawGenericMenu();
 }
 
+UINT32 check_on_hint = 0;
+
+static void M_HandleEmblemHints(INT32 choice)
+{
+	INT32 i;
+	emblem_t *emblem;
+	UINT32 stageemblems = 0;
+
+	for (i = 0; i < numemblems; i++)
+	{
+		emblem = &emblemlocations[i];
+		if (emblem->level != gamemap || emblem->type > ET_SKIN)
+			continue;
+
+		stageemblems++;
+	}
+
+
+	UINT32 j = check_on_hint;
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			S_StartSound(NULL, sfx_menu1);
+			if ((check_on_hint != stageemblems))
+			{
+				if (++j <= stageemblems - NUMHINTS)
+					check_on_hint = j;
+			}
+			return;
+
+		case KEY_UPARROW:
+			S_StartSound(NULL, sfx_menu1);
+			if (check_on_hint)
+			{
+				if (--j != -1)
+					check_on_hint = j;
+			}
+			return;
+
+		case KEY_ESCAPE:
+			if (currentMenu->prevMenu){
+				check_on_hint = 0;
+				M_SetupNextMenu(currentMenu->prevMenu);
+			}else
+				M_ClearMenus(true);
+			return;
+		default:
+			break;
+	}
+
+}
+
+static void M_EmblemHintsFull(INT32 choice)
+{
+	(void)choice;
+	M_SetupNextMenu(&SR_EmblemHintFullDef);
+	itemOn = 0;
+}
+
+
+static void M_DrawEmblemHintsFull(void)
+{
+	INT32 i, x, y = currentMenu->y, drawnemblems = 0;
+	UINT32 collected = 0, local = 0;
+	emblem_t *emblem;
+	const char *hint;
+
+	for (i = 0; i < numemblems; i++)
+	{
+		emblem = &emblemlocations[i];
+		if (emblem->level != gamemap || emblem->type > ET_SKIN)
+			continue;
+		local++;
+	}
+
+	if (!local)
+		V_DrawCenteredString(160, 48, V_YELLOWMAP, "No hidden emblems on this map.");
+	else{ 
+
+		if (check_on_hint > 0)
+			V_DrawString(310, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A");
+		if(check_on_hint < local - NUMHINTS)
+			V_DrawString(310, y+8+(skullAnimCounter/5), V_YELLOWMAP, "\x1B");
+
+		x = 4;
+		y = 16;
+
+		for (i = 0; i < numemblems; i++)
+		{
+			emblem = &emblemlocations[i];
+			if (emblem->level != gamemap || emblem->type > ET_SKIN)
+				continue;
+
+			drawnemblems++;
+
+			if (drawnemblems > check_on_hint && drawnemblems <= (check_on_hint+NUMHINTS)){
+				if (emblem->collected)
+				{
+					collected = V_GREENMAP;
+					V_DrawMappedPatch(x, y+4, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
+						R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
+				}
+				else
+				{
+					collected = 0;
+					V_DrawScaledPatch(x, y+4, 0, W_CachePatchName("NEEDIT", PU_PATCH));
+				}
+
+				if (emblem->hint[0])
+					hint = emblem->hint;
+				else
+					hint = M_GetText("No hint available for this emblem.");
+				hint = V_WordWrap(40, BASEVIDWIDTH-12, 0, hint);
+				V_DrawString(x+28, y, V_RETURN8|V_ALLOWLOWERCASE|collected, hint);
+
+				y += 32;
+			}
+		}
+	}
+
+	//M_DrawGenericMenu();
+}
+
 /*static void M_DrawSkyRoom(void)
 {
 	INT32 i, y = 0;

From 912734ffe6134c9acaa8a74759a9f61bb9b543f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bartu=20=C4=B0nce?= <bartuince97@gmail.com>
Date: Wed, 22 Jan 2020 21:52:15 +0100
Subject: [PATCH 047/129] Extra emblems display, take 2.

---
 src/m_menu.c | 229 ++++++++++++++++-----------------------------------
 1 file changed, 72 insertions(+), 157 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 0a39148ec..3e4d822f6 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -231,8 +231,8 @@ static void M_Credits(INT32 choice);
 static void M_SoundTest(INT32 choice);
 static void M_PandorasBox(INT32 choice);
 static void M_EmblemHints(INT32 choice);
-static void M_EmblemHintsFull(INT32 choice);
 static void M_HandleEmblemHints(INT32 choice);
+UINT32 hintpage = 1;
 static void M_HandleChecklist(INT32 choice);
 menu_t SR_MainDef, SR_UnlockChecklistDef;
 
@@ -344,7 +344,6 @@ static void M_DrawAddons(void);
 static void M_DrawChecklist(void);
 static void M_DrawSoundTest(void);
 static void M_DrawEmblemHints(void);
-static void M_DrawEmblemHintsFull(void);
 static void M_DrawPauseMenu(void);
 static void M_DrawServerMenu(void);
 static void M_DrawLevelPlatterMenu(void);
@@ -730,16 +729,11 @@ static menuitem_t SR_SoundTestMenu[] =
 
 static menuitem_t SR_EmblemHintMenu[] =
 {
-	{IT_STRING | IT_CALL,       NULL, "Check All Hints", M_EmblemHintsFull, 10},
+	{IT_STRING | IT_ARROWS,       NULL, "Page", M_HandleEmblemHints, 10},
 	{IT_STRING|IT_CVAR,         NULL, "Emblem Radar", &cv_itemfinder, 20},
 	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",         &SPauseDef,     30}
 };
 
-static menuitem_t SR_EmblemHintFullMenu[] =
-{
-	{IT_KEYHANDLER | IT_STRING, NULL, "", M_HandleEmblemHints, 0},
-};
-
 // --------------------------------
 // 1 Player and all of its submenus
 // --------------------------------
@@ -1747,19 +1741,6 @@ menu_t SR_EmblemHintDef =
 	NULL
 };
 
-menu_t SR_EmblemHintFullDef =
-{
-	MN_SR_MAIN + (MN_SR_EMBLEMHINT << 6),
-	NULL,
-	sizeof (SR_EmblemHintFullMenu)/sizeof (menuitem_t),
-	&SR_EmblemHintDef,
-	SR_EmblemHintFullMenu,
-	M_DrawEmblemHintsFull,
-	0, 150,
-	0,
-	NULL
-};
-
 // Single Player
 menu_t SP_MainDef = //CENTERMENUSTYLE(NULL, SP_MainMenu, &MainDef, 72);
 {
@@ -7247,6 +7228,7 @@ finishchecklist:
 }
 
 #define NUMHINTS 5
+
 static void M_EmblemHints(INT32 choice)
 {
 	INT32 i;
@@ -7262,16 +7244,17 @@ static void M_EmblemHints(INT32 choice)
 	}
 
 	(void)choice;
-	SR_EmblemHintMenu[0].status = (local > NUMHINTS*2) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+	SR_EmblemHintMenu[0].status = (local > NUMHINTS*2) ? (IT_STRING | IT_ARROWS) : (IT_DISABLED);
 	SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER)) ? (IT_CVAR|IT_STRING) : (IT_SECRET);
+	hintpage = 1;
 	M_SetupNextMenu(&SR_EmblemHintDef);
 	itemOn = 2; // always start on back.
 }
 
 static void M_DrawEmblemHints(void)
 {
-	INT32 i, j = 0, x, y, left_hints = NUMHINTS;
-	UINT32 collected = 0, local = 0;
+	INT32 i, j = 0, x, y, left_hints = NUMHINTS, pageflag = 0;
+	UINT32 collected = 0, totalemblems = 0, local = 0;
 	emblem_t *emblem;
 	const char *hint;
 
@@ -7280,17 +7263,34 @@ static void M_DrawEmblemHints(void)
 		emblem = &emblemlocations[i];
 		if (emblem->level != gamemap || emblem->type > ET_SKIN)
 			continue;
-		if (++local >= NUMHINTS*2)
-			break;
+
+		local++;
 	}
 
 	x = (local > NUMHINTS ? 4 : 12);
 	y = 8;
 
-	// If there are more than 1 page's but less than 2 pages' worth of emblems,
+	if (local > NUMHINTS){
+		if (local > ((hintpage-1)*NUMHINTS*2) && local < ((hintpage)*NUMHINTS*2)){
+			if (NUMHINTS % 2 == 1)
+				left_hints = (local - ((hintpage-1)*NUMHINTS*2)  + 1) / 2;
+			else
+				left_hints = (local - ((hintpage-1)*NUMHINTS*2)) / 2;
+		}else{
+			left_hints = NUMHINTS;
+		}
+	}
+
+	if (local > NUMHINTS*2){
+		if (itemOn == 0){
+			pageflag = V_YELLOWMAP;
+		}
+		V_DrawString(currentMenu->x + 40, currentMenu->y + 10, pageflag, va("%d",hintpage));
+	}
+
+	// If there are more than 1 page's but less than 2 pages' worth of emblems on the last possible page,
 	// put half (rounded up) of the hints on the left, and half (rounded down) on the right
-	if (local > NUMHINTS && local < (NUMHINTS*2)-1)
-		left_hints = (local + 1) / 2;
+
 
 	if (!local)
 		V_DrawCenteredString(160, 48, V_YELLOWMAP, "No hidden emblems on this map.");
@@ -7300,43 +7300,51 @@ static void M_DrawEmblemHints(void)
 		if (emblem->level != gamemap || emblem->type > ET_SKIN)
 			continue;
 
-		if (emblem->collected)
-		{
-			collected = V_GREENMAP;
-			V_DrawMappedPatch(x, y+4, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
-				R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
-		}
-		else
-		{
-			collected = 0;
-			V_DrawScaledPatch(x, y+4, 0, W_CachePatchName("NEEDIT", PU_PATCH));
-		}
+		totalemblems++;
 
-		if (emblem->hint[0])
-			hint = emblem->hint;
-		else
-			hint = M_GetText("No hint available for this emblem.");
-		hint = V_WordWrap(40, BASEVIDWIDTH-12, 0, hint);
-		if (local > NUMHINTS)
-			V_DrawThinString(x+28, y, V_RETURN8|V_ALLOWLOWERCASE|collected, hint);
-		else
-			V_DrawString(x+28, y, V_RETURN8|V_ALLOWLOWERCASE|collected, hint);
+		if (totalemblems >= ((hintpage-1)*(NUMHINTS*2) + 1) && totalemblems < (hintpage*NUMHINTS*2)+1){
 
-		y += 28;
+			if (emblem->collected)
+			{
+				collected = V_GREENMAP;
+				V_DrawMappedPatch(x, y+4, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
+					R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
+			}
+			else
+			{
+				collected = 0;
+				V_DrawScaledPatch(x, y+4, 0, W_CachePatchName("NEEDIT", PU_PATCH));
+			}
 
-		if (++j == left_hints)
-		{
-			x = 4+(BASEVIDWIDTH/2);
-			y = 8;
+			if (emblem->hint[0])
+				hint = emblem->hint;
+			else
+				hint = M_GetText("No hint available for this emblem.");
+			hint = V_WordWrap(40, BASEVIDWIDTH-12, 0, hint);
+			//always draw tiny if we have more than NUMHINTS*2, visually more appealing
+			if (local > NUMHINTS)
+				V_DrawThinString(x+28, y, V_RETURN8|V_ALLOWLOWERCASE|collected, hint);
+			else
+				V_DrawString(x+28, y, V_RETURN8|V_ALLOWLOWERCASE|collected, hint);
+
+			y += 28;
+
+			// If there are more than 1 page's but less than 2 pages' worth of emblems on the last possible page,
+			// put half (rounded up) of the hints on the left, and half (rounded down) on the right
+
+			if (++j == left_hints)
+			{
+				x = 4+(BASEVIDWIDTH/2);
+				y = 8;
+			}
+			else if (j >= NUMHINTS*2)
+				break;
 		}
-		else if (j >= NUMHINTS*2)
-			break;
 	}
 
 	M_DrawGenericMenu();
 }
 
-UINT32 check_on_hint = 0;
 
 static void M_HandleEmblemHints(INT32 choice)
 {
@@ -7354,109 +7362,16 @@ static void M_HandleEmblemHints(INT32 choice)
 	}
 
 
-	UINT32 j = check_on_hint;
-	switch (choice)
-	{
-		case KEY_DOWNARROW:
-			S_StartSound(NULL, sfx_menu1);
-			if ((check_on_hint != stageemblems))
-			{
-				if (++j <= stageemblems - NUMHINTS)
-					check_on_hint = j;
-			}
-			return;
-
-		case KEY_UPARROW:
-			S_StartSound(NULL, sfx_menu1);
-			if (check_on_hint)
-			{
-				if (--j != -1)
-					check_on_hint = j;
-			}
-			return;
-
-		case KEY_ESCAPE:
-			if (currentMenu->prevMenu){
-				check_on_hint = 0;
-				M_SetupNextMenu(currentMenu->prevMenu);
-			}else
-				M_ClearMenus(true);
-			return;
-		default:
-			break;
-	}
-
-}
-
-static void M_EmblemHintsFull(INT32 choice)
-{
-	(void)choice;
-	M_SetupNextMenu(&SR_EmblemHintFullDef);
-	itemOn = 0;
-}
-
-
-static void M_DrawEmblemHintsFull(void)
-{
-	INT32 i, x, y = currentMenu->y, drawnemblems = 0;
-	UINT32 collected = 0, local = 0;
-	emblem_t *emblem;
-	const char *hint;
-
-	for (i = 0; i < numemblems; i++)
-	{
-		emblem = &emblemlocations[i];
-		if (emblem->level != gamemap || emblem->type > ET_SKIN)
-			continue;
-		local++;
-	}
-
-	if (!local)
-		V_DrawCenteredString(160, 48, V_YELLOWMAP, "No hidden emblems on this map.");
-	else{ 
-
-		if (check_on_hint > 0)
-			V_DrawString(310, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A");
-		if(check_on_hint < local - NUMHINTS)
-			V_DrawString(310, y+8+(skullAnimCounter/5), V_YELLOWMAP, "\x1B");
-
-		x = 4;
-		y = 16;
-
-		for (i = 0; i < numemblems; i++)
-		{
-			emblem = &emblemlocations[i];
-			if (emblem->level != gamemap || emblem->type > ET_SKIN)
-				continue;
-
-			drawnemblems++;
-
-			if (drawnemblems > check_on_hint && drawnemblems <= (check_on_hint+NUMHINTS)){
-				if (emblem->collected)
-				{
-					collected = V_GREENMAP;
-					V_DrawMappedPatch(x, y+4, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
-						R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
-				}
-				else
-				{
-					collected = 0;
-					V_DrawScaledPatch(x, y+4, 0, W_CachePatchName("NEEDIT", PU_PATCH));
-				}
-
-				if (emblem->hint[0])
-					hint = emblem->hint;
-				else
-					hint = M_GetText("No hint available for this emblem.");
-				hint = V_WordWrap(40, BASEVIDWIDTH-12, 0, hint);
-				V_DrawString(x+28, y, V_RETURN8|V_ALLOWLOWERCASE|collected, hint);
-
-				y += 32;
-			}
+	if (choice == 0){
+		if (hintpage > 1){
+			hintpage--;
+		}
+	}else{
+		if (hintpage < (stageemblems/(NUMHINTS*2) + 1)){
+			hintpage++;
 		}
 	}
 
-	//M_DrawGenericMenu();
 }
 
 /*static void M_DrawSkyRoom(void)

From c72e0efee4d543230591ab0020326a49cc9f7fab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bartu=20=C4=B0nce?= <bartuince97@gmail.com>
Date: Wed, 22 Jan 2020 21:57:28 +0100
Subject: [PATCH 048/129] "page x of y"

---
 src/m_menu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 3e4d822f6..3c19e230e 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -7285,7 +7285,7 @@ static void M_DrawEmblemHints(void)
 		if (itemOn == 0){
 			pageflag = V_YELLOWMAP;
 		}
-		V_DrawString(currentMenu->x + 40, currentMenu->y + 10, pageflag, va("%d",hintpage));
+		V_DrawString(currentMenu->x + 40, currentMenu->y + 10, pageflag, va("%d of %d",hintpage, local/(NUMHINTS*2) + 1));
 	}
 
 	// If there are more than 1 page's but less than 2 pages' worth of emblems on the last possible page,

From 875774a45f26a2996e6d375c47f9d133e9360ae7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bartu=20=C4=B0nce?= <bartuince97@gmail.com>
Date: Wed, 22 Jan 2020 22:08:08 +0100
Subject: [PATCH 049/129] no message

---
 src/m_menu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 3c19e230e..957928497 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -7367,7 +7367,7 @@ static void M_HandleEmblemHints(INT32 choice)
 			hintpage--;
 		}
 	}else{
-		if (hintpage < (stageemblems/(NUMHINTS*2) + 1)){
+		if (hintpage < ((stageemblems-1)/(NUMHINTS*2) + 1)){
 			hintpage++;
 		}
 	}

From f0daea39d43d1a3cd54385474dd6c27e65da2e4c Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Wed, 22 Jan 2020 22:19:00 -0800
Subject: [PATCH 050/129] Don't set controls to keys out of array bounds

Shout-out to TAG's config that somehow had
`setcontrol2 "custom3" "KEY931926528"`, cuasing the game to crash only in
Splitscreen.
---
 src/g_input.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/g_input.c b/src/g_input.c
index ac901703f..2efae9cf3 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -662,7 +662,14 @@ INT32 G_KeyStringtoNum(const char *keystr)
 		return keystr[0];
 
 	if (!strncmp(keystr, "KEY", 3) && keystr[3] >= '0' && keystr[3] <= '9')
-		return atoi(&keystr[3]);
+	{
+		/* what if we out of range bruh? */
+		j = atoi(&keystr[3]);
+		if (j < NUMINPUTS)
+			return j;
+		else
+			return 0;
+	}
 
 	for (j = 0; j < NUMKEYNAMES; j++)
 		if (!stricmp(keynames[j].name, keystr))

From ce20642fa7597d2a4418d1572894b0b90f1cdb42 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Thu, 23 Jan 2020 18:52:16 +0100
Subject: [PATCH 051/129] Do not protect non-IT players in tag gametypes

---
 src/p_user.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/p_user.c b/src/p_user.c
index b01f702fa..832a6a30e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -12211,7 +12211,8 @@ void P_PlayerThink(player_t *player)
 	}
 
 	// IF PLAYER NOT HERE THEN FLASH END IF
-	if (player->quittime && player->powers[pw_flashing] < flashingtics - 1)
+	if (player->quittime && player->powers[pw_flashing] < flashingtics - 1
+	&& !(G_TagGametype() && !(player->pflags & PF_TAGIT)) && !player->gotflag)
 		player->powers[pw_flashing] = flashingtics - 1;
 
 	// Counters, time dependent power ups.

From b0b22b53d6a99f7a40f386e85e580e4de4866cde Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Thu, 23 Jan 2020 19:58:13 +0100
Subject: [PATCH 052/129] Ignore disconnected players in tag gametypes

Disconnected players do not become IT at round start.

If all non-ITs are disconnected, the round ends.

If all ITs are disconnected, one of the non-ITs
becomes IT, or the round ends if in Hide & Seek.
---
 src/p_inter.c | 4 ++--
 src/p_setup.c | 2 +-
 src/p_tick.c  | 4 ++++
 3 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index 71dcd70a1..8090b7406 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2257,9 +2257,9 @@ void P_CheckSurvivors(void)
 		{
 			if (players[i].spectator)
 				spectators++;
-			else if (players[i].pflags & PF_TAGIT)
+			else if ((players[i].pflags & PF_TAGIT) && players[i].quittime < 30 * TICRATE)
 				taggers++;
-			else if (!(players[i].pflags & PF_GAMETYPEOVER))
+			else if (!(players[i].pflags & PF_GAMETYPEOVER) && players[i].quittime < 30 * TICRATE)
 			{
 				survivorarray[survivors] = i;
 				survivors++;
diff --git a/src/p_setup.c b/src/p_setup.c
index 679060d44..729ee00c2 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -3097,7 +3097,7 @@ static void P_InitTagGametype(void)
 	//Also, you'd never have to loop through all 32 players slots to find anything ever again.
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (playeringame[i] && !players[i].spectator)
+		if (playeringame[i] && !(players[i].spectator && players[i].quittime))
 		{
 			playersactive[realnumplayers] = i; //stores the player's node in the array.
 			realnumplayers++;
diff --git a/src/p_tick.c b/src/p_tick.c
index 4dd3bb30a..a4b8aa097 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -599,6 +599,10 @@ void P_Ticker(boolean run)
 			if (players[i].quittime)
 			{
 				players[i].quittime++;
+
+				if (players[i].quittime == 30 * TICRATE)
+					P_CheckSurvivors();
+
 				if (server && players[i].quittime >= FixedMul(cv_rejointimeout.value, 60 * TICRATE))
 					SendKick(i, KICK_MSG_PLAYER_QUIT);
 			}

From ba127008cfcbc861feb146f7fb6706f6421ab8a0 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Thu, 23 Jan 2020 19:59:41 +0100
Subject: [PATCH 053/129] Do not spam player quit net commands

---
 src/p_tick.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/p_tick.c b/src/p_tick.c
index a4b8aa097..348f2557c 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -603,7 +603,8 @@ void P_Ticker(boolean run)
 				if (players[i].quittime == 30 * TICRATE)
 					P_CheckSurvivors();
 
-				if (server && players[i].quittime >= FixedMul(cv_rejointimeout.value, 60 * TICRATE))
+				if (server && players[i].quittime >= FixedMul(cv_rejointimeout.value, 60 * TICRATE)
+				&& !(players[i].quittime % TICRATE))
 					SendKick(i, KICK_MSG_PLAYER_QUIT);
 			}
 		}

From 3d9466e5f550a8d11da4989f62e32eb0ea888c11 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 23 Jan 2020 13:57:39 -0800
Subject: [PATCH 054/129] Semantics

---
 src/g_input.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/g_input.c b/src/g_input.c
index 2efae9cf3..ed7bc5cb6 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -667,8 +667,7 @@ INT32 G_KeyStringtoNum(const char *keystr)
 		j = atoi(&keystr[3]);
 		if (j < NUMINPUTS)
 			return j;
-		else
-			return 0;
+		return 0;
 	}
 
 	for (j = 0; j < NUMKEYNAMES; j++)

From c3f42859882e6a1a789668d9b78d1310dd5e7312 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 23 Jan 2020 18:01:59 -0800
Subject: [PATCH 055/129] Put branch name and commit hash in EXENAME for
 AppVeyor builds

srb2win-master-a6d49eaaa70be5f88c26623c7b4a2fa5a770341e.exe
---
 appveyor.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/appveyor.yml b/appveyor.yml
index 6b80b4ec3..cc77f102e 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -83,7 +83,8 @@ before_build:
 - ccache -V
 - ccache -s
 - if [%NOUPX%] == [1] ( set "NOUPX=NOUPX=1" ) else ( set "NOUPX=" )
-- set "SRB2_MFLAGS=-C src WARNINGMODE=1 CCACHE=1 NOOBJDUMP=1 %NOUPX%"
+- set "EXENAME=EXENAME=srb2win-%APPVEYOR_REPO_BRANCH%-%APPVEYOR_REPO_COMMIT%.exe"
+- set "SRB2_MFLAGS=-C src WARNINGMODE=1 CCACHE=1 NOOBJDUMP=1 %NOUPX% %EXENAME%"
 - if [%X86_64%] == [1] ( set "MINGW_FLAGS=MINGW64=1 X86_64=1 GCC81=1" ) else ( set "MINGW_FLAGS=MINGW=1 GCC91=1" )
 - set "SRB2_MFLAGS=%SRB2_MFLAGS% %MINGW_FLAGS% %CONFIGURATION%=1"
 

From d3315ae03c57296bfb5a7f9f5959492bdb7de24c Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 23 Jan 2020 18:09:34 -0800
Subject: [PATCH 056/129] Put the abbreviated commit hash in the EXENAME for
 AppVeyor

srb2win-master-a6d49eaaa.exe
---
 appveyor.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/appveyor.yml b/appveyor.yml
index cc77f102e..c4b5263e9 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -83,7 +83,9 @@ before_build:
 - ccache -V
 - ccache -s
 - if [%NOUPX%] == [1] ( set "NOUPX=NOUPX=1" ) else ( set "NOUPX=" )
-- set "EXENAME=EXENAME=srb2win-%APPVEYOR_REPO_BRANCH%-%APPVEYOR_REPO_COMMIT%.exe"
+# get abbreviated (7 char) commit hash
+- set "COMMIT=%APPVEYOR_REPO_COMMIT:~0,7%"
+- set "EXENAME=EXENAME=srb2win-%APPVEYOR_REPO_BRANCH%-%COMMIT%.exe"
 - set "SRB2_MFLAGS=-C src WARNINGMODE=1 CCACHE=1 NOOBJDUMP=1 %NOUPX% %EXENAME%"
 - if [%X86_64%] == [1] ( set "MINGW_FLAGS=MINGW64=1 X86_64=1 GCC81=1" ) else ( set "MINGW_FLAGS=MINGW=1 GCC91=1" )
 - set "SRB2_MFLAGS=%SRB2_MFLAGS% %MINGW_FLAGS% %CONFIGURATION%=1"

From b1d931d471bcf2ec9a40f6e7e52518807c5fd14b Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 23 Jan 2020 18:55:25 -0800
Subject: [PATCH 057/129] Well I'm an idiot

---
 appveyor.yml | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/appveyor.yml b/appveyor.yml
index c4b5263e9..96bd6ce84 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -83,9 +83,9 @@ before_build:
 - ccache -V
 - ccache -s
 - if [%NOUPX%] == [1] ( set "NOUPX=NOUPX=1" ) else ( set "NOUPX=" )
-# get abbreviated (7 char) commit hash
-- set "COMMIT=%APPVEYOR_REPO_COMMIT:~0,7%"
-- set "EXENAME=EXENAME=srb2win-%APPVEYOR_REPO_BRANCH%-%COMMIT%.exe"
+- cmd: git rev-parse --short %APPVEYOR_REPO_COMMIT%>%TMP%/gitshort.txt
+- cmd: set /P GITSHORT=<%TMP%/gitshort.txt
+- set "EXENAME=EXENAME=srb2win-%APPVEYOR_REPO_BRANCH%-%GITSHORT%.exe"
 - set "SRB2_MFLAGS=-C src WARNINGMODE=1 CCACHE=1 NOOBJDUMP=1 %NOUPX% %EXENAME%"
 - if [%X86_64%] == [1] ( set "MINGW_FLAGS=MINGW64=1 X86_64=1 GCC81=1" ) else ( set "MINGW_FLAGS=MINGW=1 GCC91=1" )
 - set "SRB2_MFLAGS=%SRB2_MFLAGS% %MINGW_FLAGS% %CONFIGURATION%=1"
@@ -102,8 +102,6 @@ after_build:
   )
 - if [%X86_64%] == [1] ( set "CONFIGURATION=%CONFIGURATION%64" )
 - ccache -s
-- cmd: git rev-parse --short %APPVEYOR_REPO_COMMIT%>%TMP%/gitshort.txt
-- cmd: set /P GITSHORT=<%TMP%/gitshort.txt
 - set BUILD_ARCHIVE=%APPVEYOR_REPO_BRANCH%-%GITSHORT%-%CONFIGURATION%.7z
 - set BUILDSARCHIVE=%APPVEYOR_REPO_BRANCH%-%CONFIGURATION%.7z
 - cmd: 7z a %BUILD_ARCHIVE% %BUILD_PATH% -xr!.gitignore

From ef2febe1e01bc7c93f30d2063bfd754b1a79dd3a Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 23 Jan 2020 19:58:26 -0800
Subject: [PATCH 058/129] Use the pull request repo, branch and commit

srb2win-jameds:appveyor-exename-b1d931d47.exe
---
 appveyor.yml | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/appveyor.yml b/appveyor.yml
index 96bd6ce84..543308306 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -83,9 +83,12 @@ before_build:
 - ccache -V
 - ccache -s
 - if [%NOUPX%] == [1] ( set "NOUPX=NOUPX=1" ) else ( set "NOUPX=" )
-- cmd: git rev-parse --short %APPVEYOR_REPO_COMMIT%>%TMP%/gitshort.txt
+- if defined [%APPVEYOR_PULL_REQUEST_HEAD_COMMIT%] ( set "COMMIT=%APPVEYOR_PULL_REQUEST_HEAD_COMMIT%" ) else ( set "COMMIT=%APPVEYOR_REPO_COMMIT%" )
+- cmd: git rev-parse --short %COMMIT%>%TMP%/gitshort.txt
 - cmd: set /P GITSHORT=<%TMP%/gitshort.txt
-- set "EXENAME=EXENAME=srb2win-%APPVEYOR_REPO_BRANCH%-%GITSHORT%.exe"
+# for pull requests, take the owner's name only, if this isn't the same repo of course
+- if [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [%APPVEYOR_REPO_NAME%] ( set "REPO=%APPVEYOR_REPO_BRANCH" ) else ( for /f "delims=/" %%a in ("%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%") do set "REPO=%%a:%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH" )
+- set "EXENAME=EXENAME=srb2win-%REPO%-%GITSHORT%.exe"
 - set "SRB2_MFLAGS=-C src WARNINGMODE=1 CCACHE=1 NOOBJDUMP=1 %NOUPX% %EXENAME%"
 - if [%X86_64%] == [1] ( set "MINGW_FLAGS=MINGW64=1 X86_64=1 GCC81=1" ) else ( set "MINGW_FLAGS=MINGW=1 GCC91=1" )
 - set "SRB2_MFLAGS=%SRB2_MFLAGS% %MINGW_FLAGS% %CONFIGURATION%=1"

From 6337932190d68d6f825f4f5dd47a6f3655c88f16 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 23 Jan 2020 20:13:57 -0800
Subject: [PATCH 059/129] I promise I know what I'm doing

---
 appveyor.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/appveyor.yml b/appveyor.yml
index 543308306..fac974c17 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -87,7 +87,7 @@ before_build:
 - cmd: git rev-parse --short %COMMIT%>%TMP%/gitshort.txt
 - cmd: set /P GITSHORT=<%TMP%/gitshort.txt
 # for pull requests, take the owner's name only, if this isn't the same repo of course
-- if [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [%APPVEYOR_REPO_NAME%] ( set "REPO=%APPVEYOR_REPO_BRANCH" ) else ( for /f "delims=/" %%a in ("%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%") do set "REPO=%%a:%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH" )
+- if [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [%APPVEYOR_REPO_NAME%] ( set "REPO=%APPVEYOR_REPO_BRANCH%" ) else ( for /f "delims=/" %%a in ("%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%") do set "REPO=%%a:%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%" )
 - set "EXENAME=EXENAME=srb2win-%REPO%-%GITSHORT%.exe"
 - set "SRB2_MFLAGS=-C src WARNINGMODE=1 CCACHE=1 NOOBJDUMP=1 %NOUPX% %EXENAME%"
 - if [%X86_64%] == [1] ( set "MINGW_FLAGS=MINGW64=1 X86_64=1 GCC81=1" ) else ( set "MINGW_FLAGS=MINGW=1 GCC91=1" )

From e082553f6d3a90baf97e056ab6790ce136f502d0 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 23 Jan 2020 20:34:26 -0800
Subject: [PATCH 060/129] Check if this actually is a PR before using those
 variables

---
 appveyor.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/appveyor.yml b/appveyor.yml
index fac974c17..51052ff6e 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -87,7 +87,8 @@ before_build:
 - cmd: git rev-parse --short %COMMIT%>%TMP%/gitshort.txt
 - cmd: set /P GITSHORT=<%TMP%/gitshort.txt
 # for pull requests, take the owner's name only, if this isn't the same repo of course
-- if [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [%APPVEYOR_REPO_NAME%] ( set "REPO=%APPVEYOR_REPO_BRANCH%" ) else ( for /f "delims=/" %%a in ("%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%") do set "REPO=%%a:%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%" )
+- set "REPO=%APPVEYOR_REPO_BRANCH%"
+- if not [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [] ( if not [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [%APPVEYOR_REPO_NAME%] (  for /f "delims=/" %%a in ("%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%") do set "REPO=%%a:%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%" ) )
 - set "EXENAME=EXENAME=srb2win-%REPO%-%GITSHORT%.exe"
 - set "SRB2_MFLAGS=-C src WARNINGMODE=1 CCACHE=1 NOOBJDUMP=1 %NOUPX% %EXENAME%"
 - if [%X86_64%] == [1] ( set "MINGW_FLAGS=MINGW64=1 X86_64=1 GCC81=1" ) else ( set "MINGW_FLAGS=MINGW=1 GCC91=1" )

From 70cf119a8402607116b2f8fbb79028a2de0e8d96 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 23 Jan 2020 20:35:53 -0800
Subject: [PATCH 061/129] Windows is dumb so no colon

srb2win-jameds-appveyor-exename-b1d931d47.exe
---
 appveyor.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/appveyor.yml b/appveyor.yml
index 51052ff6e..b111c8f28 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -88,7 +88,7 @@ before_build:
 - cmd: set /P GITSHORT=<%TMP%/gitshort.txt
 # for pull requests, take the owner's name only, if this isn't the same repo of course
 - set "REPO=%APPVEYOR_REPO_BRANCH%"
-- if not [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [] ( if not [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [%APPVEYOR_REPO_NAME%] (  for /f "delims=/" %%a in ("%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%") do set "REPO=%%a:%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%" ) )
+- if not [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [] ( if not [%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%] == [%APPVEYOR_REPO_NAME%] (  for /f "delims=/" %%a in ("%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%") do set "REPO=%%a-%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%" ) )
 - set "EXENAME=EXENAME=srb2win-%REPO%-%GITSHORT%.exe"
 - set "SRB2_MFLAGS=-C src WARNINGMODE=1 CCACHE=1 NOOBJDUMP=1 %NOUPX% %EXENAME%"
 - if [%X86_64%] == [1] ( set "MINGW_FLAGS=MINGW64=1 X86_64=1 GCC81=1" ) else ( set "MINGW_FLAGS=MINGW=1 GCC91=1" )

From 4304fe91e65845e3887935de046876b6313af9c9 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 23 Jan 2020 21:06:48 -0800
Subject: [PATCH 062/129] Name the archive like the EXE

---
 appveyor.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/appveyor.yml b/appveyor.yml
index b111c8f28..d33d3d3a3 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -106,8 +106,8 @@ after_build:
   )
 - if [%X86_64%] == [1] ( set "CONFIGURATION=%CONFIGURATION%64" )
 - ccache -s
-- set BUILD_ARCHIVE=%APPVEYOR_REPO_BRANCH%-%GITSHORT%-%CONFIGURATION%.7z
-- set BUILDSARCHIVE=%APPVEYOR_REPO_BRANCH%-%CONFIGURATION%.7z
+- set BUILD_ARCHIVE=%REPO%-%GITSHORT%-%CONFIGURATION%.7z
+- set BUILDSARCHIVE=%REPO%-%CONFIGURATION%.7z
 - cmd: 7z a %BUILD_ARCHIVE% %BUILD_PATH% -xr!.gitignore
 - appveyor PushArtifact %BUILD_ARCHIVE%
 - cmd: copy %BUILD_ARCHIVE% %BUILDSARCHIVE%

From 8bd897a269fc26e0894019169356eec777d88c0a Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Fri, 24 Jan 2020 19:56:57 +0100
Subject: [PATCH 063/129] Be silent when "kicking" a disconnected player

This lets the host manually remove a body if they want,
without polluting the chat with redundant messages.
---
 src/d_clisrv.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 0106d559c..841a0e322 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2918,7 +2918,8 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 	switch (msg)
 	{
 		case KICK_MSG_GO_AWAY:
-			HU_AddChatText(va("\x82*%s has been kicked (Go away)", player_names[pnum]), false);
+			if (!players[pnum].quittime)
+				HU_AddChatText(va("\x82*%s has been kicked (Go away)", player_names[pnum]), false);
 			kickreason = KR_KICK;
 			break;
 		case KICK_MSG_PING_HIGH:

From 59ed381fc08edd6888967e27545c485ab670210b Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Fri, 24 Jan 2020 16:38:46 -0800
Subject: [PATCH 064/129] Credit Tatsuru, he's done a bit

---
 src/f_finale.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/f_finale.c b/src/f_finale.c
index 87e41df78..99ff586f4 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1165,6 +1165,7 @@ static const char *credits[] = {
 	"Tasos \"tatokis\" Sahanidis", // Corrected C FixedMul, making 64-bit builds netplay compatible
 	"Wessel \"sphere\" Smit",
 	"Ben \"Cue\" Woodford",
+	"Ikaro \"Tatsuru\" Vinhas",
 	// Git contributors with 5+ approved merges of substantive quality,
 	// or contributors with at least one groundbreaking merge, may be named.
 	// Everyone else is acknowledged under "Special Thanks > SRB2 Community Contributors".

From c9bc3837f9b05be6e66dc204d98cd511bbf5a475 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Fri, 24 Jan 2020 16:44:53 -0800
Subject: [PATCH 065/129] Signed is unsigned, cool

---
 src/p_tick.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_tick.c b/src/p_tick.c
index 348f2557c..0b30ff42d 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -603,7 +603,7 @@ void P_Ticker(boolean run)
 				if (players[i].quittime == 30 * TICRATE)
 					P_CheckSurvivors();
 
-				if (server && players[i].quittime >= FixedMul(cv_rejointimeout.value, 60 * TICRATE)
+				if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE)
 				&& !(players[i].quittime % TICRATE))
 					SendKick(i, KICK_MSG_PLAYER_QUIT);
 			}

From 332b22a87ae42acea8b0c938170983195cd726d3 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Fri, 24 Jan 2020 23:54:54 -0600
Subject: [PATCH 066/129] Move spawnsnap to Special/ignore on sector trigger

Also includes splitting the starpost logic into its own function.
---
 src/p_inter.c | 203 ++++++++++++++++++++++++++------------------------
 src/p_local.h |   1 +
 src/p_spec.c  |   2 +-
 3 files changed, 109 insertions(+), 97 deletions(-)

diff --git a/src/p_inter.c b/src/p_inter.c
index 9f53a46a0..ddf90d72f 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1428,102 +1428,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 // Misc touchables //
 // *************** //
 		case MT_STARPOST:
-			if (player->bot)
-				return;
-			// In circuit, player must have touched all previous starposts
-			if (circuitmap
-				&& special->health - player->starpostnum > 1)
-			{
-				// blatant reuse of a variable that's normally unused in circuit
-				if (!player->tossdelay)
-					S_StartSound(toucher, sfx_lose);
-				player->tossdelay = 3;
-				return;
-			}
-
-			// With the parameter + angle setup, we can go up to 1365 star posts. Who needs that many?
-			if (special->health > 1365)
-			{
-				CONS_Debug(DBG_GAMELOGIC, "Bad Starpost Number!\n");
-				return;
-			}
-
-			if (player->starpostnum >= special->health)
-				return; // Already hit this post
-
-			{
-				mobj_t *checkbase = (special->spawnpoint && (special->spawnpoint->options & MTF_AMBUSH)) ? special : toucher;
-
-				if (cv_coopstarposts.value && G_GametypeUsesCoopStarposts() && (netgame || multiplayer))
-				{
-					for (i = 0; i < MAXPLAYERS; i++)
-					{
-						if (playeringame[i])
-						{
-							if (players[i].bot) // ignore dumb, stupid tails
-								continue;
-
-							players[i].starposttime = leveltime;
-							players[i].starpostx = checkbase->x>>FRACBITS;
-							players[i].starposty = checkbase->y>>FRACBITS;
-							players[i].starpostz = special->z>>FRACBITS;
-							players[i].starpostangle = special->angle;
-							players[i].starpostscale = player->mo->destscale;
-							if (special->flags2 & MF2_OBJECTFLIP)
-							{
-								players[i].starpostscale *= -1;
-								players[i].starpostz += special->height>>FRACBITS;
-							}
-							players[i].starpostnum = special->health;
-
-							if (cv_coopstarposts.value == 2 && (players[i].playerstate == PST_DEAD || players[i].spectator) && P_GetLives(&players[i]))
-								P_SpectatorJoinGame(&players[i]); //players[i].playerstate = PST_REBORN;
-						}
-					}
-					S_StartSound(NULL, special->info->painsound);
-				}
-				else
-				{
-					// Save the player's time and position.
-					player->starposttime = leveltime;
-					player->starpostx = checkbase->x>>FRACBITS;
-					player->starposty = checkbase->y>>FRACBITS;
-					player->starpostz = special->z>>FRACBITS;
-					player->starpostangle = special->angle;
-					player->starpostscale = player->mo->destscale;
-					if (special->flags2 & MF2_OBJECTFLIP)
-					{
-						player->starpostscale *= -1;
-						player->starpostz += special->height>>FRACBITS;
-					}
-					player->starpostnum = special->health;
-					S_StartSound(toucher, special->info->painsound);
-				}
-			}
-
-			P_ClearStarPost(special->health);
-
-			// Find all starposts in the level with this value - INCLUDING this one!
-			if (!(netgame && circuitmap && player != &players[consoleplayer]))
-			{
-				thinker_t *th;
-				mobj_t *mo2;
-
-				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
-				{
-					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-						continue;
-
-					mo2 = (mobj_t *)th;
-
-					if (mo2->type != MT_STARPOST)
-						continue;
-					if (mo2->health != special->health)
-						continue;
-
-					P_SetMobjState(mo2, mo2->info->painstate);
-				}
-			}
+			P_TouchStarPost(special, player, special->spawnpoint && (special->spawnpoint->options & MTF_OBJECTSPECIAL));
 			return;
 
 		case MT_FAKEMOBILE:
@@ -1875,6 +1780,112 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 	P_KillMobj(special, NULL, toucher, 0);
 }
 
+/** Saves a player's level progress at a star post
+  *
+  * \param post The star post to trigger
+  * \param player The player that should receive the checkpoint
+  * \param snaptopost If true, the respawn point will use the star post's position, otherwise player x/y and star post z
+  */
+void P_TouchStarPost(mobj_t *post, player_t *player, boolean snaptopost)
+{
+	size_t i;
+	mobj_t *toucher = player->mo;
+	mobj_t *checkbase = snaptopost ? post : toucher;
+
+	if (player->bot)
+		return;
+	// In circuit, player must have touched all previous starposts
+	if (circuitmap
+		&& post->health - player->starpostnum > 1)
+	{
+		// blatant reuse of a variable that's normally unused in circuit
+		if (!player->tossdelay)
+			S_StartSound(toucher, sfx_lose);
+		player->tossdelay = 3;
+		return;
+	}
+
+	// With the parameter + angle setup, we can go up to 1365 star posts. Who needs that many?
+	if (post->health > 1365)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "Bad Starpost Number!\n");
+		return;
+	}
+
+	if (player->starpostnum >= post->health)
+		return; // Already hit this post
+
+	if (cv_coopstarposts.value && G_GametypeUsesCoopStarposts() && (netgame || multiplayer))
+	{
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i])
+			{
+				if (players[i].bot) // ignore dumb, stupid tails
+					continue;
+
+				players[i].starposttime = leveltime;
+				players[i].starpostx = checkbase->x>>FRACBITS;
+				players[i].starposty = checkbase->y>>FRACBITS;
+				players[i].starpostz = post->z>>FRACBITS;
+				players[i].starpostangle = post->angle;
+				players[i].starpostscale = player->mo->destscale;
+				if (post->flags2 & MF2_OBJECTFLIP)
+				{
+					players[i].starpostscale *= -1;
+					players[i].starpostz += post->height>>FRACBITS;
+				}
+				players[i].starpostnum = post->health;
+
+				if (cv_coopstarposts.value == 2 && (players[i].playerstate == PST_DEAD || players[i].spectator) && P_GetLives(&players[i]))
+					P_SpectatorJoinGame(&players[i]); //players[i].playerstate = PST_REBORN;
+			}
+		}
+		S_StartSound(NULL, post->info->painsound);
+	}
+	else
+	{
+		// Save the player's time and position.
+		player->starposttime = leveltime;
+		player->starpostx = checkbase->x>>FRACBITS;
+		player->starposty = checkbase->y>>FRACBITS;
+		player->starpostz = post->z>>FRACBITS;
+		player->starpostangle = post->angle;
+		player->starpostscale = player->mo->destscale;
+		if (post->flags2 & MF2_OBJECTFLIP)
+		{
+			player->starpostscale *= -1;
+			player->starpostz += post->height>>FRACBITS;
+		}
+		player->starpostnum = post->health;
+		S_StartSound(toucher, post->info->painsound);
+	}
+
+	P_ClearStarPost(post->health);
+
+	// Find all starposts in the level with this value - INCLUDING this one!
+	if (!(netgame && circuitmap && player != &players[consoleplayer]))
+	{
+		thinker_t *th;
+		mobj_t *mo2;
+
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		{
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				continue;
+
+			mo2 = (mobj_t *)th;
+
+			if (mo2->type != MT_STARPOST)
+				continue;
+			if (mo2->health != post->health)
+				continue;
+
+			P_SetMobjState(mo2, mo2->info->painstate);
+		}
+	}
+}
+
 /** Prints death messages relating to a dying or hit player.
   *
   * \param player    Affected player.
diff --git a/src/p_local.h b/src/p_local.h
index a5f3d313c..8056d137c 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -486,6 +486,7 @@ void P_PlayerWeaponPanelOrAmmoBurst(player_t *player);
 void P_PlayerEmeraldBurst(player_t *player, boolean toss);
 
 void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck);
+void P_TouchStarPost(mobj_t *starpost, player_t *player, boolean snaptopost);
 void P_PlayerFlagBurst(player_t *player, boolean toss);
 void P_CheckTimeLimit(void);
 void P_CheckPointLimit(void);
diff --git a/src/p_spec.c b/src/p_spec.c
index 9defc33a0..a4076e513 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4684,7 +4684,7 @@ DoneSection2:
 			if (!post)
 				break;
 
-			P_TouchSpecialThing(post, player->mo, false);
+			P_TouchStarPost(post, player, false);
 			break;
 		}
 

From d30b48cfd1b74f7f2a60a1e1f2a7347dc4aaaf11 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Sat, 25 Jan 2020 14:04:16 +0100
Subject: [PATCH 067/129] Fix lava removing fire shield

---
 src/p_mobj.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index c4ba9c14f..e6d84086c 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3396,7 +3396,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 		if (!((p->powers[pw_super]) || (p->powers[pw_invulnerability])))
 		{
 			boolean electric = !!(p->powers[pw_shield] & SH_PROTECTELECTRIC);
-			if (electric || ((p->powers[pw_shield] & SH_PROTECTFIRE) && !(p->powers[pw_shield] & SH_PROTECTWATER)))
+			if (electric || ((p->powers[pw_shield] & SH_PROTECTFIRE) && !(p->powers[pw_shield] & SH_PROTECTWATER) && !(mobj->eflags & MFE_TOUCHLAVA)))
 			{ // Water removes electric and non-water fire shields...
 				P_FlashPal(p,
 				electric

From 2dd13bb2a4f434a6436cb341fccb68cd95e0c6dd Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sat, 25 Jan 2020 11:31:06 -0500
Subject: [PATCH 068/129] Fix damagetype for Cybrak flamethrower.

---
 src/info.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/info.c b/src/info.c
index de6ff475c..ef1d161de 100644
--- a/src/info.c
+++ b/src/info.c
@@ -6487,7 +6487,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
-		0,              // mass
+		DMG_FIRE,       // mass
 		1,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags

From f90c70ebe287e215415164dc341fe7905ceb2dae Mon Sep 17 00:00:00 2001
From: toaster <rollerorbital@gmail.com>
Date: Sat, 25 Jan 2020 11:34:30 -0500
Subject: [PATCH 069/129] Make the DMG actually *applied*. Consequence: flame
 will continue flying through player instead of dying in midair and dropping a
 flame, but this is actually desirable given the current behaviour looks kind
 of shitty.

---
 src/info.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/info.c b/src/info.c
index ef1d161de..45034fe37 100644
--- a/src/info.c
+++ b/src/info.c
@@ -6490,7 +6490,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		DMG_FIRE,       // mass
 		1,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_MISSILE|MF_PAIN|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 

From 65e84978fa4bf86f6563367641c26ac6feba9fc0 Mon Sep 17 00:00:00 2001
From: lachwright <lachlanwright17@gmail.com>
Date: Sun, 26 Jan 2020 04:51:00 +0800
Subject: [PATCH 070/129] Don't cancel Knuckles' landing animation on rising
 surfaces

---
 src/p_user.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/p_user.c b/src/p_user.c
index a5ccf467f..1fb5811f9 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2380,6 +2380,8 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 					}
 				}
 			}
+			else if (player->charability == CA_GLIDEANDCLIMB && (player->mo->state-states == S_PLAY_GLIDE_LANDING))
+				;
 			else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
 				;
 			else if (player->panim != PA_IDLE && player->panim != PA_WALK && player->panim != PA_RUN && player->panim != PA_DASH)

From e57589b1068ea3dec70bd3aa501f38371d409b24 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sat, 25 Jan 2020 19:03:15 -0600
Subject: [PATCH 071/129] Fix the minecart angle thing

---
 src/p_user.c | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 901cd8ae6..fd3f860cf 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -10790,12 +10790,23 @@ static void P_MinecartThink(player_t *player)
 		else if (angdiff > ANGLE_180 && angdiff < InvAngle(MINECARTCONEMAX))
 			player->mo->angle = minecart->angle - MINECARTCONEMAX;
 
-		if (angdiff + minecart->angle != player->mo->angle && (!demoplayback || P_AnalogMove(player)))
+		if (!demoplayback || P_AnalogMove(player))
 		{
+			angle_t *ang = NULL;
+
 			if (player == &players[consoleplayer])
-				localangle = player->mo->angle;
+				ang = &localangle;
 			else if (player == &players[secondarydisplayplayer])
-				localangle2 = player->mo->angle;
+				ang = &localangle2;
+
+			if (ang)
+			{
+				angdiff = *ang - minecart->angle;
+				if (angdiff < ANGLE_180 && angdiff > MINECARTCONEMAX)
+					*ang = minecart->angle + MINECARTCONEMAX;
+				else if (angdiff > ANGLE_180 && angdiff < InvAngle(MINECARTCONEMAX))
+					*ang = minecart->angle - MINECARTCONEMAX;
+			}
 		}
 	}
 

From ea28304d8c291ece7b8e489a932fc4c862241fb3 Mon Sep 17 00:00:00 2001
From: Zwip-Zwap Zapony <ZwipZwapZapony@gmail.com>
Date: Sun, 26 Jan 2020 10:53:37 +0100
Subject: [PATCH 072/129] Fix missing "RINGSNUMTICS" in dehacked.c

This fixes 16/20 of the HUD items in dehacked.c being off by one
(Same case as the MFE_ flags thing)
Also fixes dehacked.c mentioning "LAPS", which doesn't exist
---
 src/dehacked.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 4c36639a2..45f00b8cf 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -9143,6 +9143,7 @@ static const char *const HUDITEMS_LIST[] = {
 
 	"RINGS",
 	"RINGSNUM",
+	"RINGSNUMTICS",
 
 	"SCORE",
 	"SCORENUM",
@@ -9162,8 +9163,7 @@ static const char *const HUDITEMS_LIST[] = {
 	"TIMELEFTNUM",
 	"TIMEUP",
 	"HUNTPICS",
-	"POWERUPS",
-	"LAP"
+	"POWERUPS"
 };
 
 static const char *const MENUTYPES_LIST[] = {

From d08929b3d77d9e5686abddf8e07c0b2cdd927949 Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sun, 26 Jan 2020 22:46:57 -0300
Subject: [PATCH 073/129] Move line drawer setting to AM_Drawer

---
 src/am_map.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/am_map.c b/src/am_map.c
index 7dfbf4ba4..0f8f0f74f 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -354,12 +354,6 @@ static void AM_LevelInit(void)
 	f_w = vid.width;
 	f_h = vid.height;
 
-	AM_drawFline = AM_drawFline_soft;
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		AM_drawFline = HWR_drawAMline;
-#endif
-
 	AM_findMinMaxBoundaries();
 	scale_mtof = FixedDiv(min_scale_mtof*10, 7*FRACUNIT);
 	if (scale_mtof > max_scale_mtof)
@@ -1100,6 +1094,12 @@ void AM_Drawer(void)
 	if (!automapactive)
 		return;
 
+	AM_drawFline = AM_drawFline_soft;
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
+		AM_drawFline = HWR_drawAMline;
+#endif
+
 	AM_clearFB(BACKGROUND);
 	if (draw_grid) AM_drawGrid(GRIDCOLORS);
 	AM_drawWalls();

From ba6018aea44ec6cdf0245acb116deec64a5941fa Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sun, 26 Jan 2020 22:52:15 -0300
Subject: [PATCH 074/129] Optimise pixel drawing

---
 src/am_map.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/src/am_map.c b/src/am_map.c
index 0f8f0f74f..c06bd2561 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -205,6 +205,7 @@ static boolean followplayer = true; // specifies whether to follow the player ar
 typedef void (*AMDRAWFLINEFUNC) (const fline_t *fl, INT32 color);
 static AMDRAWFLINEFUNC AM_drawFline;
 
+static void AM_drawPixel(INT32 xx, INT32 yy, INT32 cc);
 static void AM_drawFline_soft(const fline_t *fl, INT32 color);
 
 static void AM_activateNewScale(void)
@@ -712,6 +713,17 @@ static boolean AM_clipMline(const mline_t *ml, fline_t *fl)
 }
 #undef DOOUTCODE
 
+//
+// Draws a pixel.
+//
+static void AM_drawPixel(INT32 xx, INT32 yy, INT32 cc)
+{
+	UINT8 *dest = screens[0];
+	if (xx < 0 || yy < 0 || xx >= vid.width || yy >= vid.height)
+		return; // off the screen
+	dest[(yy*vid.rowbytes) + (xx * vid.bpp)] = (cc & 0xFF);
+}
+
 //
 // Classic Bresenham w/ whatever optimizations needed for speed
 //
@@ -733,8 +745,6 @@ static void AM_drawFline_soft(const fline_t *fl, INT32 color)
 	}
 #endif
 
-	#define PUTDOT(xx,yy,cc) V_DrawFill(xx,yy,1,1,cc|V_NOSCALESTART);
-
 	dx = fl->b.x - fl->a.x;
 	ax = 2 * (dx < 0 ? -dx : dx);
 	sx = dx < 0 ? -1 : 1;
@@ -751,7 +761,7 @@ static void AM_drawFline_soft(const fline_t *fl, INT32 color)
 		d = ay - ax/2;
 		for (;;)
 		{
-			PUTDOT(x, y, color)
+			AM_drawPixel(x, y, color);
 			if (x == fl->b.x)
 				return;
 			if (d >= 0)
@@ -768,7 +778,7 @@ static void AM_drawFline_soft(const fline_t *fl, INT32 color)
 		d = ax - ay/2;
 		for (;;)
 		{
-			PUTDOT(x, y, color)
+			AM_drawPixel(x, y, color);
 			if (y == fl->b.y)
 				return;
 			if (d >= 0)
@@ -780,8 +790,6 @@ static void AM_drawFline_soft(const fline_t *fl, INT32 color)
 			d += ax;
 		}
 	}
-
-	#undef PUTDOT
 }
 
 //

From dd8166ca5f4f9dce7a4189a7e89992e42e0cbf9d Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sun, 26 Jan 2020 22:57:14 -0300
Subject: [PATCH 075/129] Doesn't matter.

---
 src/am_map.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/am_map.c b/src/am_map.c
index c06bd2561..4ad40b7aa 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -721,7 +721,7 @@ static void AM_drawPixel(INT32 xx, INT32 yy, INT32 cc)
 	UINT8 *dest = screens[0];
 	if (xx < 0 || yy < 0 || xx >= vid.width || yy >= vid.height)
 		return; // off the screen
-	dest[(yy*vid.rowbytes) + (xx * vid.bpp)] = (cc & 0xFF);
+	dest[(yy*vid.width) + xx] = cc;
 }
 
 //

From 8f3855d09f93da34fbe56fe62b90c36c283f65ec Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sun, 26 Jan 2020 23:12:28 -0300
Subject: [PATCH 076/129] Don't stop the automap (just restart it instead.)

---
 src/am_map.c | 24 +++++++++++++++++-------
 src/am_map.h |  3 +++
 src/screen.c |  7 +++++--
 3 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/src/am_map.c b/src/am_map.c
index 4ad40b7aa..6cd9a3f0f 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -345,16 +345,22 @@ static void AM_initVariables(void)
 	old_m_h = m_h;
 }
 
+//
+// Called when the screen size changed.
+//
+static void AM_FrameBufferInit(void)
+{
+	f_x = f_y = 0;
+	f_w = vid.width;
+	f_h = vid.height;
+}
+
 //
 // should be called at the start of every level
 // right now, i figure it out myself
 //
 static void AM_LevelInit(void)
 {
-	f_x = f_y = 0;
-	f_w = vid.width;
-	f_h = vid.height;
-
 	AM_findMinMaxBoundaries();
 	scale_mtof = FixedDiv(min_scale_mtof*10, 7*FRACUNIT);
 	if (scale_mtof > max_scale_mtof)
@@ -376,7 +382,7 @@ void AM_Stop(void)
   *
   * \sa AM_Stop
   */
-static inline void AM_Start(void)
+void AM_Start(void)
 {
 	static INT32 lastlevel = -1;
 
@@ -385,8 +391,12 @@ static inline void AM_Start(void)
 	am_stopped = false;
 	if (lastlevel != gamemap || am_recalc) // screen size changed
 	{
-		AM_LevelInit();
-		lastlevel = gamemap;
+		AM_FrameBufferInit();
+		if (lastlevel != gamemap)
+		{
+			AM_LevelInit();
+			lastlevel = gamemap;
+		}
 		am_recalc = false;
 	}
 	AM_initVariables();
diff --git a/src/am_map.h b/src/am_map.h
index 462bb3d6d..0560207bb 100644
--- a/src/am_map.h
+++ b/src/am_map.h
@@ -38,6 +38,9 @@ void AM_Ticker(void);
 // Called by main loop, instead of view drawer if automap is active.
 void AM_Drawer(void);
 
+// Enables the automap.
+void AM_Start(void);
+
 // Called to force the automap to quit if the level is completed while it is up.
 void AM_Stop(void);
 
diff --git a/src/screen.c b/src/screen.c
index 5bb304c08..fcf6c6b0b 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -360,10 +360,13 @@ void SCR_Recalc(void)
 	vid.fsmalldupy = vid.smalldupy*FRACUNIT;
 #endif
 
-	// toggle off automap because some screensize-dependent values will
+	// toggle off (then back on) the automap because some screensize-dependent values will
 	// be calculated next time the automap is activated.
 	if (automapactive)
-		AM_Stop();
+	{
+		am_recalc = true;
+		AM_Start();
+	}
 
 	// set the screen[x] ptrs on the new vidbuffers
 	V_Init();

From 2cfeaa63d2e8262dec715748132cb29fac834334 Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sun, 26 Jan 2020 23:34:04 -0300
Subject: [PATCH 077/129] Fix movement to accomodate to window scale changes

---
 src/am_map.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 61 insertions(+), 6 deletions(-)

diff --git a/src/am_map.c b/src/am_map.c
index 6cd9a3f0f..b1fad1b0c 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -160,6 +160,7 @@ static boolean am_stopped = true;
 static INT32 f_x, f_y;	// location of window on screen (always zero for both)
 static INT32 f_w, f_h;	// size of window on screen (always the screen width and height respectively)
 
+static boolean m_keydown[4]; // which window panning keys are being pressed down?
 static mpoint_t m_paninc; // how far the window pans each tic (map coords)
 static fixed_t mtof_zoommul; // how far the window zooms in each tic (map coords)
 static fixed_t ftom_zoommul; // how far the window zooms in each tic (fb coords)
@@ -422,6 +423,28 @@ static void AM_maxOutWindowScale(void)
 	AM_activateNewScale();
 }
 
+//
+// set window panning
+//
+static void AM_setWindowPanning(void)
+{
+	// up and down
+	if (m_keydown[2]) // pan up
+		m_paninc.y = FTOM(F_PANINC);
+	else if (m_keydown[3]) // pan down
+		m_paninc.y = -FTOM(F_PANINC);
+	else
+		m_paninc.y = 0;
+
+	// left and right
+	if (m_keydown[0]) // pan right
+		m_paninc.x = FTOM(F_PANINC);
+	else if (m_keydown[1]) // pan left
+		m_paninc.x = -FTOM(F_PANINC);
+	else
+		m_paninc.x = 0;
+}
+
 /** Responds to user inputs in automap mode.
   *
   * \param ev Event to possibly respond to.
@@ -454,35 +477,49 @@ boolean AM_Responder(event_t *ev)
 			{
 				case AM_PANRIGHTKEY: // pan right
 					if (!followplayer)
-						m_paninc.x = FTOM(F_PANINC);
+					{
+						m_keydown[0] = true;
+						AM_setWindowPanning();
+					}
 					else
 						rc = false;
 					break;
 				case AM_PANLEFTKEY: // pan left
 					if (!followplayer)
-						m_paninc.x = -FTOM(F_PANINC);
+					{
+						m_keydown[1] = true;
+						AM_setWindowPanning();
+					}
 					else
 						rc = false;
 					break;
 				case AM_PANUPKEY: // pan up
 					if (!followplayer)
-						m_paninc.y = FTOM(F_PANINC);
+					{
+						m_keydown[2] = true;
+						AM_setWindowPanning();
+					}
 					else
 						rc = false;
 					break;
 				case AM_PANDOWNKEY: // pan down
 					if (!followplayer)
-						m_paninc.y = -FTOM(F_PANINC);
+					{
+						m_keydown[3] = true;
+						AM_setWindowPanning();
+					}
 					else
 						rc = false;
 					break;
 				case AM_ZOOMOUTKEY: // zoom out
 					mtof_zoommul = M_ZOOMOUT;
 					ftom_zoommul = M_ZOOMIN;
+					AM_setWindowPanning();
 					break;
 				case AM_ZOOMINKEY: // zoom in
 					mtof_zoommul = M_ZOOMIN;
 					ftom_zoommul = M_ZOOMOUT;
+					AM_setWindowPanning();
 					break;
 				case AM_TOGGLEKEY:
 					AM_Stop();
@@ -515,14 +552,32 @@ boolean AM_Responder(event_t *ev)
 			switch (ev->data1)
 			{
 				case AM_PANRIGHTKEY:
+					if (!followplayer)
+					{
+						m_keydown[0] = false;
+						AM_setWindowPanning();
+					}
+					break;
 				case AM_PANLEFTKEY:
 					if (!followplayer)
-						m_paninc.x = 0;
+					{
+						m_keydown[1] = false;
+						AM_setWindowPanning();
+					}
 					break;
 				case AM_PANUPKEY:
+					if (!followplayer)
+					{
+						m_keydown[2] = false;
+						AM_setWindowPanning();
+					}
+					break;
 				case AM_PANDOWNKEY:
 					if (!followplayer)
-						m_paninc.y = 0;
+					{
+						m_keydown[3] = false;
+						AM_setWindowPanning();
+					}
 					break;
 				case AM_ZOOMOUTKEY:
 				case AM_ZOOMINKEY:

From 46cbe63b43f9cd3b4958c1c911907b4e6b9a6aa0 Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sun, 26 Jan 2020 23:39:31 -0300
Subject: [PATCH 078/129] Fix going big

---
 src/am_map.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/am_map.c b/src/am_map.c
index b1fad1b0c..f6bafe33b 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -533,6 +533,7 @@ boolean AM_Responder(event_t *ev)
 					}
 					else
 						AM_restoreScaleAndLoc();
+					AM_setWindowPanning();
 					break;
 				case AM_FOLLOWKEY:
 					followplayer = !followplayer;

From 7f57327ff7b806e3aa118c745df142c187d75fc5 Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sun, 26 Jan 2020 23:41:34 -0300
Subject: [PATCH 079/129] "changes" not "changed"

---
 src/am_map.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/am_map.c b/src/am_map.c
index f6bafe33b..b2c7de442 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -347,7 +347,7 @@ static void AM_initVariables(void)
 }
 
 //
-// Called when the screen size changed.
+// Called when the screen size changes.
 //
 static void AM_FrameBufferInit(void)
 {

From cf7b4d826a82d561b6981dd1fdce106e57fbc234 Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sun, 26 Jan 2020 23:46:07 -0300
Subject: [PATCH 080/129] Remove redundancy

---
 src/g_game.c  | 4 ++++
 src/p_setup.c | 3 +--
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index f5d7cd2fb..8383782cb 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1941,6 +1941,10 @@ boolean G_IsTitleCardAvailable(void)
 	if (gametyperules & GTR_NOTITLECARD)
 		return false;
 
+	// The current level has no name.
+	if (!mapheaderinfo[gamemap-1]->lvlttl[0])
+		return false;
+
 	// The title card is available.
 	return true;
 }
diff --git a/src/p_setup.c b/src/p_setup.c
index 2b0a5efa3..c291dc7c3 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -3684,8 +3684,7 @@ boolean P_LoadLevel(boolean fromnetsave)
 		return true;
 
 	// If so...
-	if ((!(mapheaderinfo[gamemap-1]->levelflags & LF_NOTITLECARD)) && (*mapheaderinfo[gamemap-1]->lvlttl != '\0'))
-		G_PreLevelTitleCard();
+	G_PreLevelTitleCard();
 
 	return true;
 }

From 1774ba22b6ea77fbc0442573d08e00439e9b6fbb Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sun, 26 Jan 2020 23:50:36 -0300
Subject: [PATCH 081/129] Fix title card not showing up at all if focus lost

---
 src/st_stuff.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/st_stuff.c b/src/st_stuff.c
index 4676506fc..d99d564c8 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1278,13 +1278,15 @@ void ST_preDrawTitleCard(void)
 //
 void ST_runTitleCard(void)
 {
+	boolean run = !(paused || P_AutoPause());
+
 	if (!G_IsTitleCardAvailable())
 		return;
 
 	if (lt_ticker >= (lt_endtime + TICRATE))
 		return;
 
-	if (!(paused || P_AutoPause()))
+	if (run || (lt_ticker < PRELEVELTIME))
 	{
 		// tick
 		lt_ticker++;

From 1548a22ea902a201de6779a6877bd225c7cf735e Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Mon, 27 Jan 2020 00:44:10 -0300
Subject: [PATCH 082/129] Fix M_DrawNightsAttackMountains being broken for
 obvious reasons

---
 src/m_menu.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index f8c14dd69..049b66d67 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -5388,7 +5388,8 @@ static void M_DrawNightsAttackMountains(void)
 	static INT32 bgscrollx;
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
 	patch_t *background = W_CachePatchName(curbgname, PU_PATCH);
-	INT32 x = FixedInt(bgscrollx) % SHORT(background->width);
+	INT16 w = SHORT(background->width);
+	INT32 x = FixedInt(-bgscrollx) % w;
 	INT32 y = BASEVIDHEIGHT - SHORT(background->height)*2;
 
 	if (vid.height != BASEVIDHEIGHT * dupz)
@@ -5396,11 +5397,13 @@ static void M_DrawNightsAttackMountains(void)
 	V_DrawFill(0, y+50, vid.width, BASEVIDHEIGHT, V_SNAPTOLEFT|31);
 
 	V_DrawScaledPatch(x, y, V_SNAPTOLEFT, background);
-	x += SHORT(background->width);
+	x += w;
 	if (x < BASEVIDWIDTH)
 		V_DrawScaledPatch(x, y, V_SNAPTOLEFT, background);
 
-	bgscrollx -= (FRACUNIT/2);
+	bgscrollx += (FRACUNIT/2);
+	if (bgscrollx > w<<FRACBITS)
+		bgscrollx &= 0xFFFF;
 }
 
 // NiGHTS Attack foreground.

From 28fd2380dfd980804fe18decbb9c8e274f0a3bd5 Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Mon, 27 Jan 2020 13:28:07 -0300
Subject: [PATCH 083/129] Fix F_StartContinue fading out incorrectly in OpenGL

---
 src/f_finale.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/f_finale.c b/src/f_finale.c
index bc904d8f2..ed89e05cd 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -3628,7 +3628,6 @@ void F_StartContinue(void)
 	}
 
 	wipestyleflags = WSF_FADEOUT;
-	F_TryColormapFade(31);
 	G_SetGamestate(GS_CONTINUING);
 	gameaction = ga_nothing;
 

From 636093a59ddd95b25d79c4ebff51dc6412de1956 Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Mon, 27 Jan 2020 13:55:13 -0300
Subject: [PATCH 084/129] Fix color LUT using the wrong palette

---
 src/m_anigif.c | 18 +++++-------------
 src/r_data.c   | 12 ++++++++----
 src/r_data.h   |  3 ++-
 src/v_video.c  |  3 +--
 src/v_video.h  |  5 +----
 5 files changed, 17 insertions(+), 24 deletions(-)

diff --git a/src/m_anigif.c b/src/m_anigif.c
index 32fc2746d..f062bc826 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -624,14 +624,6 @@ static void GIF_framewrite(void)
 //
 INT32 GIF_open(const char *filename)
 {
-#if 0
-	if (rendermode != render_soft)
-	{
-		CONS_Alert(CONS_WARNING, M_GetText("GIFs cannot be taken in non-software modes!\n"));
-		return 0;
-	}
-#endif
-
 	gif_out = fopen(filename, "wb");
 	if (!gif_out)
 		return 0;
@@ -640,13 +632,13 @@ INT32 GIF_open(const char *filename)
 	gif_downscale = (!!cv_gif_downscale.value);
 
 	// GIF color table
-	// In hardware mode, uses the master palette
-	gif_palette = ((cv_screenshot_colorprofile.value
+	// In hardware mode, forces the local palette
 #ifdef HWRENDER
-	&& (rendermode == render_soft)
+	if (rendermode == render_opengl)
+		gif_palette = pLocalPalette;
+	else
 #endif
-	) ? pLocalPalette
-	: pMasterPalette);
+		gif_palette = ((cv_screenshot_colorprofile.value) ? pLocalPalette : pMasterPalette);
 
 	GIF_headwrite();
 	gif_frames = 0;
diff --git a/src/r_data.c b/src/r_data.c
index 9f80e257b..e0a3d48d4 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -2465,16 +2465,20 @@ extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *ex
 
 // Thanks to quake2 source!
 // utils3/qdata/images.c
-UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b)
+UINT8 NearestPaletteColor(UINT8 r, UINT8 g, UINT8 b, RGBA_t *palette)
 {
 	int dr, dg, db;
 	int distortion, bestdistortion = 256 * 256 * 4, bestcolor = 0, i;
 
+	// Use master palette if none specified
+	if (palette == NULL)
+		palette = pMasterPalette;
+
 	for (i = 0; i < 256; i++)
 	{
-		dr = r - pMasterPalette[i].s.red;
-		dg = g - pMasterPalette[i].s.green;
-		db = b - pMasterPalette[i].s.blue;
+		dr = r - palette[i].s.red;
+		dg = g - palette[i].s.green;
+		db = b - palette[i].s.blue;
 		distortion = dr*dr + dg*dg + db*db;
 		if (distortion < bestdistortion)
 		{
diff --git a/src/r_data.h b/src/r_data.h
index f028f2f5d..145f0182b 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -171,7 +171,8 @@ const char *R_NameForColormap(extracolormap_t *extra_colormap);
 #define R_PutRgbaRGB(r, g, b) (R_PutRgbaR(r) + R_PutRgbaG(g) + R_PutRgbaB(b))
 #define R_PutRgbaRGBA(r, g, b, a) (R_PutRgbaRGB(r, g, b) + R_PutRgbaA(a))
 
-UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
+UINT8 NearestPaletteColor(UINT8 r, UINT8 g, UINT8 b, RGBA_t *palette);
+#define NearestColor(r, g, b) NearestPaletteColor(r, g, b, NULL)
 
 extern INT32 numtextures;
 
diff --git a/src/v_video.c b/src/v_video.c
index 4785a1541..81625ff9e 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -3244,7 +3244,6 @@ Unoptimized version
 #endif
 }
 
-// Taken from my videos-in-SRB2 project
 // Generates a color look-up table
 // which has up to 64 colors at each channel
 // (see the defines in v_video.h)
@@ -3261,7 +3260,7 @@ void InitColorLUT(RGBA_t *palette)
 		for (r = 0; r < CLUTSIZE; r++)
 			for (g = 0; g < CLUTSIZE; g++)
 				for (b = 0; b < CLUTSIZE; b++)
-					colorlookup[r][g][b] = NearestColor(r << SHIFTCOLORBITS, g << SHIFTCOLORBITS, b << SHIFTCOLORBITS);
+					colorlookup[r][g][b] = NearestPaletteColor(r << SHIFTCOLORBITS, g << SHIFTCOLORBITS, b << SHIFTCOLORBITS, palette);
 		clutinit = true;
 		lastpalette = palette;
 	}
diff --git a/src/v_video.h b/src/v_video.h
index eb75a414f..bc19f6e99 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -37,10 +37,7 @@ cv_allcaps;
 // Allocates buffer screens, call before R_Init.
 void V_Init(void);
 
-// Taken from my videos-in-SRB2 project
-// Generates a color look-up table
-// which has up to 64 colors at each channel
-
+// Color look-up table
 #define COLORBITS 6
 #define SHIFTCOLORBITS (8-COLORBITS)
 #define CLUTSIZE (1<<COLORBITS)

From 31ce0764115e2966c3d9f0ed0b26b0c0bec65f78 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Tue, 28 Jan 2020 14:02:36 +0100
Subject: [PATCH 085/129] Only call P_CheckSurvivors() in tag gametypes

---
 src/p_tick.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/p_tick.c b/src/p_tick.c
index 0b30ff42d..74472b71f 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -600,7 +600,7 @@ void P_Ticker(boolean run)
 			{
 				players[i].quittime++;
 
-				if (players[i].quittime == 30 * TICRATE)
+				if (players[i].quittime == 30 * TICRATE && G_TagGametype())
 					P_CheckSurvivors();
 
 				if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE)

From f4de1938098067327731d8502ff4ec68e74ee027 Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Thu, 30 Jan 2020 22:11:50 -0500
Subject: [PATCH 086/129] Fix memory leak while chat is on screen

---
 src/hu_stuff.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 274c2bdfd..69e8c99ff 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1437,7 +1437,7 @@ static void HU_drawMiniChat(void)
 
 	for (; i>0; i--)
 	{
-		const char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
+		char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
 		size_t j = 0;
 		INT32 linescount = 0;
 
@@ -1479,6 +1479,9 @@ static void HU_drawMiniChat(void)
 		dy = 0;
 		dx = 0;
 		msglines += linescount+1;
+
+		if (msg)
+			Z_Free(msg);
 	}
 
 	y = chaty - charheight*(msglines+1);
@@ -1501,7 +1504,7 @@ static void HU_drawMiniChat(void)
 		INT32 timer = ((cv_chattime.value*TICRATE)-chat_timers[i]) - cv_chattime.value*TICRATE+9; // see below...
 		INT32 transflag = (timer >= 0 && timer <= 9) ? (timer*V_10TRANS) : 0; // you can make bad jokes out of this one.
 		size_t j = 0;
-		const char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]); // get the current message, and word wrap it.
+		char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]); // get the current message, and word wrap it.
 		UINT8 *colormap = NULL;
 
 		while(msg[j]) // iterate through msg
@@ -1547,6 +1550,9 @@ static void HU_drawMiniChat(void)
 		}
 		dy += charheight;
 		dx = 0;
+
+		if (msg)
+			Z_Free(msg);
 	}
 
 	// decrement addy and make that shit smooth:
@@ -1598,7 +1604,7 @@ static void HU_drawChatLog(INT32 offset)
 	{
 		INT32 clrflag = 0;
 		INT32 j = 0;
-		const char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]); // get the current message, and word wrap it.
+		char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]); // get the current message, and word wrap it.
 		UINT8 *colormap = NULL;
 		while(msg[j]) // iterate through msg
 		{
@@ -1638,6 +1644,9 @@ static void HU_drawChatLog(INT32 offset)
 		}
 		dy += charheight;
 		dx = 0;
+
+		if (msg)
+			Z_Free(msg);
 	}
 
 

From 3afc766f5e36d094395fe0da41e7142be070e8c1 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Thu, 30 Jan 2020 23:58:35 -0800
Subject: [PATCH 087/129] Oops

---
 src/console.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/console.c b/src/console.c
index ba5ba71df..8746bf036 100644
--- a/src/console.c
+++ b/src/console.c
@@ -769,7 +769,7 @@ boolean CON_Responder(event_t *ev)
 	// check for console toggle key
 	if (ev->type != ev_console)
 	{
-		if (modeattacking || metalrecording || menuactive)
+		if (modeattacking || metalrecording)
 			return false;
 
 		if (key == gamecontrol[gc_console][0] || key == gamecontrol[gc_console][1])

From 7c83e5e420fb91ddde3c464e667b2474d3b02f2f Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Fri, 31 Jan 2020 16:37:55 -0500
Subject: [PATCH 088/129] Implement folder blacklisting

---
 src/w_wad.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/src/w_wad.c b/src/w_wad.c
index dc400987f..b0c901302 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1746,6 +1746,18 @@ W_VerifyWAD (FILE *fp, lumpchecklist_t *checklist, boolean status)
 	return true;
 }
 
+// List of blacklisted folders to use when checking the PK3
+static lumpchecklist_t folderblacklist[] =
+{
+	{"Lua/", 4},
+	{"SOC/", 4},
+	{"Sprites/",  8},
+	{"Textures/", 9},
+	{"Patches/", 8},
+	{"Flats/", 6},
+	{"Fades/", 6}
+};
+
 static int
 W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 {
@@ -1791,6 +1803,13 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 		if (fgets(fullname, zentry.namelen + 1, fp) != fullname)
 			return true;
 
+		// Check for folders first, if it's blacklisted it will return false
+		if (W_VerifyName(fullname, folderblacklist, status))
+		{
+			CONS_Printf("Blacklisted folder found - %s\n", fullname);
+			return false;
+		}
+
 		// Strip away file address and extension for the 8char name.
 		if ((trimname = strrchr(fullname, '/')) != 0)
 			trimname++;

From 71707e6dca569abc5ea62a27f614ec962741f00b Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Fri, 31 Jan 2020 14:32:47 -0800
Subject: [PATCH 089/129] Reset rollangle on TNT explosion

This is the where the extreme lag when TNT Barrels explode came from. Probably
because the sprites are big and there's four of 'em! It shouldn't matter that
these aren't rotated--they're pretty round.
---
 src/info.c | 13 +++++++------
 src/info.h |  1 +
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/info.c b/src/info.c
index 45034fe37..758f137c2 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2343,12 +2343,13 @@ state_t states[NUMSTATES] =
 
 	// TNT barrel
 	{SPR_BARR, 0, -1, {NULL}, 0, 0, S_NULL}, // S_TNTBARREL_STND1
-	{SPR_BARX, 0|FF_FULLBRIGHT, 3, {A_SetObjectFlags}, MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP, 0, S_TNTBARREL_EXPL2}, // S_TNTBARREL_EXPL1
-	{SPR_BARX, 1|FF_FULLBRIGHT, 2, {A_TNTExplode}, MT_TNTDUST, 0, S_TNTBARREL_EXPL3}, // S_TNTBARREL_EXPL2
-	{SPR_BARX, 1|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_TNTBARREL_EXPL4}, // S_TNTBARREL_EXPL3
-	{SPR_BARX, 2|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_TNTBARREL_EXPL5}, // S_TNTBARREL_EXPL4
-	{SPR_BARX, 3|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_TNTBARREL_EXPL6}, // S_TNTBARREL_EXPL5
-	{SPR_NULL, 0, 35, {NULL}, 0, 0, S_NULL}, // S_TNTBARREL_EXPL6
+	{SPR_BARX, 0, 0, {A_RollAngle}, 0, 1, S_TNTBARREL_EXPL2}, // S_TNTBARREL_EXPL1
+	{SPR_BARX, 0|FF_FULLBRIGHT, 3, {A_SetObjectFlags}, MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP, 0, S_TNTBARREL_EXPL3}, // S_TNTBARREL_EXPL2
+	{SPR_BARX, 1|FF_FULLBRIGHT, 2, {A_TNTExplode}, MT_TNTDUST, 0, S_TNTBARREL_EXPL4}, // S_TNTBARREL_EXPL3
+	{SPR_BARX, 1|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_TNTBARREL_EXPL5}, // S_TNTBARREL_EXPL4
+	{SPR_BARX, 2|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_TNTBARREL_EXPL6}, // S_TNTBARREL_EXPL5
+	{SPR_BARX, 3|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_TNTBARREL_EXPL7}, // S_TNTBARREL_EXPL6
+	{SPR_NULL, 0, 35, {NULL}, 0, 0, S_NULL}, // S_TNTBARREL_EXPL7
 #ifndef ROTSPRITE
 	{SPR_BARR, 1|FF_ANIMATE, -1, {NULL}, 7, 2, S_NULL}, // S_TNTBARREL_FLYING
 #else
diff --git a/src/info.h b/src/info.h
index 628335635..59b8eb68d 100644
--- a/src/info.h
+++ b/src/info.h
@@ -2503,6 +2503,7 @@ typedef enum state
 	S_TNTBARREL_EXPL4,
 	S_TNTBARREL_EXPL5,
 	S_TNTBARREL_EXPL6,
+	S_TNTBARREL_EXPL7,
 	S_TNTBARREL_FLYING,
 
 	// TNT proximity shell

From a46b3976593b2c33c60a90a6a203839958331c17 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Fri, 31 Jan 2020 14:35:19 -0800
Subject: [PATCH 090/129] Remove code that does effectively nothing

---
 src/r_patch.c | 12 +-----------
 1 file changed, 1 insertion(+), 11 deletions(-)

diff --git a/src/r_patch.c b/src/r_patch.c
index b4127f825..80c0f846f 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -1196,7 +1196,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 	INT32 angle;
 	patch_t *patch;
 	patch_t *newpatch;
-	UINT16 *rawsrc, *rawdst;
+	UINT16 *rawdst;
 	size_t size;
 	INT32 bflip = (flip != 0x00);
 
@@ -1242,16 +1242,6 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 			leftoffset = width - leftoffset;
 		}
 
-		// Draw the sprite to a temporary buffer.
-		size = (width*height);
-		rawsrc = Z_Malloc(size * sizeof(UINT16), PU_STATIC, NULL);
-
-		// can't memset here
-		for (i = 0; i < size; i++)
-			rawsrc[i] = 0xFF00;
-
-		R_PatchToMaskedFlat(patch, rawsrc, bflip);
-
 		// Don't cache angle = 0
 		for (angle = 1; angle < ROTANGLES; angle++)
 		{

From a2c15c5cb2c9d070e5a20420d8c45fd1db0c1832 Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Fri, 31 Jan 2020 20:21:09 -0500
Subject: [PATCH 091/129] Only check if the directory is not empty, use
 strncasecmp for case insensitive comparing, and remove debug print

---
 src/w_wad.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/src/w_wad.c b/src/w_wad.c
index b0c901302..12d912c92 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1691,7 +1691,7 @@ W_VerifyName (const char *name, lumpchecklist_t *checklist, boolean status)
 	size_t j;
 	for (j = 0; checklist[j].len && checklist[j].name; ++j)
 	{
-		if (( strncmp(name, checklist[j].name,
+		if (( strncasecmp(name, checklist[j].name,
 						checklist[j].len) != false ) == status)
 		{
 			return true;
@@ -1803,20 +1803,13 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 		if (fgets(fullname, zentry.namelen + 1, fp) != fullname)
 			return true;
 
-		// Check for folders first, if it's blacklisted it will return false
-		if (W_VerifyName(fullname, folderblacklist, status))
-		{
-			CONS_Printf("Blacklisted folder found - %s\n", fullname);
-			return false;
-		}
-
 		// Strip away file address and extension for the 8char name.
 		if ((trimname = strrchr(fullname, '/')) != 0)
 			trimname++;
 		else
 			trimname = fullname; // Care taken for root files.
 
-		if (*trimname) // Ignore directories
+		if (*trimname) // Ignore directories, well kinda
 		{
 			if ((dotpos = strrchr(trimname, '.')) == 0)
 				dotpos = fullname + strlen(fullname); // Watch for files without extension.
@@ -1826,6 +1819,10 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 
 			if (! W_VerifyName(lumpname, checklist, status))
 				return false;
+
+			// Check for directories next, if it's blacklisted it will return false
+			if (W_VerifyName(fullname, folderblacklist, status))
+				return false;
 		}
 
 		free(fullname);

From ec02a90ebc2d9edcbc1c71d46234d6700ec25932 Mon Sep 17 00:00:00 2001
From: lachwright <lachlanwright17@gmail.com>
Date: Sat, 1 Feb 2020 14:29:49 +0800
Subject: [PATCH 092/129] Make flight controls less bullshit

---
 src/p_user.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/p_user.c b/src/p_user.c
index 17a4e2ed1..636448196 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -5503,10 +5503,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						; // Can't do anything if you're a fish out of water!
 					else if (player->powers[pw_tailsfly]) // If currently flying, give an ascend boost.
 					{
-						if (!player->fly1)
-							player->fly1 = 20;
-						else
-							player->fly1 = 2;
+						player->fly1 = 20;
 
 						if (player->charability == CA_SWIM)
 							player->fly1 /= 2;
@@ -8487,7 +8484,10 @@ static void P_MovePlayer(player_t *player)
 			// Descend
 			if (cmd->buttons & BT_USE && !(player->pflags & PF_STASIS) && !player->exiting && !(player->mo->eflags & MFE_GOOWATER))
 				if (P_MobjFlip(player->mo)*player->mo->momz > -FixedMul(5*actionspd, player->mo->scale))
+				{
+					player->fly1 = 0;
 					P_SetObjectMomZ(player->mo, -actionspd/2, true);
+				}
 
 		}
 		else

From f5e49ace00820969fbd74bea158935ed9fdc6649 Mon Sep 17 00:00:00 2001
From: lachwright <lachlanwright17@gmail.com>
Date: Sat, 1 Feb 2020 14:49:48 +0800
Subject: [PATCH 093/129] Have spin set fly1 to 2 instead of 0, akin to the
 previous cutoff behavior

---
 src/p_user.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/p_user.c b/src/p_user.c
index 636448196..221e22612 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8485,7 +8485,8 @@ static void P_MovePlayer(player_t *player)
 			if (cmd->buttons & BT_USE && !(player->pflags & PF_STASIS) && !player->exiting && !(player->mo->eflags & MFE_GOOWATER))
 				if (P_MobjFlip(player->mo)*player->mo->momz > -FixedMul(5*actionspd, player->mo->scale))
 				{
-					player->fly1 = 0;
+					if (player->fly1 > 2)
+						player->fly1 = 2;
 					P_SetObjectMomZ(player->mo, -actionspd/2, true);
 				}
 

From 7dd0f2b80847e4ca794457c38c52082628b20664 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Sat, 1 Feb 2020 20:19:39 +0100
Subject: [PATCH 094/129] Fix splitscreen player being unable to move

---
 src/d_clisrv.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index ee4e62b91..79b63bb81 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -4678,6 +4678,7 @@ static void Local_Maketic(INT32 realtics)
 		G_BuildTiccmd(&localcmds2, realtics, 2);
 
 	localcmds.angleturn |= TICCMD_RECEIVED;
+	localcmds2.angleturn |= TICCMD_RECEIVED;
 }
 
 // This function is utter bullshit and is responsible for

From ab8eed6efbaabfb248c857b17d03d07a817d1345 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Sat, 1 Feb 2020 18:10:58 -0800
Subject: [PATCH 095/129] Add missing conditions to CleanupPlayerName

---
 src/d_netcmd.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 23ec00b2e..b74a8a76d 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1010,6 +1010,17 @@ static void CleanupPlayerName(INT32 playernum, const char *newname)
 
 		tmpname = p;
 
+		do
+		{
+			/* from EnsurePlayerNameIsGood */
+			if (!isprint(*p) || *p == ';' || (UINT8)*p >= 0x80)
+				break;
+		}
+		while (*++p) ;
+
+		if (*p)/* bad char found */
+			break;
+
 		// Remove trailing spaces.
 		p = &tmpname[strlen(tmpname)-1]; // last character
 		while (*p == ' ' && p >= tmpname)

From bf3b7fc5b03298cee514ab7e190de5bef99f614f Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Sat, 1 Feb 2020 18:20:35 -0800
Subject: [PATCH 096/129] Clean player name before joining!!!

---
 src/d_clisrv.c | 6 ++++++
 src/d_netcmd.c | 2 +-
 src/d_netcmd.h | 1 +
 3 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 704fc0901..bcae17aa3 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1275,8 +1275,14 @@ static boolean CL_SendJoin(void)
 	netbuffer->u.clientcfg.localplayers = localplayers;
 	netbuffer->u.clientcfg.version = VERSION;
 	netbuffer->u.clientcfg.subversion = SUBVERSION;
+
+	CleanupPlayerName(consoleplayer, cv_playername.zstring);
+	if (splitscreen)
+		CleanupPlayerName(1, cv_playername2.zstring);/* 1 is a HACK? oh no */
+
 	strncpy(netbuffer->u.clientcfg.names[0], cv_playername.zstring, MAXPLAYERNAME);
 	strncpy(netbuffer->u.clientcfg.names[1], cv_playername2.zstring, MAXPLAYERNAME);
+
 	return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak));
 }
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index b74a8a76d..cfd2162c5 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -982,7 +982,7 @@ boolean EnsurePlayerNameIsGood(char *name, INT32 playernum)
   *     SetPlayerName
   * \author Graue <graue@oceanbase.org>
   */
-static void CleanupPlayerName(INT32 playernum, const char *newname)
+void CleanupPlayerName(INT32 playernum, const char *newname)
 {
 	char *buf;
 	char *p;
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 8f857c6db..f258cde62 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -194,6 +194,7 @@ typedef union {
 // add game commands, needs cleanup
 void D_RegisterServerCommands(void);
 void D_RegisterClientCommands(void);
+void CleanupPlayerName(INT32 playernum, const char *newname);
 boolean EnsurePlayerNameIsGood(char *name, INT32 playernum);
 void D_SendPlayerConfig(void);
 void Command_ExitGame_f(void);

From d5ced42f06e9b75807b93a4c490ac69211bfc71c Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Sat, 1 Feb 2020 18:22:03 -0800
Subject: [PATCH 097/129] Remove Player 0

---
 src/d_netcmd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index cfd2162c5..35f23ab27 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -624,7 +624,7 @@ void D_RegisterClientCommands(void)
 	// Set default player names
 	// Monster Iestyn (12/08/19): not sure where else I could have actually put this, but oh well
 	for (i = 0; i < MAXPLAYERS; i++)
-		sprintf(player_names[i], "Player %d", i);
+		sprintf(player_names[i], "Player %d", 1 + i);
 
 	if (dedicated)
 		return;

From 17949496964e850128a562d0dc57b5f484b87556 Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Sun, 2 Feb 2020 18:52:41 -0500
Subject: [PATCH 098/129] Add empty entry

---
 src/w_wad.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/w_wad.c b/src/w_wad.c
index 12d912c92..c9426b46c 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1755,7 +1755,8 @@ static lumpchecklist_t folderblacklist[] =
 	{"Textures/", 9},
 	{"Patches/", 8},
 	{"Flats/", 6},
-	{"Fades/", 6}
+	{"Fades/", 6},
+	{NULL, 0},
 };
 
 static int

From bd90c203668eb4350e9df69b4eb834ef420ababe Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Sat, 1 Feb 2020 16:24:13 -0800
Subject: [PATCH 099/129] Turn the shadow scale if-else into a switch
 statement, for sake of editing and in case object types ever change
 :sweat_drops:

---
 src/p_mobj.c | 70 +++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 56 insertions(+), 14 deletions(-)

diff --git a/src/p_mobj.c b/src/p_mobj.c
index f02ed4f84..10898f70e 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -10461,6 +10461,61 @@ void P_SceneryThinker(mobj_t *mobj)
 // GAME SPAWN FUNCTIONS
 //
 
+static fixed_t P_DefaultMobjShadowScale (mobj_t *thing)
+{
+	switch (thing->type)
+	{
+		case MT_PLAYER:
+		case MT_ROLLOUTROCK:
+
+		case MT_EGGMOBILE4_MACE:
+		case MT_SMALLMACE:
+		case MT_BIGMACE:
+
+		case MT_SMALLGRABCHAIN:
+		case MT_BIGGRABCHAIN:
+
+		case MT_YELLOWSPRINGBALL:
+		case MT_REDSPRINGBALL:
+
+			return FRACUNIT;
+
+		case MT_RING:
+		case MT_FLINGRING:
+
+		case MT_BLUESPHERE:
+		case MT_FLINGBLUESPHERE:
+		case MT_BOMBSPHERE:
+
+		case MT_REDTEAMRING:
+		case MT_BLUETEAMRING:
+		case MT_REDFLAG:
+		case MT_BLUEFLAG:
+
+		case MT_EMBLEM:
+
+		case MT_TOKEN:
+		case MT_EMERALD1:
+		case MT_EMERALD2:
+		case MT_EMERALD3:
+		case MT_EMERALD4:
+		case MT_EMERALD5:
+		case MT_EMERALD6:
+		case MT_EMERALD7:
+		case MT_EMERHUNT:
+		case MT_FLINGEMERALD:
+
+			return 2*FRACUNIT/3;
+
+		default:
+
+			if (thing->flags & (MF_ENEMY|MF_BOSS))
+				return FRACUNIT;
+			else
+				return 0;
+	}
+}
+
 //
 // P_SpawnMobj
 //
@@ -10562,20 +10617,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		mobj->z = z;
 
 	// Set shadowscale here, before spawn hook so that Lua can change it
-	if (
-		type == MT_PLAYER ||
-		type == MT_ROLLOUTROCK ||
-		type == MT_EGGMOBILE4_MACE ||
-		(type >= MT_SMALLMACE && type <= MT_REDSPRINGBALL) ||
-		(mobj->flags & (MF_ENEMY|MF_BOSS))
-	)
-		mobj->shadowscale = FRACUNIT;
-	else if (
-		type >= MT_RING && type <= MT_FLINGEMERALD && type != MT_EMERALDSPAWN
-	)
-		mobj->shadowscale = 2*FRACUNIT/3;
-	else
-		mobj->shadowscale = 0;
+	mobj->shadowscale = P_DefaultMobjShadowScale(mobj);
 
 #ifdef HAVE_BLUA
 	// DANGER! This can cause P_SpawnMobj to return NULL!

From 01433c3648955308c42fef563d97c870d747fda6 Mon Sep 17 00:00:00 2001
From: colette <red@lyrawearspants.com>
Date: Mon, 3 Feb 2020 23:09:18 -0500
Subject: [PATCH 100/129] Fix title/card hud hooks grabbing the wrong functions

---
 src/lua_hudlib.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index f451944e3..d08b69ffc 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -1214,7 +1214,7 @@ void LUAh_GameHUD(player_t *stplayr)
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 2); // HUD[2] = rendering funcs
+	lua_rawgeti(gL, -1, 2+hudhook_game); // HUD[2] = rendering funcs
 	I_Assert(lua_istable(gL, -1));
 
 	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
@@ -1248,7 +1248,7 @@ void LUAh_ScoresHUD(void)
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 3); // HUD[3] = rendering funcs
+	lua_rawgeti(gL, -1, 2+hudhook_scores); // HUD[3] = rendering funcs
 	I_Assert(lua_istable(gL, -1));
 
 	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
@@ -1273,7 +1273,7 @@ void LUAh_TitleHUD(void)
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 4); // HUD[4] = rendering funcs
+	lua_rawgeti(gL, -1, 2+hudhook_title); // HUD[5] = rendering funcs
 	I_Assert(lua_istable(gL, -1));
 
 	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
@@ -1298,7 +1298,7 @@ void LUAh_TitleCardHUD(player_t *stplayr)
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 5); // HUD[5] = rendering funcs
+	lua_rawgeti(gL, -1, 2+hudhook_titlecard); // HUD[6] = rendering funcs
 	I_Assert(lua_istable(gL, -1));
 
 	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
@@ -1332,7 +1332,7 @@ void LUAh_IntermissionHUD(void)
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
-	lua_rawgeti(gL, -1, 4); // HUD[4] = rendering funcs
+	lua_rawgeti(gL, -1, 2+hudhook_intermission); // HUD[4] = rendering funcs
 	I_Assert(lua_istable(gL, -1));
 
 	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw

From f1bdaa2fda5bacd0098663b8a597f99ebd13c43e Mon Sep 17 00:00:00 2001
From: Monster Iestyn <iestynjealous@ntlworld.com>
Date: Wed, 5 Feb 2020 19:55:40 +0000
Subject: [PATCH 101/129] Updated version number to 2.2.1, increment
 MODVERSION.

Also updated CMakeLists.txt, appveyor.yml and this one Xcode project file as usual
---
 CMakeLists.txt                                   | 2 +-
 appveyor.yml                                     | 2 +-
 src/doomdef.h                                    | 8 ++++----
 src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj | 4 ++--
 4 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8bbf46945..855393de1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
 # DO NOT CHANGE THIS SRB2 STRING! Some variable names depend on this string.
 # Version change is fine.
 project(SRB2
-	VERSION 2.2.0
+	VERSION 2.2.1
 	LANGUAGES C)
 
 if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
diff --git a/appveyor.yml b/appveyor.yml
index d33d3d3a3..20b18d7d5 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.2.0.{branch}-{build}
+version: 2.2.1.{branch}-{build}
 os: MinGW
 
 environment:
diff --git a/src/doomdef.h b/src/doomdef.h
index 3d02871e4..071090285 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -143,9 +143,9 @@ extern char logfilename[1024];
 // we use comprevision and compbranch instead.
 #else
 #define VERSION    202 // Game version
-#define SUBVERSION 0  // more precise version number
-#define VERSIONSTRING "v2.2.0"
-#define VERSIONSTRINGW L"v2.2.0"
+#define SUBVERSION 1  // more precise version number
+#define VERSIONSTRING "v2.2.1"
+#define VERSIONSTRINGW L"v2.2.1"
 // Hey! If you change this, add 1 to the MODVERSION below!
 // Otherwise we can't force updates!
 #endif
@@ -210,7 +210,7 @@ extern char logfilename[1024];
 // 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.2.0 is not version "1".
-#define MODVERSION 40
+#define MODVERSION 41
 
 // To version config.cfg, MAJOREXECVERSION is set equal to MODVERSION automatically.
 // Increment MINOREXECVERSION whenever a config change is needed that does not correspond
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index ab3157c44..2b03cb8d4 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1219,7 +1219,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.2.0;
+				CURRENT_PROJECT_VERSION = 2.2.1;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1231,7 +1231,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.2.0;
+				CURRENT_PROJECT_VERSION = 2.2.1;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (

From d03d09f397e1ff07c7b430e028cf9b2f1f53a0c5 Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Wed, 5 Feb 2020 15:20:35 -0500
Subject: [PATCH 102/129] Update credits again

---
 src/f_finale.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/f_finale.c b/src/f_finale.c
index ce4ec0eb4..99ada119a 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1234,7 +1234,7 @@ static const char *credits[] = {
 	"Thomas \"Shadow Hog\" Igoe",
 	"Alexander \"DrTapeworm\" Moench-Ford",
 	"\"Kaito Sinclaire\"",
-	"\"QueenDelta\"",
+	"Anna \"QueenDelta\" Sandlin",
 	"Wessel \"sphere\" Smit",
 	"\"Spazzo\"",
 	"\"SSNTails\"",

From 7a5d7afb30a428b22831f028b3ceb565452c9fa3 Mon Sep 17 00:00:00 2001
From: lachwright <lachlanwright17@gmail.com>
Date: Thu, 6 Feb 2020 23:06:15 +0800
Subject: [PATCH 103/129] Add Rob as the game's producer

---
 src/f_finale.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/f_finale.c b/src/f_finale.c
index 99ada119a..d6ffed26f 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1120,6 +1120,9 @@ static const char *credits[] = {
 	"\1Sonic Robo Blast II",
 	"\1Credits",
 	"",
+	"\1Producer",
+	"Rob Tisdell",
+	"",
 	"\1Game Design",
 	"Ben \"Mystic\" Geyer",
 	"\"SSNTails\"",

From 5e516eb98d5f1ddff1afcb2bb9247349d244d533 Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sat, 8 Feb 2020 18:50:05 -0300
Subject: [PATCH 104/129] 1 left shifted by zero is still 1

---
 src/dehacked.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 45f00b8cf..5fd12b774 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1293,10 +1293,7 @@ static void readgametype(MYFILE *f, char *gtname)
 				UINT32 wordgt = 0;
 				for (j = 0; GAMETYPERULE_LIST[j]; j++)
 					if (fastcmp(word, GAMETYPERULE_LIST[j])) {
-						if (!j) // GTR_CAMPAIGN
-							wordgt |= 1;
-						else
-							wordgt |= (1<<j);
+						wordgt |= (1<<j);
 						if (i || word2[0] == 'T' || word2[0] == 'Y')
 							newgtrules |= wordgt;
 						break;

From 2d0e72d756213f4bdf942a668029a2652e8827cc Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sat, 8 Feb 2020 21:40:30 -0300
Subject: [PATCH 105/129] Fix broken GT_ constants with custom gametypes

---
 src/g_game.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index 8383782cb..efc96a50f 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3256,8 +3256,8 @@ void G_AddGametypeConstant(INT16 gtype, const char *newgtconst)
 {
 	size_t r = 0; // read
 	size_t w = 0; // write
-	char *gtconst = Z_Calloc(strlen(newgtconst) + 3, PU_STATIC, NULL);
-	char *tmpconst = Z_Calloc(strlen(newgtconst), PU_STATIC, NULL);
+	char *gtconst = Z_Calloc(strlen(newgtconst) + 4, PU_STATIC, NULL);
+	char *tmpconst = Z_Calloc(strlen(newgtconst) + 1, PU_STATIC, NULL);
 
 	// Copy the gametype name.
 	strcpy(tmpconst, newgtconst);

From 15c263e9c729c56f5c780656697898ec94e851b2 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Sun, 9 Feb 2020 10:35:23 -0500
Subject: [PATCH 106/129] Z_Zone: fixup Valgrind support

---
 src/z_zone.c | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/src/z_zone.c b/src/z_zone.c
index 5a0ff638b..e0e37312a 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -232,12 +232,12 @@ void Z_Free(void *ptr)
 
 	// Free the memory and get rid of the block.
 	free(block->real);
-	block->prev->next = block->next;
-	block->next->prev = block->prev;
-	free(block);
 #ifdef VALGRIND_DESTROY_MEMPOOL
 	VALGRIND_DESTROY_MEMPOOL(block);
 #endif
+	block->prev->next = block->next;
+	block->next->prev = block->prev;
+	free(block);
 }
 
 /** malloc() that doesn't accept failure.
@@ -317,13 +317,9 @@ void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 	// The mem header lives 'sizeof (memhdr_t)' bytes before given.
 	hdr = (memhdr_t *)((UINT8 *)given - sizeof *hdr);
 
-#ifdef VALGRIND_CREATE_MEMPOOL
-	VALGRIND_CREATE_MEMPOOL(block, padsize, Z_calloc);
+#ifdef HAVE_VALGRIND
 	Z_calloc = false;
 #endif
-#ifdef VALGRIND_MEMPOOL_ALLOC
-	VALGRIND_MEMPOOL_ALLOC(block, hdr, size + sizeof *hdr);
-#endif
 
 	block->next = head.next;
 	block->prev = &head;
@@ -341,6 +337,13 @@ void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 	block->size = blocksize;
 	block->realsize = size;
 
+#ifdef VALGRIND_CREATE_MEMPOOL
+	VALGRIND_CREATE_MEMPOOL(block, padsize, Z_calloc);
+#endif
+//#ifdef VALGRIND_MEMPOOL_ALLOC
+//	VALGRIND_MEMPOOL_ALLOC(block, hdr, size + sizeof *hdr);
+//#endif
+
 	hdr->id = ZONEID;
 	hdr->block = block;
 

From e5a325994e086a541744c9ee6642b7c734078985 Mon Sep 17 00:00:00 2001
From: Monster Iestyn <iestynjealous@ntlworld.com>
Date: Sun, 9 Feb 2020 19:15:04 +0000
Subject: [PATCH 107/129] Use the provided Regex strings to properly turn the
 entire info.h states/mobjtypes lists into strings for dehacked.c

...it's surprising what we actually missed in the states list, apart from just the missing state (yes this makes the states fix branch redundant)
---
 src/dehacked.c | 74 +++++++++++++++++++++++++++-----------------------
 1 file changed, 40 insertions(+), 34 deletions(-)

diff --git a/src/dehacked.c b/src/dehacked.c
index 45f00b8cf..e7ff6e400 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -4945,19 +4945,19 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PLAY_SUPER_TRANS3",
 	"S_PLAY_SUPER_TRANS4",
 	"S_PLAY_SUPER_TRANS5",
-	"S_PLAY_SUPER_TRANS6", // This has special significance in the code. If you add more frames, search for it and make the appropriate changes.
+	"S_PLAY_SUPER_TRANS6",
 
 	// technically the player goes here but it's an infinite tic state
 	"S_OBJPLACE_DUMMY",
 
-	// 1-Up Box Sprites (uses player sprite)
+	// 1-Up Box Sprites overlay (uses player sprite)
 	"S_PLAY_BOX1",
 	"S_PLAY_BOX2",
 	"S_PLAY_ICON1",
 	"S_PLAY_ICON2",
 	"S_PLAY_ICON3",
 
-	// Level end sign (uses player sprite)
+	// Level end sign overlay (uses player sprite)
 	"S_PLAY_SIGN",
 
 	// NiGHTS character (uses player sprite)
@@ -5205,7 +5205,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ROBOHOOD_JUMP2",
 	"S_ROBOHOOD_JUMP3",
 
-	// CastleBot FaceStabber
+	// Castlebot Facestabber
 	"S_FACESTABBER_STND1",
 	"S_FACESTABBER_STND2",
 	"S_FACESTABBER_STND3",
@@ -5425,6 +5425,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE_FLEE2",
 	"S_EGGMOBILE_BALL",
 	"S_EGGMOBILE_TARGET",
+
 	"S_BOSSEGLZ1",
 	"S_BOSSEGLZ2",
 
@@ -5477,7 +5478,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE3_FLEE1",
 	"S_EGGMOBILE3_FLEE2",
 
-	// Boss 3 pinch
+	// Boss 3 Pinch
 	"S_FAKEMOBILE_INIT",
 	"S_FAKEMOBILE",
 	"S_FAKEMOBILE_ATK1",
@@ -5493,7 +5494,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BOSSSEBH2",
 
 	// Boss 3 Shockwave
-
 	"S_SHOCKWAVE1",
 	"S_SHOCKWAVE2",
 
@@ -5530,9 +5530,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_JETFLAME",
 
 	// Boss 4 Spectator Eggrobo
-	"S_EGGROBO1_IDLE",
+	"S_EGGROBO1_STND",
 	"S_EGGROBO1_BSLAP1",
-	"S_EGGROBO2_BSLAP2",
+	"S_EGGROBO1_BSLAP2",
 	"S_EGGROBO1_PISSED",
 
 	// Boss 4 Spectator Eggrobo jet flame
@@ -5776,7 +5776,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_CYBRAKDEMON_NAPALM_ATTACK1",
 	"S_CYBRAKDEMON_NAPALM_ATTACK2",
 	"S_CYBRAKDEMON_NAPALM_ATTACK3",
-	"S_CYBRAKDEMON_FINISH_ATTACK", // If just attacked, remove MF2_FRET w/out going back to spawnstate
+	"S_CYBRAKDEMON_FINISH_ATTACK1", // If just attacked, remove MF2_FRET w/out going back to spawnstate
 	"S_CYBRAKDEMON_FINISH_ATTACK2", // Force a delay between attacks so you don't get bombarded with them back-to-back
 	"S_CYBRAKDEMON_PAIN1",
 	"S_CYBRAKDEMON_PAIN2",
@@ -6470,7 +6470,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_LITTLETUMBLEWEED_ROLL7",
 	"S_LITTLETUMBLEWEED_ROLL8",
 
-	// Cacti Sprites
+	// Cacti
 	"S_CACTI1",
 	"S_CACTI2",
 	"S_CACTI3",
@@ -6485,7 +6485,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_CACTITINYSEG",
 	"S_CACTISMALLSEG",
 
-	// Warning signs sprites
+	// Warning signs
 	"S_ARIDSIGN_CAUTION",
 	"S_ARIDSIGN_CACTI",
 	"S_ARIDSIGN_SHARPTURN",
@@ -6502,6 +6502,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_TNTBARREL_EXPL4",
 	"S_TNTBARREL_EXPL5",
 	"S_TNTBARREL_EXPL6",
+	"S_TNTBARREL_EXPL7",
 	"S_TNTBARREL_FLYING",
 
 	// TNT proximity shell
@@ -7047,7 +7048,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ZAPSB10",
 	"S_ZAPSB11", // blank frame
 
-	// Thunder spark
+	//Thunder spark
 	"S_THUNDERCOIN_SPARK",
 
 	// Invincibility Sparkles
@@ -7348,6 +7349,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BHORIZ7",
 	"S_BHORIZ8",
 
+	// Booster
 	"S_BOOSTERSOUND",
 	"S_YELLOWBOOSTERROLLER",
 	"S_YELLOWBOOSTERSEG_LEFT",
@@ -7378,7 +7380,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SPLISH8",
 	"S_SPLISH9",
 
-	// Lava splish
+	// Lava Splish
 	"S_LAVASPLISH",
 
 	// added water splash
@@ -7974,6 +7976,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ROCKCRUMBLEN",
 	"S_ROCKCRUMBLEO",
 	"S_ROCKCRUMBLEP",
+
+	// Level debris
 	"S_GFZDEBRIS",
 	"S_BRICKDEBRIS",
 	"S_WOODDEBRIS",
@@ -7993,7 +7997,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_THOK", // Thok! mobj
 	"MT_PLAYER",
 	"MT_TAILSOVERLAY", // c:
-	"MT_METALJETFUME", // [:
+	"MT_METALJETFUME",
 
 	// Enemies
 	"MT_BLUECRAWLA", // Crawla (Blue)
@@ -8110,7 +8114,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_CYBRAKDEMON_NAPALM_FLAMES",
 	"MT_CYBRAKDEMON_VILE_EXPLOSION",
 
-	// Metal Sonic
+	// Metal Sonic (Boss 9)
 	"MT_METALSONIC_RACE",
 	"MT_METALSONIC_BATTLE",
 	"MT_MSSHIELD_FRONT",
@@ -8124,7 +8128,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_BOMBSPHERE",
 	"MT_REDTEAMRING",  //Rings collectable by red team.
 	"MT_BLUETEAMRING", //Rings collectable by blue team.
-	"MT_TOKEN", // Special Stage Token
+	"MT_TOKEN", // Special Stage token for special stage
 	"MT_REDFLAG", // Red CTF Flag
 	"MT_BLUEFLAG", // Blue CTF Flag
 	"MT_EMBLEM",
@@ -8350,22 +8354,22 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	// Arid Canyon Scenery
 	"MT_BIGTUMBLEWEED",
 	"MT_LITTLETUMBLEWEED",
-	"MT_CACTI1",
-	"MT_CACTI2",
-	"MT_CACTI3",
-	"MT_CACTI4",
-	"MT_CACTI5",
-	"MT_CACTI6",
-	"MT_CACTI7",
-	"MT_CACTI8",
-	"MT_CACTI9",
-	"MT_CACTI10",
-	"MT_CACTI11",
-	"MT_CACTITINYSEG",
-	"MT_CACTISMALLSEG",
-	"MT_ARIDSIGN_CAUTION",
-	"MT_ARIDSIGN_CACTI",
-	"MT_ARIDSIGN_SHARPTURN",
+	"MT_CACTI1", // Tiny Red Flower Cactus
+	"MT_CACTI2", // Small Red Flower Cactus
+	"MT_CACTI3", // Tiny Blue Flower Cactus
+	"MT_CACTI4", // Small Blue Flower Cactus
+	"MT_CACTI5", // Prickly Pear
+	"MT_CACTI6", // Barrel Cactus
+	"MT_CACTI7", // Tall Barrel Cactus
+	"MT_CACTI8", // Armed Cactus
+	"MT_CACTI9", // Ball Cactus
+	"MT_CACTI10", // Tiny Cactus
+	"MT_CACTI11", // Small Cactus
+	"MT_CACTITINYSEG", // Tiny Cactus Segment
+	"MT_CACTISMALLSEG", // Small Cactus Segment
+	"MT_ARIDSIGN_CAUTION", // Caution Sign
+	"MT_ARIDSIGN_CACTI", // Cacti Sign
+	"MT_ARIDSIGN_SHARPTURN", // Sharp Turn Sign
 	"MT_OILLAMP",
 	"MT_TNTBARREL",
 	"MT_PROXIMITYTNT",
@@ -8420,7 +8424,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_GLAREGOYLEUP",
 	"MT_GLAREGOYLEDOWN",
 	"MT_GLAREGOYLELONG",
-	"MT_TARGET",
+	"MT_TARGET", // AKA Red Crystal
 	"MT_GREENFLAME",
 	"MT_BLUEGARGOYLE",
 
@@ -8471,7 +8475,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_HHZSTALAGMITE_TALL",
 	"MT_HHZSTALAGMITE_SHORT",
 
-	// Botanic Serenity
+	// Botanic Serenity scenery
 	"MT_BSZTALLFLOWER_RED",
 	"MT_BSZTALLFLOWER_PURPLE",
 	"MT_BSZTALLFLOWER_BLUE",
@@ -8751,6 +8755,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_ROCKCRUMBLE14",
 	"MT_ROCKCRUMBLE15",
 	"MT_ROCKCRUMBLE16",
+
+	// Level debris
 	"MT_GFZDEBRIS",
 	"MT_BRICKDEBRIS",
 	"MT_WOODDEBRIS",

From 4281de3b89e4ace77bff9dca003feb26d9e4dbc5 Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Sun, 9 Feb 2020 21:29:46 -0500
Subject: [PATCH 108/129] Update file hashes

---
 src/config.h.in | 8 ++++----
 src/d_main.c    | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/config.h.in b/src/config.h.in
index 233cbdc53..d4a613fdc 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -26,12 +26,12 @@
 #else
 
 /* Manually defined asset hashes for non-CMake builds
- * Last updated 2019 / 12 / 06 - v2.2.0 - main assets
+ * Last updated 2020 / 02 / 09 - v2.2.1 - main assets
  * Last updated 20?? / ?? / ?? - v2.2.? - patch.pk3
  */
-#define ASSET_HASH_SRB2_PK3   "51419a33b4982d840c6772c159ba7c0a"
-#define ASSET_HASH_ZONES_PK3  "df74843919fd51af26a0baa8e21e4c19"
-#define ASSET_HASH_PLAYER_DTA "56a247e074dd0dc794b6617efef1e918"
+#define ASSET_HASH_SRB2_PK3   "0277c9416756627004e83cbb5b2e3e28"
+#define ASSET_HASH_ZONES_PK3  "89627822f5a5c7fb022d836b138144b2"
+#define ASSET_HASH_PLAYER_DTA "129fa7d4b273a4b3dcacaa44eccead4f"
 #ifdef USE_PATCH_DTA
 #define ASSET_HASH_PATCH_PK3  "there is no patch.pk3, only zuul"
 #endif
diff --git a/src/d_main.c b/src/d_main.c
index dc9bfbfea..27f250017 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1213,7 +1213,7 @@ void D_SRB2Main(void)
 #endif
 	D_CleanFile();
 
-#ifndef DEVELOP // md5s last updated 06/12/19 (ddmmyy)
+#ifndef DEVELOP // md5s last updated 09/02/20 (ddmmyy)
 
 	// Check MD5s of autoloaded files
 	W_VerifyFileMD5(0, ASSET_HASH_SRB2_PK3); // srb2.pk3

From 021ca92019f6b7d1be29eaf171b5b11bb14eb9c5 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Sun, 9 Feb 2020 17:53:30 -0600
Subject: [PATCH 109/129] Expose stoppedclock to Lua

---
 src/lua_script.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/lua_script.c b/src/lua_script.c
index 2538fb711..eb6c54ae0 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -102,6 +102,9 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 	} else if (fastcmp(word,"circuitmap")) {
 		lua_pushboolean(L, circuitmap);
 		return 1;
+	} else if (fastcmp(word,"stoppedclock")) {
+		lua_pushboolean(L, stoppedclock);
+		return 1;
 	} else if (fastcmp(word,"netgame")) {
 		lua_pushboolean(L, netgame);
 		return 1;

From a63e9fe00247de03fe466cf510cdfc54d62d7de6 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Mon, 10 Feb 2020 00:06:16 -0600
Subject: [PATCH 110/129] Improvements to polyobjects carrying things:

- Fixed loss of precision in rotate carry causing objects to slide off
- Adjusted player carrying logic to make platforms less slippery
- Finally obsoleted the player-specific rotate hack now that I found the
  actual problem :]
---
 src/p_polyobj.c | 52 ++++++++++++++++++++++++++++++-------------------
 src/p_user.c    |  4 +++-
 2 files changed, 35 insertions(+), 21 deletions(-)

diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index cd0a44bb4..a206d0171 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -1001,15 +1001,35 @@ static void Polyobj_pushThing(polyobj_t *po, line_t *line, mobj_t *mo)
 //
 static void Polyobj_slideThing(mobj_t *mo, fixed_t dx, fixed_t dy)
 {
-	if (mo->player) { // Do something similar to conveyor movement. -Red
-		mo->player->cmomx += dx;
-		mo->player->cmomy += dy;
+	if (mo->player) { // Finally this doesn't suck eggs -fickle
+		fixed_t cdx, cdy;
 
-		dx = FixedMul(dx, CARRYFACTOR);
-		dy = FixedMul(dy, CARRYFACTOR);
+		cdx = FixedMul(dx, FRACUNIT-CARRYFACTOR);
+		cdy = FixedMul(dy, FRACUNIT-CARRYFACTOR);
 
-		mo->player->cmomx -= dx;
-		mo->player->cmomy -= dy;
+		if (mo->player->onconveyor == 1)
+		{
+			mo->momx += cdx;
+			mo->momy += cdy;
+
+			// Multiple slides in the same tic, somehow
+			mo->player->cmomx += cdx;
+			mo->player->cmomy += cdy;
+		}
+		else
+		{
+			if (mo->player->onconveyor == 3)
+			{
+				mo->momx += cdx - mo->player->cmomx;
+				mo->momy += cdy - mo->player->cmomy;
+			}
+
+			mo->player->cmomx = cdx;
+			mo->player->cmomy = cdy;
+		}
+
+		dx = FixedMul(dx, FRACUNIT - mo->friction);
+		dy = FixedMul(dy, FRACUNIT - mo->friction);
 
 		if (mo->player->pflags & PF_SPINNING && (mo->player->rmomx || mo->player->rmomy) && !(mo->player->pflags & PF_STARTDASH)) {
 #define SPINMULT 5184 // Consider this a substitute for properly calculating FRACUNIT-friction. I'm tired. -Red
@@ -1282,7 +1302,8 @@ static void Polyobj_rotateThings(polyobj_t *po, vertex_t origin, angle_t delta,
 {
 	static INT32 pomovecount = 10000;
 	INT32 x, y;
-	angle_t deltafine = delta >> ANGLETOFINESHIFT;
+	angle_t deltafine = (((po->angle + delta) >> ANGLETOFINESHIFT) - (po->angle >> ANGLETOFINESHIFT)) & FINEMASK;
+	// This fineshift trickery replaces the old delta>>ANGLETOFINESHIFT; doing it this way avoids loss of precision causing objects to slide off -fickle
 
 	pomovecount++;
 
@@ -1334,19 +1355,10 @@ static void Polyobj_rotateThings(polyobj_t *po, vertex_t origin, angle_t delta,
 					oldxoff = mo->x-origin.x;
 					oldyoff = mo->y-origin.y;
 
-					if (mo->player) // Hack to fix players sliding off of spinning polys -Red
-					{
-						fixed_t temp;
+					newxoff = FixedMul(oldxoff, c)-FixedMul(oldyoff, s) - oldxoff;
+					newyoff = FixedMul(oldyoff, c)+FixedMul(oldxoff, s) - oldyoff;
 
-						temp = FixedMul(oldxoff, c)-FixedMul(oldyoff, s);
-						oldyoff = FixedMul(oldyoff, c)+FixedMul(oldxoff, s);
-						oldxoff = temp;
-					}
-
-					newxoff = FixedMul(oldxoff, c)-FixedMul(oldyoff, s);
-					newyoff = FixedMul(oldyoff, c)+FixedMul(oldxoff, s);
-
-					Polyobj_slideThing(mo, newxoff-oldxoff, newyoff-oldyoff);
+					Polyobj_slideThing(mo, newxoff, newyoff);
 
 					if (turnthings == 2 || (turnthings == 1 && !mo->player)) {
 						mo->angle += delta;
diff --git a/src/p_user.c b/src/p_user.c
index 176b07d0a..a63022986 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -12174,7 +12174,9 @@ void P_PlayerThink(player_t *player)
 
 #ifdef POLYOBJECTS
 	if (player->onconveyor == 1)
-			player->cmomy = player->cmomx = 0;
+		player->onconveyor = 3;
+	else if (player->onconveyor == 3)
+		player->cmomy = player->cmomx = 0;
 #endif
 
 	P_DoSuperStuff(player);

From 76b397bece00e0eb318b2c99888a10fc35984013 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Mon, 10 Feb 2020 01:00:37 -0600
Subject: [PATCH 111/129] Get rotated sprites with v.getSprite(2)Patch

NOTE: since rotated sprites are offset upward by 4px from non-rotated
sprites, these functions will now return a third boolean (true) if a
rotated sprite was returned, so that scripters can offset accordingly.
---
 src/lua_hudlib.c | 40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index f451944e3..9138ae1f8 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -424,7 +424,7 @@ static int libd_cachePatch(lua_State *L)
 	return 1;
 }
 
-// v.getSpritePatch(sprite, [frame, [angle]])
+// v.getSpritePatch(sprite, [frame, [angle, [rollangle]]])
 static int libd_getSpritePatch(lua_State *L)
 {
 	UINT32 i; // sprite prefix
@@ -475,13 +475,31 @@ static int libd_getSpritePatch(lua_State *L)
 	if (angle >= ((sprframe->rotate & SRF_3DGE) ? 16 : 8)) // out of range?
 		return 0;
 
+#ifdef ROTSPRITE
+	if (lua_isnumber(L, 4))
+	{
+		// rotsprite?????
+		angle_t rollangle = luaL_checkangle(L, 4);
+		INT32 rot = R_GetRollAngle(rollangle);
+
+		if (rot) {
+			if (!(sprframe->rotsprite.cached & (1<<angle)))
+				R_CacheRotSprite(i, frame, NULL, sprframe, angle, sprframe->flip & (1<<angle));
+			LUA_PushUserdata(L, sprframe->rotsprite.patch[angle][rot], META_PATCH);
+			lua_pushboolean(L, false);
+			lua_pushboolean(L, true);
+			return 3;
+		}
+	}
+#endif
+
 	// push both the patch and it's "flip" value
 	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_PATCH), META_PATCH);
 	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);
 	return 2;
 }
 
-// v.getSprite2Patch(skin, sprite, [super?,] [frame, [angle]])
+// v.getSprite2Patch(skin, sprite, [super?,] [frame, [angle, [rollangle]]])
 static int libd_getSprite2Patch(lua_State *L)
 {
 	INT32 i; // skin number
@@ -570,6 +588,24 @@ static int libd_getSprite2Patch(lua_State *L)
 	if (angle >= ((sprframe->rotate & SRF_3DGE) ? 16 : 8)) // out of range?
 		return 0;
 
+#ifdef ROTSPRITE
+	if (lua_isnumber(L, 4))
+	{
+		// rotsprite?????
+		angle_t rollangle = luaL_checkangle(L, 4);
+		INT32 rot = R_GetRollAngle(rollangle);
+
+		if (rot) {
+			if (!(sprframe->rotsprite.cached & (1<<angle)))
+				R_CacheRotSprite(SPR_PLAY, frame, &skins[i].sprinfo[j], sprframe, angle, sprframe->flip & (1<<angle));
+			LUA_PushUserdata(L, sprframe->rotsprite.patch[angle][rot], META_PATCH);
+			lua_pushboolean(L, false);
+			lua_pushboolean(L, true);
+			return 3;
+		}
+	}
+#endif
+
 	// push both the patch and it's "flip" value
 	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_PATCH), META_PATCH);
 	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);

From c417ffae4c61e5fa4a389b2d1f0c4183a699a6f2 Mon Sep 17 00:00:00 2001
From: lachwright <lachlanwright17@gmail.com>
Date: Tue, 11 Feb 2020 15:53:25 +0800
Subject: [PATCH 112/129] Add proper support for animated signpost

---
 src/info.c    |  2 +-
 src/p_enemy.c | 13 ++++++++-----
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/info.c b/src/info.c
index 758f137c2..a634bd8d3 100644
--- a/src/info.c
+++ b/src/info.c
@@ -762,7 +762,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_LIFE, 20, {NULL}, 0,  4, S_NULL},       // S_PLAY_ICON3
 
 	// Level end sign (uses player sprite)
-	{SPR_PLAY, SPR2_SIGN|FF_PAPERSPRITE, -1, {NULL}, 0, 29, S_PLAY_SIGN},         // S_PLAY_SIGN
+	{SPR_PLAY, SPR2_SIGN|FF_PAPERSPRITE, 2, {NULL}, 0, 29, S_PLAY_SIGN},         // S_PLAY_SIGN
 
 	// NiGHTS Player, transforming
 	{SPR_PLAY, SPR2_TRNS|FF_ANIMATE,     7, {NULL},          0, 4, S_PLAY_NIGHTS_TRANS2}, // S_PLAY_NIGHTS_TRANS1
diff --git a/src/p_enemy.c b/src/p_enemy.c
index db297e684..8ff490a05 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -5198,8 +5198,8 @@ void A_SignPlayer(mobj_t *actor)
 			player_t *player = actor->target ? actor->target->player : NULL;
 			UINT8 skinnum;
 			UINT8 skincount = 0;
-			for (skincount = 0; skincount < numskins; skincount++)
-				if (!skincheck(skincount))
+			for (skinnum = 0; skinnum < numskins; skinnum++)
+				if (!skincheck(skinnum))
 					skincount++;
 			skinnum = P_RandomKey(skincount);
 			for (skincount = 0; skincount < numskins; skincount++)
@@ -5232,20 +5232,23 @@ void A_SignPlayer(mobj_t *actor)
 		{
 			ov->color = facecolor;
 			ov->skin = skin;
-			P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN
+			if (ov->state-states != actor->info->seestate)
+				P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN
 		}
 		else // CLEAR! sign
 		{
 			ov->color = SKINCOLOR_NONE;
 			ov->skin = NULL; // needs to be NULL in the case of SF_HIRES characters
-			P_SetMobjState(ov, actor->info->missilestate); // S_CLEARSIGN
+			if (ov->state-states != actor->info->missilestate)
+				P_SetMobjState(ov, actor->info->missilestate); // S_CLEARSIGN
 		}
 	}
 	else // Eggman face
 	{
 		ov->color = SKINCOLOR_NONE;
 		ov->skin = NULL;
-		P_SetMobjState(ov, actor->info->meleestate); // S_EGGMANSIGN
+		if (ov->state-states != actor->info->meleestate)
+			P_SetMobjState(ov, actor->info->meleestate); // S_EGGMANSIGN
 		if (!signcolor)
 			signcolor = SKINCOLOR_CARBON;
 	}

From 248a80cac48be372effa0e92170a70007dcdbdd4 Mon Sep 17 00:00:00 2001
From: lachwright <lachlanwright17@gmail.com>
Date: Tue, 11 Feb 2020 20:36:48 +0800
Subject: [PATCH 113/129] Cast to statenum_t for 32-bit compatibility

---
 src/p_enemy.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/p_enemy.c b/src/p_enemy.c
index 8ff490a05..2ddbd8d29 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -5232,14 +5232,14 @@ void A_SignPlayer(mobj_t *actor)
 		{
 			ov->color = facecolor;
 			ov->skin = skin;
-			if (ov->state-states != actor->info->seestate)
+			if ((statenum_t)(ov->state-states) != actor->info->seestate)
 				P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN
 		}
 		else // CLEAR! sign
 		{
 			ov->color = SKINCOLOR_NONE;
 			ov->skin = NULL; // needs to be NULL in the case of SF_HIRES characters
-			if (ov->state-states != actor->info->missilestate)
+			if ((statenum_t)(ov->state-states) != actor->info->missilestate)
 				P_SetMobjState(ov, actor->info->missilestate); // S_CLEARSIGN
 		}
 	}
@@ -5247,7 +5247,7 @@ void A_SignPlayer(mobj_t *actor)
 	{
 		ov->color = SKINCOLOR_NONE;
 		ov->skin = NULL;
-		if (ov->state-states != actor->info->meleestate)
+		if ((statenum_t)(ov->state-states) != actor->info->meleestate)
 			P_SetMobjState(ov, actor->info->meleestate); // S_EGGMANSIGN
 		if (!signcolor)
 			signcolor = SKINCOLOR_CARBON;

From aeab33ee030cd6de9a4e86298a22fcf98b6b19d7 Mon Sep 17 00:00:00 2001
From: lachwright <lachlanwright17@gmail.com>
Date: Wed, 12 Feb 2020 01:41:11 +0800
Subject: [PATCH 114/129] Defuse minecarts

---
 src/info.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/info.c b/src/info.c
index 758f137c2..dc7b7659a 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2395,7 +2395,7 @@ state_t states[NUMSTATES] =
 
 	// Minecart
 	{SPR_NULL, 0,                            1, {NULL},                 0, 0, S_MINECART_IDLE},   // S_MINECART_IDLE
-	{SPR_NULL, 0,                            0, {A_KillSegments},       0, 0, S_TNTBARREL_EXPL3}, // S_MINECART_DTH1
+	{SPR_NULL, 0,                            0, {A_KillSegments},       0, 0, S_TNTBARREL_EXPL4}, // S_MINECART_DTH1
 	{SPR_MCRT, 8|FF_PAPERSPRITE,            -1, {NULL},                 0, 0, S_NULL},            // S_MINECARTEND
 	{SPR_MCRT, 0|FF_PAPERSPRITE,            -1, {NULL},                 0, 0, S_NULL},            // S_MINECARTSEG_FRONT
 	{SPR_MCRT, 1|FF_PAPERSPRITE,            -1, {NULL},                 0, 0, S_NULL},            // S_MINECARTSEG_BACK

From 9f7bfd901b591bd180d6b77b5241dd789303670c Mon Sep 17 00:00:00 2001
From: GoldenTails <milestailsprower101n2@gmail.com>
Date: Tue, 11 Feb 2020 19:07:48 -0600
Subject: [PATCH 115/129] I broke SHIFT in the Connect IP Textbox.. whoops.

It's okay, I literally had to remove one line lmao.
---
 src/m_menu.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/m_menu.c b/src/m_menu.c
index 049b66d67..01db26148 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -10585,7 +10585,6 @@ static void M_HandleConnectIP(INT32 choice)
 					default: // otherwise do nothing.
 						break;
 				}
-				break; // don't check for typed keys
 			}
 
 			if (l >= 28-1)

From ae2041d68633e3d089c29b6f146669d3f97b738a Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Tue, 11 Feb 2020 19:36:09 -0800
Subject: [PATCH 116/129] Remove extra tokens if we got all 7 emeroods

---
 src/g_game.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/g_game.c b/src/g_game.c
index efc96a50f..aaab89163 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3739,7 +3739,10 @@ static void G_DoCompleted(void)
 			}
 
 		if (i == 7)
+		{
 			gottoken = false;
+			token = 0;
+		}
 	}
 
 	if (spec && !gottoken)

From b7a6773ff5c7e22b5c4a0a7a86dfa6e0c6e84598 Mon Sep 17 00:00:00 2001
From: fickleheart <fickle@tinted.red>
Date: Tue, 11 Feb 2020 23:24:43 -0600
Subject: [PATCH 117/129] Don't error when checking patch.valid on invalid
 patches

---
 src/lua_hudlib.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index f451944e3..b58a82299 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -270,8 +270,13 @@ static int patch_get(lua_State *L)
 
 	// patches are CURRENTLY always valid, expected to be cached with PU_STATIC
 	// this may change in the future, so patch.valid still exists
-	if (!patch)
+	if (!patch) {
+		if (field == patch_valid) {
+			lua_pushboolean(L, 0);
+			return 1;
+		}
 		return LUA_ErrInvalid(L, "patch_t");
+	}
 
 	switch (field)
 	{

From d3e5cbffbad67766b0b559f631b68da22d80d44d Mon Sep 17 00:00:00 2001
From: colette <red@lyrawearspants.com>
Date: Wed, 12 Feb 2020 08:54:20 -0500
Subject: [PATCH 118/129] comment

---
 src/lua_hudlib.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index b58a82299..6e3c1181d 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -268,8 +268,7 @@ static int patch_get(lua_State *L)
 #endif
 	enum patch field = luaL_checkoption(L, 2, NULL, patch_opt);
 
-	// patches are CURRENTLY always valid, expected to be cached with PU_STATIC
-	// this may change in the future, so patch.valid still exists
+	// patches are invalidated when switching renderers
 	if (!patch) {
 		if (field == patch_valid) {
 			lua_pushboolean(L, 0);

From 841094976b8a84ed56942cea96515cb9b19245e5 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Wed, 12 Feb 2020 18:02:36 -0800
Subject: [PATCH 119/129] Add flag name variant of SF_NONIGHTSROTATION to
 S_SKIN

---
 src/r_things.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/r_things.c b/src/r_things.c
index 7f0f43281..ca285644f 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -3463,6 +3463,7 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
 	GETFLAG(DASHMODE)
 	GETFLAG(FASTEDGE)
 	GETFLAG(MULTIABILITY)
+	GETFLAG(NONIGHTSROTATION)
 #undef GETFLAG
 
 	else // let's check if it's a sound, otherwise error out

From e7a0fc65bde8a0429d1ca12508f6f0142cb0c10c Mon Sep 17 00:00:00 2001
From: MascaraSnake <jonassauer27@gmail.com>
Date: Sat, 15 Feb 2020 12:44:03 +0100
Subject: [PATCH 120/129] Update hashes (again)

---
 src/config.h.in | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/config.h.in b/src/config.h.in
index d4a613fdc..6c909890b 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -30,8 +30,8 @@
  * Last updated 20?? / ?? / ?? - v2.2.? - patch.pk3
  */
 #define ASSET_HASH_SRB2_PK3   "0277c9416756627004e83cbb5b2e3e28"
-#define ASSET_HASH_ZONES_PK3  "89627822f5a5c7fb022d836b138144b2"
-#define ASSET_HASH_PLAYER_DTA "129fa7d4b273a4b3dcacaa44eccead4f"
+#define ASSET_HASH_ZONES_PK3  "04fcff3b888ebb4970139166e75c08e9"
+#define ASSET_HASH_PLAYER_DTA "ad49e07b17cc662f1ad70c454910b4ae"
 #ifdef USE_PATCH_DTA
 #define ASSET_HASH_PATCH_PK3  "there is no patch.pk3, only zuul"
 #endif

From 5dc0f0649e55584f1288796724c68e4dde565f15 Mon Sep 17 00:00:00 2001
From: Jaime Passos <lazymyuutsu@gmail.com>
Date: Sat, 15 Feb 2020 13:07:53 -0300
Subject: [PATCH 121/129] Fix overtime not working

---
 src/g_game.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/g_game.c b/src/g_game.c
index efc96a50f..0d89a0cf6 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3203,17 +3203,17 @@ UINT32 gametypedefaultrules[NUMGAMETYPES] =
 	GTR_RACE|GTR_SPAWNENEMIES|GTR_SPAWNINVUL|GTR_ALLOWEXIT,
 
 	// Match
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD|GTR_DEATHPENALTY,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD|GTR_DEATHPENALTY,
 	// Team Match
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
 
 	// Tag
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_STARTCOUNTDOWN|GTR_BLINDFOLDED|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_STARTCOUNTDOWN|GTR_BLINDFOLDED|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY,
 	// Hide and Seek
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_STARTCOUNTDOWN|GTR_BLINDFOLDED|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_STARTCOUNTDOWN|GTR_BLINDFOLDED|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY,
 
 	// CTF
-	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_TEAMFLAGS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_TEAMFLAGS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD,
 };
 
 //

From 7b13f0a2dea61dc38b90d8718f843c14b1020d79 Mon Sep 17 00:00:00 2001
From: Louis-Antoine <lamr@free.fr>
Date: Sat, 15 Feb 2020 23:47:11 +0100
Subject: [PATCH 122/129] Fix dedicated servers not running gamelogic

Note to future testers:
Remember to test dedicated servers before merging.
And splitscreen also.
---
 src/d_clisrv.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index c4b5f6677..77118110e 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -404,8 +404,8 @@ static void ExtraDataTicker(void)
 		}
 
 	// If you are a client, you can safely forget the net commands for this tic
-	// If you are the server, you need to remember them until every client has been aknowledged,
-	// because if you need to resend a PT_SERVERTICS packet, you need to put the commands in it
+	// If you are the server, you need to remember them until every client has been acknowledged,
+	// because if you need to resend a PT_SERVERTICS packet, you will need to put the commands in it
 	if (client)
 		D_FreeTextcmd(gametic);
 }
@@ -4510,7 +4510,7 @@ static void CL_SendClientCmd(void)
 		packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
 		HSendPacket(servernode, false, 0, packetsize);
 	}
-	else if (gamestate != GS_NULL && addedtogame)
+	else if (gamestate != GS_NULL && (addedtogame || dedicated))
 	{
 		G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
 		netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);

From 3a82864ce9de523af5cc62daef20957343b1f5f4 Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Sun, 16 Feb 2020 11:34:02 -0500
Subject: [PATCH 123/129] Update comment

---
 src/config.h.in | 2 +-
 src/d_main.c    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/config.h.in b/src/config.h.in
index 6c909890b..3b501c97a 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -26,7 +26,7 @@
 #else
 
 /* Manually defined asset hashes for non-CMake builds
- * Last updated 2020 / 02 / 09 - v2.2.1 - main assets
+ * Last updated 2020 / 02 / 15 - v2.2.1 - main assets
  * Last updated 20?? / ?? / ?? - v2.2.? - patch.pk3
  */
 #define ASSET_HASH_SRB2_PK3   "0277c9416756627004e83cbb5b2e3e28"
diff --git a/src/d_main.c b/src/d_main.c
index 27f250017..ccf2d1428 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1213,7 +1213,7 @@ void D_SRB2Main(void)
 #endif
 	D_CleanFile();
 
-#ifndef DEVELOP // md5s last updated 09/02/20 (ddmmyy)
+#ifndef DEVELOP // md5s last updated 15/02/20 (ddmmyy)
 
 	// Check MD5s of autoloaded files
 	W_VerifyFileMD5(0, ASSET_HASH_SRB2_PK3); // srb2.pk3

From 512c6f24a902215d45ea93a2ef5eb497e5968f03 Mon Sep 17 00:00:00 2001
From: MascaraSnake <jonassauer27@gmail.com>
Date: Sun, 16 Feb 2020 20:19:24 +0100
Subject: [PATCH 124/129] Clean up the mess that is extracolormap_t::fog

---
 src/hardware/hw_main.c | 14 ++----------
 src/p_saveg.c          | 10 ++++-----
 src/p_spec.c           | 17 +++++++--------
 src/r_data.c           | 48 +++++++++++++++++++++---------------------
 src/r_data.h           | 10 ++++-----
 src/r_defs.h           |  5 ++++-
 src/r_plane.c          | 15 ++-----------
 src/r_segs.c           | 30 ++++++++++++--------------
 src/r_things.c         |  6 +++---
 9 files changed, 66 insertions(+), 89 deletions(-)

diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 7e913c4c7..5dd222dfd 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5022,12 +5022,7 @@ void HWR_AddTransparentFloor(levelflat_t *levelflat, extrasubsector_t *xsub, boo
 
 	planeinfo[numplanes].isceiling = isceiling;
 	planeinfo[numplanes].fixedheight = fixedheight;
-
-	if (planecolormap && (planecolormap->fog & 1))
-		planeinfo[numplanes].lightlevel = lightlevel;
-	else
-		planeinfo[numplanes].lightlevel = 255;
-
+	planeinfo[numplanes].lightlevel = (planecolormap && (planecolormap->flags & CMF_FOG)) ? lightlevel : 255;
 	planeinfo[numplanes].levelflat = levelflat;
 	planeinfo[numplanes].xsub = xsub;
 	planeinfo[numplanes].alpha = alpha;
@@ -5059,12 +5054,7 @@ void HWR_AddTransparentPolyobjectFloor(levelflat_t *levelflat, polyobj_t *polyse
 
 	polyplaneinfo[numpolyplanes].isceiling = isceiling;
 	polyplaneinfo[numpolyplanes].fixedheight = fixedheight;
-
-	if (planecolormap && (planecolormap->fog & 1))
-		polyplaneinfo[numpolyplanes].lightlevel = lightlevel;
-	else
-		polyplaneinfo[numpolyplanes].lightlevel = 255;
-
+	polyplaneinfo[numpolyplanes].lightlevel = (planecolormap && (planecolormap->flags & CMF_FOG)) ? lightlevel : 255;
 	polyplaneinfo[numpolyplanes].levelflat = levelflat;
 	polyplaneinfo[numpolyplanes].polysector = polysector;
 	polyplaneinfo[numpolyplanes].alpha = alpha;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 853856880..31ac79931 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -609,7 +609,7 @@ static void P_NetArchiveColormaps(void)
 
 		WRITEUINT8(save_p, exc->fadestart);
 		WRITEUINT8(save_p, exc->fadeend);
-		WRITEUINT8(save_p, exc->fog);
+		WRITEUINT8(save_p, exc->flags);
 
 		WRITEINT32(save_p, exc->rgba);
 		WRITEINT32(save_p, exc->fadergba);
@@ -639,7 +639,7 @@ static void P_NetUnArchiveColormaps(void)
 
 	for (exc = net_colormaps; i < num_net_colormaps; i++, exc = exc_next)
 	{
-		UINT8 fadestart, fadeend, fog;
+		UINT8 fadestart, fadeend, flags;
 		INT32 rgba, fadergba;
 #ifdef EXTRACOLORMAPLUMPS
 		char lumpname[9];
@@ -647,7 +647,7 @@ static void P_NetUnArchiveColormaps(void)
 
 		fadestart = READUINT8(save_p);
 		fadeend = READUINT8(save_p);
-		fog = READUINT8(save_p);
+		flags = READUINT8(save_p);
 
 		rgba = READINT32(save_p);
 		fadergba = READINT32(save_p);
@@ -679,7 +679,7 @@ static void P_NetUnArchiveColormaps(void)
 
 		exc->fadestart = fadestart;
 		exc->fadeend = fadeend;
-		exc->fog = fog;
+		exc->flags = flags;
 
 		exc->rgba = rgba;
 		exc->fadergba = fadergba;
@@ -689,7 +689,7 @@ static void P_NetUnArchiveColormaps(void)
 		exc->lumpname[0] = 0;
 #endif
 
-		existing_exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, fog);
+		existing_exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags);
 
 		if (existing_exc)
 			exc->colormap = existing_exc->colormap;
diff --git a/src/p_spec.c b/src/p_spec.c
index 76a80d754..28d48d5d8 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3516,7 +3516,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						false,                     // subtract FadeA (no flag for this, just pass negative alpha)
 						false,                     // subtract FadeStart (we ran out of flags)
 						false,                     // subtract FadeEnd (we ran out of flags)
-						false,                     // ignore Fog (we ran out of flags)
+						false,                     // ignore Flags (we ran out of flags)
 						line->flags & ML_DONTPEGBOTTOM,
 						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].textureoffset >> FRACBITS) : 0,
 						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].rowoffset >> FRACBITS) : 0,
@@ -3883,7 +3883,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						false,                     // subtract FadeA (no flag for this, just pass negative alpha)
 						false,                     // subtract FadeStart (we ran out of flags)
 						false,                     // subtract FadeEnd (we ran out of flags)
-						false,                     // ignore Fog (we ran out of flags)
+						false,                     // ignore Flags (we ran out of flags)
 						line->flags & ML_DONTPEGBOTTOM,
 						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].textureoffset >> FRACBITS) : 0,
 						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].rowoffset >> FRACBITS) : 0,
@@ -7081,10 +7081,9 @@ void P_SpawnSpecials(boolean fromnetsave)
 			case 202: // Fog
 				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_FOG|FF_BOTHPLANES|FF_INVERTPLANES|FF_ALLSIDES|FF_INVERTSIDES|FF_CUTEXTRA|FF_EXTRA|FF_DOUBLESHADOW|FF_CUTSPRITES;
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				// SoM: Because it's fog, check for an extra colormap and set
-				// the fog flag...
+				// SoM: Because it's fog, check for an extra colormap and set the fog flag...
 				if (sectors[sec].extra_colormap)
-					sectors[sec].extra_colormap->fog = 1;
+					sectors[sec].extra_colormap->flags = CMF_FOG;
 				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
 				break;
 
@@ -8472,7 +8471,7 @@ void T_FadeColormap(fadecolormap_t *d)
 		extracolormap_t *exc;
 		INT32 duration = d->ticbased ? d->duration : 256;
 		fixed_t factor = min(FixedDiv(duration - d->timer, duration), 1*FRACUNIT);
-		INT16 cr, cg, cb, ca, fadestart, fadeend, fog;
+		INT16 cr, cg, cb, ca, fadestart, fadeend, flags;
 		INT32 rgba, fadergba;
 
 		// NULL failsafes (or intentionally set to signify default)
@@ -8521,7 +8520,7 @@ void T_FadeColormap(fadecolormap_t *d)
 
 		fadestart = APPLYFADE(d->dest_exc->fadestart, d->source_exc->fadestart, d->sector->extra_colormap->fadestart);
 		fadeend = APPLYFADE(d->dest_exc->fadeend, d->source_exc->fadeend, d->sector->extra_colormap->fadeend);
-		fog = abs(factor) > FRACUNIT/2 ? d->dest_exc->fog : d->source_exc->fog; // set new fog flag halfway through fade
+		flags = abs(factor) > FRACUNIT/2 ? d->dest_exc->flags : d->source_exc->flags; // set new flags halfway through fade
 
 #undef APPLYFADE
 
@@ -8529,12 +8528,12 @@ void T_FadeColormap(fadecolormap_t *d)
 		// setup new colormap
 		//////////////////
 
-		if (!(d->sector->extra_colormap = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, fog)))
+		if (!(d->sector->extra_colormap = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags)))
 		{
 			exc = R_CreateDefaultColormap(false);
 			exc->fadestart = fadestart;
 			exc->fadeend = fadeend;
-			exc->fog = (boolean)fog;
+			exc->flags = flags;
 			exc->rgba = rgba;
 			exc->fadergba = fadergba;
 			exc->colormap = R_CreateLightTable(exc);
diff --git a/src/r_data.c b/src/r_data.c
index 5608fdbde..f5a24c2c4 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -1799,7 +1799,7 @@ extracolormap_t *R_CreateDefaultColormap(boolean lighttable)
 	extracolormap_t *exc = Z_Calloc(sizeof (*exc), PU_LEVEL, NULL);
 	exc->fadestart = 0;
 	exc->fadeend = 31;
-	exc->fog = 0;
+	exc->flags = 0;
 	exc->rgba = 0;
 	exc->fadergba = 0x19000000;
 	exc->colormap = lighttable ? R_CreateLightTable(exc) : NULL;
@@ -1903,17 +1903,17 @@ void R_AddColormapToList(extracolormap_t *extra_colormap)
 //
 #ifdef EXTRACOLORMAPLUMPS
 boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams,
-	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog, lumpnum_t lump)
+	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags, lumpnum_t lump)
 #else
 boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams,
-	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog)
+	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags)
 #endif
 {
 	return (
 		(!checkparams ? true :
 			(fadestart == 0
 				&& fadeend == 31
-				&& !fog)
+				&& !flags)
 			)
 		&& (!checkrgba ? true : rgba == 0)
 		&& (!checkfadergba ? true : fadergba == 0x19000000)
@@ -1930,9 +1930,9 @@ boolean R_CheckDefaultColormap(extracolormap_t *extra_colormap, boolean checkrgb
 		return true;
 
 #ifdef EXTRACOLORMAPLUMPS
-	return R_CheckDefaultColormapByValues(checkrgba, checkfadergba, checkparams, extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->fog, extra_colormap->lump);
+	return R_CheckDefaultColormapByValues(checkrgba, checkfadergba, checkparams, extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->flags, extra_colormap->lump);
 #else
-	return R_CheckDefaultColormapByValues(checkrgba, checkfadergba, checkparams, extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->fog);
+	return R_CheckDefaultColormapByValues(checkrgba, checkfadergba, checkparams, extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->flags);
 #endif
 }
 
@@ -1952,7 +1952,7 @@ boolean R_CheckEqualColormaps(extracolormap_t *exc_a, extracolormap_t *exc_b, bo
 		(!checkparams ? true :
 			(exc_a->fadestart == exc_b->fadestart
 				&& exc_a->fadeend == exc_b->fadeend
-				&& exc_a->fog == exc_b->fog)
+				&& exc_a->flags == exc_b->flags)
 			)
 		&& (!checkrgba ? true : exc_a->rgba == exc_b->rgba)
 		&& (!checkfadergba ? true : exc_a->fadergba == exc_b->fadergba)
@@ -1968,9 +1968,9 @@ boolean R_CheckEqualColormaps(extracolormap_t *exc_a, extracolormap_t *exc_b, bo
 // NOTE: Returns NULL if no match is found
 //
 #ifdef EXTRACOLORMAPLUMPS
-extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog, lumpnum_t lump)
+extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags, lumpnum_t lump)
 #else
-extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog)
+extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags)
 #endif
 {
 	extracolormap_t *exc;
@@ -1982,7 +1982,7 @@ extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8
 			&& fadergba == exc->fadergba
 			&& fadestart == exc->fadestart
 			&& fadeend == exc->fadeend
-			&& fog == exc->fog
+			&& flags == exc->flags
 #ifdef EXTRACOLORMAPLUMPS
 			&& (lump != LUMPERROR && lump == exc->lump)
 #endif
@@ -2001,9 +2001,9 @@ extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8
 extracolormap_t *R_GetColormapFromList(extracolormap_t *extra_colormap)
 {
 #ifdef EXTRACOLORMAPLUMPS
-	return R_GetColormapFromListByValues(extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->fog, extra_colormap->lump);
+	return R_GetColormapFromListByValues(extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->flags, extra_colormap->lump);
 #else
-	return R_GetColormapFromListByValues(extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->fog);
+	return R_GetColormapFromListByValues(extra_colormap->rgba, extra_colormap->fadergba, extra_colormap->fadestart, extra_colormap->fadeend, extra_colormap->flags);
 #endif
 }
 
@@ -2035,7 +2035,7 @@ extracolormap_t *R_ColormapForName(char *name)
 	// is no real way to tell how GL should handle a colormap lump anyway..
 	exc->fadestart = 0;
 	exc->fadeend = 31;
-	exc->fog = 0;
+	exc->flags = 0;
 	exc->rgba = 0;
 	exc->fadergba = 0x19000000;
 
@@ -2192,7 +2192,7 @@ extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3)
 	// default values
 	UINT8 cr = 0, cg = 0, cb = 0, ca = 0, cfr = 0, cfg = 0, cfb = 0, cfa = 25;
 	UINT32 fadestart = 0, fadeend = 31;
-	UINT8 fog = 0;
+	UINT8 flags = 0;
 	INT32 rgba = 0, fadergba = 0x19000000;
 
 #define HEX2INT(x) (UINT32)(x >= '0' && x <= '9' ? x - '0' : x >= 'a' && x <= 'f' ? x - 'a' + 10 : x >= 'A' && x <= 'F' ? x - 'A' + 10 : 0)
@@ -2241,12 +2241,12 @@ extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3)
 
 #define NUMFROMCHAR(c) (c >= '0' && c <= '9' ? c - '0' : 0)
 
-	// Get parameters like fadestart, fadeend, and the fogflag
+	// Get parameters like fadestart, fadeend, and flags
 	if (p2[0] == '#')
 	{
 		if (p2[1])
 		{
-			fog = NUMFROMCHAR(p2[1]);
+			flags = NUMFROMCHAR(p2[1]);
 			if (p2[2] && p2[3])
 			{
 				fadestart = NUMFROMCHAR(p2[3]) + (NUMFROMCHAR(p2[2]) * 10);
@@ -2313,18 +2313,18 @@ extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3)
 
 	// Did we just make a default colormap?
 #ifdef EXTRACOLORMAPLUMPS
-	if (R_CheckDefaultColormapByValues(true, true, true, rgba, fadergba, fadestart, fadeend, fog, LUMPERROR))
+	if (R_CheckDefaultColormapByValues(true, true, true, rgba, fadergba, fadestart, fadeend, flags, LUMPERROR))
 		return NULL;
 #else
-	if (R_CheckDefaultColormapByValues(true, true, true, rgba, fadergba, fadestart, fadeend, fog))
+	if (R_CheckDefaultColormapByValues(true, true, true, rgba, fadergba, fadestart, fadeend, flags))
 		return NULL;
 #endif
 
 	// Look for existing colormaps
 #ifdef EXTRACOLORMAPLUMPS
-	exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, fog, LUMPERROR);
+	exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags, LUMPERROR);
 #else
-	exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, fog);
+	exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags);
 #endif
 	if (exc)
 		return exc;
@@ -2336,7 +2336,7 @@ extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3)
 
 	extra_colormap->fadestart = (UINT16)fadestart;
 	extra_colormap->fadeend = (UINT16)fadeend;
-	extra_colormap->fog = fog;
+	extra_colormap->flags = flags;
 
 	extra_colormap->rgba = rgba;
 	extra_colormap->fadergba = fadergba;
@@ -2363,7 +2363,7 @@ extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3)
 extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *exc_addend,
 	boolean subR, boolean subG, boolean subB, boolean subA,
 	boolean subFadeR, boolean subFadeG, boolean subFadeB, boolean subFadeA,
-	boolean subFadeStart, boolean subFadeEnd, boolean ignoreFog,
+	boolean subFadeStart, boolean subFadeEnd, boolean ignoreFlags,
 	boolean useAltAlpha, INT16 altAlpha, INT16 altFadeAlpha,
 	boolean lighttable)
 {
@@ -2451,8 +2451,8 @@ extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *ex
 				// HACK: fadeend defaults to 31, so don't add anything in this case
 		, 31), 0);
 
-	if (!ignoreFog) // overwrite fog with new value
-		exc_augend->fog = exc_addend->fog;
+	if (!ignoreFlags) // overwrite flags with new value
+		exc_augend->flags = exc_addend->flags;
 
 	///////////////////
 	// put it together
diff --git a/src/r_data.h b/src/r_data.h
index 145f0182b..0569893cd 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -135,12 +135,12 @@ void R_AddColormapToList(extracolormap_t *extra_colormap);
 
 #ifdef EXTRACOLORMAPLUMPS
 boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams,
-	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog, lumpnum_t lump);
-extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog, lumpnum_t lump);
+	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags, lumpnum_t lump);
+extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags, lumpnum_t lump);
 #else
 boolean R_CheckDefaultColormapByValues(boolean checkrgba, boolean checkfadergba, boolean checkparams,
-	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog);
-extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 fog);
+	INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags);
+extracolormap_t *R_GetColormapFromListByValues(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags);
 #endif
 boolean R_CheckDefaultColormap(extracolormap_t *extra_colormap, boolean checkrgba, boolean checkfadergba, boolean checkparams);
 boolean R_CheckEqualColormaps(extracolormap_t *exc_a, extracolormap_t *exc_b, boolean checkrgba, boolean checkfadergba, boolean checkparams);
@@ -151,7 +151,7 @@ extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3);
 extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *exc_addend,
 	boolean subR, boolean subG, boolean subB, boolean subA,
 	boolean subFadeR, boolean subFadeG, boolean subFadeB, boolean subFadeA,
-	boolean subFadeStart, boolean subFadeEnd, boolean ignoreFog,
+	boolean subFadeStart, boolean subFadeEnd, boolean ignoreFlags,
 	boolean useAltAlpha, INT16 altAlpha, INT16 altFadeAlpha,
 	boolean lighttable);
 #ifdef EXTRACOLORMAPLUMPS
diff --git a/src/r_defs.h b/src/r_defs.h
index 88d418fc9..f2774edbc 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -53,11 +53,14 @@ typedef struct
 // Could even use more than 32 levels.
 typedef UINT8 lighttable_t;
 
+#define CMF_FADEFULLBRIGHTSPRITES  1
+#define CMF_FOG 4
+
 // ExtraColormap type. Use for extra_colormaps from now on.
 typedef struct extracolormap_s
 {
 	UINT8 fadestart, fadeend;
-	UINT8 fog; // categorical value, not boolean
+	UINT8 flags;
 
 	// store rgba values in combined bitwise
 	// also used in OpenGL instead lighttables
diff --git a/src/r_plane.c b/src/r_plane.c
index 5d5e1f20d..e62e571e4 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -44,9 +44,6 @@
 // Quincunx antialiasing of flats!
 //#define QUINCUNX
 
-// good night sweet prince
-#define SHITPLANESPARENCY
-
 //SoM: 3/23/2000: Use Boom visplane hashing.
 
 visplane_t *visplanes[MAXVISPLANES];
@@ -995,11 +992,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 		else // Opaque, but allow transparent flat pixels
 			spanfunctype = SPANDRAWFUNC_SPLAT;
 
-#ifdef SHITPLANESPARENCY
-		if ((spanfunctype == SPANDRAWFUNC_SPLAT) != (pl->extra_colormap && (pl->extra_colormap->fog & 4)))
-#else
-		if (!pl->extra_colormap || !(pl->extra_colormap->fog & 2))
-#endif
+		if ((spanfunctype == SPANDRAWFUNC_SPLAT) || (pl->extra_colormap && (pl->extra_colormap->flags & CMF_FOG)))
 			light = (pl->lightlevel >> LIGHTSEGSHIFT);
 		else
 			light = LIGHTLEVELS-1;
@@ -1053,11 +1046,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 			else // Opaque, but allow transparent flat pixels
 				spanfunctype = SPANDRAWFUNC_SPLAT;
 
-#ifdef SHITPLANESPARENCY
-			if ((spanfunctype == SPANDRAWFUNC_SPLAT) != (pl->extra_colormap && (pl->extra_colormap->fog & 4)))
-#else
-			if (!pl->extra_colormap || !(pl->extra_colormap->fog & 2))
-#endif
+			if ((spanfunctype == SPANDRAWFUNC_SPLAT) || (pl->extra_colormap && (pl->extra_colormap->flags & CMF_FOG)))
 				light = (pl->lightlevel >> LIGHTSEGSHIFT);
 			else
 				light = LIGHTLEVELS-1;
diff --git a/src/r_segs.c b/src/r_segs.c
index dcb5fc160..88897ab8e 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -418,14 +418,14 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 			rlight->extra_colormap = *light->extra_colormap;
 			rlight->flags = light->flags;
 
-			if (rlight->flags & FF_FOG || (rlight->extra_colormap && rlight->extra_colormap->fog))
+			if ((colfunc != colfuncs[COLDRAWFUNC_FUZZY])
+				|| (rlight->flags & FF_FOG)
+				|| (rlight->extra_colormap && (rlight->extra_colormap->flags & CMF_FOG)))
 				lightnum = (rlight->lightlevel >> LIGHTSEGSHIFT);
-			else if (colfunc == colfuncs[COLDRAWFUNC_FUZZY])
-				lightnum = LIGHTLEVELS - 1;
 			else
-				lightnum = (rlight->lightlevel >> LIGHTSEGSHIFT);
+				lightnum = LIGHTLEVELS - 1;
 
-			if (rlight->extra_colormap && rlight->extra_colormap->fog)
+			if (rlight->extra_colormap && (rlight->extra_colormap->flags & CMF_FOG))
 				;
 			else if (curline->v1->y == curline->v2->y)
 				lightnum--;
@@ -437,18 +437,14 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	}
 	else
 	{
-		if (colfunc == colfuncs[COLDRAWFUNC_FUZZY])
-		{
-			if (frontsector->extra_colormap && frontsector->extra_colormap->fog)
-				lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT);
-			else
-				lightnum = LIGHTLEVELS - 1;
-		}
-		else
+		if ((colfunc != colfuncs[COLDRAWFUNC_FUZZY])
+			|| frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG))
 			lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT);
+		else
+			lightnum = LIGHTLEVELS - 1;
 
 		if (colfunc == colfuncs[COLDRAWFUNC_FOG]
-			|| (frontsector->extra_colormap && frontsector->extra_colormap->fog))
+			|| (frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG)))
 			;
 		else if (curline->v1->y == curline->v2->y)
 			lightnum--;
@@ -947,7 +943,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 			else
 				rlight->lightnum = (rlight->lightlevel >> LIGHTSEGSHIFT);
 
-			if (pfloor->flags & FF_FOG || rlight->flags & FF_FOG || (rlight->extra_colormap && rlight->extra_colormap->fog))
+			if (pfloor->flags & FF_FOG || rlight->flags & FF_FOG || (rlight->extra_colormap && (rlight->extra_colormap->flags & CMF_FOG)))
 				;
 			else if (curline->v1->y == curline->v2->y)
 				rlight->lightnum--;
@@ -962,7 +958,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	else
 	{
 		// Get correct light level!
-		if ((frontsector->extra_colormap && frontsector->extra_colormap->fog))
+		if ((frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG)))
 			lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT);
 		else if (pfloor->flags & FF_FOG)
 			lightnum = (pfloor->master->frontsector->lightlevel >> LIGHTSEGSHIFT);
@@ -972,7 +968,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 			lightnum = R_FakeFlat(frontsector, &tempsec, &templight, &templight, false)
 				->lightlevel >> LIGHTSEGSHIFT;
 
-		if (pfloor->flags & FF_FOG || (frontsector->extra_colormap && frontsector->extra_colormap->fog));
+		if (pfloor->flags & FF_FOG || (frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG)));
 			else if (curline->v1->y == curline->v2->y)
 		lightnum--;
 		else if (curline->v1->x == curline->v2->x)
diff --git a/src/r_things.c b/src/r_things.c
index ca285644f..cf81e3e04 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1094,8 +1094,8 @@ static void R_SplitSprite(vissprite_t *sprite)
 
 			newsprite->extra_colormap = *sector->lightlist[i].extra_colormap;
 
-			if (!((newsprite->cut & SC_FULLBRIGHT)
-				&& (!newsprite->extra_colormap || !(newsprite->extra_colormap->fog & 1))))
+			if (!(newsprite->cut & SC_FULLBRIGHT)
+				|| (newsprite->extra_colormap && (newsprite->extra_colormap->flags & CMF_FADEFULLBRIGHTSPRITES)))
 			{
 				lindex = FixedMul(sprite->xscale, FixedDiv(640, vid.width))>>(LIGHTSCALESHIFT);
 
@@ -1882,7 +1882,7 @@ static void R_ProjectSprite(mobj_t *thing)
 		vis->cut |= SC_FULLBRIGHT;
 
 	if (vis->cut & SC_FULLBRIGHT
-		&& (!vis->extra_colormap || !(vis->extra_colormap->fog & 1)))
+		&& (!vis->extra_colormap || !(vis->extra_colormap->flags & CMF_FADEFULLBRIGHTSPRITES)))
 	{
 		// full bright: goggles
 		vis->colormap = colormaps;

From cb97aafd054a1f888a89db53232c135feb282205 Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Sun, 16 Feb 2020 18:45:49 -0500
Subject: [PATCH 125/129] Fix object shadow not appearing for mid-joiners

---
 src/p_saveg.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/p_saveg.c b/src/p_saveg.c
index 853856880..7d755edc6 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1255,6 +1255,7 @@ typedef enum
 #endif
 	MD2_COLORIZED    = 1<<12,
 	MD2_ROLLANGLE    = 1<<13,
+	MD2_SHADOWSCALE  = 1<<14,
 } mobj_diff2_t;
 
 typedef enum
@@ -1476,6 +1477,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		diff2 |= MD2_COLORIZED;
 	if (mobj->rollangle)
 		diff2 |= MD2_ROLLANGLE;
+	if (mobj->shadowscale)
+		diff2 |= MD2_SHADOWSCALE;
 	if (diff2 != 0)
 		diff |= MD_MORE;
 
@@ -1642,6 +1645,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEUINT8(save_p, mobj->colorized);
 	if (diff2 & MD2_ROLLANGLE)
 		WRITEANGLE(save_p, mobj->rollangle);
+	if (diff2 & MD2_SHADOWSCALE)
+		WRITEFIXED(save_p, mobj->shadowscale);
 
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
@@ -2721,6 +2726,8 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		mobj->colorized = READUINT8(save_p);
 	if (diff2 & MD2_ROLLANGLE)
 		mobj->rollangle = READANGLE(save_p);
+	if (diff2 & MD2_SHADOWSCALE)
+		mobj->shadowscale = READFIXED(save_p);
 
 	if (diff & MD_REDFLAG)
 	{

From b44358df637ebaf6e35528a7106d3e1d299a0a20 Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Sun, 16 Feb 2020 18:54:19 -0500
Subject: [PATCH 126/129] Update file hash yet again

---
 src/config.h.in | 2 +-
 src/d_main.c    | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/config.h.in b/src/config.h.in
index 3b501c97a..498f3086d 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -30,7 +30,7 @@
  * Last updated 20?? / ?? / ?? - v2.2.? - patch.pk3
  */
 #define ASSET_HASH_SRB2_PK3   "0277c9416756627004e83cbb5b2e3e28"
-#define ASSET_HASH_ZONES_PK3  "04fcff3b888ebb4970139166e75c08e9"
+#define ASSET_HASH_ZONES_PK3  "f7e88afb6af7996a834c7d663144bead"
 #define ASSET_HASH_PLAYER_DTA "ad49e07b17cc662f1ad70c454910b4ae"
 #ifdef USE_PATCH_DTA
 #define ASSET_HASH_PATCH_PK3  "there is no patch.pk3, only zuul"
diff --git a/src/d_main.c b/src/d_main.c
index ccf2d1428..149fb3071 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1213,7 +1213,7 @@ void D_SRB2Main(void)
 #endif
 	D_CleanFile();
 
-#ifndef DEVELOP // md5s last updated 15/02/20 (ddmmyy)
+#ifndef DEVELOP // md5s last updated 16/02/20 (ddmmyy)
 
 	// Check MD5s of autoloaded files
 	W_VerifyFileMD5(0, ASSET_HASH_SRB2_PK3); // srb2.pk3

From 773ed0a0563ae31f5650f73e1e1bd1a3ab5e768a Mon Sep 17 00:00:00 2001
From: Steel Titanium <steeltitanium1@gmail.com>
Date: Sun, 16 Feb 2020 20:10:30 -0500
Subject: [PATCH 127/129] Update credit at Rob's request

---
 src/f_finale.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/f_finale.c b/src/f_finale.c
index d6ffed26f..66f963bbb 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1261,7 +1261,7 @@ static const char *credits[] = {
 	"Cody \"SRB2 Playah\" Koester",
 	"Skye \"OmegaVelocity\" Meredith",
 	"Stephen \"HEDGESMFG\" Moellering",
-	"Nick \"ST218\" Molina",
+	"Rosalie \"ST218\" Molina",
 	"Samuel \"Prime 2.0\" Peters",
 	"Colin \"Sonict\" Pfaff",
 	"Bill \"Tets\" Reed",

From 705cf9fd4078ce34857d98043023612e9205773c Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Sun, 16 Feb 2020 17:14:54 -0800
Subject: [PATCH 128/129] Revert "Let the console open in menus"

This reverts commit ef3d462eb7d6de12fb950894e3fa6aaece75ef1c.
---
 src/console.c | 11 ++++++++++-
 src/d_main.c  | 15 ++++++++-------
 2 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/src/console.c b/src/console.c
index 8746bf036..59d2b3e6c 100644
--- a/src/console.c
+++ b/src/console.c
@@ -613,6 +613,15 @@ void CON_Ticker(void)
 	con_tick++;
 	con_tick &= 7;
 
+	// if the menu is open then close the console.
+	if (menuactive && con_destlines)
+	{
+		consoletoggle = false;
+		con_destlines = 0;
+		CON_ClearHUD();
+		I_UpdateMouseGrab();
+	}
+
 	// console key was pushed
 	if (consoletoggle)
 	{
@@ -784,7 +793,7 @@ boolean CON_Responder(event_t *ev)
 		// check other keys only if console prompt is active
 		if (!consoleready && key < NUMINPUTS) // metzgermeister: boundary check!!
 		{
-			if (! menuactive && bindtable[key])
+			if (bindtable[key])
 			{
 				COM_BufAddText(bindtable[key]);
 				COM_BufAddText("\n");
diff --git a/src/d_main.c b/src/d_main.c
index 32972c151..71ea946b6 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -188,14 +188,14 @@ void D_ProcessEvents(void)
 				continue;
 		}
 
-		// console input
-		if (CON_Responder(ev))
-			continue; // ate the event
-
 		// Menu input
 		if (M_Responder(ev))
 			continue; // menu ate the event
 
+		// console input
+		if (CON_Responder(ev))
+			continue; // ate the event
+
 		G_Responder(ev);
 	}
 }
@@ -502,12 +502,13 @@ static void D_Display(void)
 	// vid size change is now finished if it was on...
 	vid.recalc = 0;
 
-	M_Drawer(); // menu is drawn even on top of everything
-	// focus lost moved to M_Drawer
-
+	// FIXME: draw either console or menu, not the two
 	if (gamestate != GS_TIMEATTACK)
 		CON_Drawer();
 
+	M_Drawer(); // menu is drawn even on top of everything
+	// focus lost moved to M_Drawer
+
 	//
 	// wipe update
 	//

From f4c2a4551bf1f45113ef7a1f74d41f8238908e09 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Sun, 16 Feb 2020 20:45:09 -0800
Subject: [PATCH 129/129] How many bruh moments can we have?

---
 src/r_segs.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/r_segs.c b/src/r_segs.c
index 88897ab8e..df998898f 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -438,7 +438,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	else
 	{
 		if ((colfunc != colfuncs[COLDRAWFUNC_FUZZY])
-			|| frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG))
+			|| (frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG)))
 			lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT);
 		else
 			lightnum = LIGHTLEVELS - 1;