diff --git a/src/config.h.in b/src/config.h.in
index d4a613fdc2aa5227aa2478ccae3a5f53ccf5e077..498f3086d3279655264160139dde53287356fac3 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 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"
-#define ASSET_HASH_ZONES_PK3  "89627822f5a5c7fb022d836b138144b2"
-#define ASSET_HASH_PLAYER_DTA "129fa7d4b273a4b3dcacaa44eccead4f"
+#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"
 #endif
diff --git a/src/console.c b/src/console.c
index 8746bf03667f6cbc7403122d676337684394f663..59d2b3e6c7f9aea0e50b1654aee1551d271f63ed 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_clisrv.c b/src/d_clisrv.c
index c4b5f66771e57fdd8e071c6da5e18fec8b33c90e..77118110e41417149d0d953511f1b9d7e386c360 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]);
diff --git a/src/d_main.c b/src/d_main.c
index 27f25001776a57c086a488e898b80d3d879f731d..2ff0042fd655040abae37d84167009621812f07d 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
 	//
@@ -1213,7 +1214,7 @@ void D_SRB2Main(void)
 #endif
 	D_CleanFile();
 
-#ifndef DEVELOP // md5s last updated 09/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
diff --git a/src/f_finale.c b/src/f_finale.c
index d6ffed26f8ab600dc14c20d9401ee88c4d21e525..66f963bbbec415a1cdb4ed20ed641a7da35bb47d 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",
diff --git a/src/g_game.c b/src/g_game.c
index 0d89a0cf6e332b007fee2f41c064b3d8f6dcbb75..3f45988b15d1521c8cc28cb92781503b496fa0f9 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)
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index d08b69ffc5f5cfbbedf368f23426c88e82f5c14c..dbab801dfa662d43886319353a91c5bc954bd305 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -268,10 +268,14 @@ 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
-	if (!patch)
+	// patches are invalidated when switching renderers
+	if (!patch) {
+		if (field == patch_valid) {
+			lua_pushboolean(L, 0);
+			return 1;
+		}
 		return LUA_ErrInvalid(L, "patch_t");
+	}
 
 	switch (field)
 	{
diff --git a/src/m_menu.c b/src/m_menu.c
index 916de03fa822cc4ed56c3612f554c0a04fe03606..97c04ebd57a81a7fbd69f2f13e782e86de8b1450 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -10846,7 +10846,6 @@ static void M_HandleConnectIP(INT32 choice)
 					default: // otherwise do nothing.
 						break;
 				}
-				break; // don't check for typed keys
 			}
 
 			if (l >= 28-1)
diff --git a/src/p_mobj.c b/src/p_mobj.c
index f02ed4f84eea9611c32a86b0a103e36cf7c996de..10898f70e79a0bfae8ad69ca2d1e6f9d129c93ad 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!
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index cd0a44bb4a78e913addbd4e91a64cc1791c30b2a..a206d01712aac0eb960191604bcbd1f8d8793e40 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;
-
-						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);
+					newxoff = FixedMul(oldxoff, c)-FixedMul(oldyoff, s) - oldxoff;
+					newyoff = FixedMul(oldyoff, c)+FixedMul(oldxoff, s) - oldyoff;
 
-					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_saveg.c b/src/p_saveg.c
index 8538568804352af0231b2743735e75dad0f271e9..7d755edc6915af64f44847befd4478294b0de928 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)
 	{
diff --git a/src/p_user.c b/src/p_user.c
index 176b07d0ad8744c7bcb0cbd2c9d5eed5aef099c9..a6302298620c93875942dbb6ef29b0584f111d25 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);
diff --git a/src/r_things.c b/src/r_things.c
index 7f0f43281ca9f2abe6f8ad16f67f71f758a3bd33..ca285644fba5773ec9c5aff3b47a3fe956a25eee 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
diff --git a/src/w_wad.c b/src/w_wad.c
index 1df2eacc71b31ebea2941c2a04ffbb92de97804a..e544378c82ae399bb970c21636f95f4f172bb96c 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;
@@ -1746,6 +1746,19 @@ 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},
+	{NULL, 0},
+};
+
 static int
 W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 {
@@ -1797,7 +1810,7 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 		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.
@@ -1807,6 +1820,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);