diff --git a/appveyor.yml b/appveyor.yml
index b9f84f395a5afccc741f0999b64e766695fce7d2..348b727b1d8a8f31dc202b6c822d1420325e7ddc 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.2.9.{branch}-{build}
+version: 2.2.10.{branch}-{build}
 os: MinGW
 
 environment:
diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt
index 3ea7c28dfb0b950b34b46d2866001ce2d2017380..ef228759c2f00721047884590d1f0443b6034463 100644
--- a/assets/CMakeLists.txt
+++ b/assets/CMakeLists.txt
@@ -22,8 +22,7 @@ set(SRB2_ASSET_INSTALL ON
 set(SRB2_ASSET_HASHED
 "srb2.pk3;\
 player.dta;\
-zones.pk3;\
-patch.pk3"
+zones.pk3"
 	CACHE STRING "Asset filenames to apply MD5 checks. No spaces between entries!"
 )
 
diff --git a/assets/README.txt b/assets/README.txt
index 8d2fefa54ce90136a6b9c3d368bd3cd817057459..5480cb7b0a7376622c6b5c04b07ff36fd883845b 100644
--- a/assets/README.txt
+++ b/assets/README.txt
@@ -1,24 +1,21 @@
 SONIC ROBO BLAST 2
 
-Sonic Robo Blast 2 (SRB2) is a 3D Sonic the Hedgehog fangame based on a
-modified version of Doom Legacy.
+Sonic Robo Blast 2 (SRB2) is a 3D Sonic the Hedgehog fangame, based on a modified version of Doom Legacy.
+
+https://www.srb2.org
 
 LICENSE
 
-The source code for SRB2 is licensed under the GNU General Public
-License, Version 2. See LICENSE.txt for the full text of this license.
+The source code for SRB2 is licensed under the GNU General Public License, Version 2. See LICENSE.txt for the full text of this license.
 
-SRB2 uses various third-party libraries, including SDL, SDL Mixer, and
-their dependencies. See LICENSE-3RD-PARTY.txt for the licenses of these
-libraries.
+SRB2 uses various third-party libraries, including SDL, SDL Mixer, and their dependencies. See LICENSE-3RD-PARTY.txt for the licenses of these libraries.
 
 SOURCE CODE
 
-You may obtain the source code for SRB2, including the source code for
-specific version releases, at the following web sites:
+You may obtain the source code for SRB2, including the source code for specific version releases, at the following web sites:
 
 STJr GitLab:
-https://git.magicalgirl.moe/STJr/SRB2
+https://git.do.srb2.org/STJr/SRB2
 
 GitHub:
 https://github.com/STJr/SRB2
@@ -27,25 +24,27 @@ CONTACT
 
 You may contact Sonic Team Junior via the following web sites:
 
-SRB2.ORG:
-https://www.srb2.org
-
 SRB2 Message Board:
 https://mb.srb2.org
 
 SRB2 Official Discord:
-https://discord.gg/pYDXzpX (13+)
+https://discord.gg/b3BGb8A
+
+Twitter:
+https://twitter.com/SonicTeamJr
+
+Facebook:
+https://facebook.com/SonicRoboBlast2
+
 
 COPYRIGHT AND DISCLAIMER
 
-Design and content on SRB2 is copyright 1998-2019 by Sonic Team Junior.
-All non-original material on SRB2.ORG is copyrighted by their
-respective owners, and no copyright infringement is intended. The owner
-of the SRB2.ORG domain is only acting as an ISP, and is therefore not
-responsible for any content on SRB2.ORG under the 1998 DMCA. This
-site, its webmaster, and its staff make no profit whatsoever (in fact,
-we lose money). Sonic Team Junior assumes no responsibility for the
-content on any Sonic Team Junior fan sites.
-
-Sonic Team Junior is in no way affiliated with SEGA or Sonic Team. We do
-not claim ownership of any of SEGA's intellectual property used in SRB2.
+Design and content in Sonic Robo Blast 2 is copyright 1998-2022 by Sonic Team Jr.
+
+All original material in this game is copyrighted by their respective owners, and no copyright infringement is intended. Sonic Team Jr. is in no way affiliated with SEGA or Sonic Team, and we do not claim ownership of any of SEGA's intellectual property used in SRB2.
+
+Sonic Robo Blast 2 is not commercial software. If you purchased this game, you have been scammed! Sonic Team Jr.'s staff makes no profit whatsoever (in fact, we lose money).
+
+The owner of the srb2.org domain is only acting as an ISP, and is therefore not responsible for any content on srb2.org under the 1998 DMCA. Sonic Team Jr. assumes no responsibility for the content on any Sonic Team Jr. fan sites.
+
+This software is provided as-is with no warranty whatsoever.
diff --git a/extras/conf/SRB2-22.cfg b/extras/conf/SRB2-22.cfg
index 3b6962417c1a42d2b96e41cdd9d41261a04e4fb9..50c6fbe4c3df440872215285d9ade69612995d1f 100644
--- a/extras/conf/SRB2-22.cfg
+++ b/extras/conf/SRB2-22.cfg
@@ -4051,16 +4051,16 @@ thingtypes
 		{
 			title = "Egg Mobile";
 			sprite = "EGGMA1";
-			width = 24;
-			height = 76;
+			width = 36;
+			height = 84;
 			flags4text = "[4] End level on death";
 		}
 		201
 		{
 			title = "Egg Slimer";
 			sprite = "EGGNA1";
-			width = 24;
-			height = 76;
+			width = 36;
+			height = 84;
 			flags4text = "[4] End level on death";
 			flags8text = "[8] Speed up when hit";
 		}
@@ -4068,7 +4068,7 @@ thingtypes
 		{
 			title = "Sea Egg";
 			sprite = "EGGOA1";
-			width = 32;
+			width = 36;
 			height = 116;
 			flags4text = "[4] End level on death";
 		}
@@ -4076,8 +4076,8 @@ thingtypes
 		{
 			title = "Egg Colosseum";
 			sprite = "EGGPA1";
-			width = 24;
-			height = 76;
+			width = 36;
+			height = 84;
 			flags4text = "[4] End level on death";
 		}
 		204
@@ -6312,10 +6312,10 @@ thingtypes
 		}
 		1602
 		{
-			title = "Pian";
-			sprite = "NTPNALAR";
+			title = "Nightopian";
+			sprite = "NTPNA1";
 			width = 16;
-			height = 32;
+			height = 40;
 		}
 	}
 
@@ -6894,6 +6894,20 @@ thingtypes
 		}
 	}
 
+	tutorial
+	{
+		color = 10; // Green
+		title = "Tutorial";
+		
+		799
+		{
+			title = "Tutorial Plant";
+			sprite = "TUPFH0";
+			width = 40;
+			height = 144;
+			parametertext = "Start frame";
+		}
+
 	flickies
 	{
 		color = 10; // Green
diff --git a/extras/conf/udb/Includes/SRB222_things.cfg b/extras/conf/udb/Includes/SRB222_things.cfg
index 113c1a4c26eb08a7109da2afeba747dfb1cdda14..0407741fc9871c421340e19aa4a1b8ecd0a76476 100644
--- a/extras/conf/udb/Includes/SRB222_things.cfg
+++ b/extras/conf/udb/Includes/SRB222_things.cfg
@@ -2528,10 +2528,10 @@ dreamhill
 	}
 	1602
 	{
-		title = "Pian";
-		sprite = "NTPNALAR";
+		title = "Nightopian";
+		sprite = "NTPNA1";
 		width = 16;
-		height = 32;
+		height = 40;
 	}
 }
 
diff --git a/src/Makefile b/src/Makefile
index 9659a4994c1dce6e94981ff090b0b4f1e9a171bf..c1aa3574283131db2b8d6bf2ff59efe2bb52211b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -2,8 +2,8 @@
 # the poly3 Makefile adapted over and over...
 #
 # Copyright 1998-2000 DooM Legacy Team.
-# Copyright 2020-2021 James R.
-# Copyright 2003-2021 Sonic Team Junior.
+# Copyright 2020-2022 James R.
+# Copyright 2003-2022 Sonic Team Junior.
 #
 # This program is free software distributed under the
 # terms of the GNU General Public License, version 2.
diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk
index 46194390d70f1f4c8b2f186f6ebf5542c4e228f9..8ba33383bb2f0c8169e92f91c6c8fea25c6c852f 100644
--- a/src/Makefile.d/features.mk
+++ b/src/Makefile.d/features.mk
@@ -1,75 +1,75 @@
-#
-# Makefile for feature flags.
-#
-
-passthru_opts+=\
-	NONET NO_IPV6 NOHW NOMD5 NOPOSTPROCESSING\
-	MOBJCONSISTANCY PACKETDROP ZDEBUG\
-	HAVE_MINIUPNPC\
-
-# build with debugging information
-ifdef DEBUGMODE
-PACKETDROP=1
-opts+=-DPARANOIA -DRANGECHECK
-endif
-
-ifndef NOHW
-opts+=-DHWRENDER
-sources+=$(call List,hardware/Sourcefile)
-endif
-
-ifndef NOASM
-ifndef NONX86
-sources+=tmap.nas tmap_mmx.nas
-opts+=-DUSEASM
-endif
-endif
-
-ifndef NOMD5
-sources+=md5.c
-endif
-
-ifndef NOZLIB
-ifndef NOPNG
-ifdef PNG_PKGCONFIG
-$(eval $(call Use_pkg_config,PNG_PKGCONFIG))
-else
-PNG_CONFIG?=$(call Prefix,libpng-config)
-$(eval $(call Configure,PNG,$(PNG_CONFIG) \
-	$(if $(PNG_STATIC),--static),,--ldflags))
-endif
-ifdef LINUX
-opts+=-D_LARGFILE64_SOURCE
-endif
-opts+=-DHAVE_PNG
-sources+=apng.c
-endif
-endif
-
-ifndef NONET
-ifndef NOCURL
-CURLCONFIG?=curl-config
-$(eval $(call Configure,CURL,$(CURLCONFIG)))
-opts+=-DHAVE_CURL
-endif
-endif
-
-ifdef HAVE_MINIUPNPC
-libs+=-lminiupnpc
-endif
-
-# (Valgrind is a memory debugger.)
-ifdef VALGRIND
-VALGRIND_PKGCONFIG?=valgrind
-$(eval $(call Use_pkg_config,VALGRIND))
-ZDEBUG=1
-opts+=-DHAVE_VALGRIND
-endif
-
-default_packages:=\
-	GME/libgme/LIBGME\
-	OPENMPT/libopenmpt/LIBOPENMPT\
-	ZLIB/zlib\
-
-$(foreach p,$(default_packages),\
-	$(eval $(call Check_pkg_config,$(p))))
+#
+# Makefile for feature flags.
+#
+
+passthru_opts+=\
+	NONET NO_IPV6 NOHW NOMD5 NOPOSTPROCESSING\
+	MOBJCONSISTANCY PACKETDROP ZDEBUG\
+	HAVE_MINIUPNPC\
+
+# build with debugging information
+ifdef DEBUGMODE
+PACKETDROP=1
+opts+=-DPARANOIA -DRANGECHECK
+endif
+
+ifndef NOHW
+opts+=-DHWRENDER
+sources+=$(call List,hardware/Sourcefile)
+endif
+
+ifndef NOASM
+ifndef NONX86
+sources+=tmap.nas tmap_mmx.nas
+opts+=-DUSEASM
+endif
+endif
+
+ifndef NOMD5
+sources+=md5.c
+endif
+
+ifndef NOZLIB
+ifndef NOPNG
+ifdef PNG_PKGCONFIG
+$(eval $(call Use_pkg_config,PNG_PKGCONFIG))
+else
+PNG_CONFIG?=$(call Prefix,libpng-config)
+$(eval $(call Configure,PNG,$(PNG_CONFIG) \
+	$(if $(PNG_STATIC),--static),,--ldflags))
+endif
+ifdef LINUX
+opts+=-D_LARGEFILE64_SOURCE
+endif
+opts+=-DHAVE_PNG
+sources+=apng.c
+endif
+endif
+
+ifndef NONET
+ifndef NOCURL
+CURLCONFIG?=curl-config
+$(eval $(call Configure,CURL,$(CURLCONFIG)))
+opts+=-DHAVE_CURL
+endif
+endif
+
+ifdef HAVE_MINIUPNPC
+libs+=-lminiupnpc
+endif
+
+# (Valgrind is a memory debugger.)
+ifdef VALGRIND
+VALGRIND_PKGCONFIG?=valgrind
+$(eval $(call Use_pkg_config,VALGRIND))
+ZDEBUG=1
+opts+=-DHAVE_VALGRIND
+endif
+
+default_packages:=\
+	GME/libgme/LIBGME\
+	OPENMPT/libopenmpt/LIBOPENMPT\
+	ZLIB/zlib\
+
+$(foreach p,$(default_packages),\
+	$(eval $(call Check_pkg_config,$(p))))
diff --git a/src/am_map.c b/src/am_map.c
index 24379e2f13d91822c8aed096ae3f972a95a0bfee..65a57c09e2f0f85655f7ce50b7c2195e418018f1 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/am_map.h b/src/am_map.h
index 022a7208b3fdf5a94f38101f5f1daccb2ff802ed..89c4ad9fab22538aae10b051c579678b7d9b5b15 100644
--- a/src/am_map.h
+++ b/src/am_map.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/apng.c b/src/apng.c
index 36b205c60998099009c21ce6a49fe97a71d44faa..f4c08d979faef761ac3ceed6fed93489162eee68 100644
--- a/src/apng.c
+++ b/src/apng.c
@@ -1,5 +1,5 @@
 /*
-Copyright 2019-2021, James R.
+Copyright 2019-2022, James R.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/src/apng.h b/src/apng.h
index 893b523cbcacb8fd9f58a5be59b8dac3ef787596..6b934742486461951377998ae02e687cc0a1e1ac 100644
--- a/src/apng.h
+++ b/src/apng.h
@@ -1,5 +1,5 @@
 /*
-Copyright 2019-2021, James R.
+Copyright 2019-2022, James R.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/src/asm_defs.inc b/src/asm_defs.inc
index 9074f20f86d53523bf19030f01fb9e4e63e6283d..a8c60f19ea9be18916ac626caf384ffc77a7436c 100644
--- a/src/asm_defs.inc
+++ b/src/asm_defs.inc
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/b_bot.c b/src/b_bot.c
index bf2dbbb68586aab8390557568622d2e9e6f89e0a..775a13e294cf31e8070a9f6fc51894f91317496c 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2007-2016 by John "JTE" Muniz.
-// Copyright (C) 2011-2021 by Sonic Team Junior.
+// Copyright (C) 2011-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -17,6 +17,7 @@
 #include "p_local.h"
 #include "b_bot.h"
 #include "lua_hook.h"
+#include "i_system.h" // I_BaseTiccmd
 
 void B_UpdateBotleader(player_t *player)
 {
@@ -132,17 +133,17 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 	// Update catchup_tics
 	if (mem->thinkstate == AI_SPINFOLLOW)
 	{
-		mem-> catchup_tics = 0;
+		mem->catchup_tics = 0;
 	}
 	else if (dist > followmax || zdist > comfortheight || stalled)
 	{
-		mem-> catchup_tics = min(mem-> catchup_tics + 2, 70);
-		if (mem-> catchup_tics >= 70)
+		mem->catchup_tics = min(mem->catchup_tics + 2, 70);
+		if (mem->catchup_tics >= 70)
 			mem->thinkstate = AI_CATCHUP;
 	}
 	else
 	{
-		mem-> catchup_tics = max(mem-> catchup_tics - 1, 0);
+		mem->catchup_tics = max(mem->catchup_tics - 1, 0);
 		if (mem->thinkstate == AI_CATCHUP)
 			mem->thinkstate = AI_FOLLOW;
 	}
@@ -317,7 +318,6 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 		{
 			// Copy inputs
 			cmd->angleturn = (sonic->angle) >> 16; // NOT FRACBITS DAMNIT
-			bot->drawangle = ang;
 			cmd->forwardmove = 8 * pcmd->forwardmove / 10;
 			cmd->sidemove = 8 * pcmd->sidemove / 10;
 		}
@@ -344,7 +344,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 		else if (!jump_last && !(bot->pflags & PF_JUMPED) //&& !(player->pflags & PF_SPINNING)
 			&& ((zdist > 32*scale && player->pflags & PF_JUMPED) // Following
 				|| (zdist > 64*scale && mem->thinkstate == AI_CATCHUP) // Vertical catch-up
-				|| (stalled && mem-> catchup_tics > 20 && bot->powers[pw_carry] == CR_NONE)
+				|| (stalled && mem->catchup_tics > 20 && bot->powers[pw_carry] == CR_NONE)
 				//|| (bmom < scale>>3 && dist > followthres && !(bot->powers[pw_carry])) // Stopped & not in carry state
 				|| (bot->pflags & PF_SPINNING && !(bot->pflags & PF_JUMPED)))) // Spinning
 					jump = true;
@@ -371,6 +371,8 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 
 void B_BuildTiccmd(player_t *player, ticcmd_t *cmd)
 {
+	G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver
+
 	// Can't build a ticcmd if we aren't spawned...
 	if (!player->mo)
 		return;
@@ -390,7 +392,7 @@ void B_BuildTiccmd(player_t *player, ticcmd_t *cmd)
 		return;
 
 	// Make sure we have a valid main character to follow
-	 B_UpdateBotleader(player);
+	B_UpdateBotleader(player);
 	if (!player->botleader)
 		return;
 
@@ -403,7 +405,7 @@ void B_KeysToTiccmd(mobj_t *mo, ticcmd_t *cmd, boolean forward, boolean backward
 {
 	player_t *player = mo->player;
 	// don't try to do stuff if your sonic is in a minecart or something
-	if (&player->botleader && player->botleader->powers[pw_carry] && player->botleader->powers[pw_carry] != CR_PLAYER)
+	if (player->botleader && player->botleader->powers[pw_carry] && player->botleader->powers[pw_carry] != CR_PLAYER)
 		return;
 	// Turn the virtual keypresses into ticcmd_t.
 	if (twodlevel || mo->flags2 & MF2_TWOD) {
@@ -593,25 +595,43 @@ void B_HandleFlightIndicator(player_t *player)
 {
 	mobj_t *tails = player->mo;
 	botmem_t *mem = &player->botmem;
+	boolean shouldExist;
+
 	if (!tails)
 		return;
 
-	if (mem->thinkstate == AI_THINKFLY && player->bot == BOT_2PAI && tails->health)
+	shouldExist = (mem->thinkstate == AI_THINKFLY) && player->botleader
+		&& player->bot == BOT_2PAI && player->playerstate == PST_LIVE;
+
+	// check whether the indicator doesn't exist
+	if (P_MobjWasRemoved(tails->hnext))
 	{
-		if (!tails->hnext)
-		{
-			P_SetTarget(&tails->hnext, P_SpawnMobjFromMobj(tails, 0, 0, 0, MT_OVERLAY));
-			if (tails->hnext)
-			{
-				P_SetTarget(&tails->hnext->target, tails);
-				P_SetTarget(&tails->hnext->hprev, tails);
-				P_SetMobjState(tails->hnext, S_FLIGHTINDICATOR);
-			}
-		}
+		// if it shouldn't exist, everything is fine
+		if (!shouldExist)
+			return;
+
+		// otherwise, spawn it
+		P_SetTarget(&tails->hnext, P_SpawnMobjFromMobj(tails, 0, 0, 0, MT_OVERLAY));
+		P_SetTarget(&tails->hnext->target, tails);
+		P_SetTarget(&tails->hnext->hprev, tails);
+		P_SetMobjState(tails->hnext, S_FLIGHTINDICATOR);
 	}
-	else if (tails->hnext && tails->hnext->type == MT_OVERLAY && tails->hnext->state == states+S_FLIGHTINDICATOR)
+
+	// if the mobj isn't a flight indicator, let's not mess with it
+	if (tails->hnext->type != MT_OVERLAY || (tails->hnext->state != states+S_FLIGHTINDICATOR))
+		return;
+
+	// if it shouldn't exist, remove it
+	if (!shouldExist)
 	{
 		P_RemoveMobj(tails->hnext);
 		P_SetTarget(&tails->hnext, NULL);
+		return;
 	}
+
+	// otherwise, update its visibility
+	if (P_IsLocalPlayer(player->botleader))
+		tails->hnext->flags2 &= ~MF2_DONTDRAW;
+	else
+		tails->hnext->flags2 |= MF2_DONTDRAW;
 }
diff --git a/src/b_bot.h b/src/b_bot.h
index a89cfab19535477180971b0ba428f248b678b455..c29974c505bbb7c865665beadbd42306e3c61cbe 100644
--- a/src/b_bot.h
+++ b/src/b_bot.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2007-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/byteptr.h b/src/byteptr.h
index 4c8414fae29c7d3498a9a085f607243d261c3af9..33c2c8a4b69fb0e986ad928981995c1ea58f7a95 100644
--- a/src/byteptr.h
+++ b/src/byteptr.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -150,26 +150,78 @@ FUNCINLINE static ATTRINLINE UINT32 readulong(void *ptr)
 
 #undef DEALIGNED
 
-#define WRITESTRINGN(p,s,n) do { size_t tmp_i = 0; for (; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); if (tmp_i < n) WRITECHAR(p, '\0');} while (0)
-#define WRITESTRING(p,s)    do { size_t tmp_i = 0; for (;              s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); WRITECHAR(p, '\0');} while (0)
-#define WRITEMEM(p,s,n)     do { memcpy(p, s, n); p += n; } while (0)
-
-#define SKIPSTRING(p)       while (READCHAR(p) != '\0')
-
-#define READSTRINGN(p,s,n)  ({ size_t tmp_i = 0; for (; tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';})
-#define READSTRING(p,s)     ({ size_t tmp_i = 0; for (;              (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';})
-#define READMEM(p,s,n)      ({ memcpy(s, p, n); p += n; })
-
-#if 0 // old names
-#define WRITEBYTE(p,b)      WRITEUINT8(p,b)
-#define WRITESHORT(p,b)     WRITEINT16(p,b)
-#define WRITEUSHORT(p,b)    WRITEUINT16(p,b)
-#define WRITELONG(p,b)      WRITEINT32(p,b)
-#define WRITEULONG(p,b)     WRITEUINT32(p,b)
-
-#define READBYTE(p)         READUINT8(p)
-#define READSHORT(p)        READINT16(p)
-#define READUSHORT(p)       READUINT16(p)
-#define READLONG(p)         READINT32(p)
-#define READULONG(p)        READUINT32(p)
-#endif
+#define WRITESTRINGN(p, s, n) ({                            \
+	size_t tmp_i;                                           \
+                                                            \
+	for (tmp_i = 0; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) \
+		WRITECHAR(p, s[tmp_i]);                             \
+                                                            \
+	if (tmp_i < n)                                          \
+		WRITECHAR(p, '\0');                                 \
+})
+
+#define WRITESTRINGL(p, s, n) ({                                \
+	size_t tmp_i;                                               \
+                                                                \
+	for (tmp_i = 0; tmp_i < n - 1 && s[tmp_i] != '\0'; tmp_i++) \
+		WRITECHAR(p, s[tmp_i]);                                 \
+                                                                \
+	WRITECHAR(p, '\0');                                         \
+})
+
+#define WRITESTRING(p, s) ({                   \
+	size_t tmp_i;                              \
+                                               \
+	for (tmp_i = 0; s[tmp_i] != '\0'; tmp_i++) \
+		WRITECHAR(p, s[tmp_i]);                \
+                                               \
+	WRITECHAR(p, '\0');                        \
+})
+
+#define WRITEMEM(p, s, n) ({ \
+	memcpy(p, s, n);         \
+	p += n;                  \
+})
+
+#define SKIPSTRING(p) while (READCHAR(p) != '\0')
+
+#define SKIPSTRINGN(p, n) ({                 \
+	size_t tmp_i = 0;                        \
+                                             \
+	while (tmp_i < n && READCHAR(p) != '\0') \
+		tmp_i++;                             \
+})
+
+#define SKIPSTRINGL(p, n) SKIPSTRINGN(p, n)
+
+#define READSTRINGN(p, s, n) ({                           \
+	size_t tmp_i = 0;                                     \
+                                                          \
+	while (tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0') \
+		tmp_i++;                                          \
+                                                          \
+	s[tmp_i] = '\0';                                      \
+})
+
+#define READSTRINGL(p, s, n) ({                               \
+	size_t tmp_i = 0;                                         \
+                                                              \
+	while (tmp_i < n - 1 && (s[tmp_i] = READCHAR(p)) != '\0') \
+		tmp_i++;                                              \
+                                                              \
+	s[tmp_i] = '\0';                                          \
+})
+
+#define READSTRING(p, s) ({                  \
+	size_t tmp_i = 0;                        \
+                                             \
+	while ((s[tmp_i] = READCHAR(p)) != '\0') \
+		tmp_i++;                             \
+                                             \
+	s[tmp_i] = '\0';                         \
+})
+
+#define READMEM(p, s, n) ({ \
+	memcpy(s, p, n);        \
+	p += n;                 \
+})
diff --git a/src/command.c b/src/command.c
index ae4a7178e437c9039ae4717defb377c091fad215..50310f11255b57d30228a65cfe26b676c5c89497 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/command.h b/src/command.h
index 34fd15963262d6ec4e4416ac8cbb514301f72d6f..30d7e5bbe6af641b67c60acadf81bd1054b1c1e4 100644
--- a/src/command.h
+++ b/src/command.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/config.h.in b/src/config.h.in
index db794cccc82a59eb378f53f9adaa4d5d1bd3cb20..587a881c73bfb7fa7b55e38920501c0a49e7643c 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -35,10 +35,11 @@
  * Last updated 2020 / 09 / 27 - v2.2.7 - patch.pk3
  * Last updated 2020 / 10 / 02 - v2.2.8 - patch.pk3
  * Last updated 2021 / 05 / 06 - v2.2.9 - patch.pk3 & zones.pk3
+ * Last updated 2022 / 03 / 06 - v2.2.10 - main assets
  */
-#define ASSET_HASH_SRB2_PK3   "0277c9416756627004e83cbb5b2e3e28"
-#define ASSET_HASH_ZONES_PK3  "f8f3e2b5deacf40f14e36686a07d44bb"
-#define ASSET_HASH_PLAYER_DTA "49dad7b24634c89728cc3e0b689e12bb"
+#define ASSET_HASH_SRB2_PK3   "ad911f29a28a18968ee5b2d11c2acb39"
+#define ASSET_HASH_ZONES_PK3  "86ae55cae4e0a93ceda868635706a093"
+#define ASSET_HASH_PLAYER_DTA "2e7aaae8a6b1b77d90ffe7606ceadb6c"
 #ifdef USE_PATCH_DTA
 #define ASSET_HASH_PATCH_PK3  "7d467a883f7887b3c311798ee2f56b6a"
 #endif
diff --git a/src/console.c b/src/console.c
index 6f21aeb3dd4d022c51e37c1781ec28aa029a652c..40fb43121f05adfb54f94403b36bca60caba5062 100644
--- a/src/console.c
+++ b/src/console.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/console.h b/src/console.h
index accf89d960faf6975ecd02345bba66d6ef3fd264..1cd032ac10f6cab0fcddc65c39d035450ff83525 100644
--- a/src/console.h
+++ b/src/console.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 78a3ebe6cb961cff2fb22e5e0f4520559078865a..ac8bba608288063b7b34dff533f9d2ef0e9c84e2 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -2077,6 +2077,7 @@ static boolean CL_FinishedFileList(void)
 	return true;
 }
 
+#ifndef NONET
 static const char * InvalidServerReason (serverinfo_pak *info)
 {
 #define EOT "\nPress ESC\n"
@@ -2140,6 +2141,7 @@ static const char * InvalidServerReason (serverinfo_pak *info)
 
 #undef EOT
 }
+#endif // ifndef NONET
 
 /** Called by CL_ServerConnectionTicker
   *
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 8e75fb963c860d64e91557bc47b17daea22af7f4..bf3f0b64f5126eca92510a07686e10afbd07cba7 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_event.h b/src/d_event.h
index c30a8ced2b09cd7887446211cbc2b96622c0aba6..c0b9cef773b0453b3acc67d1eeafc6b8982bc7ad 100644
--- a/src/d_event.h
+++ b/src/d_event.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_main.c b/src/d_main.c
index 83419d266c84703694d06644a28a953e3550f6b1..fa9e21337ced42286e27b75a7bd2987ee62538e5 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -1102,10 +1102,7 @@ static void IdentifyVersion(void)
 		}
 
 		MUSICTEST("music.dta")
-		MUSICTEST("patch_music.pk3")
-#ifdef DEVELOP // remove when music_new.dta is merged into music.dta
-		MUSICTEST("music_new.dta")
-#endif
+		//MUSICTEST("patch_music.pk3")
 	}
 #endif
 }
@@ -1141,7 +1138,7 @@ void D_SRB2Main(void)
 	// Print GPL notice for our console users (Linux)
 	CONS_Printf(
 	"\n\nSonic Robo Blast 2\n"
-	"Copyright (C) 1998-2021 by Sonic Team Junior\n\n"
+	"Copyright (C) 1998-2022 by Sonic Team Junior\n\n"
 	"This program comes with ABSOLUTELY NO WARRANTY.\n\n"
 	"This is free software, and you are welcome to redistribute it\n"
 	"and/or modify it under the terms of the GNU General Public License\n"
diff --git a/src/d_main.h b/src/d_main.h
index e282906d9577fa9f869018dbc57b5e7899634d60..8189a9f2b39f277ba6d5c854551a8e9d21bd35c1 100644
--- a/src/d_main.h
+++ b/src/d_main.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_net.c b/src/d_net.c
index 3a4746002eb87efe8dd57e45729cefc96943bdca..5e5c10889c2a3f2a0383f005107d0ce277e7082c 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -1144,8 +1144,9 @@ boolean HGetPacket(void)
 		if (netbuffer->checksum != NetbufferChecksum())
 		{
 			DEBFILE("Bad packet checksum\n");
-			//Net_CloseConnection(nodejustjoined ? (doomcom->remotenode | FORCECLOSE) : doomcom->remotenode);
-			Net_CloseConnection(doomcom->remotenode);
+			// Do not disconnect or anything, just ignore the packet.
+			// Bad checksums with UDP tend to happen very scarcely
+			// so they are not normally an issue.
 			continue;
 		}
 
diff --git a/src/d_net.h b/src/d_net.h
index dbc6d8ba5ab6288ad76e2003cf67fdd2d6aa43b1..5baa593a0573d53ebbe66aac193ce217fc5089f9 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index fe7e7678fe447cfc186b9eaf4134cb6eeeb6db27..d9080d342a432a87e44d7c1b4e54a981111eb4b5 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 7bb7eab03a1345190c670b3d9309c159cc3d3c07..0beeae15491ba555122e0ae0b5760f346d5b8791 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_netfil.c b/src/d_netfil.c
index fdc0026a8bbf2a87b12fa0444fbfa2156e40c5c1..37fb7265f8abe714d90dab7c0882f55d73bc1cb0 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_netfil.h b/src/d_netfil.h
index 3d713c150fad6f520a618d8e158f96599f081323..f778a518ff2eee3076170885ac9bdded15cd9f8f 100644
--- a/src/d_netfil.h
+++ b/src/d_netfil.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_player.h b/src/d_player.h
index a0db1402df153beb928c54d743bc4388f3bc2122..755926480ae9c2b2b90383659ec079230e48a881 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_think.h b/src/d_think.h
index c3f91edc4392238c92499146ea6aefd869e7848b..90a58ab6875f53b28e6d5b5b0590f50c2790c9a6 100644
--- a/src/d_think.h
+++ b/src/d_think.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h
index 182b30e6aef84b9e4148157594918be325c75095..e632a74a8d33244f189d8a9668bf1166973a5fd1 100644
--- a/src/d_ticcmd.h
+++ b/src/d_ticcmd.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/deh_lua.c b/src/deh_lua.c
index a2ffca95b0622826dd69d350c60dba52e221b0f9..1f4d22dcae5a316e1504faf3d227957690fa8c0d 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/deh_lua.h b/src/deh_lua.h
index 9df4028bdcf9619a14fe267d202aed2343125e2f..657e66b6ecf2fbeeac0392da978334a9aaeed055 100644
--- a/src/deh_lua.h
+++ b/src/deh_lua.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/deh_soc.c b/src/deh_soc.c
index 3a611f3ba18daaacd3c9f5154505176f3a1ad4cd..9e3c2f2428ebe5c3eb13ab51401ca3c6b3a45520 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/deh_soc.h b/src/deh_soc.h
index 28e3c9512336b91700a648f35f0fe3e922356d69..f972ec26ecbb7732098e131d427ebd34b5f621ef 100644
--- a/src/deh_soc.h
+++ b/src/deh_soc.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/deh_tables.c b/src/deh_tables.c
index eecd5f61d6609843408cdc9b9ea0f36fddad09cc..78ebfb86f4f7ceeeba2cdbeb7c834cd804318ca0 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -91,6 +91,8 @@ actionpointer_t actionpointers[] =
 	{{A_FaceTracer},             "A_FACETRACER"},
 	{{A_Scream},                 "A_SCREAM"},
 	{{A_BossDeath},              "A_BOSSDEATH"},
+	{{A_SetShadowScale},         "A_SETSHADOWSCALE"},
+	{{A_ShadowScream},           "A_SHADOWSCREAM"},
 	{{A_CustomPower},            "A_CUSTOMPOWER"},
 	{{A_GiveWeapon},             "A_GIVEWEAPON"},
 	{{A_RingBox},                "A_RINGBOX"},
@@ -224,6 +226,8 @@ actionpointer_t actionpointers[] =
 	{{A_SetObjectFlags2},        "A_SETOBJECTFLAGS2"},
 	{{A_RandomState},            "A_RANDOMSTATE"},
 	{{A_RandomStateRange},       "A_RANDOMSTATERANGE"},
+	{{A_StateRangeByAngle},      "A_STATERANGEBYANGLE"},
+	{{A_StateRangeByParameter},  "A_STATERANGEBYPARAMETER"},
 	{{A_DualAction},             "A_DUALACTION"},
 	{{A_RemoteAction},           "A_REMOTEACTION"},
 	{{A_ToggleFlameJet},         "A_TOGGLEFLAMEJET"},
@@ -1751,6 +1755,56 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
 	// The letter
 	"S_LETTER",
 
+	// Tutorial Scenery
+	"S_TUTORIALLEAF1",
+	"S_TUTORIALLEAF2",
+	"S_TUTORIALLEAF3",
+	"S_TUTORIALLEAF4",
+	"S_TUTORIALLEAF5",
+	"S_TUTORIALLEAF6",
+	"S_TUTORIALLEAF7",
+	"S_TUTORIALLEAF8",
+	"S_TUTORIALLEAF9",
+	"S_TUTORIALLEAF10",
+	"S_TUTORIALLEAF11",
+	"S_TUTORIALLEAF12",
+	"S_TUTORIALLEAF13",
+	"S_TUTORIALLEAF14",
+	"S_TUTORIALLEAF15",
+	"S_TUTORIALLEAF16",
+	"S_TUTORIALFLOWER1",
+	"S_TUTORIALFLOWER2",
+	"S_TUTORIALFLOWER3",
+	"S_TUTORIALFLOWER4",
+	"S_TUTORIALFLOWER5",
+	"S_TUTORIALFLOWER6",
+	"S_TUTORIALFLOWER7",
+	"S_TUTORIALFLOWER8",
+	"S_TUTORIALFLOWER9",
+	"S_TUTORIALFLOWER10",
+	"S_TUTORIALFLOWER11",
+	"S_TUTORIALFLOWER12",
+	"S_TUTORIALFLOWER13",
+	"S_TUTORIALFLOWER14",
+	"S_TUTORIALFLOWER15",
+	"S_TUTORIALFLOWER16",
+	"S_TUTORIALFLOWERF1",
+	"S_TUTORIALFLOWERF2",
+	"S_TUTORIALFLOWERF3",
+	"S_TUTORIALFLOWERF4",
+	"S_TUTORIALFLOWERF5",
+	"S_TUTORIALFLOWERF6",
+	"S_TUTORIALFLOWERF7",
+	"S_TUTORIALFLOWERF8",
+	"S_TUTORIALFLOWERF9",
+	"S_TUTORIALFLOWERF10",
+	"S_TUTORIALFLOWERF11",
+	"S_TUTORIALFLOWERF12",
+	"S_TUTORIALFLOWERF13",
+	"S_TUTORIALFLOWERF14",
+	"S_TUTORIALFLOWERF15",
+	"S_TUTORIALFLOWERF16",
+
 	// GFZ flowers
 	"S_GFZFLOWERA",
 	"S_GFZFLOWERB",
@@ -3293,14 +3347,13 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
 	"S_NIGHTOPIANHELPER9",
 
 	// Nightopian
-	"S_PIAN0",
-	"S_PIAN1",
-	"S_PIAN2",
-	"S_PIAN3",
-	"S_PIAN4",
-	"S_PIAN5",
-	"S_PIAN6",
-	"S_PIANSING",
+	"S_PIAN_LOOK1",
+	"S_PIAN_LOOK2",
+	"S_PIAN_LOOK3",
+	"S_PIAN_FLY1",
+	"S_PIAN_FLY2",
+	"S_PIAN_FLY3",
+	"S_PIAN_SING",
 
 	// Shleep
 	"S_SHLEEP1",
@@ -3763,6 +3816,12 @@ const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for sanity t
 	// The letter
 	"MT_LETTER",
 
+	// Tutorial Scenery
+	"MT_TUTORIALPLANT",
+	"MT_TUTORIALLEAF",
+	"MT_TUTORIALFLOWER",
+	"MT_TUTORIALFLOWERF",
+
 	// Greenflower Scenery
 	"MT_GFZFLOWER1",
 	"MT_GFZFLOWER2",
@@ -4269,6 +4328,7 @@ const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for sanity t
 	"MT_YELLOWBRICKDEBRIS",
 
 	"MT_NAMECHECK",
+	"MT_RAY",
 };
 
 const char *const MOBJFLAG_LIST[] = {
diff --git a/src/deh_tables.h b/src/deh_tables.h
index 1f265cc9992da1c3d5c6e53781f6151710bc798f..972b08838ea417b210ab7f8b14de39768e5b7cf3 100644
--- a/src/deh_tables.h
+++ b/src/deh_tables.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/dehacked.c b/src/dehacked.c
index da8c81c351f845a98b59e3cd494e8f29c2a6b7d1..3f339e4778be72a2a2554723f378d2db27fb12f9 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/dehacked.h b/src/dehacked.h
index 1b200e2466f58994bb4db317db2dd9908565d5e5..b4651c66ae11dc6d2d6ee622d529435405a71b9b 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/doomdata.h b/src/doomdata.h
index 630e18615603ee8a1786e1e3f22463f010cec75a..abfd81a4e17abe2f4d5a5f3b65aebafe50bacb81 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/doomdef.h b/src/doomdef.h
index 7e7e355990d422a8a6ecc86f54f49a17902ea66b..42ae03c3e11c2acbb2a61267c0c297241600b2ae 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -150,7 +150,7 @@ extern char logfilename[1024];
 
 // Does this version require an added patch file?
 // Comment or uncomment this as necessary.
-#define USE_PATCH_DTA
+// #define USE_PATCH_DTA
 
 // Enforce a limit of loaded WAD files.
 //#define ENFORCE_WAD_LIMIT
@@ -530,6 +530,22 @@ extern boolean capslock;
 // i_system.c, replace getchar() once the keyboard has been appropriated
 INT32 I_GetKey(void);
 
+/* http://www.cse.yorku.ca/~oz/hash.html */
+static inline
+UINT32 quickncasehash (const char *p, size_t n)
+{
+	size_t i = 0;
+	UINT32 x = 5381;
+
+	while (i < n && p[i])
+	{
+		x = (x * 33) ^ tolower(p[i]);
+		i++;
+	}
+
+	return x;
+}
+
 #ifndef min // Double-Check with WATTCP-32's cdefs.h
 #define min(x, y) (((x) < (y)) ? (x) : (y))
 #endif
diff --git a/src/doomstat.h b/src/doomstat.h
index 32669b68bdecbda94eea9cebd7564907149b4d14..bce43416b840e7704a870857708ae1e019062868 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/doomtype.h b/src/doomtype.h
index 3a57d90e81f25998e80fc7181cd1aa14df50bde3..5ddd9ae44e189deca59be07b6fccf653f6489551 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/endian.h b/src/endian.h
index e78204e723a43715cd7182b6aaebf59e61ee4358..86297f0cb6b6e604edc2df2e29811d35e6077ea6 100644
--- a/src/endian.h
+++ b/src/endian.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/f_finale.c b/src/f_finale.c
index 8dd03d44f5d068f2b5762dbaea7e51dbd408e5b7..b5715b863f8dcde6ee8d9f5cc621ab9ea7f72ccb 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/f_finale.h b/src/f_finale.h
index 4aa2c3f05b121a17c4fcfe7c4163e4b1db274415..efdc9d4ada3f88ce4de8780c70ad41a34e0897b5 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 7526aeca36f6bd94cd8179c79fc7be299d395e40..43b7180b754408faf39387ef259d54aae590bf9f 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2013-2016 by Matthew "Kaito Sinclaire" Walsh.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/g_demo.c b/src/g_demo.c
index c97dbcf9ee0559e23e6635e4a62f2de1cb843af2..e293ad9dc5e76a1af37e3b384763383e23326c70 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -94,7 +94,7 @@ demoghost *ghosts = NULL;
 // DEMO RECORDING
 //
 
-#define DEMOVERSION 0x000e
+#define DEMOVERSION 0x000f
 #define DEMOHEADER  "\xF0" "SRB2Replay" "\x0F"
 
 #define DF_GHOST        0x01 // This demo contains ghost data too!
@@ -1029,7 +1029,11 @@ void G_ReadMetalTic(mobj_t *metal)
 	if (ziptic & GZT_ANGLE)
 		metal->angle = READUINT8(metal_p)<<24;
 	if (ziptic & GZT_FRAME)
+	{
 		oldmetal.frame = READUINT32(metal_p);
+		if (metalversion < 0x000f)
+			oldmetal.frame = G_ConvertOldFrameFlags(oldmetal.frame);
+	}
 	if (ziptic & GZT_SPR2)
 		oldmetal.sprite2 = READUINT8(metal_p);
 
@@ -1169,6 +1173,8 @@ void G_ReadMetalTic(mobj_t *metal)
 					follow->sprite2 = 0;
 				follow->sprite = READUINT16(metal_p);
 				follow->frame = READUINT32(metal_p); // NOT & FF_FRAMEMASK here, so 32 bits
+				if (metalversion < 0x000f)
+					follow->frame = G_ConvertOldFrameFlags(follow->frame);
 				follow->angle = metal->angle;
 				follow->color = (metalversion==0x000c) ? READUINT8(metal_p) : READUINT16(metal_p);
 
@@ -1680,8 +1686,9 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname)
 	switch(oldversion) // demoversion
 	{
 	case DEMOVERSION: // latest always supported
-	case 0x000d: // The previous demoversion also supported
-	case 0x000c: // all that changed between then and now was longer color name
+	case 0x000e: // The previous demoversions also supported
+	case 0x000d: // all that changed between then and now was longer color name
+	case 0x000c:
 		break;
 	// too old, cannot support.
 	default:
@@ -1824,6 +1831,7 @@ void G_DoPlayDemo(char *defdemoname)
 	switch(demoversion)
 	{
 	case 0x000d:
+	case 0x000e:
 	case DEMOVERSION: // latest always supported
 		cnamelen = MAXCOLORNAME;
 		break;
@@ -2077,6 +2085,7 @@ void G_AddGhost(char *defdemoname)
 	switch(ghostversion)
 	{
 	case 0x000d:
+	case 0x000e:
 	case DEMOVERSION: // latest always supported
 		cnamelen = MAXCOLORNAME;
 		break;
@@ -2337,8 +2346,9 @@ void G_DoPlayMetal(void)
 	switch(metalversion)
 	{
 	case DEMOVERSION: // latest always supported
-	case 0x000d: // There are checks wheter the momentum is from older demo versions or not
-	case 0x000c: // all that changed between then and now was longer color name
+	case 0x000e: // There are checks wheter the momentum is from older demo versions or not
+	case 0x000d: // all that changed between then and now was longer color name
+	case 0x000c:
 		break;
 	// too old, cannot support.
 	default:
@@ -2554,3 +2564,45 @@ boolean G_CheckDemoStatus(void)
 
 	return false;
 }
+
+// 2.2.10 shifted some frame flags around, this function converts frame flags from older versions to their 2.2.10 equivalents.
+INT32 G_ConvertOldFrameFlags(INT32 frame)
+{
+	if (frame & 0x01000000) // was FF_ANIMATE, is now FF_VERTICALFLIP
+	{
+		frame &= ~0x01000000;
+		frame |= FF_ANIMATE;
+	}
+
+	if (frame & 0x02000000) // was FF_RANDOMANIM, is now FF_HORIZONTALFLIP
+	{
+		frame &= ~0x02000000;
+		frame |= FF_RANDOMANIM;
+	}
+
+	if (frame & 0x04000000) // was FF_GLOBALANIM, is now empty
+	{
+		frame &= ~0x04000000;
+		frame |= FF_GLOBALANIM;
+	}
+
+	if (frame & 0x00200000) // was FF_VERTICALFLIP, is now FF_FULLDARK
+	{
+		frame &= ~0x00200000;
+		frame |= FF_VERTICALFLIP;
+	}
+
+	if (frame & 0x00400000) // was FF_HORIZONTALFLIP, is now FF_PAPERSPRITE
+	{
+		frame &= ~0x00400000;
+		frame |= FF_HORIZONTALFLIP;
+	}
+
+	if (frame & 0x00800000) // was FF_PAPERSPRITE, is now FF_FLOORSPRITE
+	{
+		frame &= ~0x00800000;
+		frame |= FF_PAPERSPRITE;
+	}
+
+	return frame;
+}
diff --git a/src/g_demo.h b/src/g_demo.h
index 73cf273582ff1baeffcb2638ebe81654e6b8d70d..37664dc71fb7ce74a394b54b28f4b663f3a596d0 100644
--- a/src/g_demo.h
+++ b/src/g_demo.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -82,5 +82,6 @@ void G_StopMetalDemo(void);
 ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill);
 void G_StopDemo(void);
 boolean G_CheckDemoStatus(void);
+INT32 G_ConvertOldFrameFlags(INT32 frame);
 
 #endif // __G_DEMO__
diff --git a/src/g_game.c b/src/g_game.c
index 44860bbbc5a19190e5ef705dff024d70dc6d1445..39d0030565c468823819c0a35d2653d2a773db01 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -1411,7 +1411,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 			newtarget = P_SpawnMobj(ticcmd_ztargetfocus[forplayer]->x, ticcmd_ztargetfocus[forplayer]->y, ticcmd_ztargetfocus[forplayer]->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 			P_SetTarget(&newtarget->target, ticcmd_ztargetfocus[forplayer]);
 
-			if (P_AproxDistance(
+			if (player->mo && P_AproxDistance(
 				player->mo->x - ticcmd_ztargetfocus[forplayer]->x,
 				player->mo->y - ticcmd_ztargetfocus[forplayer]->y
 			) > 50*player->mo->scale)
@@ -1547,12 +1547,17 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	cmd->sidemove = (SINT8)(cmd->sidemove + side);
 
 	// Note: Majority of botstuffs are handled in G_Ticker now.
-	if (player->bot == BOT_2PHUMAN) //Player-controlled bot
+	if (player->bot == BOT_2PAI
+		&& !player->powers[pw_tailsfly]
+		&& (cmd->forwardmove || cmd->sidemove || cmd->buttons))
 	{
-		// Fix offset angle for P2-controlled Tailsbot when P2's controls are set to non-Strafe
-		cmd->angleturn = (INT16)((localangle - *myangle) >> 16);
+		player->bot = BOT_2PHUMAN; // A player-controlled bot. Returns to AI when it respawns.
+		CV_SetValue(&cv_analog[1], true);
 	}
 
+	if (player->bot == BOT_2PHUMAN)
+		cmd->angleturn = (INT16)((localangle - *myangle) >> 16);
+
 	*myangle += (cmd->angleturn<<16);
 
 	if (controlstyle == CS_LMAOGALOG) {
@@ -2307,65 +2312,44 @@ void G_Ticker(boolean run)
 
 	buf = gametic % BACKUPTICS;
 
+	// Generate ticcmds for bots FIRST, then copy received ticcmds for players.
+	// This emulates pre-2.2.10 behaviour where the bot referenced their leader's last copied ticcmd,
+	// which is desirable because P_PlayerThink can override inputs (e.g. while PF_STASIS is applied or in a waterslide),
+	// and the bot AI needs to respect that.
+#define ISHUMAN (players[i].bot == BOT_NONE || players[i].bot == BOT_2PHUMAN)
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (playeringame[i])
+		if (playeringame[i] && !ISHUMAN) // Less work is required if we're building a bot ticcmd.
 		{
-			INT16 received;
-			// Save last frame's button readings
-			players[i].lastbuttons = players[i].cmd.buttons;
+			players[i].lastbuttons = players[i].cmd.buttons; // Save last frame's button readings
+			B_BuildTiccmd(&players[i], &players[i].cmd);
+
+			// Since bot TicCmd is pre-determined for both the client and server, the latency and packet checks are simplified.
+			players[i].cmd.latency = 0;
+			P_SetPlayerAngle(&players[i], players[i].cmd.angleturn << 16);
+		}
+	}
 
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playeringame[i] && ISHUMAN)
+		{
+			players[i].lastbuttons = players[i].cmd.buttons; // Save last frame's button readings
 			G_CopyTiccmd(&players[i].cmd, &netcmds[buf][i], 1);
-			// Bot ticcmd handling
-			// Yes, ordinarily this would be handled in G_BuildTiccmd...
-			// ...however, bot players won't have a corresponding consoleplayer or splitscreen player 2 to send that information.
-			// Therefore, this has to be done after ticcmd sends are received.
-			if (players[i].bot == BOT_2PAI) { // Tailsbot for P2
-				if (!players[i].powers[pw_tailsfly] && (players[i].cmd.forwardmove || players[i].cmd.sidemove || players[i].cmd.buttons))
-				{
-					players[i].bot = BOT_2PHUMAN; // A player-controlled bot. Returns to AI when it respawns.
-					CV_SetValue(&cv_analog[1], true);
-				}
-				else
-				{
-					B_BuildTiccmd(&players[i], &players[i].cmd);
-				}
-				B_HandleFlightIndicator(&players[i]);
-			}
-			else if (players[i].bot == BOT_MPAI) {
-				B_BuildTiccmd(&players[i], &players[i].cmd);
-			}
+
+			// Use the leveltime sent in the player's ticcmd to determine control lag
+			players[i].cmd.latency = min(((leveltime & 0xFF) - players[i].cmd.latency) & 0xFF, MAXPREDICTTICS-1);
 
 			// Do angle adjustments.
-			if (players[i].bot == BOT_NONE || players[i].bot == BOT_2PHUMAN)
-			{
-				received = (players[i].cmd.angleturn & TICCMD_RECEIVED);
-				players[i].angleturn += players[i].cmd.angleturn - players[i].oldrelangleturn;
-				players[i].oldrelangleturn = players[i].cmd.angleturn;
-				if (P_ControlStyle(&players[i]) == CS_LMAOGALOG)
-					P_ForceLocalAngle(&players[i], players[i].angleturn << 16);
-				else
-					players[i].cmd.angleturn = players[i].angleturn;
-    			if (P_ControlStyle(&players[i]) == CS_LMAOGALOG)
-    				P_ForceLocalAngle(&players[i], players[i].angleturn << 16);
-    			else
-    				players[i].cmd.angleturn = players[i].angleturn;
-
-    			players[i].cmd.angleturn &= ~TICCMD_RECEIVED;
-				// Use the leveltime sent in the player's ticcmd to determine control lag
-    			players[i].cmd.latency = min(((leveltime & 0xFF) - players[i].cmd.latency) & 0xFF, MAXPREDICTTICS-1);
-			}
-			else // Less work is required if we're building a bot ticcmd.
-			{
-    			// Since bot TicCmd is pre-determined for both the client and server, the latency and packet checks are simplified.
-    			received = 1;
-    			players[i].cmd.latency = 0;
-				players[i].angleturn = players[i].cmd.angleturn;
-				players[i].oldrelangleturn = players[i].cmd.angleturn;
-			}
-			players[i].cmd.angleturn |= received;
+			players[i].angleturn += players[i].cmd.angleturn - players[i].oldrelangleturn;
+			players[i].oldrelangleturn = players[i].cmd.angleturn;
+			if (P_ControlStyle(&players[i]) == CS_LMAOGALOG)
+				P_ForceLocalAngle(&players[i], players[i].angleturn << 16);
+			else
+				players[i].cmd.angleturn = (players[i].angleturn & ~TICCMD_RECEIVED) | (players[i].cmd.angleturn & TICCMD_RECEIVED);
 		}
 	}
+#undef ISHUMAN
 
 	// do main actions
 	switch (gamestate)
diff --git a/src/g_game.h b/src/g_game.h
index f98269fcec2280ceac908c310236c88bb01ee73f..dca043f2e0fae899ec86ac55255f2613dfdde0eb 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/g_input.c b/src/g_input.c
index 250a2477223659fe3bc9988aa9fa4010deb14f87..7bb2e799da1379b2ea06238aa273a0d6e1937608 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/g_input.h b/src/g_input.h
index 2e9f53dcf4e4ff57aa9deccbffc7537ebaa6c261..bf6ad39b3d24e8c941f95330a7d0c07a6cf06d09 100644
--- a/src/g_input.h
+++ b/src/g_input.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/g_state.h b/src/g_state.h
index 589dc6361705747ad0da81fddf79cbf327849228..a6ac1970d96308ef08c70ae2a61b5e08c382726d 100644
--- a/src/g_state.h
+++ b/src/g_state.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_batching.c b/src/hardware/hw_batching.c
index da0319bccfecbd70901fe05b1658a49c54c24998..f9c6542ae631b07a44356273aafafce3307fef92 100644
--- a/src/hardware/hw_batching.c
+++ b/src/hardware/hw_batching.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_batching.h b/src/hardware/hw_batching.h
index 9ccc7de3df503a12211b41d871c2734af70b0bf6..df5c478a323397fb799ff295f98459abe8e6e0a5 100644
--- a/src/hardware/hw_batching.h
+++ b/src/hardware/hw_batching.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 317efd320e749fb4c60e2d5ab36a044d3543a699..fe0b65c5021c0a8ef7db1ae189ad37709f76d33d 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h
index 5aba6a2a9b14e27d98fa7f57c6847826c8e791eb..ceefe9abdd465913078173548da2e0c0bc279731 100644
--- a/src/hardware/hw_data.h
+++ b/src/hardware/hw_data.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 8df9b8916b2e563d7c1eebb16511b222a636a312..fca9b80a3006340bb053997f8e68a51de89bdf6d 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index e02dbea5b6fbab58944c13cba4d232f95d5271ea..691e3cd3f0c70d76b0a015f3f3ee36196361a436 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -347,19 +347,16 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p
 	v[2].t = v[3].t = hwrPatch->max_t;
 
 	// clip it since it is used for bunny scroll in doom I
-	if (blendmode)
-		flags = HWR_GetBlendModeFlag(blendmode+1)|PF_NoDepthTest;
-	else
-		flags = PF_Translucent|PF_NoDepthTest;
+	flags = HWR_GetBlendModeFlag(blendmode+1)|PF_NoDepthTest;
 
 	if (alphalevel)
 	{
 		FSurfaceInfo Surf;
 		Surf.PolyColor.s.red = Surf.PolyColor.s.green = Surf.PolyColor.s.blue = 0xff;
 
-		if (alphalevel == 10) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency];
-		else if (alphalevel == 11) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency];
-		else if (alphalevel == 12) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency];
+		if (alphalevel == 10) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency]; // V_HUDTRANSHALF
+		else if (alphalevel == 11) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency]; // V_HUDTRANS
+		else if (alphalevel == 12) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency]; // V_HUDTRANSDOUBLE
 		else Surf.PolyColor.s.alpha = softwaretranstogl[10-alphalevel];
 		flags |= PF_Modulated;
 		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
@@ -644,19 +641,16 @@ void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 	}
 
 	// clip it since it is used for bunny scroll in doom I
-	if (blendmode)
-		flags = HWR_GetBlendModeFlag(blendmode+1)|PF_NoDepthTest;
-	else
-		flags = PF_Translucent|PF_NoDepthTest;
+	flags = HWR_GetBlendModeFlag(blendmode+1)|PF_NoDepthTest;
 
 	if (alphalevel)
 	{
 		FSurfaceInfo Surf;
 		Surf.PolyColor.s.red = Surf.PolyColor.s.green = Surf.PolyColor.s.blue = 0xff;
 
-		if (alphalevel == 10) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency];
-		else if (alphalevel == 11) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency];
-		else if (alphalevel == 12) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency];
+		if (alphalevel == 10) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency]; // V_HUDTRANSHALF
+		else if (alphalevel == 11) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency]; // V_HUDTRANS
+		else if (alphalevel == 12) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency]; // V_HUDTRANSDOUBLE
 		else Surf.PolyColor.s.alpha = softwaretranstogl[10-alphalevel];
 
 		flags |= PF_Modulated;
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index d4a586d418de9f3da9a16d432690608b99961c8f..718774773c1dead0dd452617fbb8a5065433fbe7 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 37d77b467306b823cd36a9a1bdcb57500723cc2e..8b30a346832987304e307800eb222cf6d3ac0908 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index e83d9a6ec025143150754ab6910afed140b4fdcd..eb3c9bbbb04d8b4658063a705e65f50b3ac4242e 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_light.h b/src/hardware/hw_light.h
index 244cc921f567e63422868e64259b07f1cb5c0ac4..a0a9e93ad2ca4fa6693fbc6a4b1f0b41b7a64301 100644
--- a/src/hardware/hw_light.h
+++ b/src/hardware/hw_light.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 10dafa722b0467e71462f65176a202a2e63bae39..e26cbdac031a6ba17a5cc28221f00c3e5c23aae5 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -3605,6 +3605,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	FBITFIELD blendmode = PF_Translucent|PF_Modulated;
 	INT32 shader = SHADER_DEFAULT;
 	UINT8 i;
+	INT32 heightsec, phs;
 	SINT8 flip = P_MobjFlip(thing);
 
 	INT32 light;
@@ -3617,7 +3618,23 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 
 	groundz = R_GetShadowZ(thing, &groundslope);
 
-	//if (abs(groundz - gl_viewz) / tz > 4) return; // Prevent stretchy shadows and possible crashes
+	heightsec = thing->subsector->sector->heightsec;
+	if (viewplayer->mo && viewplayer->mo->subsector)
+		phs = viewplayer->mo->subsector->sector->heightsec;
+	else
+		phs = -1;
+
+	if (heightsec != -1 && phs != -1) // only clip things which are in special sectors
+	{
+		if (gl_viewz < FIXED_TO_FLOAT(sectors[phs].floorheight) ?
+		thing->z >= sectors[heightsec].floorheight :
+		thing->z < sectors[heightsec].floorheight)
+			return;
+		if (gl_viewz > FIXED_TO_FLOAT(sectors[phs].ceilingheight) ?
+		thing->z < sectors[heightsec].ceilingheight && gl_viewz >= FIXED_TO_FLOAT(sectors[heightsec].ceilingheight) :
+		thing->z >= sectors[heightsec].ceilingheight)
+			return;
+	}
 
 	floordiff = abs((flip < 0 ? thing->height : 0) + thing->z - groundz);
 
@@ -5274,13 +5291,19 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	if (heightsec != -1 && phs != -1) // only clip things which are in special sectors
 	{
+		float top = gzt;
+		float bottom = FIXED_TO_FLOAT(thing->z);
+
+		if (R_ThingIsFloorSprite(thing))
+			top = bottom;
+
 		if (gl_viewz < FIXED_TO_FLOAT(sectors[phs].floorheight) ?
-		FIXED_TO_FLOAT(thing->z) >= FIXED_TO_FLOAT(sectors[heightsec].floorheight) :
-		gzt < FIXED_TO_FLOAT(sectors[heightsec].floorheight))
+		bottom >= FIXED_TO_FLOAT(sectors[heightsec].floorheight) :
+		top < FIXED_TO_FLOAT(sectors[heightsec].floorheight))
 			return;
 		if (gl_viewz > FIXED_TO_FLOAT(sectors[phs].ceilingheight) ?
-		gzt < FIXED_TO_FLOAT(sectors[heightsec].ceilingheight) && gl_viewz >= FIXED_TO_FLOAT(sectors[heightsec].ceilingheight) :
-		FIXED_TO_FLOAT(thing->z) >= FIXED_TO_FLOAT(sectors[heightsec].ceilingheight))
+		top < FIXED_TO_FLOAT(sectors[heightsec].ceilingheight) && gl_viewz >= FIXED_TO_FLOAT(sectors[heightsec].ceilingheight) :
+		bottom >= FIXED_TO_FLOAT(sectors[heightsec].ceilingheight))
 			return;
 	}
 
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 3f90f0ae17a58070948148f168c48f248510ce61..cd822c0c153bedece9dcd9c92665944b1e5504c8 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index b66f91e1962579788ebaae5bdc5baec73cdff325..e765656ca8e5056e39613808ee8182f64ae8bcb0 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -1346,12 +1346,18 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		//if (tics > durs)
 			//durs = tics;
 
+		INT32 blendmode;
+		if (spr->mobj->frame & FF_BLENDMASK)
+			blendmode = ((spr->mobj->frame & FF_BLENDMASK) >> FF_BLENDSHIFT) + 1;
+		else
+			blendmode = spr->mobj->blendmode;
+
 		if (spr->mobj->frame & FF_TRANSMASK)
-			Surf.PolyFlags = HWR_SurfaceBlend(spr->mobj->blendmode, (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
+			Surf.PolyFlags = HWR_SurfaceBlend(blendmode, (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
 		else
 		{
 			Surf.PolyColor.s.alpha = (spr->mobj->flags2 & MF2_SHADOW) ? 0x40 : 0xff;
-			Surf.PolyFlags = HWR_GetBlendModeFlag(spr->mobj->blendmode);
+			Surf.PolyFlags = HWR_GetBlendModeFlag(blendmode);
 		}
 
 		// don't forget to enable the depth test because we can't do this
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 9249c034c2b1394f289681e1d33cc8726a9f161a..966ed016b898fdd9a698c0ec7a6dffa146da6353 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index de0e8c6a65aab0c1cbdc8e6e7f77cccd9118c6fd..7ec7ee2702f4bdaadbef776a0b281b7c3506f6a9 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 1998-2021 by Sonic Team Junior.
+// Copyright (C) 1998-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/http-mserv.c b/src/http-mserv.c
index f9134ba5008b0ba5f27e0b6ae48feee7ec95feef..b0ef37fa169bf8857e70b4497fa769eafb652ddf 100644
--- a/src/http-mserv.c
+++ b/src/http-mserv.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by James R.
+// Copyright (C) 2020-2022 by James R.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index cf7118fbe0694c206a2d26497196f2cc81d5787f..5d893a5515fcf21e82f72959b9aa0cc8773ad96c 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -76,7 +76,7 @@ patch_t *nto_font[NT_FONTSIZE];
 
 static player_t *plr;
 boolean chat_on; // entering a chat message?
-static char w_chat[HU_MAXMSGLEN];
+static char w_chat[HU_MAXMSGLEN + 1];
 static size_t c_input = 0; // let's try to make the chat input less shitty.
 static boolean headsupactive = false;
 boolean hu_showscores; // draw rankings
@@ -461,7 +461,7 @@ void HU_AddChatText(const char *text, boolean playsound)
 
 static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 {
-	char buf[254];
+	char buf[2 + HU_MAXMSGLEN + 1];
 	size_t numwords, ix;
 	char *msg = &buf[2];
 	const size_t msgspace = sizeof buf - 2;
@@ -537,7 +537,7 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
 		}
 		buf[0] = target;
 		newmsg = msg+5+spc;
-		strlcpy(msg, newmsg, 252);
+		strlcpy(msg, newmsg, HU_MAXMSGLEN + 1);
 	}
 
 	SendNetXCmd(XD_SAY, buf, strlen(msg) + 1 + msg-buf);
@@ -644,7 +644,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	target = READSINT8(*p);
 	flags = READUINT8(*p);
 	msg = (char *)*p;
-	SKIPSTRING(*p);
+	SKIPSTRINGL(*p, HU_MAXMSGLEN + 1);
 
 	if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !(IsPlayerAdmin(playernum)))
 	{
@@ -858,72 +858,6 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 #endif
 }
 
-// Handles key input and string input
-//
-static inline boolean HU_keyInChatString(char *s, char ch)
-{
-	size_t l;
-
-	if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART])
-	  || ch == ' ') // Allow spaces, of course
-	{
-		l = strlen(s);
-		if (l < HU_MAXMSGLEN - 1)
-		{
-			if (c_input >= strlen(s)) // don't do anything complicated
-			{
-				s[l++] = ch;
-				s[l]=0;
-			}
-			else
-			{
-
-				// move everything past c_input for new characters:
-				size_t m = HU_MAXMSGLEN-1;
-				while (m>=c_input)
-				{
-					if (s[m])
-						s[m+1] = (s[m]);
-					if (m == 0) // prevent overflow
-						break;
-					m--;
-				}
-				s[c_input] = ch; // and replace this.
-			}
-			c_input++;
-			return true;
-		}
-		return false;
-	}
-	else if (ch == KEY_BACKSPACE)
-	{
-		size_t i = c_input;
-
-		if (c_input <= 0)
-			return false;
-
-		if (!s[i-1])
-			return false;
-
-		if (i >= strlen(s)-1)
-		{
-			s[strlen(s)-1] = 0;
-			c_input--;
-			return false;
-		}
-
-		for (; (i < HU_MAXMSGLEN); i++)
-		{
-			s[i-1] = s[i];
-		}
-		c_input--;
-	}
-	else if (ch != KEY_ENTER)
-		return false; // did not eat key
-
-	return true; // ate the key
-}
-
 #endif
 
 //
@@ -945,151 +879,123 @@ void HU_Ticker(void)
 #ifndef NONET
 
 static boolean teamtalk = false;
+static boolean justscrolleddown;
+static boolean justscrolledup;
+static INT16 typelines = 1; // number of drawfill lines we need when drawing the chat. it's some weird hack and might be one frame off but I'm lazy to make another loop.
+// It's up here since it has to be reset when we open the chat.
 
-// Clear spaces so we don't end up with messages only made out of emptiness
-static boolean HU_clearChatSpaces(void)
+static boolean HU_chatboxContainsOnlySpaces(void)
 {
-	size_t i = 0; // Used to just check our message
-	char c; // current character we're iterating.
-	boolean nothingbutspaces = true;
+	size_t i;
 
-	for (; i < strlen(w_chat); i++) // iterate through message and eradicate all spaces that don't belong.
-	{
-		c = w_chat[i];
-		if (!c)
-			break; // if there's nothing, it's safe to assume our message has ended, so let's not waste any more time here.
+	for (i = 0; w_chat[i]; i++)
+		if (w_chat[i] != ' ')
+			return false;
 
-		if (c != ' ') // Isn't a space
-		{
-			nothingbutspaces = false;
-		}
-	}
-	return nothingbutspaces;
+	return true;
 }
 
-//
-//
-static void HU_queueChatChar(char c)
+static void HU_sendChatMessage(void)
 {
-	// send automaticly the message (no more chat char)
-	if (c == KEY_ENTER)
+	char buf[2 + HU_MAXMSGLEN + 1];
+	char *msg = &buf[2];
+	size_t ci;
+	INT32 target = 0;
+
+	// if our message was nothing but spaces, don't send it.
+	if (HU_chatboxContainsOnlySpaces())
+		return;
+
+	// copy printable characters and terminating '\0' only.
+	for (ci = 2; w_chat[ci-2]; ci++)
 	{
-		char buf[2+256];
-		char *msg = &buf[2];
-		size_t i = 0;
-		size_t ci = 2;
-		INT32 target = 0;
+		char c = w_chat[ci-2];
+		if (c >= ' ' && !(c & 0x80))
+			buf[ci] = c;
+	};
+	buf[ci] = '\0';
 
-		if (HU_clearChatSpaces()) // Avoids being able to send empty messages, or something.
-			return; // If this returns true, that means our message was NOTHING but spaces, so don't send it period.
+	memset(w_chat, '\0', sizeof(w_chat));
+	c_input = 0;
 
-		do {
-			c = w_chat[-2+ci++];
-			if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only.
-				buf[ci-1]=c;
-		} while (c);
+	// last minute mute check
+	if (CHAT_MUTE)
+	{
+		HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
+		return;
+	}
 
-		for (;(i<HU_MAXMSGLEN);i++)
-			w_chat[i] = 0; // reset this.
+	if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
+	{
+		INT32 spc = 1; // used if playernum[1] is a space.
+		char playernum[3];
+		const char *newmsg;
 
-		c_input = 0;
+		// what we're gonna do now is check if the player exists
+		// with that logic, characters 4 and 5 are our numbers:
 
-		// last minute mute check
-		if (CHAT_MUTE)
+		// teamtalk can't send PMs, just don't send it, else everyone would be able to see it, and no one wants to see your sex RP sicko.
+		if (teamtalk)
 		{
-			HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
+			HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"), false);
 			return;
 		}
 
-		if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
+		strncpy(playernum, msg+3, 3);
+		// check for undesirable characters in our "number"
+		if (!(isdigit(playernum[0]) && isdigit(playernum[1])))
 		{
-			INT32 spc = 1; // used if playernum[1] is a space.
-			char playernum[3];
-			const char *newmsg;
-
-			// what we're gonna do now is check if the player exists
-			// with that logic, characters 4 and 5 are our numbers:
-
-			// teamtalk can't send PMs, just don't send it, else everyone would be able to see it, and no one wants to see your sex RP sicko.
-			if (teamtalk)
-			{
-				HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"), false);
-				return;
-			}
-
-			strncpy(playernum, msg+3, 3);
-			// check for undesirable characters in our "number"
-			if (((playernum[0] < '0') || (playernum[0] > '9')) || ((playernum[1] < '0') || (playernum[1] > '9')))
-			{
-				// check if playernum[1] is a space
-				if (playernum[1] == ' ')
-					spc = 0;
-					// let it slide
-				else
-				{
-					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<player num> \'.", false);
-					return;
-				}
-			}
-			// I'm very bad at C, I swear I am, additional checks eww!
-			if (spc != 0)
-			{
-				if (msg[5] != ' ')
-				{
-					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<player num> \'.", false);
-					return;
-				}
-			}
-
-			target = atoi(playernum); // turn that into a number
-			//CONS_Printf("%d\n", target);
-
-			// check for target player, if it doesn't exist then we can't send the message!
-			if (target < MAXPLAYERS && playeringame[target]) // player exists
-				target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
+			// check if playernum[1] is a space
+			if (playernum[1] == ' ')
+				spc = 0;
+				// let it slide
 			else
 			{
-				HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
+				HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<player num> \'.", false);
 				return;
 			}
-
-			// we need to get rid of the /pm<player num>
-			newmsg = msg+5+spc;
-			strlcpy(msg, newmsg, 255);
 		}
-		if (ci > 3) // don't send target+flags+empty message.
+		// I'm very bad at C, I swear I am, additional checks eww!
+		if (spc != 0 && msg[5] != ' ')
 		{
-			if (teamtalk)
-				buf[0] = -1; // target
-			else
-				buf[0] = target;
+			HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<player num> \'.", false);
+			return;
+		}
 
-			buf[1] = 0; // flags
-			SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1);
+		target = atoi(playernum); // turn that into a number
+
+		// check for target player, if it doesn't exist then we can't send the message!
+		if (target < MAXPLAYERS && playeringame[target]) // player exists
+			target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
+		else
+		{
+			HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
+			return;
 		}
-		return;
+
+		// we need to get rid of the /pm<player num>
+		newmsg = msg+5+spc;
+		strlcpy(msg, newmsg, HU_MAXMSGLEN + 1);
+	}
+	if (ci > 2) // don't send target+flags+empty message.
+	{
+		buf[0] = teamtalk ? -1 : target; // target
+		buf[1] = 0; // flags
+		SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1);
 	}
 }
+
 #endif
 
 void HU_clearChatChars(void)
 {
-	size_t i = 0;
-	for (;i<HU_MAXMSGLEN;i++)
-		w_chat[i] = 0; // reset this.
+	memset(w_chat, '\0', sizeof(w_chat));
 	chat_on = false;
 	c_input = 0;
 
 	I_UpdateMouseGrab();
 }
 
-#ifndef NONET
-static boolean justscrolleddown;
-static boolean justscrolledup;
-static INT16 typelines = 1; // number of drawfill lines we need when drawing the chat. it's some weird hack and might be one frame off but I'm lazy to make another loop.
-// It's up here since it has to be reset when we open the chat.
-#endif
-
 //
 // Returns true if key eaten
 //
@@ -1171,21 +1077,23 @@ boolean HU_Responder(event_t *ev)
 			if (shiftdown ^ capslock)
 				c = shiftxform[c];
 		}
-		else	// if we're holding shift we should still shift non letter symbols
+		else // if we're holding shift we should still shift non letter symbols
 		{
 			if (shiftdown)
 				c = shiftxform[c];
 		}
 
 		// pasting. pasting is cool. chat is a bit limited, though :(
-		if (((c == 'v' || c == 'V') && ctrldown) && !CHAT_MUTE)
+		if ((c == 'v' || c == 'V') && ctrldown)
 		{
-			const char *paste = I_ClipboardPaste();
+			const char *paste;
 			size_t chatlen;
 			size_t pastelen;
 
-			// create a dummy string real quickly
+			if (CHAT_MUTE)
+				return true;
 
+			paste = I_ClipboardPaste();
 			if (paste == NULL)
 				return true;
 
@@ -1194,40 +1102,16 @@ boolean HU_Responder(event_t *ev)
 			if (chatlen+pastelen > HU_MAXMSGLEN)
 				return true; // we can't paste this!!
 
-			if (c_input >= strlen(w_chat)) // add it at the end of the string.
-			{
-				memcpy(&w_chat[chatlen], paste, pastelen); // copy all of that.
-				c_input += pastelen;
-				/*size_t i = 0;
-				for (;i<pastelen;i++)
-				{
-					HU_queueChatChar(paste[i]); // queue it so that it's actually sent. (this chat write thing is REALLY messy.)
-				}*/
-				return true;
-			}
-			else	// otherwise, we need to shift everything and make space, etc etc
-			{
-				size_t i = HU_MAXMSGLEN-1;
-				while (i >= c_input)
-				{
-					if (w_chat[i])
-						w_chat[i+pastelen] = w_chat[i];
-					if (i == 0) // prevent overflow
-						break;
-					i--;
-				}
-				memcpy(&w_chat[c_input], paste, pastelen); // copy all of that.
-				c_input += pastelen;
-				return true;
-			}
-		}
-
-		if (!CHAT_MUTE && HU_keyInChatString(w_chat,c))
-		{
-			HU_queueChatChar(c);
+			memmove(&w_chat[c_input + pastelen], &w_chat[c_input], pastelen);
+			memcpy(&w_chat[c_input], paste, pastelen); // copy all of that.
+			c_input += pastelen;
+			return true;
 		}
-		if (c == KEY_ENTER)
+		else if (c == KEY_ENTER)
 		{
+			if (!CHAT_MUTE)
+				HU_sendChatMessage();
+
 			chat_on = false;
 			c_input = 0; // reset input cursor
 			chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :)
@@ -1268,6 +1152,32 @@ boolean HU_Responder(event_t *ev)
 			else
 				c_input++;
 		}
+		else if ((c >= HU_FONTSTART && c <= HU_FONTEND && hu_font[c-HU_FONTSTART])
+			|| c == ' ') // Allow spaces, of course
+		{
+			if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN)
+				return true;
+
+			memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1);
+			w_chat[c_input] = c;
+			c_input++;
+		}
+		else if (c == KEY_BACKSPACE)
+		{
+			if (CHAT_MUTE || c_input <= 0)
+				return true;
+
+			memmove(&w_chat[c_input - 1], &w_chat[c_input], strlen(w_chat) - c_input + 1);
+			c_input--;
+		}
+		else if (c == KEY_DEL)
+		{
+			if (CHAT_MUTE || c_input >= strlen(w_chat))
+				return true;
+
+			memmove(&w_chat[c_input], &w_chat[c_input + 1], strlen(w_chat) - c_input);
+		}
+
 		return true;
 	}
 #endif
@@ -1863,64 +1773,25 @@ static void HU_DrawChat_Old(void)
 }
 #endif
 
-// draw the Crosshair, at the exact center of the view.
-//
+// Draw crosshairs at the exact center of the view.
+// In splitscreen, crosshairs are stretched vertically to compensate for V_PERPLAYER squishing them.
 // Crosshairs are pre-cached at HU_Init
 
-static inline void HU_DrawCrosshair(void)
+static inline void HU_DrawCrosshairs(void)
 {
-	INT32 i, y, dupz;
+	INT32 cross1 = cv_crosshair.value & 3;
+	INT32 cross2 = cv_crosshair2.value & 3;
 
-	i = cv_crosshair.value & 3;
-	if (!i)
+	if (automapactive || demoplayback)
 		return;
 
-	if ((netgame || multiplayer) && players[displayplayer].spectator)
-		return;
-
-#ifdef HWRENDER
-	if (rendermode != render_soft)
-		y = (INT32)gl_basewindowcentery;
-	else
-#endif
-		y = viewwindowy + (viewheight>>1);
+	stplyr = ((stplyr == &players[displayplayer]) ? &players[secondarydisplayplayer] : &players[displayplayer]);
+	if (!players[displayplayer].spectator && (!camera.chase || ticcmd_ztargetfocus[0]) && cross1)
+		V_DrawStretchyFixedPatch((BASEVIDWIDTH/2)<<FRACBITS, (BASEVIDHEIGHT/2)<<FRACBITS, FRACUNIT, splitscreen ? 2*FRACUNIT : FRACUNIT, V_TRANSLUCENT|V_PERPLAYER, crosshair[cross1 - 1], NULL);
 
-	dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-
-	V_DrawFixedPatch(vid.width<<(FRACBITS-1), y<<FRACBITS, FRACUNIT/dupz, V_TRANSLUCENT, crosshair[i - 1], NULL);
-}
-
-static inline void HU_DrawCrosshair2(void)
-{
-	INT32 i, y, dupz;
-
-	i = cv_crosshair2.value & 3;
-	if (!i)
-		return;
-
-	if ((netgame || multiplayer) && players[secondarydisplayplayer].spectator)
-		return;
-
-#ifdef HWRENDER
-	if (rendermode != render_soft)
-		y = (INT32)gl_basewindowcentery;
-	else
-#endif
-		y = viewwindowy + (viewheight>>1);
-
-	if (!splitscreen)
-		return;
-
-	#ifdef HWRENDER
-		if (rendermode != render_soft)
-			y += (INT32)gl_viewheight;
-		else
-	#endif
-			y += viewheight;
-
-	dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-
-	V_DrawFixedPatch(vid.width<<(FRACBITS-1), y<<FRACBITS, FRACUNIT/dupz, V_TRANSLUCENT, crosshair[i - 1], NULL);
+	stplyr = ((stplyr == &players[displayplayer]) ? &players[secondarydisplayplayer] : &players[displayplayer]);
+	if (!players[secondarydisplayplayer].spectator && (!camera2.chase || ticcmd_ztargetfocus[1]) && cross2 && splitscreen)
+		V_DrawStretchyFixedPatch((BASEVIDWIDTH/2)<<FRACBITS, (BASEVIDHEIGHT/2)<<FRACBITS, FRACUNIT, 2*FRACUNIT, V_TRANSLUCENT|V_PERPLAYER, crosshair[cross2 - 1], NULL);
 }
 
 static void HU_DrawCEcho(void)
@@ -2114,19 +1985,9 @@ void HU_Drawer(void)
 	if (gamestate != GS_LEVEL)
 		return;
 
-	// draw the crosshair, not when viewing demos nor with chasecam
+	// draw the crosshair
 	if (LUA_HudEnabled(hud_crosshair))
-	{
-		if (!automapactive && cv_crosshair.value && !demoplayback &&
-			(!camera.chase || ticcmd_ztargetfocus[0])
-		&& !players[displayplayer].spectator)
-			HU_DrawCrosshair();
-
-		if (!automapactive && cv_crosshair2.value && !demoplayback &&
-			(!camera2.chase || ticcmd_ztargetfocus[1])
-		&& !players[secondarydisplayplayer].spectator)
-			HU_DrawCrosshair2();
-	}
+		HU_DrawCrosshairs();
 
 	// draw desynch text
 	if (hu_redownloadinggamestate)
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index 9b7cee2d3053cb63138a08d32dcfb75565ee537e..11048637853e8a84cc19a9c547baa19556835418 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -62,7 +62,7 @@ typedef struct
 //------------------------------------
 //           chat stuff
 //------------------------------------
-#define HU_MAXMSGLEN 224
+#define HU_MAXMSGLEN 223
 #define CHAT_BUFSIZE 64		// that's enough messages, right? We'll delete the older ones when that gets out of hand.
 #ifdef NETSPLITSCREEN
 #define OLDCHAT (cv_consolechat.value == 1 || dedicated || vid.width < 640)
diff --git a/src/i_addrinfo.c b/src/i_addrinfo.c
index 5dcea100299805644612e5773392062ebca3c3cd..49aadf27d203e20b58a76b757c1097f69bbb9e2c 100644
--- a/src/i_addrinfo.c
+++ b/src/i_addrinfo.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2011-2021 by Sonic Team Junior.
+// Copyright (C) 2011-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_addrinfo.h b/src/i_addrinfo.h
index 397a1969d94c866a10e4030abb74df2d73e4e5b4..592e693f4c9d57b2aeba9b5a86f84f0167590a6b 100644
--- a/src/i_addrinfo.h
+++ b/src/i_addrinfo.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2011-2021 by Sonic Team Junior.
+// Copyright (C) 2011-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_joy.h b/src/i_joy.h
index 0c7c8dd3f45003909c956c536653bcdfb247f333..27584cea6ed818a8a01348e7a4d73e3510a64c91 100644
--- a/src/i_joy.h
+++ b/src/i_joy.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_net.h b/src/i_net.h
index dbc82db65cd94480277e03e23222741741524d48..62b7528d59f0bcf6877f55ab467687fc7a55dad5 100644
--- a/src/i_net.h
+++ b/src/i_net.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_sound.h b/src/i_sound.h
index e38a17626b95dac976295a27c53e5583ce4043fb..6358fbefb9edb99a956d808428486a04b08334df 100644
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_system.h b/src/i_system.h
index a2dd81cca3ef815ec6121d09b64308f54c6adc3e..27fcdeb3f21d247e1001d12d7e8b1bee028e62a8 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_tcp.c b/src/i_tcp.c
index cae97a7d1039349aa570272fe5443d057e4be949..8838ba7257efb99adef6379935f36ca57cfd3876 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_tcp.h b/src/i_tcp.h
index 7857344156448712b934b0989b242e2e6fc554da..b6e5b92351d211f31c36c188598d99643443ee94 100644
--- a/src/i_tcp.h
+++ b/src/i_tcp.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_threads.h b/src/i_threads.h
index bc752181f521c447d736368cb0c17b09ae998572..c7b71d26cfe09c0a018713e08d3f3a4163a2405c 100644
--- a/src/i_threads.h
+++ b/src/i_threads.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by James R.
+// Copyright (C) 2020-2022 by James R.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/i_video.h b/src/i_video.h
index 2d07fcf10700973e4f3aad0b15e9566101ef9ec3..638fcb6689f026722ffbb4e777111260ec54e97c 100644
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/info.c b/src/info.c
index 331e114b6381cf2161fb83d121ec2b77c9b7853b..94293488861c94d85d03e0f45ceac37371ba06ac 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -203,6 +203,10 @@ char sprnames[NUMSPRITES + 1][5] =
 	// The letter
 	"LETR",
 
+	// Tutorial Scenery
+	"TUPL",
+	"TUPF",
+
 	// Greenflower Scenery
 	"FWR1",
 	"FWR2", // GFZ Sunflower
@@ -2117,6 +2121,56 @@ state_t states[NUMSTATES] =
 
 	{SPR_LETR, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_LETTER
 
+	// Tutorial scenery
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|0, 3, {NULL}, 0, 0, S_TUTORIALLEAF2}, // S_TUTORIALLEAF1
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|1, 3, {NULL}, 0, 0, S_TUTORIALLEAF3}, // S_TUTORIALLEAF2
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|2, 3, {NULL}, 0, 0, S_TUTORIALLEAF4}, // S_TUTORIALLEAF3
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|3, 3, {NULL}, 0, 0, S_TUTORIALLEAF5}, // S_TUTORIALLEAF4
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|4, 3, {NULL}, 0, 0, S_TUTORIALLEAF6}, // S_TUTORIALLEAF5
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|5, 3, {NULL}, 0, 0, S_TUTORIALLEAF7}, // S_TUTORIALLEAF6
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|6, 3, {NULL}, 0, 0, S_TUTORIALLEAF8}, // S_TUTORIALLEAF7
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|7, 3, {NULL}, 0, 0, S_TUTORIALLEAF9}, // S_TUTORIALLEAF8
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|7, 3, {NULL}, 0, 0, S_TUTORIALLEAF10}, // S_TUTORIALLEAF9
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|6, 3, {NULL}, 0, 0, S_TUTORIALLEAF11}, // S_TUTORIALLEAF10
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|5, 3, {NULL}, 0, 0, S_TUTORIALLEAF12}, // S_TUTORIALLEAF11
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|4, 3, {NULL}, 0, 0, S_TUTORIALLEAF13}, // S_TUTORIALLEAF12
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|3, 3, {NULL}, 0, 0, S_TUTORIALLEAF14}, // S_TUTORIALLEAF13
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|2, 3, {NULL}, 0, 0, S_TUTORIALLEAF15}, // S_TUTORIALLEAF14
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|1, 3, {NULL}, 0, 0, S_TUTORIALLEAF16}, // S_TUTORIALLEAF15
+	{SPR_TUPL, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|0, 3, {NULL}, 0, 0, S_TUTORIALLEAF1}, // S_TUTORIALLEAF16
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|0, 3, {NULL}, 0, 0, S_TUTORIALFLOWER2}, // S_TUTORIALFLOWER1
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|1, 3, {NULL}, 0, 0, S_TUTORIALFLOWER3}, // S_TUTORIALFLOWER2
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|2, 3, {NULL}, 0, 0, S_TUTORIALFLOWER4}, // S_TUTORIALFLOWER3
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|3, 3, {NULL}, 0, 0, S_TUTORIALFLOWER5}, // S_TUTORIALFLOWER4
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|4, 3, {NULL}, 0, 0, S_TUTORIALFLOWER6}, // S_TUTORIALFLOWER5
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|5, 3, {NULL}, 0, 0, S_TUTORIALFLOWER7}, // S_TUTORIALFLOWER6
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|6, 3, {NULL}, 0, 0, S_TUTORIALFLOWER8}, // S_TUTORIALFLOWER7
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|7, 3, {NULL}, 0, 0, S_TUTORIALFLOWER9}, // S_TUTORIALFLOWER8
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|7, 3, {NULL}, 0, 0, S_TUTORIALFLOWER10}, // S_TUTORIALFLOWER9
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|6, 3, {NULL}, 0, 0, S_TUTORIALFLOWER11}, // S_TUTORIALFLOWER10
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|5, 3, {NULL}, 0, 0, S_TUTORIALFLOWER12}, // S_TUTORIALFLOWER11
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|4, 3, {NULL}, 0, 0, S_TUTORIALFLOWER13}, // S_TUTORIALFLOWER12
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|3, 3, {NULL}, 0, 0, S_TUTORIALFLOWER14}, // S_TUTORIALFLOWER13
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|2, 3, {NULL}, 0, 0, S_TUTORIALFLOWER15}, // S_TUTORIALFLOWER14
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|1, 3, {NULL}, 0, 0, S_TUTORIALFLOWER16}, // S_TUTORIALFLOWER15
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_PAPERSPRITE|0, 3, {NULL}, 0, 0, S_TUTORIALFLOWER1}, // S_TUTORIALFLOWER16
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|0, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF2}, // S_TUTORIALFLOWERF1
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|1, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF3}, // S_TUTORIALFLOWERF2
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|2, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF4}, // S_TUTORIALFLOWERF3
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|3, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF5}, // S_TUTORIALFLOWERF4
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|4, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF6}, // S_TUTORIALFLOWERF5
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|5, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF7}, // S_TUTORIALFLOWERF6
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|6, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF8}, // S_TUTORIALFLOWERF7
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|7, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF9}, // S_TUTORIALFLOWERF8
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|7, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF10}, // S_TUTORIALFLOWERF9
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|6, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF11}, // S_TUTORIALFLOWERF10
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|5, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF12}, // S_TUTORIALFLOWERF11
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|4, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF13}, // S_TUTORIALFLOWERF12
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|3, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF14}, // S_TUTORIALFLOWERF13
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|2, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF15}, // S_TUTORIALFLOWERF14
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|1, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF16}, // S_TUTORIALFLOWERF15
+	{SPR_TUPF, FF_SEMIBRIGHT|FF_ADD|FF_FLOORSPRITE|0, 3, {NULL}, 0, 0, S_TUTORIALFLOWERF1}, // S_TUTORIALFLOWERF16
+
 	// GFZ flowers
 	{SPR_FWR1, FF_ANIMATE, -1, {NULL},  7, 3, S_NULL}, // S_GFZFLOWERA
 	{SPR_FWR2, FF_ANIMATE, -1, {NULL}, 19, 3, S_NULL}, // S_GFZFLOWERB
@@ -2168,7 +2222,7 @@ state_t states[NUMSTATES] =
 	{SPR_GARG, 1, -1, {NULL}, 0, 0, S_NULL},  // S_BIGGARGOYLE
 
 	// DSZ Seaweed
-	{SPR_SEWE, 0, -1, {NULL}, 0, 0, S_SEAWEED2}, // S_SEAWEED1
+	{SPR_SEWE, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 26, 3, S_SEAWEED1}, // S_SEAWEED1
 	{SPR_SEWE, 1, 5, {NULL}, 0, 0, S_SEAWEED3}, // S_SEAWEED2
 	{SPR_SEWE, 2, 5, {NULL}, 0, 0, S_SEAWEED4}, // S_SEAWEED3
 	{SPR_SEWE, 3, 5, {NULL}, 0, 0, S_SEAWEED5}, // S_SEAWEED4
@@ -2938,10 +2992,10 @@ state_t states[NUMSTATES] =
 	{SPR_NULL,                           0, 15*2, {NULL}, 0, 0, S_ZAPSB2 }, // S_ZAPSB11
 
 	// Thunder spark
-	{SPR_SSPK, FF_ANIMATE, -1, {NULL}, 1, 2, S_NULL},   // S_THUNDERCOIN_SPARK
+	{SPR_SSPK, FF_ANIMATE|FF_FULLBRIGHT, -1, {NULL}, 1, 2, S_NULL},   // S_THUNDERCOIN_SPARK
 
 	// Invincibility Sparkles
-	{SPR_IVSP, FF_ANIMATE, 32, {NULL}, 31, 1, S_NULL},   // S_IVSP
+	{SPR_IVSP, FF_ANIMATE|FF_FULLBRIGHT, 32, {NULL}, 31, 1, S_NULL},   // S_IVSP
 
 	// Super Sonic Spark
 	{SPR_SSPK,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_SSPK2}, // S_SSPK1
@@ -3736,14 +3790,13 @@ state_t states[NUMSTATES] =
 	{SPR_FL01, 3, 1, {A_OrbitNights}, ANG2*2, 180 | 0x10000, S_NIGHTOPIANHELPER1}, // S_NIGHTOPIANHELPER9
 
 	// Nightopian
-	{SPR_NTPN, 0, 4, {A_Look}, 0, 0, S_PIAN0}, // S_PIAN0
-	{SPR_NTPN, 0, 4, {A_JetgThink}, 0, 0, S_PIAN2}, // S_PIAN1
-	{SPR_NTPN, 1, 4, {NULL}, 0, 0, S_PIAN3}, // S_PIAN2
-	{SPR_NTPN, 2, 4, {NULL}, 0, 0, S_PIAN4}, // S_PIAN3
-	{SPR_NTPN, 3, 4, {NULL}, 0, 0, S_PIAN5}, // S_PIAN4
-	{SPR_NTPN, 2, 4, {NULL}, 0, 0, S_PIAN6}, // S_PIAN5
-	{SPR_NTPN, 1, 4, {NULL}, 0, 0, S_PIAN1}, // S_PIAN6
-	{SPR_NTPN, 5|FF_ANIMATE, 4, {NULL}, 1, 4, S_PIAN1}, // S_PIANSING
+	{SPR_NTPN, 0, 2, {A_Look}, 1, 1, S_PIAN_LOOK2}, // S_PIAN_LOOK1
+	{SPR_NTPN, 1, 2, {A_Look}, 1, 1, S_PIAN_LOOK3}, // S_PIAN_LOOK2
+	{SPR_NTPN, 2, 2, {A_Look}, 1, 1, S_PIAN_LOOK1}, // S_PIAN_LOOK3
+	{SPR_NTPN, 0, 2, {A_JetgThink}, 0, 0, S_PIAN_FLY2}, // S_PIAN_FLY1
+	{SPR_NTPN, 1, 2, {NULL}, 0, 0, S_PIAN_FLY3}, // S_PIAN_FLY2
+	{SPR_NTPN, 2, 2, {NULL}, 0, 0, S_PIAN_FLY1}, // S_PIAN_FLY3
+	{SPR_NTPN, 3|FF_ANIMATE, 24, {NULL}, 2, 2, S_PIAN_FLY1}, // S_PIAN_SING
 
 	// Shleep
 	{SPR_SHLP, 0, 15, {NULL}, 0, 0, S_SHLEEP2}, // S_SHLEEP1
@@ -3883,23 +3936,23 @@ state_t states[NUMSTATES] =
 	{SPR_SPRK, FF_TRANS20|FF_ANIMATE|9, 18, {NULL}, 8, 2, S_NULL},  // S_SPRK3
 
 	// Robot Explosion
-	{SPR_BOM1, 0, 0, {A_FlickySpawn}, 0, 0, S_XPLD1}, // S_XPLD_FLICKY
-	{SPR_BOM1, 0, 2, {A_Scream},      0, 0, S_XPLD2}, // S_XPLD1
-	{SPR_BOM1, 1, 2, {NULL},          0, 0, S_XPLD3}, // S_XPLD2
-	{SPR_BOM1, 2, 3, {NULL},          0, 0, S_XPLD4}, // S_XPLD3
-	{SPR_BOM1, 3, 3, {NULL},          0, 0, S_XPLD5}, // S_XPLD4
-	{SPR_BOM1, 4, 4, {NULL},          0, 0, S_XPLD6}, // S_XPLD5
-	{SPR_BOM1, 5, 4, {NULL},          0, 0, S_NULL},  // S_XPLD6
+	{SPR_BOM1, 0, 0, {A_FlickySpawn},  0, 0, S_XPLD1}, // S_XPLD_FLICKY
+	{SPR_BOM1, 0, 2, {A_ShadowScream}, 0, 0, S_XPLD2}, // S_XPLD1
+	{SPR_BOM1, 1, 2, {NULL},           0, 0, S_XPLD3}, // S_XPLD2
+	{SPR_BOM1, 2, 3, {NULL},           0, 0, S_XPLD4}, // S_XPLD3
+	{SPR_BOM1, 3, 3, {NULL},           0, 0, S_XPLD5}, // S_XPLD4
+	{SPR_BOM1, 4, 4, {NULL},           0, 0, S_XPLD6}, // S_XPLD5
+	{SPR_BOM1, 5, 4, {NULL},           0, 0, S_NULL},  // S_XPLD6
 
 	{SPR_BOM1, FF_ANIMATE,   21, {NULL},          5, 4, S_INVISIBLE}, // S_XPLD_EGGTRAP
 
 	// Underwater Explosion
-	{SPR_BOM4, 0, 3, {A_Scream}, 0, 0, S_WPLD2}, // S_WPLD1
-	{SPR_BOM4, 1, 3, {NULL},     0, 0, S_WPLD3}, // S_WPLD2
-	{SPR_BOM4, 2, 3, {NULL},     0, 0, S_WPLD4}, // S_WPLD3
-	{SPR_BOM4, 3, 3, {NULL},     0, 0, S_WPLD5}, // S_WPLD4
-	{SPR_BOM4, 4, 3, {NULL},     0, 0, S_WPLD6}, // S_WPLD5
-	{SPR_BOM4, 5, 3, {NULL},     0, 0, S_NULL},  // S_WPLD6
+	{SPR_BOM4, 0, 3, {A_ShadowScream}, 0, 0, S_WPLD2}, // S_WPLD1
+	{SPR_BOM4, 1, 3, {NULL},           0, 0, S_WPLD3}, // S_WPLD2
+	{SPR_BOM4, 2, 3, {NULL},           0, 0, S_WPLD4}, // S_WPLD3
+	{SPR_BOM4, 3, 3, {NULL},           0, 0, S_WPLD5}, // S_WPLD4
+	{SPR_BOM4, 4, 3, {NULL},           0, 0, S_WPLD6}, // S_WPLD5
+	{SPR_BOM4, 5, 3, {NULL},           0, 0, S_NULL},  // S_WPLD6
 
 	{SPR_DUST,   FF_TRANS40, 4, {NULL}, 0, 0, S_DUST2}, // S_DUST1
 	{SPR_DUST, 1|FF_TRANS50, 5, {NULL}, 0, 0, S_DUST3}, // S_DUST2
@@ -5601,8 +5654,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_EGGMOBILE_FLEE1, // xdeathstate
 		sfx_s3kb4,         // deathsound
 		4,                 // speed
-		24*FRACUNIT,       // radius
-		76*FRACUNIT,       // height
+		36*FRACUNIT,       // radius
+		84*FRACUNIT,       // height
 		0,                 // display offset
 		sfx_None,          // mass
 		3,                 // damage
@@ -5736,8 +5789,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_EGGMOBILE2_FLEE1,// xdeathstate
 		sfx_s3kb4,         // deathsound
 		2*FRACUNIT,        // speed
-		24*FRACUNIT,       // radius
-		76*FRACUNIT,       // height
+		36*FRACUNIT,       // radius
+		84*FRACUNIT,       // height
 		0,                 // display offset
 		0,                 // mass
 		3,                 // damage
@@ -5844,7 +5897,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_EGGMOBILE3_FLEE1, // xdeathstate
 		sfx_s3kb4,          // deathsound
 		8*FRACUNIT,         // speed
-		32*FRACUNIT,        // radius
+		36*FRACUNIT,        // radius
 		116*FRACUNIT,       // height
 		0,                  // display offset
 		MT_FAKEMOBILE,      // mass
@@ -5871,7 +5924,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,             // xdeathstate
 		sfx_mswarp,         // deathsound
 		8*FRACUNIT,         // speed
-		32*FRACUNIT,        // radius
+		36*FRACUNIT,        // radius
 		116*FRACUNIT,       // height
 		0,                  // display offset
 		0,                  // mass
@@ -5925,8 +5978,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_EGGMOBILE4_FLEE1,// xdeathstate
 		sfx_s3kb4,         // deathsound
 		0,                 // speed
-		24*FRACUNIT,       // radius
-		76*FRACUNIT,       // height
+		36*FRACUNIT,       // radius
+		84*FRACUNIT,       // height
 		0,                 // display offset
 		0,                 // mass
 		3,                 // damage
@@ -7977,7 +8030,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SCENERY,  // flags
+		MF_NOBLOCKMAP|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -8004,7 +8057,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_NOGRAVITY|MF_SCENERY|MF_PAPERCOLLISION,  // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SCENERY|MF_NOCLIPHEIGHT|MF_PAPERCOLLISION,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -8031,7 +8084,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SCENERY|MF_NOCLIP|MF_NOCLIPTHING,  // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPTHING,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -9776,8 +9829,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_MINE_BOOM1,   // deathstate
-		S_MINE_BOOM1,   // xdeathstate
+		S_XPLD1,        // deathstate
+		S_XPLD1,        // xdeathstate
 		sfx_cybdth,     // deathsound
 		0,              // speed
 		8*FRACUNIT,     // radius
@@ -9979,6 +10032,114 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_TUTORIALPLANT
+		799,            // doomednum
+		S_NULL,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		32*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_TUTORIALLEAF
+		-1,            // doomednum
+		S_TUTORIALLEAF1,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		32*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_TUTORIALFLOWER
+		-1,            // doomednum
+		S_TUTORIALFLOWER1,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		32*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_TUTORIALFLOWERF
+		-1,            // doomednum
+		S_TUTORIALFLOWERF1,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		32*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_GFZFLOWER1
 		800,            // doomednum
 		S_GFZFLOWERA,   // spawnstate
@@ -20119,28 +20280,28 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_PIAN
 		1602,           // doomednum
-		S_PIAN0,        // spawnstate
+		S_PIAN_LOOK1,   // spawnstate
 		1000,           // spawnhealth
-		S_PIAN1,        // seestate
+		S_PIAN_FLY1,    // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		200,            // painchance
 		sfx_None,       // painsound
-		S_PIANSING,     // meleestate
-		S_NULL,         // missilestate
+		S_NULL,         // meleestate
+		S_PIAN_SING,    // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		FRACUNIT,       // speed
 		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		40*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SLIDEME|MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY, // flags
+		MF_SLIDEME|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -21685,6 +21846,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY|MF_NOSECTOR, // flags
 		S_NULL          // raisestate
 	},
+
+	{           // MT_RAY
+		-1,             // doomednum
+		S_NULL,         // spawnstate
+		0,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		0,              // radius
+		0,              // height
+		0,              // display offset
+		0,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
 };
 
 skincolor_t skincolors[MAXSKINCOLORS] = {
diff --git a/src/info.h b/src/info.h
index 031a08b4316a00d135cf45a45827ff117181373e..1b7a201cea519d8eef3d041839917153f93ff9cc 100644
--- a/src/info.h
+++ b/src/info.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -44,6 +44,8 @@ enum actionnum
 	A_FACETRACER,
 	A_SCREAM,
 	A_BOSSDEATH,
+	A_SETSHADOWSCALE,
+	A_SHADOWSCREAM,
 	A_CUSTOMPOWER,
 	A_GIVEWEAPON,
 	A_RINGBOX,
@@ -177,6 +179,8 @@ enum actionnum
 	A_SETOBJECTFLAGS2,
 	A_RANDOMSTATE,
 	A_RANDOMSTATERANGE,
+	A_STATERANGEBYANGLE,
+	A_STATERANGEBYPARAMETER,
 	A_DUALACTION,
 	A_REMOTEACTION,
 	A_TOGGLEFLAMEJET,
@@ -310,6 +314,8 @@ void A_FaceTarget();
 void A_FaceTracer();
 void A_Scream();
 void A_BossDeath();
+void A_SetShadowScale();
+void A_ShadowScream(); // MARIA!!!!!!
 void A_CustomPower(); // Use this for a custom power
 void A_GiveWeapon(); // Gives the player weapon(s)
 void A_RingBox(); // Obtained Ring Box Tails
@@ -443,6 +449,8 @@ void A_SetObjectFlags();
 void A_SetObjectFlags2();
 void A_RandomState();
 void A_RandomStateRange();
+void A_StateRangeByAngle();
+void A_StateRangeByParameter();
 void A_DualAction();
 void A_RemoteAction();
 void A_ToggleFlameJet();
@@ -737,6 +745,10 @@ typedef enum sprite
 	// The letter
 	SPR_LETR,
 
+	// Tutorial scenery
+	SPR_TUPL,
+	SPR_TUPF,
+
 	// Greenflower Scenery
 	SPR_FWR1,
 	SPR_FWR2, // GFZ Sunflower
@@ -2551,6 +2563,56 @@ typedef enum state
 	// The letter
 	S_LETTER,
 
+	// Tutorial scenery
+	S_TUTORIALLEAF1,
+	S_TUTORIALLEAF2,
+	S_TUTORIALLEAF3,
+	S_TUTORIALLEAF4,
+	S_TUTORIALLEAF5,
+	S_TUTORIALLEAF6,
+	S_TUTORIALLEAF7,
+	S_TUTORIALLEAF8,
+	S_TUTORIALLEAF9,
+	S_TUTORIALLEAF10,
+	S_TUTORIALLEAF11,
+	S_TUTORIALLEAF12,
+	S_TUTORIALLEAF13,
+	S_TUTORIALLEAF14,
+	S_TUTORIALLEAF15,
+	S_TUTORIALLEAF16,
+	S_TUTORIALFLOWER1,
+	S_TUTORIALFLOWER2,
+	S_TUTORIALFLOWER3,
+	S_TUTORIALFLOWER4,
+	S_TUTORIALFLOWER5,
+	S_TUTORIALFLOWER6,
+	S_TUTORIALFLOWER7,
+	S_TUTORIALFLOWER8,
+	S_TUTORIALFLOWER9,
+	S_TUTORIALFLOWER10,
+	S_TUTORIALFLOWER11,
+	S_TUTORIALFLOWER12,
+	S_TUTORIALFLOWER13,
+	S_TUTORIALFLOWER14,
+	S_TUTORIALFLOWER15,
+	S_TUTORIALFLOWER16,
+	S_TUTORIALFLOWERF1,
+	S_TUTORIALFLOWERF2,
+	S_TUTORIALFLOWERF3,
+	S_TUTORIALFLOWERF4,
+	S_TUTORIALFLOWERF5,
+	S_TUTORIALFLOWERF6,
+	S_TUTORIALFLOWERF7,
+	S_TUTORIALFLOWERF8,
+	S_TUTORIALFLOWERF9,
+	S_TUTORIALFLOWERF10,
+	S_TUTORIALFLOWERF11,
+	S_TUTORIALFLOWERF12,
+	S_TUTORIALFLOWERF13,
+	S_TUTORIALFLOWERF14,
+	S_TUTORIALFLOWERF15,
+	S_TUTORIALFLOWERF16,
+
 	// GFZ flowers
 	S_GFZFLOWERA,
 	S_GFZFLOWERB,
@@ -4093,14 +4155,13 @@ typedef enum state
 	S_NIGHTOPIANHELPER9,
 
 	// Nightopian
-	S_PIAN0,
-	S_PIAN1,
-	S_PIAN2,
-	S_PIAN3,
-	S_PIAN4,
-	S_PIAN5,
-	S_PIAN6,
-	S_PIANSING,
+	S_PIAN_LOOK1,
+	S_PIAN_LOOK2,
+	S_PIAN_LOOK3,
+	S_PIAN_FLY1,
+	S_PIAN_FLY2,
+	S_PIAN_FLY3,
+	S_PIAN_SING,
 
 	// Shleep
 	S_SHLEEP1,
@@ -4583,6 +4644,12 @@ typedef enum mobj_type
 	// The letter
 	MT_LETTER,
 
+	// Tutorial Scenery
+	MT_TUTORIALPLANT,
+	MT_TUTORIALLEAF,
+	MT_TUTORIALFLOWER,
+	MT_TUTORIALFLOWERF,
+
 	// Greenflower Scenery
 	MT_GFZFLOWER1,
 	MT_GFZFLOWER2,
@@ -5089,6 +5156,7 @@ typedef enum mobj_type
 	MT_YELLOWBRICKDEBRIS, // for CEZ3
 
 	MT_NAMECHECK,
+	MT_RAY, // General purpose mobj
 
 	MT_FIRSTFREESLOT,
 	MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
diff --git a/src/keys.h b/src/keys.h
index b19259320e59574effaa3b48ba7a713ac7af0c86..df12c95aefa58ee65abec831e8eec1bdd5de6b85 100644
--- a/src/keys.h
+++ b/src/keys.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 12ad4fee0549bbaad9d5ea2dd3106c9bd9b8f955..120ab671e92e1fafbfba8eaf520ee5133fdad691 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -3455,7 +3455,7 @@ static int lib_gAddPlayer(lua_State *L)
 		lua_pushnil(L);
 		return 1;
 	}
-	
+
 
 	newplayernum = i;
 
@@ -3486,15 +3486,15 @@ static int lib_gAddPlayer(lua_State *L)
 
 	// Read the bot name, if given
 	if (!lua_isnoneornil(L, 3))
-		strcpy(player_names[newplayernum], luaL_checkstring(L, 3));
-	
+		strlcpy(player_names[newplayernum], luaL_checkstring(L, 3), sizeof(*player_names));
+
 	bot = luaL_optinteger(L, 4, 3);
 	newplayer->bot = (bot >= BOT_NONE && bot <= BOT_MPAI) ? bot : BOT_MPAI;
-	
+
 	// If our bot is a 2P type, we'll need to set its leader so it can spawn
 	if (newplayer->bot == BOT_2PAI || newplayer->bot == BOT_2PHUMAN)
 		B_UpdateBotleader(newplayer);
-	
+
 	// Set the skin (can't do this until AFTER bot type is set!)
 	SetPlayerSkinByNum(newplayernum, skinnum);
 
@@ -3507,7 +3507,7 @@ static int lib_gAddPlayer(lua_State *L)
 		strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum));
 		HU_AddChatText(joinmsg, false);
 	}
-	
+
 	LUA_PushUserdata(L, newplayer, META_PLAYER);
 	return 1;
 }
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
index 9089d19b6a02c5126aeefde5e74aa1ec3ff8511e..8c63a9d6d2adaf3a15f0fda6cff79913aad0dc17 100644
--- a/src/lua_blockmaplib.c
+++ b/src/lua_blockmaplib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2016-2021 by Iestyn "Monster Iestyn" Jealous.
-// Copyright (C) 2016-2021 by Sonic Team Junior.
+// Copyright (C) 2016-2022 by Iestyn "Monster Iestyn" Jealous.
+// Copyright (C) 2016-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 2b8cad69b8b5fb920294bdc4e1c0475fa6a59da0..c8e914e6de3e9299725ad26a26064c2c7d7fc04b 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 531d16288ab507e19dc6f47e92e0157772306aa8..fc6a5f4ee4e4c4aa92683126c8b939c5ee36d2c3 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 287a185bc36b06b5d04cc18d5667b9e869047452..48980f4a4c1f809b8bf296d48c0332ea682e5234 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -246,7 +246,6 @@ int LUA_HookLib(lua_State *L)
 }
 
 /* TODO: remove in next backwards incompatible release */
-#if MODID == 18
 int lib_hudadd(lua_State *L);/* yeah compiler */
 int lib_hudadd(lua_State *L)
 {
@@ -260,7 +259,6 @@ int lib_hudadd(lua_State *L)
 
 	return 0;
 }
-#endif
 
 typedef struct Hook_State Hook_State;
 typedef void (*Hook_Callback)(Hook_State *);
@@ -347,6 +345,10 @@ static boolean prepare_mobj_hook
 		int          hook_type,
 		mobjtype_t   mobj_type
 ){
+#ifdef PARANOIA
+	if (mobj_type == MT_NULL)
+		I_Error("MT_NULL has been passed to a mobj hook\n");
+#endif
 	return init_hook_type(hook, default_status,
 			hook_type, mobj_type, NULL,
 			mobj_hook_available(hook_type, mobj_type));
diff --git a/src/lua_hud.h b/src/lua_hud.h
index c1d2d164b97d6952612e088d80f0bd256a117d4b..ad2b51d3ef7d58b4df71ace0c18a9560962e947e 100644
--- a/src/lua_hud.h
+++ b/src/lua_hud.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2014-2016 by John "JTE" Muniz.
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 0dd951efd964e28a04074d311215d059564d88a7..c7f2bbc28ec31d6266aa9eb435248663a4418232 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2014-2016 by John "JTE" Muniz.
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index af2d99a0c015cf05001ccaa31b3c54fffa09ea04..ac41de419ee31d37a3a655249694925778602afc 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_inputlib.c b/src/lua_inputlib.c
index 661d9364166f4e2eec28d281dd0711dfbd45480a..1710b035522755526a1450cf53e00649fc2622ef 100644
--- a/src/lua_inputlib.c
+++ b/src/lua_inputlib.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2021 by Sonic Team Junior.
+// Copyright (C) 2021-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 8903834e861c32c72dc9eaf2f22c864cefc218a7..b4a891edb83d68f77075e8671cc13c76b1df0de3 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 467d0560ac90dc8eda56b7d9d8fa4c8e85213235..1f3203a5a68bd09a8f635d2578f0992cf64fd158 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -88,6 +88,7 @@ enum line_e {
 	line_v2,
 	line_dx,
 	line_dy,
+	line_angle,
 	line_flags,
 	line_special,
 	line_tag,
@@ -113,6 +114,7 @@ static const char *const line_opt[] = {
 	"v2",
 	"dx",
 	"dy",
+	"angle",
 	"flags",
 	"special",
 	"tag",
@@ -835,6 +837,9 @@ static int line_get(lua_State *L)
 	case line_dy:
 		lua_pushfixed(L, line->dy);
 		return 1;
+	case line_angle:
+		lua_pushangle(L, line->angle);
+		return 1;
 	case line_flags:
 		lua_pushinteger(L, line->flags);
 		return 1;
diff --git a/src/lua_mathlib.c b/src/lua_mathlib.c
index bd9218a3d54bc076093c8596ae502c0075464602..c7501da6047fbf0828392cc6c92663d86dc58d38 100644
--- a/src/lua_mathlib.c
+++ b/src/lua_mathlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index cf8ccab2cec113df3e7038c5657a0be395b1f7ea..953b390006c3419be9a63c2a925d112716568eb4 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 1c634da45e233f5b55afdfe3320aedf508fc5cee..58cfab76cc8958674bddbbf5caebb8a1f6ca1f1d 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_polyobjlib.c b/src/lua_polyobjlib.c
index 5d76a912de0c06948dee8ecbb8039bfb356033ec..e254b0e4ab698b5f3d85f6db66648b91eac870b5 100644
--- a/src/lua_polyobjlib.c
+++ b/src/lua_polyobjlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Iestyn "Monster Iestyn" Jealous.
-// Copyright (C) 2020-2021 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by Iestyn "Monster Iestyn" Jealous.
+// Copyright (C) 2020-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_script.c b/src/lua_script.c
index a1376ca2e37fbdad25f9c2db6fb34907e87c8cdd..a36e5bf985e477a4ebc188c622ab02ac7b260d4f 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_script.h b/src/lua_script.h
index e882569414452951429a99986c313137cc9613e9..e586b04a886d292ec65669d8f831123d0d6b57d1 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c
index e66a379e9d13549610607b2aad173b29c184494c..9c7c4ad03e8ac8ee7e62e4b65828f0b493138f7f 100644
--- a/src/lua_skinlib.c
+++ b/src/lua_skinlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2014-2016 by John "JTE" Muniz.
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_taglib.c b/src/lua_taglib.c
index d0cf385a9d89217113d21161df820c5a838e2c01..b69416362e257aae6b281191c7cb373b56ab7429 100644
--- a/src/lua_taglib.c
+++ b/src/lua_taglib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by James R.
-// Copyright (C) 2020-2021 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by James R.
+// Copyright (C) 2020-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/lua_thinkerlib.c b/src/lua_thinkerlib.c
index 65bf8c313b14c942e77454f4890a81df62cdfcec..963fdbd5ad401053f90d0df1bee243460793b5cf 100644
--- a/src/lua_thinkerlib.c
+++ b/src/lua_thinkerlib.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by John "JTE" Muniz.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_aatree.c b/src/m_aatree.c
index b228ed63de0fd6d29d2cfe027a96f9c8eefd2e46..522e38a53ac4576233ad165f2bddd9c18b5faafc 100644
--- a/src/m_aatree.c
+++ b/src/m_aatree.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_aatree.h b/src/m_aatree.h
index 5a240394f77920845e53b023f314d223f27076b4..ed011644e0daa741eca8656d095bbbc222399ff4 100644
--- a/src/m_aatree.h
+++ b/src/m_aatree.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_anigif.c b/src/m_anigif.c
index fe04a5cb41fd42141295001d1860161a16c35f3d..b3a1d0fe22304faea9f7b9656bd9dcd4e7a872a9 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 2013-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 2013      by "Ninji".
-// Copyright (C) 2013-2021 by Sonic Team Junior.
+// Copyright (C) 2013-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_anigif.h b/src/m_anigif.h
index ca7563b1e9c6de3b537e287220cd83fc775d17ae..ad64dff7b78c5ca105ec8af0c7712947967722ee 100644
--- a/src/m_anigif.h
+++ b/src/m_anigif.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2013-2016 by Matthew "Kaito Sinclaire" Walsh.
-// Copyright (C) 2013-2021 by Sonic Team Junior.
+// Copyright (C) 2013-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_argv.c b/src/m_argv.c
index 453d6e45c1ff9e8ee53c6a44bfe2e774a0efdc6b..1444f0c38ab758c9b231ee25472196aece274276 100644
--- a/src/m_argv.c
+++ b/src/m_argv.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_argv.h b/src/m_argv.h
index f39db513ffdb0d371e06916a51bd964a2cbf821e..cdb6aa246abdf0f6069710b441dc8444b7574d48 100644
--- a/src/m_argv.h
+++ b/src/m_argv.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_bbox.c b/src/m_bbox.c
index e0505fd95a485a4f1fad9eb5e3b004fd22fa6fa2..7fde0c171d1260c7ab93f0c30778b982b889abd5 100644
--- a/src/m_bbox.c
+++ b/src/m_bbox.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_bbox.h b/src/m_bbox.h
index c56bd22c06714a7eb93c5cf533f3ea8ed7b342a4..588000faead6dd225473c8836635559a5b9d1205 100644
--- a/src/m_bbox.h
+++ b/src/m_bbox.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_cheat.c b/src/m_cheat.c
index ef896c9911975550905069813394f7c4a4e654d1..82d0b9d5a59af96319b8f004809b5ec2ec0639fc 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_cheat.h b/src/m_cheat.h
index ee4ba5f557eafe7c98b85605c08394f5bd28144d..c22e262fbb4898e33d4a9581956e204486ad7675 100644
--- a/src/m_cheat.h
+++ b/src/m_cheat.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_cond.c b/src/m_cond.c
index 85d732a48d3d7dea1a8c4289a8acaf24ce859e64..1406317c59b56bf5250420be2fbc8d5681fed4df 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_cond.h b/src/m_cond.h
index b2c6d65e6046b030a44721568ae429baece6909a..f36c8000922d1d364046469cfe27a9d5ce66f1db 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
-// Copyright (C) 2012-2021 by Sonic Team Junior.
+// Copyright (C) 2012-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_dllist.h b/src/m_dllist.h
index 65303b4a339fb81ec9f7a2c9eb70f2b8cd55a20a..d8ca6648a8d0806e264b44c4e1bb2fce2077f380 100644
--- a/src/m_dllist.h
+++ b/src/m_dllist.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2005      by James Haley
-// Copyright (C) 2005-2021 by Sonic Team Junior.
+// Copyright (C) 2005-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_easing.c b/src/m_easing.c
index c871d3106e53c2e62216a8a731dab50cf3c6fb15..0f1cc1d026af55cfe5f28a5e3c585c84746ba838 100644
--- a/src/m_easing.c
+++ b/src/m_easing.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2020-2022 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_easing.h b/src/m_easing.h
index 435ad35e7a0b6b2302ce5256713155bf18c4258f..229222a15778af2f8b0d6b5d4e975828dd2accff 100644
--- a/src/m_easing.h
+++ b/src/m_easing.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2020-2022 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_fixed.c b/src/m_fixed.c
index d40ccd98e35604a706dd7fa39f16234e1eb28f5a..70b7623da8d8ca14cf046e4279061c80a7ef6b25 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_fixed.h b/src/m_fixed.h
index 1cf2f00d1e482d8fae5c77ea48a47b6940826297..fe5efc5512e5bc23940056dc3056df04c771d10a 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_menu.c b/src/m_menu.c
index 3c1d8d7caa07ba20ec416afddf848a75324017ab..48ee6850d025bd7b33cda222dbbecaeb2aca6f28 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2011-2016 by Matthew "Kaito Sinclaire" Walsh.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -4751,7 +4751,7 @@ static void M_DrawPauseMenu(void)
 						emblemslot = 2;
 						break;
 					case ET_NGRADE:
-						snprintf(targettext, 9, "%u", P_GetScoreForGrade(gamemap, 0, emblem->var));
+						snprintf(targettext, 9, "%u", P_GetScoreForGradeOverall(gamemap, emblem->var));
 						snprintf(currenttext, 9, "%u", G_GetBestNightsScore(gamemap, 0));
 
 						targettext[8] = 0;
@@ -5408,11 +5408,13 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 
 					if (actnum)
 						sprintf(mapname, "%s %d", mapheaderinfo[headingIterate]->lvlttl, actnum);
+					else if (V_ThinStringWidth(mapheaderinfo[headingIterate]->lvlttl, 0) <= 80)
+						strlcpy(mapname, mapheaderinfo[headingIterate]->lvlttl, 22);
 					else
-						strcpy(mapname, mapheaderinfo[headingIterate]->lvlttl);
-
-					if (strlen(mapname) >= 17)
-						strcpy(mapname+17-3, "...");
+					{
+						strlcpy(mapname, mapheaderinfo[headingIterate]->lvlttl, 15);
+						strcat(mapname, "...");
+					}
 
 					strcpy(levelselect.rows[row].mapnames[col], (const char *)mapname);
 				}
@@ -5747,7 +5749,7 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 		? 159 : 63));
 
 	if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "AERIAL GARDEN" vs "ACT 18" - "THE ACT" intentionally compressed
-		V_DrawThinString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+		V_DrawThinString(x, y+50+1, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
 	else
 		V_DrawString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
 }
@@ -8748,12 +8750,12 @@ static void M_ReadSavegameInfo(UINT32 slot)
 
 	if(!mapheaderinfo[(fake-1) & 8191])
 		savegameinfo[slot].levelname[0] = '\0';
+	else if (V_ThinStringWidth(mapheaderinfo[(fake-1) & 8191]->lvlttl, 0) <= 78)
+		strlcpy(savegameinfo[slot].levelname, mapheaderinfo[(fake-1) & 8191]->lvlttl, 22);
 	else
 	{
-		strlcpy(savegameinfo[slot].levelname, mapheaderinfo[(fake-1) & 8191]->lvlttl, 17+1);
-
-		if (strlen(mapheaderinfo[(fake-1) & 8191]->lvlttl) >= 17)
-			strcpy(savegameinfo[slot].levelname+17-3, "...");
+		strlcpy(savegameinfo[slot].levelname, mapheaderinfo[(fake-1) & 8191]->lvlttl, 15);
+		strcat(savegameinfo[slot].levelname, "...");
 	}
 
 	savegameinfo[slot].gamemap = fake;
diff --git a/src/m_menu.h b/src/m_menu.h
index ba9c326a00eb42d0e9f9dfa5330ad69b4e886ddb..a7072b0c10438150c1b0cce07fe7f2931e9262b3 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2011-2016 by Matthew "Kaito Sinclaire" Walsh.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -389,9 +389,9 @@ typedef struct
 // level select platter
 typedef struct
 {
-	char header[22+5]; // mapheader_t lvltttl max length + " ZONE"
+	char header[22+5]; // mapheader_t lvlttl max length + " ZONE"
 	INT32 maplist[3];
-	char mapnames[3][17+1];
+	char mapnames[3][22]; // lvlttl max length
 	boolean mapavailable[4]; // mapavailable[3] == wide or not
 } levelselectrow_t;
 
diff --git a/src/m_misc.c b/src/m_misc.c
index 59783d5d30dc8d195d5b732d2de474833459f816..d7d6d6bbb6e9d4931c545b985e386d303af54941 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_misc.h b/src/m_misc.h
index 82ccd58c75738ef121e0697ba0a2dc6177906e83..5b79c6c8c4d55473053563f722504257fe3ea157 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_perfstats.c b/src/m_perfstats.c
index 439a9da1cd009106bc80fa4f3c81e3337a0acbdf..9fc41000d24548deb4a58f3b5071c3f6a9fa77e1 100644
--- a/src/m_perfstats.c
+++ b/src/m_perfstats.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -228,10 +228,13 @@ static boolean PS_IsLevelActive(void)
 // Is the row valid in the current context?
 static boolean PS_IsRowValid(perfstatrow_t *row)
 {
-	return !((row->flags & PS_LEVEL && !PS_IsLevelActive()) ||
-		(row->flags & PS_SW && rendermode != render_soft) ||
-		(row->flags & PS_HW && rendermode != render_opengl) ||
-		(row->flags & PS_BATCHING && !cv_glbatching.value));
+	return !((row->flags & PS_LEVEL && !PS_IsLevelActive())
+		|| (row->flags & PS_SW && rendermode != render_soft)
+		|| (row->flags & PS_HW && rendermode != render_opengl)
+#ifdef HWRENDER
+		|| (row->flags & PS_BATCHING && !cv_glbatching.value)
+#endif
+		);
 }
 
 // Should the row be visible on the screen?
diff --git a/src/m_perfstats.h b/src/m_perfstats.h
index 3ff0e6c6b014b4c24099be602e507d3c07cfcc43..f6a7c1f74032196229c73890da44861a0d7adac9 100644
--- a/src/m_perfstats.h
+++ b/src/m_perfstats.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_queue.c b/src/m_queue.c
index a337ca4ce9b283afad602a6d06376136bb010aad..2cc3f7cb819fb5e62ed336bf8e7fcaf7881b5f15 100644
--- a/src/m_queue.c
+++ b/src/m_queue.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2003      by James Haley
-// Copyright (C) 2003-2021 by Sonic Team Junior.
+// Copyright (C) 2003-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_queue.h b/src/m_queue.h
index cc64b8dd7917acdd136ddc7f832ef2737904e66f..071f9d8fa1b5db4a816eac16603d0535c318ca85 100644
--- a/src/m_queue.h
+++ b/src/m_queue.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2003      by James Haley
-// Copyright (C) 2003-2021 by Sonic Team Junior.
+// Copyright (C) 2003-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_random.c b/src/m_random.c
index 2e6213e1277ce027fc04cd6e7435dbaa8e49963d..1127955006ba9aa8ba6c7e4646bd772e4864a50b 100644
--- a/src/m_random.c
+++ b/src/m_random.c
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_random.h b/src/m_random.h
index df10b4bb371ed14394b897bc07bb9abe78c0f096..aa5ffb0bbbb82e9b793188af4442b8020a362396 100644
--- a/src/m_random.h
+++ b/src/m_random.h
@@ -3,7 +3,7 @@
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/m_swap.h b/src/m_swap.h
index 6aa347d97d146bc9c33d1d69cde0ff296b04a8a5..df5f3e907145cd5c19227c341d188dfa6d104a29 100644
--- a/src/m_swap.h
+++ b/src/m_swap.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/mserv.c b/src/mserv.c
index f64c7bea91b6487efca07f01ba7bb41ad786358c..bff562c95fb02cdfe7dab811972aad42e83f0065 100644
--- a/src/mserv.c
+++ b/src/mserv.c
@@ -1,8 +1,8 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
-// Copyright (C) 2020-2021 by James R.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by James R.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -98,6 +98,7 @@ void AddMServCommands(void)
 	CV_RegisterVar(&cv_servername);
 #ifdef MASTERSERVER
 	COM_AddCommand("listserv", Command_Listserv_f);
+	COM_AddCommand("masterserver_update", Update_parameters); // allows people to updates manually in case you were delisted by accident
 #endif
 #endif
 }
diff --git a/src/mserv.h b/src/mserv.h
index 7a3b3d8ec8461d9b4734353a23e37e587369be86..23b26fbc54670fff4d5e2673dcd742c8bf0e950c 100644
--- a/src/mserv.h
+++ b/src/mserv.h
@@ -1,8 +1,8 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
-// Copyright (C) 2020-2021 by James R.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by James R.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_ceilng.c b/src/p_ceilng.c
index aa31e4301442daf78d8a1128ff6cf57ef3e7c159..51976994f9b6e57ac90f2d1c140e04d350c17947 100644
--- a/src/p_ceilng.c
+++ b/src/p_ceilng.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 1216bb09569ad812f2a7a3030109b84ba2c3f7db..a6a1d24e5914943aced4f7cd512c55ca69252cfc 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -107,6 +107,8 @@ void A_GoldMonitorRestore(mobj_t *actor);
 void A_GoldMonitorSparkle(mobj_t *actor);
 void A_Explode(mobj_t *actor);
 void A_BossDeath(mobj_t *actor);
+void A_SetShadowScale(mobj_t *actor);
+void A_ShadowScream(mobj_t *actor);
 void A_CustomPower(mobj_t *actor);
 void A_GiveWeapon(mobj_t *actor);
 void A_RingBox(mobj_t *actor);
@@ -200,6 +202,8 @@ void A_SetObjectFlags(mobj_t *actor);
 void A_SetObjectFlags2(mobj_t *actor);
 void A_RandomState(mobj_t *actor);
 void A_RandomStateRange(mobj_t *actor);
+void A_StateRangeByAngle(mobj_t *actor);
+void A_StateRangeByParameter(mobj_t *actor);
 void A_DualAction(mobj_t *actor);
 void A_RemoteAction(mobj_t *actor);
 void A_ToggleFlameJet(mobj_t *actor);
@@ -2655,7 +2659,7 @@ void A_LobShot(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2 >> 16;
-	mobj_t *shot, *hitspot;
+	mobj_t *shot;
 	angle_t an;
 	fixed_t z;
 	fixed_t dist;
@@ -2694,11 +2698,6 @@ void A_LobShot(mobj_t *actor)
 		P_SetScale(shot, actor->scale);
 	}
 
-	// Keep track of where it's going to land
-	hitspot = P_SpawnMobj(actor->target->x&(64*FRACUNIT-1), actor->target->y&(64*FRACUNIT-1), actor->target->subsector->sector->floorheight, MT_NULL);
-	hitspot->tics = airtime;
-	P_SetTarget(&shot->tracer, hitspot);
-
 	P_SetTarget(&shot->target, actor); // where it came from
 
 	shot->angle = an = actor->angle;
@@ -3334,20 +3333,18 @@ void A_SkullAttack(mobj_t *actor)
 		actor->angle += (P_RandomChance(FRACUNIT/2)) ? ANGLE_90 : -ANGLE_90;
 	else if (locvar1 == 3)
 	{
-		statenum_t oldspawnstate = mobjinfo[MT_NULL].spawnstate;
-		UINT32 oldflags = mobjinfo[MT_NULL].flags;
-		fixed_t oldradius = mobjinfo[MT_NULL].radius;
-		fixed_t oldheight = mobjinfo[MT_NULL].height;
-		mobj_t *check;
+		statenum_t oldspawnstate = mobjinfo[MT_RAY].spawnstate;
+		UINT32 oldflags = mobjinfo[MT_RAY].flags;
+		fixed_t oldradius = mobjinfo[MT_RAY].radius;
+		fixed_t oldheight = mobjinfo[MT_RAY].height;
 		INT32 i, j;
 		static INT32 k;/* static for (at least) GCC 9.1 weirdness */
-		boolean allow;
 		angle_t testang = 0;
 
-		mobjinfo[MT_NULL].spawnstate = S_INVISIBLE;
-		mobjinfo[MT_NULL].flags = MF_NOGRAVITY|MF_NOTHINK|MF_NOCLIPTHING|MF_NOBLOCKMAP;
-		mobjinfo[MT_NULL].radius = mobjinfo[actor->type].radius;
-		mobjinfo[MT_NULL].height = mobjinfo[actor->type].height;
+		mobjinfo[MT_RAY].spawnstate = S_INVISIBLE;
+		mobjinfo[MT_RAY].flags = MF_NOGRAVITY|MF_NOTHINK|MF_NOCLIPTHING|MF_NOBLOCKMAP;
+		mobjinfo[MT_RAY].radius = mobjinfo[actor->type].radius;
+		mobjinfo[MT_RAY].height = mobjinfo[actor->type].height;
 
 		if (P_RandomChance(FRACUNIT/2)) // port priority 1?
 		{
@@ -3360,15 +3357,12 @@ void A_SkullAttack(mobj_t *actor)
 			j = 9;
 		}
 
-#define dostuff(q) check = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_NULL);\
+#define dostuff(q) \
 			testang = actor->angle + ((i+(q))*ANG10);\
-			allow = (P_TryMove(check,\
-				P_ReturnThrustX(check, testang, dist + 2*actor->radius),\
-				P_ReturnThrustY(check, testang, dist + 2*actor->radius),\
-				true));\
-			P_RemoveMobj(check);\
-			if (allow)\
-				break;
+			if (P_CheckMove(actor,\
+				P_ReturnThrustX(actor, testang, dist + 2*actor->radius),\
+				P_ReturnThrustY(actor, testang, dist + 2*actor->radius),\
+				true)) break;
 
 		if (P_RandomChance(FRACUNIT/2)) // port priority 2?
 		{
@@ -3394,10 +3388,10 @@ void A_SkullAttack(mobj_t *actor)
 
 #undef dostuff
 
-		mobjinfo[MT_NULL].spawnstate = oldspawnstate;
-		mobjinfo[MT_NULL].flags = oldflags;
-		mobjinfo[MT_NULL].radius = oldradius;
-		mobjinfo[MT_NULL].height = oldheight;
+		mobjinfo[MT_RAY].spawnstate = oldspawnstate;
+		mobjinfo[MT_RAY].flags = oldflags;
+		mobjinfo[MT_RAY].radius = oldradius;
+		mobjinfo[MT_RAY].height = oldheight;
 	}
 
 	an = actor->angle >> ANGLETOFINESHIFT;
@@ -4164,6 +4158,41 @@ bossjustdie:
 	}
 }
 
+// Function: A_SetShadowScale
+//
+// Description: Sets the target's shadowscale.
+//
+// var1 = new fixed_t shadowscale (default = FRACUNIT)
+// var2 = unused
+//
+void A_SetShadowScale(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+
+	if (LUA_CallAction(A_SETSHADOWSCALE, actor))
+		return;
+
+	actor->shadowscale = locvar1;
+}
+
+
+// Function: A_ShadowScream
+//
+// Description: Sets the target's shadowscale and starts the death sound of the object.
+//
+// var1 = new fixed_t shadowscale (default = FRACUNIT)
+// var2 = unused
+//
+void A_ShadowScream(mobj_t *actor)
+{
+	if (LUA_CallAction(A_SHADOWSCREAM, actor))
+		return;
+
+	A_SetShadowScale(actor);
+	A_Scream(actor);
+}
+
+
 // Function: A_CustomPower
 //
 // Description: Provides a custom powerup. Target (must be a player) is awarded the powerup. Reactiontime of the object is used as an index to the powers array.
@@ -4176,7 +4205,6 @@ void A_CustomPower(mobj_t *actor)
 	player_t *player;
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
-	boolean spawnshield = false;
 
 	if (LUA_CallAction(A_CUSTOMPOWER, actor))
 		return;
@@ -4195,15 +4223,10 @@ void A_CustomPower(mobj_t *actor)
 
 	player = actor->target->player;
 
-	if (locvar1 == pw_shield && player->powers[pw_shield] != locvar2)
-		spawnshield = true;
+	P_SetPower(player, locvar1, locvar2);
 
-	player->powers[locvar1] = (UINT16)locvar2;
 	if (actor->info->seesound)
 		S_StartSound(player->mo, actor->info->seesound);
-
-	if (spawnshield) //workaround for a bug
-		P_SpawnShieldOrb(player);
 }
 
 // Function: A_GiveWeapon
@@ -6529,7 +6552,7 @@ void A_OldRingExplode(mobj_t *actor) {
 	{
 		const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
 
-		mo = P_SpawnMobj(actor->x, actor->y, actor->z, locvar1);
+		mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1);
 		P_SetTarget(&mo->target, actor->target); // Transfer target so player gets the points
 
 		mo->momx = FixedMul(FINECOSINE(fa),ns);
@@ -6555,7 +6578,7 @@ void A_OldRingExplode(mobj_t *actor) {
 		}
 	}
 
-	mo = P_SpawnMobj(actor->x, actor->y, actor->z, locvar1);
+	mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1);
 
 	P_SetTarget(&mo->target, actor->target);
 	mo->momz = ns;
@@ -6570,7 +6593,7 @@ void A_OldRingExplode(mobj_t *actor) {
 			mo->color = skincolor_bluering;
 	}
 
-	mo = P_SpawnMobj(actor->x, actor->y, actor->z, locvar1);
+	mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1);
 
 	P_SetTarget(&mo->target, actor->target);
 	mo->momz = -ns;
@@ -8279,7 +8302,7 @@ void A_Boss3ShockThink(mobj_t *actor)
 			snew->angle = (actor->angle + snext->angle) >> 1;
 			P_SetTarget(&snew->target, actor->target);
 			snew->fuse = actor->fuse;
-			
+
 			P_SetScale(snew, actor->scale);
 			snew->destscale = actor->destscale;
 			snew->scalespeed = actor->scalespeed;
@@ -9277,6 +9300,49 @@ void A_RandomStateRange(mobj_t *actor)
 	P_SetMobjState(actor, P_RandomRange(locvar1, locvar2));
 }
 
+// Function: A_StateRangeByAngle
+//
+// Description: Chooses a state within the range supplied, depending on the actor's angle.
+//
+// var1 = Minimum state number to use.
+// var2 = Maximum state number to use. The difference will act as a modulo operator.
+//
+void A_StateRangeByAngle(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+	if (LUA_CallAction(A_STATERANGEBYANGLE, actor))
+		return;
+
+	if (locvar2 - locvar1 < 0)
+		return; // invalid range
+
+	P_SetMobjState(actor, locvar1 + (AngleFixed(actor->angle)>>FRACBITS % (1 + locvar2 - locvar1)));
+}
+
+// Function: A_StateRangeByParameter
+//
+// Description: Chooses a state within the range supplied, depending on the actor's parameter/extrainfo value.
+//
+// var1 = Minimum state number to use.
+// var2 = Maximum state number to use. The difference will act as a modulo operator.
+//
+void A_StateRangeByParameter(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	UINT8 parameter = (actor->spawnpoint ? actor->spawnpoint->extrainfo : 0);
+
+	if (LUA_CallAction(A_STATERANGEBYPARAMETER, actor))
+		return;
+
+	if (locvar2 - locvar1 < 0)
+		return; // invalid range
+
+	P_SetMobjState(actor, locvar1 + (parameter % (1 + locvar2 - locvar1)));
+}
+
 // Function: A_DualAction
 //
 // Description: Calls two actions. Be careful, if you reference the same state this action is called from, you can create an infinite loop.
diff --git a/src/p_floor.c b/src/p_floor.c
index 16c949d0943dfb934272989922751087bf6a2e58..db485430cea7072a79047b5829d008b21dfc32a0 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_inter.c b/src/p_inter.c
index d84cb8ce57f846f9495fa90f3657e9a4eb8fd921..665be4201d6bf8436d0906de01114a898ffbdfc4 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -470,14 +470,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 				{
 					fixed_t setmomz = -toucher->momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
-					
+
 					if (elementalpierce == 2) // Reset bubblewrap, part 1
 						P_DoBubbleBounce(player);
 					toucher->momz = setmomz;
 					if (elementalpierce == 2) // Reset bubblewrap, part 2
 					{
 						boolean underwater = toucher->eflags & MFE_UNDERWATER;
-							
+
 						if (underwater)
 							toucher->momz /= 2;
 						toucher->momz -= (toucher->momz/(underwater ? 8 : 4)); // Cap the height!
@@ -738,12 +738,11 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		// Secret emblem thingy
 		case MT_EMBLEM:
 			{
-				if (demoplayback || (player->bot && player->bot != BOT_MPAI))
+				if (demoplayback || (player->bot && player->bot != BOT_MPAI) || special->health <= 0 || special->health > MAXEMBLEMS)
 					return;
 				emblemlocations[special->health-1].collected = true;
 
 				M_UpdateUnlockablesAndExtraEmblems();
-
 				G_SaveGameData();
 				break;
 			}
@@ -1611,7 +1610,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 				if (special->tracer && !(special->tracer->flags2 & MF2_STRONGBOX))
 					macespin = true;
-				
+
 				if (macespin ? (player->powers[pw_ignorelatch] & (1<<15)) : (player->powers[pw_ignorelatch]))
 					return;
 
diff --git a/src/p_lights.c b/src/p_lights.c
index 1e41146da682a2ed5d93166974f1b04843bd40ed..d7bbea90cea852f48820663e3e8da9b3935509d5 100644
--- a/src/p_lights.c
+++ b/src/p_lights.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_local.h b/src/p_local.h
index 28a77afe5c9a5022a5e5f92641df34806f32b91d..ba8cbe166aa1df73a96536c9d813565255a1841b 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -155,6 +155,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff);
 
 void P_SetObjectMomZ(mobj_t *mo, fixed_t value, boolean relative);
 void P_RestoreMusic(player_t *player);
+void P_SetPower(player_t *player, powertype_t power, UINT16 value);
 void P_SpawnShieldOrb(player_t *player);
 void P_SwitchShield(player_t *player, UINT16 shieldtype);
 mobj_t *P_SpawnGhostMobj(mobj_t *mobj);
@@ -410,6 +411,7 @@ void P_SetUnderlayPosition(mobj_t *thing);
 
 boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y);
 boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam);
+boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff);
 boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff);
 boolean P_Move(mobj_t *actor, fixed_t speed);
 boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z);
diff --git a/src/p_map.c b/src/p_map.c
index 329224d0bd353c8a345f5483ffd5142695b9f0e3..9f80d92b5558d2590407a752d7b4bc937d975a1d 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -1465,6 +1465,86 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		return true;
 	}
 
+	// Sprite Spikes!
+	// Do not return because solidity code comes below.
+	if (tmthing->type == MT_SPIKE && tmthing->flags & MF_SOLID && thing->player) // moving spike rams into player?!
+	{
+		if (tmthing->eflags & MFE_VERTICALFLIP)
+		{
+			if (thing->z + thing->height <= tmthing->z + FixedMul(FRACUNIT, tmthing->scale)
+			&& thing->z + thing->height + thing->momz  >= tmthing->z + FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
+			&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && thing->eflags & MFE_VERTICALFLIP))
+				P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
+		}
+		else if (thing->z >= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale)
+		&& thing->z + thing->momz <= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
+		&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && !(thing->eflags & MFE_VERTICALFLIP)))
+			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
+	}
+	else if (thing->type == MT_SPIKE && thing->flags & MF_SOLID && tmthing->player) // unfortunate player falls into spike?!
+	{
+		if (thing->eflags & MFE_VERTICALFLIP)
+		{
+			if (tmthing->z + tmthing->height <= thing->z - FixedMul(FRACUNIT, thing->scale)
+			&& tmthing->z + tmthing->height + tmthing->momz >= thing->z - FixedMul(FRACUNIT, thing->scale)
+			&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && tmthing->eflags & MFE_VERTICALFLIP))
+				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
+		}
+		else if (tmthing->z >= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
+		&& tmthing->z + tmthing->momz <= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
+		&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && !(tmthing->eflags & MFE_VERTICALFLIP)))
+			P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
+	}
+
+	if (tmthing->type == MT_WALLSPIKE && tmthing->flags & MF_SOLID && thing->player) // wall spike impales player
+	{
+		fixed_t bottomz, topz;
+		bottomz = tmthing->z;
+		topz = tmthing->z + tmthing->height;
+		if (tmthing->eflags & MFE_VERTICALFLIP)
+			bottomz -= FixedMul(FRACUNIT, tmthing->scale);
+		else
+			topz += FixedMul(FRACUNIT, tmthing->scale);
+
+		if (thing->z + thing->height > bottomz // above bottom
+		&&  thing->z < topz) // below top
+		// don't check angle, the player was clearly in the way in this case
+			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
+	}
+	else if (thing->type == MT_WALLSPIKE && thing->flags & MF_SOLID && tmthing->player)
+	{
+		fixed_t bottomz, topz;
+		angle_t touchangle = R_PointToAngle2(thing->tracer->x, thing->tracer->y, tmthing->x, tmthing->y);
+
+		if (P_PlayerInPain(tmthing->player) && (tmthing->momx || tmthing->momy))
+		{
+			angle_t playerangle = R_PointToAngle2(0, 0, tmthing->momx, tmthing->momy) - touchangle;
+			if (playerangle > ANGLE_180)
+				playerangle = InvAngle(playerangle);
+			if (playerangle < ANGLE_90)
+				return true; // Yes, this is intentionally outside the z-height check. No standing on spikes whilst moving away from them.
+		}
+
+		bottomz = thing->z;
+		topz = thing->z + thing->height;
+
+		if (thing->eflags & MFE_VERTICALFLIP)
+			bottomz -= FixedMul(FRACUNIT, thing->scale);
+		else
+			topz += FixedMul(FRACUNIT, thing->scale);
+
+		if (tmthing->z + tmthing->height > bottomz // above bottom
+		&&  tmthing->z < topz // below top
+		&& !P_MobjWasRemoved(thing->tracer)) // this probably wouldn't work if we didn't have a tracer
+		{ // use base as a reference point to determine what angle you touched the spike at
+			touchangle = thing->angle - touchangle;
+			if (touchangle > ANGLE_180)
+				touchangle = InvAngle(touchangle);
+			if (touchangle <= ANGLE_22h) // if you touched it at this close an angle, you get poked!
+				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
+		}
+	}
+
 	if (thing->flags & MF_PUSHABLE)
 	{
 		if (tmthing->type == MT_FAN || tmthing->type == MT_STEAM)
@@ -1543,22 +1623,6 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	if (thing->player)
 	{
-		if (tmthing->type == MT_WALLSPIKE && (tmthing->flags & MF_SOLID)) // wall spike impales player
-		{
-			fixed_t bottomz, topz;
-			bottomz = tmthing->z;
-			topz = tmthing->z + tmthing->height;
-			if (tmthing->eflags & MFE_VERTICALFLIP)
-				bottomz -= FixedMul(FRACUNIT, tmthing->scale);
-			else
-				topz += FixedMul(FRACUNIT, tmthing->scale);
-
-			if (thing->z + thing->height > bottomz // above bottom
-			&&  thing->z < topz) // below top
-			// don't check angle, the player was clearly in the way in this case
-				P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
-		}
-
 		// Doesn't matter what gravity player's following! Just do your stuff in YOUR direction only
 		if (tmthing->eflags & MFE_VERTICALFLIP
 		&& (tmthing->z + tmthing->height + tmthing->momz < thing->z
@@ -1593,55 +1657,6 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		if (!tmthing->health)
 			return true;
 
-		if (thing->type == MT_SPIKE && (thing->flags & MF_SOLID)) // unfortunate player falls into spike?!
-		{
-			if (thing->eflags & MFE_VERTICALFLIP)
-			{
-				if (tmthing->z + tmthing->height <= thing->z - FixedMul(FRACUNIT, thing->scale)
-				&& tmthing->z + tmthing->height + tmthing->momz >= thing->z - FixedMul(FRACUNIT, thing->scale)
-				&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && tmthing->eflags & MFE_VERTICALFLIP))
-					P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
-			}
-			else if (tmthing->z >= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
-			&& tmthing->z + tmthing->momz <= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
-			&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && !(tmthing->eflags & MFE_VERTICALFLIP)))
-				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
-		}
-
-		if (thing->type == MT_WALLSPIKE && (thing->flags & MF_SOLID))
-		{
-			fixed_t bottomz, topz;
-			angle_t touchangle = R_PointToAngle2(thing->tracer->x, thing->tracer->y, tmthing->x, tmthing->y);
-
-			if (P_PlayerInPain(tmthing->player) && (tmthing->momx || tmthing->momy))
-			{
-				angle_t playerangle = R_PointToAngle2(0, 0, tmthing->momx, tmthing->momy) - touchangle;
-				if (playerangle > ANGLE_180)
-					playerangle = InvAngle(playerangle);
-				if (playerangle < ANGLE_90)
-					return true; // Yes, this is intentionally outside the z-height check. No standing on spikes whilst moving away from them.
-			}
-
-			bottomz = thing->z;
-			topz = thing->z + thing->height;
-
-			if (thing->eflags & MFE_VERTICALFLIP)
-				bottomz -= FixedMul(FRACUNIT, thing->scale);
-			else
-				topz += FixedMul(FRACUNIT, thing->scale);
-
-			if (tmthing->z + tmthing->height > bottomz // above bottom
-			&&  tmthing->z < topz // below top
-			&& !P_MobjWasRemoved(thing->tracer)) // this probably wouldn't work if we didn't have a tracer
-			{ // use base as a reference point to determine what angle you touched the spike at
-				touchangle = thing->angle - touchangle;
-				if (touchangle > ANGLE_180)
-					touchangle = InvAngle(touchangle);
-				if (touchangle <= ANGLE_22h) // if you touched it at this close an angle, you get poked!
-					P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
-			}
-		}
-
 		if (thing->type == MT_FAN || thing->type == MT_STEAM)
 			P_DoFanAndGasJet(thing, tmthing);
 		else if (thing->flags & MF_SPRING && tmthing->player->powers[pw_carry] != CR_MINECART)
@@ -1706,8 +1721,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		}
 	}
 
-	if ((thing->player) && (tmthing->flags & MF_SPRING || tmthing->type == MT_STEAM))
-		; // springs and gas jets should never be able to step up onto a player
+	if ((tmthing->flags & MF_SPRING || tmthing->type == MT_STEAM || tmthing->type == MT_SPIKE || tmthing->type == MT_WALLSPIKE) && (thing->player))
+		; // springs, gas jets and springs should never be able to step up onto a player
 	// z checking at last
 	// Treat noclip things as non-solid!
 	else if ((thing->flags & (MF_SOLID|MF_NOCLIP)) == MF_SOLID
@@ -1715,9 +1730,6 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	{
 		fixed_t topz, tmtopz;
 
-		if (tmthing->type == MT_SPIKE || tmthing->type == MT_WALLSPIKE) // do not run height checks if you are a spike
-			return true;
-
 		if (tmthing->eflags & MFE_VERTICALFLIP)
 		{
 			// pass under
@@ -2654,17 +2666,17 @@ boolean PIT_PushableMoved(mobj_t *thing)
 	return true;
 }
 
-//
-// P_TryMove
-// Attempt to move to a new position.
-//
-boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
+static boolean
+increment_move
+(		mobj_t * thing,
+		fixed_t x,
+		fixed_t y,
+		boolean allowdropoff)
 {
 	fixed_t tryx = thing->x;
 	fixed_t tryy = thing->y;
 	fixed_t radius = thing->radius;
 	fixed_t thingtop;
-	fixed_t startingonground = P_IsObjectOnGround(thing);
 	floatok = false;
 
 	if (radius < MAXRADIUS/2)
@@ -2802,7 +2814,38 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 		}
 	} while (tryx != x || tryy != y);
 
+	return true;
+}
+
+//
+// P_CheckMove
+// Check if a P_TryMove would be successful.
+//
+boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
+{
+	boolean moveok;
+	mobj_t *hack = P_SpawnMobjFromMobj(thing, 0, 0, 0, MT_RAY);
+
+	hack->radius = thing->radius;
+	hack->height = thing->height;
+
+	moveok = increment_move(hack, x, y, allowdropoff);
+	P_RemoveMobj(hack);
+
+	return moveok;
+}
+
+//
+// P_TryMove
+// Attempt to move to a new position.
+//
+boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
+{
+	fixed_t startingonground = P_IsObjectOnGround(thing);
+
 	// The move is ok!
+	if (!increment_move(thing, x, y, allowdropoff))
+		return false;
 
 	// If it's a pushable object, check if anything is
 	// standing on top and move it, too.
@@ -3058,7 +3101,7 @@ static void P_HitCameraSlideLine(line_t *ld, camera_t *thiscam)
 	}
 
 	side = P_PointOnLineSide(thiscam->x, thiscam->y, ld);
-	lineangle = R_PointToAngle2(0, 0, ld->dx, ld->dy);
+	lineangle = ld->angle;
 
 	if (side == 1)
 		lineangle += ANGLE_180;
@@ -3104,7 +3147,7 @@ static void P_HitSlideLine(line_t *ld)
 
 	side = P_PointOnLineSide(slidemo->x, slidemo->y, ld);
 
-	lineangle = R_PointToAngle2(0, 0, ld->dx, ld->dy);
+	lineangle = ld->angle;
 
 	if (side == 1)
 		lineangle += ANGLE_180;
@@ -3147,7 +3190,7 @@ static void P_HitBounceLine(line_t *ld)
 		return;
 	}
 
-	lineangle = R_PointToAngle2(0, 0, ld->dx, ld->dy);
+	lineangle = ld->angle;
 
 	if (lineangle >= ANGLE_180)
 		lineangle -= ANGLE_180;
diff --git a/src/p_maputl.c b/src/p_maputl.c
index efcebe7363741a3ff1bb3a851dfc7e8653e6f3c8..43a7e92a185f4ba88ef5a2767d774bb928252bca 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_maputl.h b/src/p_maputl.h
index cec344d03df8b810f4a952dabb69de2746dccacd..b7779d88a3d5a4f5f43441cbb4bad3072b2565d3 100644
--- a/src/p_maputl.h
+++ b/src/p_maputl.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 87c93c300e120498b17c825fe26fb3cc3f6269f5..25fb49b2e4a7762748506263577cffd5d4c41bdd 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -7850,48 +7850,6 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 		if (P_MobjFlip(mobj)*mobj->momz < mobj->info->speed)
 			mobj->momz = P_MobjFlip(mobj)*mobj->info->speed;
 		break;
-	case MT_SPIKE:
-		if (mobj->fuse)
-		{
-			mobj->fuse--;
-			break;
-		}
-		P_SetMobjState(mobj, mobj->state->nextstate);
-		mobj->fuse = mobj->info->speed;
-		if (mobj->spawnpoint)
-			mobj->fuse += mobj->spawnpoint->angle;
-		break;
-	case MT_WALLSPIKE:
-		if (mobj->fuse)
-		{
-			mobj->fuse--;
-			break;
-		}
-		P_SetMobjState(mobj, mobj->state->nextstate);
-		mobj->fuse = mobj->info->speed;
-		if (mobj->spawnpoint)
-			mobj->fuse += (mobj->spawnpoint->angle / 360);
-		break;
-	case MT_WALLSPIKEBASE:
-		if (!mobj->target)
-		{
-			P_RemoveMobj(mobj);
-			return;
-		}
-		mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
-#if 0
-		if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
-		{
-			mobj_t* target = mobj->target; // shortcut
-			const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
-			P_UnsetThingPosition(mobj);
-			mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
-			mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
-			P_SetThingPosition(mobj);
-			mobj->angle = target->angle + ANGLE_90;
-		}
-#endif
-		break;
 	case MT_ROCKCRUMBLE1:
 	case MT_ROCKCRUMBLE2:
 	case MT_ROCKCRUMBLE3:
@@ -7911,7 +7869,8 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 	case MT_WOODDEBRIS:
 	case MT_BRICKDEBRIS:
 	case MT_BROKENROBOT:
-		if (mobj->z <= P_FloorzAtPos(mobj->x, mobj->y, mobj->z, mobj->height)
+		if (((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z <= P_FloorzAtPos(mobj->x, mobj->y, mobj->z, mobj->height))
+			|| (mobj->eflags & MFE_VERTICALFLIP && mobj->z + mobj->height >= P_CeilingzAtPos(mobj->x, mobj->y, mobj->z, mobj->height)))
 			&& mobj->state != &states[mobj->info->deathstate])
 		{
 			P_SetMobjState(mobj, mobj->info->deathstate);
@@ -7983,6 +7942,9 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 		P_SetScale(mobj, mobj->target->scale);
 	}
 	break;
+	case MT_TUTORIALFLOWER:
+		mobj->angle += FixedAngle(3*FRACUNIT);
+	break;
 	case MT_VWREF:
 	case MT_VWREB:
 	{
@@ -8477,7 +8439,10 @@ static boolean P_HangsterThink(mobj_t *mobj)
 	}
 	//after swooping back up, check for ceiling
 	else if ((st == S_HANGSTER_RETURN1 || st == S_HANGSTER_RETURN2) && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height))
+	{
 		P_SetMobjState(mobj, (st = S_HANGSTER_RETURN3));
+		mobj->momx = mobj->momy = 0;
+	}
 
 	//should you roost on a ceiling with F_SKY1 as its flat, disappear forever
 	if (st == S_HANGSTER_RETURN3 && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height)
@@ -9288,6 +9253,25 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 
 	switch (mobj->type)
 	{
+	case MT_WALLSPIKEBASE:
+		if (!mobj->target) {
+			P_RemoveMobj(mobj);
+			return false;
+		}
+		mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
+#if 0
+		if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
+		{
+			mobj_t* target = mobj->target; // shortcut
+			const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
+			P_UnsetThingPosition(mobj);
+			mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
+			mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
+			P_SetThingPosition(mobj);
+			mobj->angle = target->angle + ANGLE_90;
+		}
+#endif
+		break;
 	case MT_FALLINGROCK:
 		// Despawn rocks here in case zmovement code can't do so (blame slopes)
 		if (!mobj->momx && !mobj->momy && !mobj->momz
@@ -9748,7 +9732,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 		if (P_IsObjectOnGround(mobj))
 			mobj->rollangle = 0;
 		else
-			mobj->rollangle = R_PointToAngle2(0, 0, mobj->momz, (mobj->scale << 1) - min(abs(mobj->momz), mobj->scale << 1));
+			mobj->rollangle = R_PointToAngle2(0, 0, P_MobjFlip(mobj)*mobj->momz, (mobj->scale << 1) - min(abs(mobj->momz), mobj->scale << 1));
 		break;
 	case MT_SPINFIRE:
 		if (mobj->flags & MF_NOGRAVITY)
@@ -9973,6 +9957,18 @@ static boolean P_FuseThink(mobj_t *mobj)
 		break;
 	case MT_METALSONIC_BATTLE:
 		break; // don't remove
+	case MT_SPIKE:
+		P_SetMobjState(mobj, mobj->state->nextstate);
+		mobj->fuse = mobj->info->speed;
+		if (mobj->spawnpoint)
+			mobj->fuse += mobj->spawnpoint->angle;
+		break;
+	case MT_WALLSPIKE:
+		P_SetMobjState(mobj, mobj->state->nextstate);
+		mobj->fuse = mobj->info->speed;
+		if (mobj->spawnpoint)
+			mobj->fuse += (mobj->spawnpoint->angle / 360);
+		break;
 	case MT_NIGHTSCORE:
 		P_RemoveMobj(mobj);
 		return false;
@@ -10425,6 +10421,7 @@ static fixed_t P_DefaultMobjShadowScale (mobj_t *thing)
 	switch (thing->type)
 	{
 		case MT_PLAYER:
+		case MT_METALSONIC_RACE:
 		case MT_ROLLOUTROCK:
 
 		case MT_EGGMOBILE4_MACE:
@@ -10469,6 +10466,27 @@ static fixed_t P_DefaultMobjShadowScale (mobj_t *thing)
 
 			return 2*FRACUNIT/3;
 
+		case MT_FLICKY_01:
+		case MT_FLICKY_02:
+		case MT_FLICKY_03:
+		case MT_FLICKY_04:
+		case MT_FLICKY_05:
+		case MT_FLICKY_06:
+		case MT_FLICKY_07:
+		case MT_FLICKY_08:
+		case MT_FLICKY_09:
+		case MT_FLICKY_10:
+		case MT_FLICKY_11:
+		case MT_FLICKY_12:
+		case MT_FLICKY_13:
+		case MT_FLICKY_14:
+		case MT_FLICKY_15:
+		case MT_FLICKY_16:
+		case MT_SECRETFLICKY_01:
+		case MT_SECRETFLICKY_02:
+
+			return FRACUNIT;
+
 		default:
 
 			if (thing->flags & (MF_ENEMY|MF_BOSS))
@@ -10486,7 +10504,24 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 	const mobjinfo_t *info = &mobjinfo[type];
 	SINT8 sc = -1;
 	state_t *st;
-	mobj_t *mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
+	mobj_t *mobj;
+
+	if (type == MT_NULL)
+	{
+#if 0
+#ifdef PARANOIA
+		I_Error("Tried to spawn MT_NULL\n");
+#endif
+		return NULL;
+#endif
+		// Hack: Some code assumes that P_SpawnMobj can never return NULL
+		// So replace MT_NULL with MT_RAY in the meantime
+		// Remove when dealt properly
+		CONS_Debug(DBG_GAMELOGIC, "Tried to spawn MT_NULL, using MT_RAY\n");
+		type = MT_RAY;
+	}
+
+	mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
 
 	// this is officially a mobj, declared as soon as possible.
 	mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;
@@ -12832,6 +12867,25 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		P_SpawnMobjFromMobj(mobj, -FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_270;
 	}
 	break;
+	case MT_TUTORIALPLANT:
+	{
+		INT32 i;
+		mobj_t *segment;
+		for (i = 0; i < 6; i++)
+		{
+			segment = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_TUTORIALLEAF);
+			segment->angle = mobj->angle + FixedAngle(i*60*FRACUNIT);
+			P_SetMobjState(segment, S_TUTORIALLEAF1 + mthing->extrainfo);
+		}
+		for (i = 0; i < 3; i++)
+		{
+			segment = P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWER);
+			segment->angle = mobj->angle + FixedAngle(i*120*FRACUNIT);
+			P_SetMobjState(segment, S_TUTORIALFLOWER1 + mthing->extrainfo);
+		}
+		P_SetMobjState(P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWERF), S_TUTORIALFLOWERF1 + mthing->extrainfo);
+	}
+	break;
 	case MT_CEZPOLE1:
 	case MT_CEZPOLE2:
 	{ // Spawn the banner
@@ -12975,18 +13029,17 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		// Pop up spikes!
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
+			mobj->flags &= ~MF_SCENERY;
 			mobj->fuse = (16 - mthing->extrainfo)*(mthing->angle + mobj->info->speed)/16;
 			if (mthing->options & MTF_EXTRA)
 				P_SetMobjState(mobj, mobj->info->meleestate);
 		}
-		else
-			mobj->flags |= MF_NOTHINK;
-		// no collision for spikes if the ambush flag is checked
-		if ((mthing->options & MTF_AMBUSH) || metalrecording)
+		// Use per-thing collision for spikes if the deaf flag isn't checked.
+		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
 		{
 			P_UnsetThingPosition(mobj);
-			mobj->flags |= (MF_NOBLOCKMAP|MF_NOCLIPHEIGHT);
-			mobj->flags &= ~MF_SOLID;
+			mobj->flags &= ~(MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT);
+			mobj->flags |= MF_SOLID;
 			P_SetThingPosition(mobj);
 		}
 		break;
@@ -12994,20 +13047,20 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		// Pop up spikes!
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
+			mobj->flags &= ~MF_SCENERY;
 			mobj->fuse = (16 - mthing->extrainfo)*((mthing->angle/360) + mobj->info->speed)/16;
 			if (mthing->options & MTF_EXTRA)
 				P_SetMobjState(mobj, mobj->info->meleestate);
 		}
-		else
-			mobj->flags |= MF_NOTHINK;
-		// no collision for spikes if the ambush flag is checked
-		if ((mthing->options & MTF_AMBUSH) || metalrecording)
+		// Use per-thing collision for spikes if the deaf flag isn't checked.
+		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
 		{
 			P_UnsetThingPosition(mobj);
-			mobj->flags |= (MF_NOBLOCKMAP|MF_NOCLIPHEIGHT);
-			mobj->flags &= ~MF_SOLID;
+			mobj->flags &= ~(MF_NOBLOCKMAP | MF_NOCLIPHEIGHT);
+			mobj->flags |= MF_SOLID;
 			P_SetThingPosition(mobj);
 		}
+
 		// spawn base
 		{
 			const angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS); // the mobj's own angle hasn't been set quite yet so...
@@ -13021,8 +13074,6 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 			P_SetScale(base, mobj->scale);
 			P_SetTarget(&base->target, mobj);
 			P_SetTarget(&mobj->tracer, base);
-			if (!(mthing->options & MTF_OBJECTSPECIAL))
-				base->flags |= MF_NOTHINK;
 		}
 		break;
 	case MT_RING_BOX:
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 2d096385bd1689f2fb3402a5ca1489cbfd7bca40..b1b79fd82e8a14009ece5e5a7b2cb55d261621d8 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -251,7 +251,7 @@ typedef enum
 	MFE_FORCENOSUPER		= 1<<13,
 	// Makes an object use super sprites where they wouldn't have otherwise and vice-versa
 	MFE_REVERSESUPER		= MFE_FORCESUPER|MFE_FORCENOSUPER
-	
+
 	// free: to and including 1<<15
 } mobjeflag_t;
 
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 6431e46248f0f426de801e2c2e95d9835ab07ff0..6d7ef399904317675d913e5e0a65489401689adf 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2006      by James Haley
-// Copyright (C) 2006-2021 by Sonic Team Junior.
+// Copyright (C) 2006-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -785,7 +785,7 @@ static void Polyobj_pushThing(polyobj_t *po, line_t *line, mobj_t *mo)
 	vertex_t closest;
 
 	// calculate angle of line and subtract 90 degrees to get normal
-	lineangle = R_PointToAngle2(0, 0, line->dx, line->dy) - ANGLE_90;
+	lineangle = line->angle - ANGLE_90;
 	lineangle >>= ANGLETOFINESHIFT;
 	momx = FixedMul(po->thrust, FINECOSINE(lineangle));
 	momy = FixedMul(po->thrust, FINESINE(lineangle));
@@ -1060,6 +1060,8 @@ static void Polyobj_rotateLine(line_t *ld)
 	ld->dx = v2->x - v1->x;
 	ld->dy = v2->y - v1->y;
 
+	ld->angle = R_PointToAngle2(0, 0, ld->dx, ld->dy);
+
 	// determine slopetype
 	ld->slopetype = !ld->dx ? ST_VERTICAL : !ld->dy ? ST_HORIZONTAL :
 			((ld->dy > 0) == (ld->dx > 0)) ? ST_POSITIVE : ST_NEGATIVE;
diff --git a/src/p_polyobj.h b/src/p_polyobj.h
index 7c814e0bf14766bf6f6cf2af134675389d0f8797..4cc1221dbacb6a149b23aa24b57c3557072e5ba3 100644
--- a/src/p_polyobj.h
+++ b/src/p_polyobj.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2006      by James Haley
-// Copyright (C) 2006-2021 by Sonic Team Junior.
+// Copyright (C) 2006-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_pspr.h b/src/p_pspr.h
index 27002b7136f74747d73ef08c04da46b89c91a909..4136c211823c4b7082891afecfe250e2034ff02c 100644
--- a/src/p_pspr.h
+++ b/src/p_pspr.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 2ccebb0504e5537ba6fd6035bc201ac21c8555e9..bf7cf65562ba04e9cf7888f9a4f9dc480bc8818e 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_saveg.h b/src/p_saveg.h
index a909282fe55e85ce1077d43fa6e69621e2da8a02..9f4a2633fd6a97f861a477a1fce723ff2a56cd15 100644
--- a/src/p_saveg.h
+++ b/src/p_saveg.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_setup.c b/src/p_setup.c
index f28820a3e24fb8bddff0b85cd1fbc7e527655537..92296af1c448e3982a1e494d69c64965a401431a 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -508,6 +508,17 @@ UINT32 P_GetScoreForGrade(INT16 map, UINT8 mare, UINT8 grade)
 	return mapheaderinfo[map-1]->grades[mare].grade[grade-1];
 }
 
+UINT32 P_GetScoreForGradeOverall(INT16 map, UINT8 grade)
+{
+	UINT8 mares;
+	INT32 i;
+	UINT32 score = 0;
+	mares = mapheaderinfo[map-1]->numGradedMares;
+	for (i = 0; i < mares; ++i)
+			score += P_GetScoreForGrade(map, i, grade);
+	return score;
+}
+
 //
 // levelflats
 //
@@ -1058,6 +1069,8 @@ static void P_InitializeLinedef(line_t *ld)
 	ld->dx = v2->x - v1->x;
 	ld->dy = v2->y - v1->y;
 
+	ld->angle = R_PointToAngle2(0, 0, ld->dx, ld->dy);
+
 	ld->bbox[BOXLEFT] = min(v1->x, v2->x);
 	ld->bbox[BOXRIGHT] = max(v1->x, v2->x);
 	ld->bbox[BOXBOTTOM] = min(v1->y, v2->y);
@@ -2512,7 +2525,10 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		P_InitializeSeg(seg);
 		seg->angle = R_PointToAngle2(v1->x, v1->y, v2->x, v2->y);
 		if (seg->linedef)
-			segs[i].offset = FixedHypot(v1->x - seg->linedef->v1->x, v1->y - seg->linedef->v1->y);
+		{
+			vertex_t *v = (seg->side == 1) ? seg->linedef->v2 : seg->linedef->v1;
+			segs[i].offset = FixedHypot(v1->x - v->x, v1->y - v->y);
+		}
 		seg->length = P_SegLength(seg);
 #ifdef HWRENDER
 		seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0;
@@ -4923,7 +4939,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 
 	// internal game map
 	maplumpname = G_BuildMapName(gamemap);
-	lastloadedmaplumpnum = W_CheckNumForName(maplumpname);
+	lastloadedmaplumpnum = W_CheckNumForMap(maplumpname);
 	if (lastloadedmaplumpnum == LUMPERROR)
 		I_Error("Map %s not found.\n", maplumpname);
 
@@ -5272,7 +5288,7 @@ static boolean P_LoadAddon(UINT16 wadnum, UINT16 numlumps)
 	// Reload it all anyway, just in case they
 	// added some textures but didn't insert a
 	// TEXTURES/etc. list.
-	R_LoadTextures(); // numtexture changes
+	R_LoadTexturesPwad(wadnum); // numtexture changes
 
 	// Reload ANIMDEFS
 	P_InitPicAnims();
diff --git a/src/p_setup.h b/src/p_setup.h
index c3c680fdd3176392580373f8aedec1d154987006..d0c47a521e8407aa22782a92c6e898a7bcafb266 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -122,5 +122,6 @@ void P_AddGradesForMare(INT16 i, UINT8 mare, char *gtext);
 UINT8 P_GetGrade(UINT32 pscore, INT16 map, UINT8 mare);
 UINT8 P_HasGrades(INT16 map, UINT8 mare);
 UINT32 P_GetScoreForGrade(INT16 map, UINT8 mare, UINT8 grade);
+UINT32 P_GetScoreForGradeOverall(INT16 map, UINT8 grade);
 
 #endif
diff --git a/src/p_sight.c b/src/p_sight.c
index 706745f35b45aa4f1439d8e415cc2171d32083a1..1aa231a6c3db613e2bc573b312f0ba4c8d367853 100644
--- a/src/p_sight.c
+++ b/src/p_sight.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_slopes.c b/src/p_slopes.c
index bfca153a628c8246fb5c6a2629e6d609115b7a13..ffbfef2d300f5c2645b54c7dc62fa8c9dc8668b5 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2004      by Stephen McGranahan
-// Copyright (C) 2015-2021 by Sonic Team Junior.
+// Copyright (C) 2015-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_slopes.h b/src/p_slopes.h
index 43cd3edb0d9009f341b9ab82c168c3429c3521a5..d1a053d28fb92d1a0767e566af8c39b86eeabb08 100644
--- a/src/p_slopes.h
+++ b/src/p_slopes.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2004      by Stephen McGranahan
-// Copyright (C) 2015-2021 by Sonic Team Junior.
+// Copyright (C) 2015-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_spec.c b/src/p_spec.c
index 98fdebfef08c072d1c12ddb3b287074d2ef60019..4c533b5d0b313b8e84f5cd992ad176ac6c7cbe66 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -1242,7 +1242,7 @@ static boolean PolyFlag(line_t *line)
 
 	pfd.polyObjNum = Tag_FGet(&line->tags);
 	pfd.speed = P_AproxDistance(line->dx, line->dy) >> FRACBITS;
-	pfd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y) >> ANGLETOFINESHIFT;
+	pfd.angle = line->angle >> ANGLETOFINESHIFT;
 	pfd.momx = sides[line->sidenum[0]].textureoffset >> FRACBITS;
 
 	return EV_DoPolyObjFlag(&pfd);
@@ -2094,7 +2094,7 @@ void P_SwitchWeather(INT32 weathernum)
 	{
 		case PRECIP_SNOW: // snow
 			curWeather = PRECIP_SNOW;
-			
+
 			if (purge)
 				P_SpawnPrecipitation();
 
@@ -2879,25 +2879,20 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 434: // Custom Power
 			if (mo && mo->player)
 			{
-				mobj_t *dummy = P_SpawnMobj(mo->x, mo->y, mo->z, MT_NULL);
-
-				var1 = sides[line->sidenum[0]].toptexture; //(line->dx>>FRACBITS)-1;
+				powertype_t power = sides[line->sidenum[0]].toptexture; //(line->dx>>FRACBITS)-1;
+				UINT16 value;
 
 				if (line->sidenum[1] != 0xffff && line->flags & ML_BLOCKMONSTERS) // read power from back sidedef
-					var2 = sides[line->sidenum[1]].toptexture;
+					value = sides[line->sidenum[1]].toptexture;
 				else if (line->flags & ML_NOCLIMB) // 'Infinite'
-					var2 = UINT16_MAX;
+					value = UINT16_MAX;
 				else
-					var2 = sides[line->sidenum[0]].textureoffset>>FRACBITS;
+					value = sides[line->sidenum[0]].textureoffset>>FRACBITS;
 
-				P_SetTarget(&dummy->target, mo);
-				A_CustomPower(dummy);
+				P_SetPower(mo->player, power, value);
 
-				if (bot) {
-					P_SetTarget(&dummy->target, bot);
-					A_CustomPower(dummy);
-				}
-				P_RemoveMobj(dummy);
+				if (bot)
+					P_SetPower(bot->player, power, value);
 			}
 			break;
 
diff --git a/src/p_spec.h b/src/p_spec.h
index 81fb0dfc0218bb9a080e6e35144dfc37adff722a..3d715d79184c28b7abe4c1b086b9fe6f34055ba3 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_telept.c b/src/p_telept.c
index 6bac5ad208e54f795288925342adb1becb05e021..cbbd0ff6bcffc7b91db3a5d6c7323d55394a4b4c 100644
--- a/src/p_telept.c
+++ b/src/p_telept.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_tick.c b/src/p_tick.c
index 55a16fd81cb8b582fd8ab53c2d64c4f18809b8e2..28ace92883a6c9d5078e1b1fe30098b07536639a 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_tick.h b/src/p_tick.h
index ae481c6a2aa10df64f680f33aca97b43b5b13bc6..d355bc6d756661e92f7dd2451732b6bbc6a66fbf 100644
--- a/src/p_tick.h
+++ b/src/p_tick.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/p_user.c b/src/p_user.c
index 55da3fdf0bcc1247b3b7a32ea7b4aa216d88f75e..943fbd9dfa8e918d4db50cdb03f76e8c33572723 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -1045,7 +1045,8 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 			fallbackspeed = FixedMul(4*FRACUNIT, player->mo->scale);
 		}
 
-		player->drawangle = ang + ANGLE_180;
+		if (player->pflags & PF_DIRECTIONCHAR)
+			player->drawangle = ang + ANGLE_180;
 		P_InstaThrust(player->mo, ang, fallbackspeed);
 	}
 
@@ -2007,6 +2008,24 @@ void P_SwitchShield(player_t *player, UINT16 shieldtype)
 	}
 }
 
+//
+// P_SetPower
+//
+// Sets a power and spawns a shield orb if required.
+//
+void P_SetPower(player_t *player, powertype_t power, UINT16 value)
+{
+	boolean spawnshield = false;
+
+	if (power == pw_shield && player->powers[pw_shield] != value)
+		spawnshield = true;
+
+	player->powers[power] = value;
+
+	if (spawnshield) //workaround for a bug
+		P_SpawnShieldOrb(player);
+}
+
 //
 // P_SpawnGhostMobj
 //
@@ -2032,12 +2051,22 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 
 	ghost->angle = (mobj->player ? mobj->player->drawangle : mobj->angle);
 	ghost->rollangle = mobj->rollangle;
+	
 	ghost->sprite = mobj->sprite;
 	ghost->sprite2 = mobj->sprite2;
 	ghost->frame = mobj->frame;
 	ghost->tics = -1;
 	ghost->frame &= ~FF_TRANSMASK;
 	ghost->frame |= tr_trans50<<FF_TRANSSHIFT;
+	
+	ghost->renderflags = mobj->renderflags;
+	ghost->blendmode = mobj->blendmode;
+	
+	ghost->spritexscale = mobj->spritexscale;
+	ghost->spriteyscale = mobj->spriteyscale;
+	ghost->spritexoffset = mobj->spritexoffset;
+	ghost->spriteyoffset = mobj->spriteyoffset;
+	
 	ghost->fuse = ghost->info->damage;
 	ghost->skin = mobj->skin;
 
@@ -6284,18 +6313,11 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 	if (player->exiting)
 		return;
 
+	if (!P_CheckMove(player->mo,
+				player->mo->x + player->mo->momx,
+				player->mo->y + player->mo->momy, true))
 	{
-		boolean notallowed;
-		mobj_t *hack = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_NULL);
-		hack->flags = MF_NOGRAVITY;
-		hack->radius = player->mo->radius;
-		hack->height = player->mo->height;
-		hack->z = player->mo->z;
-		P_SetThingPosition(hack);
-		notallowed = (!(P_TryMove(hack, player->mo->x+player->mo->momx, player->mo->y+player->mo->momy, true)));
-		P_RemoveMobj(hack);
-		if (notallowed)
-			return;
+		return;
 	}
 
 	{
@@ -6687,7 +6709,7 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 //
 static void P_DoNiGHTSCapsule(player_t *player)
 {
-	INT32 i, spherecount, totalduration, popduration, deductinterval, deductquantity, sphereresult, firstpoptic, startingspheres;
+	INT32 i, spherecount, totalduration, popduration, deductinterval, deductquantity, sphereresult, firstpoptic;
 	INT32 tictimer = ++player->capsule->extravalue2;
 
 	if (abs(player->mo->x-player->capsule->x) <= 3*FRACUNIT)
@@ -6810,15 +6832,20 @@ static void P_DoNiGHTSCapsule(player_t *player)
 
 				if (player->capsule->health > sphereresult && player->spheres > 0)
 				{
+					// If spherecount isn't a multiple of deductquantity, the final deduction might steal too many spheres from the player
+					// E.g. with 80 capsule health, deductquantity is 3, 3*26 is 78, 78+3=81, and then it'll have stolen more than the 80 that it was meant to!
+					// So let's adjust deductquantity accordingly for the final deduction
+					deductquantity = min(deductquantity, player->capsule->health - sphereresult);
+
 					player->spheres -= deductquantity;
 					player->capsule->health -= deductquantity;
-				}
 
-				if (player->spheres < 0)
-					player->spheres = 0;
+					if (player->spheres < 0) // This can't happen... without Lua, setrings, et cetera
+						player->spheres = 0;
 
-				if (player->capsule->health < sphereresult)
-					player->capsule->health = sphereresult;
+					//if (player->capsule->health < sphereresult) // This can't happen
+						//player->capsule->health = sphereresult;
+				}
 			}
 
 			// Spawn a 'pop' for every 2 tics
@@ -6839,9 +6866,8 @@ static void P_DoNiGHTSCapsule(player_t *player)
 				}
 				else
 				{
-					startingspheres = player->spheres - player->capsule->health;
+					player->spheres -= player->capsule->health;
 					player->capsule->health = 0;
-					player->spheres = startingspheres;
 				}
 			}
 
@@ -11444,6 +11470,8 @@ void P_PlayerThink(player_t *player)
 		{
 			if (B_CheckRespawn(player))
 				player->playerstate = PST_REBORN;
+			else
+				B_HandleFlightIndicator(player);
 		}
 		if (player->playerstate == PST_REBORN)
 		{
diff --git a/src/r_bsp.c b/src/r_bsp.c
index b8559d39e54cb5debb00878487bae7b9b7a5a29c..c9f2698166aed732ac9c9d69834b1d305809466f 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_bsp.h b/src/r_bsp.h
index 40d24ffece796beb2fcdb1cd2cb5289ad30206ed..88757cf4bccfd6d2b16812c4a889aa19ea32f384 100644
--- a/src/r_bsp.h
+++ b/src/r_bsp.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_data.c b/src/r_data.c
index 2cfe9cb7ace3139d40a32a9d8dd0ba21afa9b794..51ed15dd6961f4e7b0cddd705fd8a9d032a3360d 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_data.h b/src/r_data.h
index 7580a94ea0eeab4d446b5b671e9dc760ff69a52b..63772e7b08380ed402b1faf7706642e0ca39dbfb 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_defs.h b/src/r_defs.h
index 8bb0b4b9fed3a0dc7caf29991c5bff3480a2dcd7..d4b82d39607b474b9b9beec6917de058c44dfab8 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -412,6 +412,7 @@ typedef struct line_s
 	vertex_t *v2;
 
 	fixed_t dx, dy; // Precalculated v2 - v1 for side checking.
+	angle_t angle; // Precalculated angle between dx and dy
 
 	// Animation related.
 	INT16 flags;
diff --git a/src/r_draw.c b/src/r_draw.c
index 65bb87bfb54de7a8002717c94c868972803409ee..e12d7ebdd46027973fcaa45f6c0ad4eeacca6ceb 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_draw.h b/src/r_draw.h
index 2173c7a5a36e5c9b92063657aa833dbb5b457726..c96b29e3016dfc464c09197786c0eeba283d552d 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -170,6 +170,7 @@ void R_DrawViewBorder(void);
 void R_DrawColumn_8(void);
 void R_DrawShadeColumn_8(void);
 void R_DrawTranslucentColumn_8(void);
+void R_DrawDropShadowColumn_8(void);
 void R_DrawTranslatedColumn_8(void);
 void R_DrawTranslatedTranslucentColumn_8(void);
 void R_Draw2sMultiPatchColumn_8(void);
diff --git a/src/r_draw16.c b/src/r_draw16.c
index 1a2fed77316b6fd80c6b91f5d1357e1b47eee99f..763fd1631e7ac57208c6901adc0cd57df4c71e16 100644
--- a/src/r_draw16.c
+++ b/src/r_draw16.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_draw8.c b/src/r_draw8.c
index b8a63d5c042d7ce49f8480bac269b763c43f5dd6..c9a9e957502ba09444fb9b9f3aca8f51540d1d01 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -416,6 +416,39 @@ void R_DrawTranslucentColumn_8(void)
 	}
 }
 
+// Hack: A cut-down copy of R_DrawTranslucentColumn_8 that does not read texture
+// data since something about calculating the texture reading address for drop shadows is broken.
+// dc_texturemid and dc_iscale get wrong values for drop shadows, however those are not strictly
+// needed for the current design of the shadows, so this function bypasses the issue
+// by not using those variables at all.
+void R_DrawDropShadowColumn_8(void)
+{
+	register INT32 count;
+	register UINT8 *dest;
+
+	count = dc_yh - dc_yl + 1;
+
+	if (count <= 0) // Zero length, column does not exceed a pixel.
+		return;
+
+	dest = &topleft[dc_yl*vid.width + dc_x];
+
+	{
+#define DSCOLOR 31 // palette index for the color of the shadow
+		register const UINT8 *transmap_offset = dc_transmap + (dc_colormap[DSCOLOR] << 8);
+#undef DSCOLOR
+		while ((count -= 2) >= 0)
+		{
+			*dest = *(transmap_offset + (*dest));
+			dest += vid.width;
+			*dest = *(transmap_offset + (*dest));
+			dest += vid.width;
+		}
+		if (count & 1)
+			*dest = *(transmap_offset + (*dest));
+	}
+}
+
 /**	\brief The R_DrawTranslatedTranslucentColumn_8 function
 	Spiffy function. Not only does it colormap a sprite, but does translucency as well.
 	Uber-kudos to Cyan Helkaraxe
diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c
index 2433cb4024295401017fae6b08394a7db7d0d0df..49ec28dd8d0c7a8394c614ad3900ab88a5d0c7b9 100644
--- a/src/r_draw8_npo2.c
+++ b/src/r_draw8_npo2.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -992,6 +992,9 @@ void R_DrawTiltedFloorSprite_NPO2_8(void)
 	double endz, endu, endv;
 	UINT32 stepu, stepv;
 
+	struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth);
+	struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight);
+
 	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
 	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
 	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
@@ -1033,12 +1036,13 @@ void R_DrawTiltedFloorSprite_NPO2_8(void)
 
 			// Carefully align all of my Friends.
 			if (x < 0)
-				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+			else
+				x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
 			if (y < 0)
-				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
-
-			x %= ds_flatwidth;
-			y %= ds_flatheight;
+				y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+			else
+				y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
 
 			val = source[((y * ds_flatwidth) + x)];
 			if (val & 0xFF00)
@@ -1065,12 +1069,13 @@ void R_DrawTiltedFloorSprite_NPO2_8(void)
 
 				// Carefully align all of my Friends.
 				if (x < 0)
-					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+					x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+				else
+					x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
 				if (y < 0)
-					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
-
-				x %= ds_flatwidth;
-				y %= ds_flatheight;
+					y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+				else
+					y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
 
 				val = source[((y * ds_flatwidth) + x)];
 				if (val & 0xFF00)
@@ -1101,12 +1106,13 @@ void R_DrawTiltedFloorSprite_NPO2_8(void)
 
 				// Carefully align all of my Friends.
 				if (x < 0)
-					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+					x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+				else
+					x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
 				if (y < 0)
-					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
-
-				x %= ds_flatwidth;
-				y %= ds_flatheight;
+					y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+				else
+					y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
 
 				val = source[((y * ds_flatwidth) + x)];
 				if (val & 0xFF00)
@@ -1142,6 +1148,9 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
 	double endz, endu, endv;
 	UINT32 stepu, stepv;
 
+	struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth);
+	struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight);
+
 	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
 	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
 	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
@@ -1183,12 +1192,13 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
 
 			// Carefully align all of my Friends.
 			if (x < 0)
-				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+			else
+				x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
 			if (y < 0)
-				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
-
-			x %= ds_flatwidth;
-			y %= ds_flatheight;
+				y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+			else
+				y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
 
 			val = source[((y * ds_flatwidth) + x)];
 			if (val & 0xFF00)
@@ -1215,12 +1225,13 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
 
 				// Carefully align all of my Friends.
 				if (x < 0)
-					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+					x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+				else
+					x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
 				if (y < 0)
-					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
-
-				x %= ds_flatwidth;
-				y %= ds_flatheight;
+					y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+				else
+					y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
 
 				val = source[((y * ds_flatwidth) + x)];
 				if (val & 0xFF00)
@@ -1251,12 +1262,13 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
 
 				// Carefully align all of my Friends.
 				if (x < 0)
-					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+					x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+				else
+					x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
 				if (y < 0)
-					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
-
-				x %= ds_flatwidth;
-				y %= ds_flatheight;
+					y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+				else
+					y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
 
 				val = source[((y * ds_flatwidth) + x)];
 				if (val & 0xFF00)
diff --git a/src/r_local.h b/src/r_local.h
index ba78ea87dbae64b69864e8afcbd05915113e6af5..a5b590e5cea24dff0cb63952241ce8b8bd9975fb 100644
--- a/src/r_local.h
+++ b/src/r_local.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_main.c b/src/r_main.c
index 8729b5dcb36ccedb8aed999dbf3afcb60e0faf04..13d2413fae4611ab4a1079e08e260466790b1529 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -1450,7 +1450,7 @@ static void Mask_Post (maskcount_t* m)
 
 void R_RenderPlayerView(player_t *player)
 {
-	UINT8			nummasks	= 1;
+	INT32			nummasks	= 1;
 	maskcount_t*	masks		= malloc(sizeof(maskcount_t));
 
 	if (cv_homremoval.value && player == &players[displayplayer]) // if this is display player 1
diff --git a/src/r_main.h b/src/r_main.h
index 5f3bed9803102cb7592ac755ef09f30870cc3f9f..c0edb31b30175295ecbf9fba247ef49a699953b9 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_patch.c b/src/r_patch.c
index 6827cd12c75d397fdaa771f3373c5d4812a83952..e771e5c94d4ed4a381ee7ddeb937a5e146ecef5f 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2020-2022 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_patch.h b/src/r_patch.h
index 96fbb0e28c11d3bbd91f84edc999a7d6d5223c70..26c28e1f9c4bbfb2a1b46b55e8fd737a0a0b3804 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2020-2022 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c
index 5dbc30286630c4f51eee55bb7fe2a8d57f4a10e4..b24e065ba6cfb172ce926805e4557c11b693a00e 100644
--- a/src/r_patchrotation.c
+++ b/src/r_patchrotation.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2020-2022 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_patchrotation.h b/src/r_patchrotation.h
index 689b7d411b63d0143804d789da0501fb1f0d1415..e6bee80edd77392f60a6880aeafb0f1984ee352d 100644
--- a/src/r_patchrotation.h
+++ b/src/r_patchrotation.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2020-2022 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_picformats.c b/src/r_picformats.c
index 5c81d1e02186902818415c84088fcfc182235681..6aa4659b9ea7288272d0d779f007391955a5bba6 100644
--- a/src/r_picformats.c
+++ b/src/r_picformats.c
@@ -2,8 +2,8 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 2005-2009 by Andrey "entryway" Budko.
-// Copyright (C) 2018-2021 by Jaime "Lactozilla" Passos.
-// Copyright (C) 2019-2021 by Sonic Team Junior.
+// Copyright (C) 2018-2022 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2019-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_picformats.h b/src/r_picformats.h
index c74f8a13a60a2c2602d656c5dad6920360f87d9c..f3080479fc9c4555004173a6ac43cd7d0f3ef0b6 100644
--- a/src/r_picformats.h
+++ b/src/r_picformats.h
@@ -1,8 +1,8 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
-// Copyright (C) 2018-2021 by Jaime "Lactozilla" Passos.
-// Copyright (C) 2019-2021 by Sonic Team Junior.
+// Copyright (C) 2018-2022 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2019-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -105,6 +105,7 @@ typedef struct
 } spriteinfo_t;
 
 // Portable Network Graphics
+#define PNG_HEADER_SIZE (8)
 boolean Picture_IsLumpPNG(const UINT8 *d, size_t s);
 #define Picture_ThrowPNGError(lumpname, wadfilename) I_Error("W_Wad: Lump \"%s\" in file \"%s\" is a .png - please convert to either Doom or Flat (raw) image format.", lumpname, wadfilename); // Fears Of LJ Sonic
 
diff --git a/src/r_plane.c b/src/r_plane.c
index 34073e0e7e7ad16265d737aac0dfa7324552dadc..7ea10f61676530486a798ed36920b10ca54f7f18 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_plane.h b/src/r_plane.h
index 862b95069ddacdd85382ef7f55c476a592b4177b..09648feadc938bdb66e9d03403a401c543a50c96 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_portal.c b/src/r_portal.c
index 3026f4e4c0a99ea9cde1503eb90952e06b49a26b..4d413213373c1e98522862e9e0e241396a4627b4 100644
--- a/src/r_portal.c
+++ b/src/r_portal.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -132,6 +132,7 @@ static portal_t* Portal_Add (const INT16 x1, const INT16 x2)
 
 void Portal_Remove (portal_t* portal)
 {
+	portalcullsector = NULL;
 	portal_base = portal->next;
 	Z_Free(portal->ceilingclip);
 	Z_Free(portal->floorclip);
diff --git a/src/r_portal.h b/src/r_portal.h
index 0effd07b5b272e5f799dc04848c6f66ef8dbdede..687ee058f0f4cf9e5c0e4d4c8b6d705cf4c32834 100644
--- a/src/r_portal.h
+++ b/src/r_portal.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_segs.c b/src/r_segs.c
index 157cf466e6f05385ef324f5fdf87ce57e76245c0..41ffa4103aab482ceda2070afd595c5e2d6156d1 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_segs.h b/src/r_segs.h
index da7d44ad4689f0c28da9e0554caba54e7b8aa0ba..4075cc0bba99fcbce78ad97f1e95826f84bbe1ad 100644
--- a/src/r_segs.h
+++ b/src/r_segs.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_skins.c b/src/r_skins.c
index 86c0bbc544b7907f30b6ed4ef6c07326f8669a98..cd53128d2297558c48ed7d79d026d081f0c1b8a1 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_skins.h b/src/r_skins.h
index a38997f4dd623aad8dc7cdf21aff1283f8c8aa93..aeaa9f3e05a328ebd1e299190fe1179220191e33 100644
--- a/src/r_skins.h
+++ b/src/r_skins.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_sky.c b/src/r_sky.c
index 041cccfc5546f679894a7d7ab2e6cc93c0d8c8c4..e21b7cbf16b53720dddf75a3d5b014ead890f56a 100644
--- a/src/r_sky.c
+++ b/src/r_sky.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_sky.h b/src/r_sky.h
index f4356dcfae3f4f6e47248bb7d6ef46e21495fa31..31c821d2204c14d37bede811d25e4d25916e8ea0 100644
--- a/src/r_sky.h
+++ b/src/r_sky.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_splats.c b/src/r_splats.c
index c554e9b1f002937671e19b1e043460d3168c3e2e..0a84a3a336b6a7f7fad2bf80d025fa2163f18b02 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -482,7 +482,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 			continue;
 
 		for (i = x1; i <= x2; i++)
-			cliptab[i] = (y >= mfloorclip[i]);
+			cliptab[i] = (y >= mfloorclip[i] || y <= mceilingclip[i]);
 
 		// clip left
 		while (cliptab[x1])
diff --git a/src/r_splats.h b/src/r_splats.h
index 7e31406d1290e94861a1f660299a05be3dfc8670..ec6885e269249cb7090a771e90093b4d645d73a7 100644
--- a/src/r_splats.h
+++ b/src/r_splats.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_state.h b/src/r_state.h
index 5a606ed8c9fa2804f5802d8834a1f7d85b963260..69989e7ac36a5540c6e99fc514d61948d1a92633 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/r_textures.c b/src/r_textures.c
index 793e5237f62e64e937c32f97d4d01bbb08a6c065..03f8f53a50a9e80f7df130a425a1331801deb876 100644
--- a/src/r_textures.c
+++ b/src/r_textures.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -59,6 +59,7 @@ INT32 *texturetranslation;
 // Painfully simple texture id cacheing to make maps load faster. :3
 static struct {
 	char name[9];
+	UINT32 hash;
 	INT32 id;
 } *tidcache = NULL;
 static INT32 tidcachelen = 0;
@@ -725,6 +726,7 @@ Rloadflats (INT32 i, INT32 w)
 	UINT16 texstart, texend;
 	texture_t *texture;
 	texpatch_t *patch;
+	UINT8 header[PNG_HEADER_SIZE];
 
 	// Yes
 	if (W_FileHasFolders(wadfiles[w]))
@@ -743,7 +745,6 @@ Rloadflats (INT32 i, INT32 w)
 		// Work through each lump between the markers in the WAD.
 		for (j = 0; j < (texend - texstart); j++)
 		{
-			UINT8 *flatlump;
 			UINT16 wadnum = (UINT16)w;
 			lumpnum_t lumpnum = texstart + j;
 			size_t lumplength;
@@ -755,7 +756,7 @@ Rloadflats (INT32 i, INT32 w)
 					continue; // If it is then SKIP IT
 			}
 
-			flatlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+			W_ReadLumpHeaderPwad(wadnum, lumpnum, header, sizeof header, 0);
 			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
 
 			switch (lumplength)
@@ -788,14 +789,17 @@ Rloadflats (INT32 i, INT32 w)
 
 			// Set texture properties.
 			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
+			texture->hash = quickncasehash(texture->name, 8);
 
 #ifndef NO_PNG_LUMPS
-			if (Picture_IsLumpPNG((UINT8 *)flatlump, lumplength))
+			if (Picture_IsLumpPNG(header, lumplength))
 			{
+				UINT8 *flatlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
 				INT32 width, height;
 				Picture_PNGDimensions((UINT8 *)flatlump, &width, &height, NULL, NULL, lumplength);
 				texture->width = (INT16)width;
 				texture->height = (INT16)height;
+				Z_Free(flatlump);
 			}
 			else
 #endif
@@ -814,8 +818,6 @@ Rloadflats (INT32 i, INT32 w)
 			patch->lump = texstart + j;
 			patch->flip = 0;
 
-			Z_Unlock(flatlump);
-
 			texturewidth[i] = texture->width;
 			textureheight[i] = texture->height << FRACBITS;
 			i++;
@@ -835,8 +837,8 @@ Rloadtextures (INT32 i, INT32 w)
 	UINT16 j;
 	UINT16 texstart, texend, texturesLumpPos;
 	texture_t *texture;
-	softwarepatch_t *patchlump;
 	texpatch_t *patch;
+	softwarepatch_t patchlump;
 
 	// Get the lump numbers for the markers in the WAD, if they exist.
 	if (W_FileHasFolders(wadfiles[w]))
@@ -876,7 +878,7 @@ Rloadtextures (INT32 i, INT32 w)
 					continue; // If it is then SKIP IT
 			}
 
-			patchlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+			W_ReadLumpHeaderPwad(wadnum, lumpnum, &patchlump, PNG_HEADER_SIZE, 0);
 #ifndef NO_PNG_LUMPS
 			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
 #endif
@@ -886,20 +888,23 @@ Rloadtextures (INT32 i, INT32 w)
 
 			// Set texture properties.
 			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
+			texture->hash = quickncasehash(texture->name, 8);
 
 #ifndef NO_PNG_LUMPS
-			if (Picture_IsLumpPNG((UINT8 *)patchlump, lumplength))
+			if (Picture_IsLumpPNG((UINT8 *)&patchlump, lumplength))
 			{
+				UINT8 *png = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
 				INT32 width, height;
-				Picture_PNGDimensions((UINT8 *)patchlump, &width, &height, NULL, NULL, lumplength);
+				Picture_PNGDimensions(png, &width, &height, NULL, NULL, lumplength);
 				texture->width = (INT16)width;
 				texture->height = (INT16)height;
+				Z_Free(png);
 			}
 			else
 #endif
 			{
-				texture->width = SHORT(patchlump->width);
-				texture->height = SHORT(patchlump->height);
+				texture->width = SHORT(patchlump.width);
+				texture->height = SHORT(patchlump.height);
 			}
 
 			texture->type = TEXTURETYPE_SINGLEPATCH;
@@ -915,8 +920,6 @@ Rloadtextures (INT32 i, INT32 w)
 			patch->lump = texstart + j;
 			patch->flip = 0;
 
-			Z_Unlock(patchlump);
-
 			texturewidth[i] = texture->width;
 			textureheight[i] = texture->height << FRACBITS;
 			i++;
@@ -926,28 +929,54 @@ Rloadtextures (INT32 i, INT32 w)
 	return i;
 }
 
-//
-// R_LoadTextures
-// Initializes the texture list with the textures from the world map.
-//
-void R_LoadTextures(void)
+static INT32
+count_range
+(		const char * marker_start,
+		const char * marker_end,
+		const char * folder,
+		UINT16 wadnum)
 {
-	INT32 i, w;
 	UINT16 j;
-	UINT16 texstart, texend, texturesLumpPos;
+	UINT16 texstart, texend;
+	INT32 count = 0;
 
-	// Free previous memory before numtextures change.
-	if (numtextures)
+	// Count flats
+	if (W_FileHasFolders(wadfiles[wadnum]))
 	{
-		for (i = 0; i < numtextures; i++)
+		texstart = W_CheckNumForFolderStartPK3(folder, wadnum, 0);
+		texend = W_CheckNumForFolderEndPK3(folder, wadnum, texstart);
+	}
+	else
+	{
+		texstart = W_CheckNumForMarkerStartPwad(marker_start, wadnum, 0);
+		texend = W_CheckNumForNamePwad(marker_end, wadnum, texstart);
+	}
+
+	if (texstart != INT16_MAX && texend != INT16_MAX)
+	{
+		// PK3s have subfolders, so we can't just make a simple sum
+		if (W_FileHasFolders(wadfiles[wadnum]))
 		{
-			Z_Free(textures[i]);
-			Z_Free(texturecache[i]);
+			for (j = texstart; j < texend; j++)
+			{
+				if (!W_IsLumpFolder(wadnum, j)) // Check if lump is a folder; if not, then count it
+					count++;
+			}
+		}
+		else // Add all the textures between markers
+		{
+			count += (texend - texstart);
 		}
-		Z_Free(texturetranslation);
-		Z_Free(textures);
 	}
 
+	return count;
+}
+
+static INT32 R_CountTextures(UINT16 wadnum)
+{
+	UINT16 texturesLumpPos;
+	INT32 count = 0;
+
 	// Load patches and textures.
 
 	// Get the number of textures to check.
@@ -955,106 +984,88 @@ void R_LoadTextures(void)
 	// the markers.
 	// This system will allocate memory for all duplicate/patched textures even if it never uses them,
 	// but the alternative is to spend a ton of time checking and re-checking all previous entries just to skip any potentially patched textures.
-	for (w = 0, numtextures = 0; w < numwadfiles; w++)
-	{
+
 #ifdef WALLFLATS
-		// Count flats
-		if (W_FileHasFolders(wadfiles[w]))
-		{
-			texstart = W_CheckNumForFolderStartPK3("flats/", (UINT16)w, 0);
-			texend = W_CheckNumForFolderEndPK3("flats/", (UINT16)w, texstart);
-		}
-		else
-		{
-			texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
-			texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
-		}
+	count += count_range("F_START", "F_END", "flats/", wadnum);
+#endif
 
-		if (!( texstart == INT16_MAX || texend == INT16_MAX ))
-		{
-			// PK3s have subfolders, so we can't just make a simple sum
-			if (W_FileHasFolders(wadfiles[w]))
-			{
-				for (j = texstart; j < texend; j++)
-				{
-					if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
-						numtextures++;
-				}
-			}
-			else // Add all the textures between F_START and F_END
-			{
-				numtextures += (UINT32)(texend - texstart);
-			}
-		}
-#endif/*WALLFLATS*/
+	// Count the textures from TEXTURES lumps
+	texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", wadnum, 0);
 
-		// Count the textures from TEXTURES lumps
-		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
-		while (texturesLumpPos != INT16_MAX)
-		{
-			numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
-			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
-		}
+	while (texturesLumpPos != INT16_MAX)
+	{
+		count += R_CountTexturesInTEXTURESLump(wadnum, texturesLumpPos);
+		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", wadnum, texturesLumpPos + 1);
+	}
 
-		// Count single-patch textures
-		if (W_FileHasFolders(wadfiles[w]))
-		{
-			texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
-			texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
-		}
-		else
-		{
-			texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
-			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
-		}
+	// Count single-patch textures
+	count += count_range(TX_START, TX_END, "textures/", wadnum);
 
-		if (texstart == INT16_MAX || texend == INT16_MAX)
-			continue;
+	return count;
+}
 
-		// PK3s have subfolders, so we can't just make a simple sum
-		if (W_FileHasFolders(wadfiles[w]))
-		{
-			for (j = texstart; j < texend; j++)
-			{
-				if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
-					numtextures++;
-			}
-		}
-		else // Add all the textures between TX_START and TX_END
-		{
-			numtextures += (UINT32)(texend - texstart);
-		}
-	}
+static void
+recallocuser
+(		void * user,
+		size_t old,
+		size_t new)
+{
+	char *p = Z_Realloc(*(void**)user,
+			new, PU_STATIC, user);
 
-	// If no textures found by this point, bomb out
-	if (!numtextures)
-		I_Error("No textures detected in any WADs!\n");
+	if (new > old)
+		memset(&p[old], 0, (new - old));
+}
+
+static void R_AllocateTextures(INT32 add)
+{
+	const INT32 newtextures = (numtextures + add);
+	const size_t newsize = newtextures * sizeof (void*);
+	const size_t oldsize = numtextures * sizeof (void*);
+
+	INT32 i;
 
 	// Allocate memory and initialize to 0 for all the textures we are initialising.
-	// There are actually 5 buffers allocated in one for convenience.
-	textures = Z_Calloc((numtextures * sizeof(void *)) * 5, PU_STATIC, NULL);
+	recallocuser(&textures, oldsize, newsize);
 
 	// Allocate texture column offset table.
-	texturecolumnofs = (void *)((UINT8 *)textures + (numtextures * sizeof(void *)));
+	recallocuser(&texturecolumnofs, oldsize, newsize);
 	// Allocate texture referencing cache.
-	texturecache     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 2));
+	recallocuser(&texturecache, oldsize, newsize);
 	// Allocate texture width table.
-	texturewidth     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 3));
+	recallocuser(&texturewidth, oldsize, newsize);
 	// Allocate texture height table.
-	textureheight    = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 4));
+	recallocuser(&textureheight, oldsize, newsize);
 	// Create translation table for global animation.
-	texturetranslation = Z_Malloc((numtextures + 1) * sizeof(*texturetranslation), PU_STATIC, NULL);
+	Z_Realloc(texturetranslation, (newtextures + 1) * sizeof(*texturetranslation), PU_STATIC, &texturetranslation);
 
-	for (i = 0; i < numtextures; i++)
-		texturetranslation[i] = i;
+	for (i = 0; i < numtextures; ++i)
+	{
+		// R_FlushTextureCache relies on the user for
+		// Z_Free, texturecache has been reallocated so the
+		// user is now garbage memory.
+		Z_SetUser(texturecache[i],
+				(void**)&texturecache[i]);
+	}
 
-	for (i = 0, w = 0; w < numwadfiles; w++)
+	while (i < newtextures)
 	{
+		texturetranslation[i] = i;
+		i++;
+	}
+}
+
+static INT32 R_DefineTextures(INT32 i, UINT16 w)
+{
 #ifdef WALLFLATS
-		i = Rloadflats(i, w);
+	i = Rloadflats(i, w);
 #endif
-		i = Rloadtextures(i, w);
-	}
+	return Rloadtextures(i, w);
+}
+
+static void R_FinishLoadingTextures(INT32 add)
+{
+	numtextures += add;
 
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
@@ -1062,6 +1073,43 @@ void R_LoadTextures(void)
 #endif
 }
 
+//
+// R_LoadTextures
+// Initializes the texture list with the textures from the world map.
+//
+void R_LoadTextures(void)
+{
+	INT32 i, w;
+	INT32 newtextures = 0;
+
+	for (w = 0; w < numwadfiles; w++)
+	{
+		newtextures += R_CountTextures((UINT16)w);
+	}
+
+	// If no textures found by this point, bomb out
+	if (!newtextures)
+		I_Error("No textures detected in any WADs!\n");
+
+	R_AllocateTextures(newtextures);
+
+	for (i = 0, w = 0; w < numwadfiles; w++)
+	{
+		i = R_DefineTextures(i, w);
+	}
+
+	R_FinishLoadingTextures(newtextures);
+}
+
+void R_LoadTexturesPwad(UINT16 wadnum)
+{
+	INT32 newtextures = R_CountTextures(wadnum);
+
+	R_AllocateTextures(newtextures);
+	R_DefineTextures(numtextures, wadnum);
+	R_FinishLoadingTextures(newtextures);
+}
+
 static texpatch_t *R_ParsePatch(boolean actuallyLoadPatch)
 {
 	char *texturesToken;
@@ -1368,6 +1416,7 @@ static texture_t *R_ParseTexture(boolean actuallyLoadTexture)
 			// Allocate memory for a zero-patch texture. Obviously, we'll be adding patches momentarily.
 			resultTexture = (texture_t *)Z_Calloc(sizeof(texture_t),PU_STATIC,NULL);
 			M_Memcpy(resultTexture->name, newTextureName, 8);
+			resultTexture->hash = quickncasehash(newTextureName, 8);
 			resultTexture->width = newTextureWidth;
 			resultTexture->height = newTextureHeight;
 			resultTexture->type = TEXTURETYPE_COMPOSITE;
@@ -1594,19 +1643,22 @@ void R_ClearTextureNumCache(boolean btell)
 INT32 R_CheckTextureNumForName(const char *name)
 {
 	INT32 i;
+	UINT32 hash;
 
 	// "NoTexture" marker.
 	if (name[0] == '-')
 		return 0;
 
+	hash = quickncasehash(name, 8);
+
 	for (i = 0; i < tidcachelen; i++)
-		if (!strncasecmp(tidcache[i].name, name, 8))
+		if (tidcache[i].hash == hash && !strncasecmp(tidcache[i].name, name, 8))
 			return tidcache[i].id;
 
 	// Need to parse the list backwards, so textures loaded more recently are used in lieu of ones loaded earlier
 	//for (i = 0; i < numtextures; i++) <- old
 	for (i = (numtextures - 1); i >= 0; i--) // <- new
-		if (!strncasecmp(textures[i]->name, name, 8))
+		if (textures[i]->hash == hash && !strncasecmp(textures[i]->name, name, 8))
 		{
 			tidcachelen++;
 			Z_Realloc(tidcache, tidcachelen * sizeof(*tidcache), PU_STATIC, &tidcache);
@@ -1615,6 +1667,7 @@ INT32 R_CheckTextureNumForName(const char *name)
 #ifndef ZDEBUG
 			CONS_Debug(DBG_SETUP, "texture #%s: %s\n", sizeu1(tidcachelen), tidcache[tidcachelen-1].name);
 #endif
+			tidcache[tidcachelen-1].hash = hash;
 			tidcache[tidcachelen-1].id = i;
 			return i;
 		}
diff --git a/src/r_textures.h b/src/r_textures.h
index dd286b6ac57c7082f57bece9445d9c1695958d13..9aa11ad4d9da7fce8b85b961d24b1f2109d3429d 100644
--- a/src/r_textures.h
+++ b/src/r_textures.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -54,6 +54,7 @@ typedef struct
 {
 	// Keep name for switch changing, etc.
 	char name[8];
+	UINT32 hash;
 	UINT8 type; // TEXTURETYPE_
 	INT16 width, height;
 	boolean holes;
@@ -76,6 +77,7 @@ extern UINT8 **texturecache; // graphics data for each generated full-size textu
 
 // Load TEXTURES definitions, create lookup tables
 void R_LoadTextures(void);
+void R_LoadTexturesPwad(UINT16 wadnum);
 void R_FlushTextureCache(void);
 
 // Texture generation
diff --git a/src/r_things.c b/src/r_things.c
index accd1e2b3cd16795a27700d23f128d25e3c99fbb..db4263a6aca9819f1de6e4ebbd5bf11308d5240e 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -837,6 +837,12 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	else if (vis->mobj->sprite == SPR_PLAY) // Looks like a player, but doesn't have a color? Get rid of green sonic syndrome.
 		colfunc = colfuncs[COLDRAWFUNC_TRANS];
 
+	// Hack: Use a special column function for drop shadows that bypasses
+	// invalid memory access crashes caused by R_ProjectDropShadow putting wrong values
+	// in dc_texturemid and dc_iscale when the shadow is sloped.
+	if (vis->cut & SC_SHADOW)
+		colfunc = R_DrawDropShadowColumn_8;
+
 	if (vis->extra_colormap && !(vis->renderflags & RF_NOCOLORMAPS))
 	{
 		if (!dc_colormap)
@@ -1269,6 +1275,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	vissprite_t *shadow;
 	patch_t *patch;
 	fixed_t xscale, yscale, shadowxscale, shadowyscale, shadowskew, x1, x2;
+	INT32 heightsec, phs;
 	INT32 light = 0;
 	fixed_t scalemul; UINT8 trans;
 	fixed_t floordiff;
@@ -1280,6 +1287,24 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 
 	if (abs(groundz-viewz)/tz > 4) return; // Prevent stretchy shadows and possible crashes
 
+	heightsec = thing->subsector->sector->heightsec;
+	if (viewplayer->mo && viewplayer->mo->subsector)
+		phs = viewplayer->mo->subsector->sector->heightsec;
+	else
+		phs = -1;
+
+	if (heightsec != -1 && phs != -1) // only clip things which are in special sectors
+	{
+		if (viewz < sectors[phs].floorheight ?
+		groundz >= sectors[heightsec].floorheight :
+		groundz < sectors[heightsec].floorheight)
+			return;
+		if (viewz > sectors[phs].ceilingheight ?
+		groundz < sectors[heightsec].ceilingheight && viewz >= sectors[heightsec].ceilingheight :
+		groundz >= sectors[heightsec].ceilingheight)
+			return;
+	}
+
 	floordiff = abs((isflipped ? thing->height : 0) + thing->z - groundz);
 
 	trans = floordiff / (100*FRACUNIT) + 3;
@@ -1586,7 +1611,16 @@ static void R_ProjectSprite(mobj_t *thing)
 	if (thing->rollangle
 	&& !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)))
 	{
-		rollangle = R_GetRollAngle(thing->rollangle);
+		if (papersprite && ang >= ANGLE_180)
+		{
+			// Makes Software act much more sane like OpenGL
+			rollangle = R_GetRollAngle(InvAngle(thing->rollangle));
+		}
+		else
+		{
+			rollangle = R_GetRollAngle(thing->rollangle);
+		}
+
 		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle);
 
 		if (rotsprite != NULL)
@@ -1936,13 +1970,19 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	if (heightsec != -1 && phs != -1) // only clip things which are in special sectors
 	{
+		fixed_t top = gzt;
+		fixed_t bottom = thing->z;
+
+		if (splat)
+			top = bottom;
+
 		if (viewz < sectors[phs].floorheight ?
-		thing->z >= sectors[heightsec].floorheight :
-		gzt < sectors[heightsec].floorheight)
+		bottom >= sectors[heightsec].floorheight :
+		top < sectors[heightsec].floorheight)
 			return;
 		if (viewz > sectors[phs].ceilingheight ?
-		gzt < sectors[heightsec].ceilingheight && viewz >= sectors[heightsec].ceilingheight :
-		thing->z >= sectors[heightsec].ceilingheight)
+		top < sectors[heightsec].ceilingheight && viewz >= sectors[heightsec].ceilingheight :
+		bottom >= sectors[heightsec].ceilingheight)
 			return;
 	}
 
@@ -2816,6 +2856,57 @@ static void R_DrawPrecipitationSprite(vissprite_t *spr)
 	R_DrawPrecipitationVisSprite(spr);
 }
 
+//SoM: 3/17/2000: Clip sprites in water.
+static void R_HeightSecClip(vissprite_t *spr, INT32 x1, INT32 x2)
+{
+	fixed_t mh, h;
+	INT32 x, phs;
+
+	if (spr->heightsec == -1)
+		return;
+
+	if (spr->cut & (SC_SPLAT | SC_SHADOW) || spr->renderflags & RF_SHADOWDRAW)
+		return;
+
+	phs = viewplayer->mo->subsector->sector->heightsec;
+
+	if ((mh = sectors[spr->heightsec].floorheight) > spr->gz &&
+		(h = centeryfrac - FixedMul(mh -= viewz, spr->sortscale)) >= 0 &&
+		(h >>= FRACBITS) < viewheight)
+	{
+		if (mh <= 0 || (phs != -1 && viewz > sectors[phs].floorheight))
+		{                          // clip bottom
+			for (x = x1; x <= x2; x++)
+				if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+					spr->clipbot[x] = (INT16)h;
+		}
+		else						// clip top
+		{
+			for (x = x1; x <= x2; x++)
+				if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+					spr->cliptop[x] = (INT16)h;
+		}
+	}
+
+	if ((mh = sectors[spr->heightsec].ceilingheight) < spr->gzt &&
+		(h = centeryfrac - FixedMul(mh-viewz, spr->sortscale)) >= 0 &&
+		(h >>= FRACBITS) < viewheight)
+	{
+		if (phs != -1 && viewz >= sectors[phs].ceilingheight)
+		{                         // clip bottom
+			for (x = x1; x <= x2; x++)
+				if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+					spr->clipbot[x] = (INT16)h;
+		}
+		else                       // clip top
+		{
+			for (x = x1; x <= x2; x++)
+				if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+					spr->cliptop[x] = (INT16)h;
+		}
+	}
+}
+
 // R_ClipVisSprite
 // Clips vissprites without drawing, so that portals can work. -Red
 void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, portal_t* portal)
@@ -2917,47 +3008,9 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 			}
 		}
 	}
-	//SoM: 3/17/2000: Clip sprites in water.
-	if (spr->heightsec != -1)  // only things in specially marked sectors
-	{
-		fixed_t mh, h;
-		INT32 phs = viewplayer->mo->subsector->sector->heightsec;
-		if ((mh = sectors[spr->heightsec].floorheight) > spr->gz &&
-			(h = centeryfrac - FixedMul(mh -= viewz, spr->sortscale)) >= 0 &&
-			(h >>= FRACBITS) < viewheight)
-		{
-			if (mh <= 0 || (phs != -1 && viewz > sectors[phs].floorheight))
-			{                          // clip bottom
-				for (x = x1; x <= x2; x++)
-					if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
-						spr->clipbot[x] = (INT16)h;
-			}
-			else						// clip top
-			{
-				for (x = x1; x <= x2; x++)
-					if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
-						spr->cliptop[x] = (INT16)h;
-			}
-		}
 
-		if ((mh = sectors[spr->heightsec].ceilingheight) < spr->gzt &&
-			(h = centeryfrac - FixedMul(mh-viewz, spr->sortscale)) >= 0 &&
-			(h >>= FRACBITS) < viewheight)
-		{
-			if (phs != -1 && viewz >= sectors[phs].ceilingheight)
-			{                         // clip bottom
-				for (x = x1; x <= x2; x++)
-					if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
-						spr->clipbot[x] = (INT16)h;
-			}
-			else                       // clip top
-			{
-				for (x = x1; x <= x2; x++)
-					if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
-						spr->cliptop[x] = (INT16)h;
-			}
-		}
-	}
+	R_HeightSecClip(spr, x1, x2);
+
 	if (spr->cut & SC_TOP && spr->cut & SC_BOTTOM)
 	{
 		for (x = x1; x <= x2; x++)
@@ -3001,13 +3054,25 @@ void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, p
 
 	if (portal)
 	{
-		for (x = x1; x <= x2; x++)
+		INT32 start_index = max(portal->start, x1);
+		INT32 end_index = min(portal->start + portal->end - portal->start, x2);
+		for (x = x1; x < start_index; x++)
+		{
+			spr->clipbot[x] = -1;
+			spr->cliptop[x] = -1;
+		}
+		for (x = start_index; x <= end_index; x++)
 		{
 			if (spr->clipbot[x] > portal->floorclip[x - portal->start])
 				spr->clipbot[x] = portal->floorclip[x - portal->start];
 			if (spr->cliptop[x] < portal->ceilingclip[x - portal->start])
 				spr->cliptop[x] = portal->ceilingclip[x - portal->start];
 		}
+		for (x = end_index + 1; x <= x2; x++)
+		{
+			spr->clipbot[x] = -1;
+			spr->cliptop[x] = -1;
+		}
 	}
 }
 
@@ -3168,10 +3233,10 @@ static void R_DrawMaskedList (drawnode_t* head)
 	}
 }
 
-void R_DrawMasked(maskcount_t* masks, UINT8 nummasks)
+void R_DrawMasked(maskcount_t* masks, INT32 nummasks)
 {
 	drawnode_t *heads;	/**< Drawnode lists; as many as number of views/portals. */
-	SINT8 i;
+	INT32 i;
 
 	heads = calloc(nummasks, sizeof(drawnode_t));
 
diff --git a/src/r_things.h b/src/r_things.h
index b1ff32b1ee4a3e723bb020f27a397c71e3f91297..857b03b24564be256e0fc8825b77e4ec6255e008 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -100,7 +100,7 @@ typedef struct
 	sector_t* viewsector;
 } maskcount_t;
 
-void R_DrawMasked(maskcount_t* masks, UINT8 nummasks);
+void R_DrawMasked(maskcount_t* masks, INT32 nummasks);
 
 // ----------
 // VISSPRITES
diff --git a/src/s_sound.c b/src/s_sound.c
index 76f0d67c16e490777c3e9c6e59bdf625e221945c..7e61e8a5585843240f5da219ab726157d35232a6 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/s_sound.h b/src/s_sound.h
index 8fcb816d906accd7f63b6756d8915327a97c4bc0..6223c4fdbaf3e3dca643be1a237e6b02792c4bb1 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/screen.c b/src/screen.c
index 770f1c8026aaf4fcb5dd9df97da55271f717b547..73af4313deab86ec5bdf4418f8428d6b035a4e70 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/screen.h b/src/screen.h
index 67880e2b964dc16a7693d754a6646bd031f14c04..37695316916ad839be0e8211a8531e8addf0180d 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index ccec37093698edbcc7865702fea0138db5996749..9de632734de97440b62e831a39c12a93020e7429 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -5,7 +5,7 @@
 //
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Portions Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -358,9 +358,10 @@ static void I_ReportSignal(int num, int coredumped)
 
 	I_OutputMsg("\nProcess killed by signal: %s\n\n", sigmsg);
 
-	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
-		"Process killed by signal",
-		sigmsg, NULL);
+	if (!M_CheckParm("-dedicated"))
+		SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
+			"Process killed by signal",
+			sigmsg, NULL);
 }
 
 #ifndef NEWSIGNALHANDLER
@@ -2202,9 +2203,10 @@ static void newsignalhandler_Warn(const char *pr)
 
 	I_OutputMsg("%s\n", text);
 
-	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
-		"Startup error",
-		text, NULL);
+	if (!M_CheckParm("-dedicated"))
+		SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
+			"Startup error",
+			text, NULL);
 
 	I_ShutdownConsole();
 	exit(-1);
@@ -2405,9 +2407,10 @@ void I_Error(const char *error, ...)
 			// Implement message box with SDL_ShowSimpleMessageBox,
 			// which should fail gracefully if it can't put a message box up
 			// on the target system
-			SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
-				"SRB2 "VERSIONSTRING" Recursive Error",
-				buffer, NULL);
+			if (!M_CheckParm("-dedicated"))
+				SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
+					"SRB2 "VERSIONSTRING" Recursive Error",
+					buffer, NULL);
 
 			W_Shutdown();
 			exit(-1); // recursive errors detected
@@ -2449,9 +2452,10 @@ void I_Error(const char *error, ...)
 	// Implement message box with SDL_ShowSimpleMessageBox,
 	// which should fail gracefully if it can't put a message box up
 	// on the target system
-	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
-		"SRB2 "VERSIONSTRING" Error",
-		buffer, NULL);
+	if (!M_CheckParm("-dedicated"))
+		SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
+			"SRB2 "VERSIONSTRING" Error",
+			buffer, NULL);
 	// Note that SDL_ShowSimpleMessageBox does *not* require SDL to be
 	// initialized at the time, so calling it after SDL_Quit() is
 	// perfectly okay! In addition, we do this on purpose so the
diff --git a/src/sdl/i_threads.c b/src/sdl/i_threads.c
index f73d00bcfc2ee70eba47f432812ec8cb4db7ec39..a182ae197c82ec2c97d1bca3c40d1c06f1dd70d1 100644
--- a/src/sdl/i_threads.c
+++ b/src/sdl/i_threads.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2020-2021 by James R.
+// Copyright (C) 2020-2022 by James R.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index ed766ff23dfb79395bbb5648211be73c45fc34b6..a27a5ebd2687b9e7c6dee529e3a40aa9c76f54e1 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -4,7 +4,7 @@
 //
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Portions Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index 35a79acc0b16ece0a457018db2df9d7a94b2781b..748cd374b6a54f5b65bb0637853648582163b16c 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index bdc693ca5306fee0a6ea20c13dc12f4a190b0821..67e98d4f5fe8c9bcbaebbe9558a1bc9b6a537c20 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 //
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
diff --git a/src/sdl/ogl_sdl.h b/src/sdl/ogl_sdl.h
index 8f87f688e36a4b897268c1f0fb3f55747779b908..9744bc6f12b8b3ae4babe4e97c05c889ab2a2291 100644
--- a/src/sdl/ogl_sdl.h
+++ b/src/sdl/ogl_sdl.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 //
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
diff --git a/src/sdl/sdl_sound.c b/src/sdl/sdl_sound.c
index 058b601c350072e93f41bae736a89f432d5d79cf..0de3788fe3f36db13dfe37e710abe0f95a2d84d8 100644
--- a/src/sdl/sdl_sound.c
+++ b/src/sdl/sdl_sound.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 //
 // Copyright (C) 1993-1996 by id Software, Inc.
-// Copyright (C) 2014-2021 by Sonic Team Junior.
+// Copyright (C) 2014-2022 by Sonic Team Junior.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
diff --git a/src/sdl/sdlmain.h b/src/sdl/sdlmain.h
index a9676b5c2f1261bbc7aab49d0ada71e4cba50f91..6b6e79d9756e20b6fd4faa1072f6dd45bdce0cd1 100644
--- a/src/sdl/sdlmain.h
+++ b/src/sdl/sdlmain.h
@@ -1,7 +1,7 @@
 // Emacs style mode select   -*- C++ -*-
 //-----------------------------------------------------------------------------
 //
-// Copyright (C) 2006-2021 by Sonic Team Junior.
+// Copyright (C) 2006-2022 by Sonic Team Junior.
 //
 // This program is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
diff --git a/src/sounds.c b/src/sounds.c
index 4c5b11ee98294cf21662512c87eb2c566cae7069..f7f3ad328c12f5e2dac4b06f949091c30996a786 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/sounds.h b/src/sounds.h
index 2dd37953c5750e87f2f904ffa11dc17d413611af..eec5186896d25b637065e4059acbcac3d6e3e784 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/st_stuff.c b/src/st_stuff.c
index f17b58fa62d0161d859c5371beb3d5c0a161c6da..6c9a0eeca1461dfdfa7630b0d33ab379673edfdd 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/st_stuff.h b/src/st_stuff.h
index b1ea2942d3be73188a1a8a4905f27b5a38b5758f..c59bc2ac69ebe9f73fdbe6b013d7b93e739090fe 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/strcasestr.c b/src/strcasestr.c
index 1cbee286a433a722dcb0b95a25fa5dccf64485c9..6a686d6dc8eabdac832168b00e7addf521746774 100644
--- a/src/strcasestr.c
+++ b/src/strcasestr.c
@@ -2,7 +2,7 @@
 strcasestr -- case insensitive substring searching function.
 */
 /*
-Copyright 2019-2021 James R.
+Copyright 2019-2022 James R.
 All rights reserved.
 
 Redistribution and use in source forms, with or without modification, is
diff --git a/src/string.c b/src/string.c
index f32025612283c02e7da2522be5c94fe8596efc56..5534a3f0ceb83b41edbaa6e3517eeb38f304e38d 100644
--- a/src/string.c
+++ b/src/string.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2006      by Graue.
-// Copyright (C) 2006-2021 by Sonic Team Junior.
+// Copyright (C) 2006-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/tables.c b/src/tables.c
index 9263f42d327f679e10a9a3899acd81eee4171ddf..13949b6a78c337a9183a7fd857c04135899acc7e 100644
--- a/src/tables.c
+++ b/src/tables.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/tables.h b/src/tables.h
index baa3adf36de62eb88cb31dfed7e2dcd535205f50..c44c7d525b5d205197843ad083bb84d1fc257a68 100644
--- a/src/tables.h
+++ b/src/tables.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/taglist.c b/src/taglist.c
index d08750f9b096a8e647bdec63e83feb2c7dc66c35..6b50a51ab081b3604b0aef8f93b725a3e2ab3c35 100644
--- a/src/taglist.c
+++ b/src/taglist.c
@@ -1,8 +1,8 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
-// Copyright (C) 2020-2021 by Nev3r.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by Nev3r.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/taglist.h b/src/taglist.h
index ae1a81c896dfb8bc19c348f50ef99eb258975d4f..d326ef357855efeb3ea70729fcc7d1812a47391c 100644
--- a/src/taglist.h
+++ b/src/taglist.h
@@ -1,8 +1,8 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
-// Copyright (C) 2020-2021 by Nev3r.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
+// Copyright (C) 2020-2022 by Nev3r.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/tmap.nas b/src/tmap.nas
index 5bf28359e6b75b1753e94eea8d0fa77b077978b8..f096c8141c14fd8b3d1e2138013a1963b62f0991 100644
--- a/src/tmap.nas
+++ b/src/tmap.nas
@@ -1,7 +1,7 @@
 ;; SONIC ROBO BLAST 2
 ;;-----------------------------------------------------------------------------
 ;; Copyright (C) 1998-2000 by DooM Legacy Team.
-;; Copyright (C) 1999-2021 by Sonic Team Junior.
+;; Copyright (C) 1999-2022 by Sonic Team Junior.
 ;;
 ;; This program is free software distributed under the
 ;; terms of the GNU General Public License, version 2.
diff --git a/src/tmap.s b/src/tmap.s
index 62dcf85dcc00ed350e3bf145e03eedb885e4ee6a..5bb2dea12739094aac7792f2fd6ec5681dabb04a 100644
--- a/src/tmap.s
+++ b/src/tmap.s
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/tmap_asm.s b/src/tmap_asm.s
index b5a0a51e91dd00f330b33d2cf0ca9cee60373323..8e307f42b71023b3a4cbd5a5687d6556113342c4 100644
--- a/src/tmap_asm.s
+++ b/src/tmap_asm.s
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/tmap_mmx.nas b/src/tmap_mmx.nas
index 8b6ef91a60aeff852abee8824eb8f49a6572df92..5312f3c7667edbdd0ee9c6866e99cab61f8813de 100644
--- a/src/tmap_mmx.nas
+++ b/src/tmap_mmx.nas
@@ -1,7 +1,7 @@
 ;; SONIC ROBO BLAST 2
 ;;-----------------------------------------------------------------------------
 ;; Copyright (C) 1998-2000 by DOSDOOM.
-;; Copyright (C) 2010-2021 by Sonic Team Junior.
+;; Copyright (C) 2010-2022 by Sonic Team Junior.
 ;;
 ;; This program is free software distributed under the
 ;; terms of the GNU General Public License, version 2.
diff --git a/src/tmap_vc.nas b/src/tmap_vc.nas
index b6ee26e6b8f22d481b419dac46227b010f78058f..44b2d2e7beb2495be9983cfc75cee4b62ce846ce 100644
--- a/src/tmap_vc.nas
+++ b/src/tmap_vc.nas
@@ -1,7 +1,7 @@
 ;; SONIC ROBO BLAST 2
 ;;-----------------------------------------------------------------------------
 ;; Copyright (C) 1998-2000 by DooM Legacy Team.
-;; Copyright (C) 1999-2021 by Sonic Team Junior.
+;; Copyright (C) 1999-2022 by Sonic Team Junior.
 ;;
 ;; This program is free software distributed under the
 ;; terms of the GNU General Public License, version 2.
diff --git a/src/v_video.c b/src/v_video.c
index 12588f9c2fc2bebda04d6292e137778dff504d63..da725f78ad4d195b2d37ba0df9d50e2a3903a041 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -539,25 +539,21 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 	patchdrawfunc = standardpdraw;
 
 	v_translevel = NULL;
-	if (alphalevel)
+	if (alphalevel || blendmode)
 	{
-		if (alphalevel == 10)
+		if (alphalevel == 10) // V_HUDTRANSHALF
 			alphalevel = hudminusalpha[st_translucency];
-		else if (alphalevel == 11)
+		else if (alphalevel == 11) // V_HUDTRANS
 			alphalevel = 10 - st_translucency;
-		else if (alphalevel == 12)
+		else if (alphalevel == 12) // V_HUDTRANSDOUBLE
 			alphalevel = hudplusalpha[st_translucency];
 
 		if (alphalevel >= 10)
 			return; // invis
 
-		if (alphalevel)
+		if (alphalevel || blendmode)
 		{
-			if (blendmode)
-				v_translevel = R_GetBlendTable(blendmode+1, alphalevel);
-			else
-				v_translevel = R_GetTranslucencyTable(alphalevel);
-
+			v_translevel = R_GetBlendTable(blendmode+1, alphalevel);
 			patchdrawfunc = translucentpdraw;
 		}
 	}
@@ -833,25 +829,21 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, IN
 	patchdrawfunc = standardpdraw;
 
 	v_translevel = NULL;
-	if (alphalevel)
+	if (alphalevel || blendmode)
 	{
-		if (alphalevel == 10)
+		if (alphalevel == 10) // V_HUDTRANSHALF
 			alphalevel = hudminusalpha[st_translucency];
-		else if (alphalevel == 11)
+		else if (alphalevel == 11) // V_HUDTRANS
 			alphalevel = 10 - st_translucency;
-		else if (alphalevel == 12)
+		else if (alphalevel == 12) // V_HUDTRANSDOUBLE
 			alphalevel = hudplusalpha[st_translucency];
 
 		if (alphalevel >= 10)
 			return; // invis
 
-		if (alphalevel)
+		if (alphalevel || blendmode)
 		{
-			if (blendmode)
-				v_translevel = R_GetBlendTable(blendmode+1, alphalevel);
-			else
-				v_translevel = R_GetTranslucencyTable(alphalevel);
-
+			v_translevel = R_GetBlendTable(blendmode+1, alphalevel);
 			patchdrawfunc = translucentpdraw;
 		}
 	}
@@ -1410,11 +1402,11 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 
 	if ((alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT)))
 	{
-		if (alphalevel == 10)
+		if (alphalevel == 10) // V_HUDTRANSHALF
 			alphalevel = hudminusalpha[st_translucency];
-		else if (alphalevel == 11)
+		else if (alphalevel == 11) // V_HUDTRANS
 			alphalevel = 10 - st_translucency;
-		else if (alphalevel == 12)
+		else if (alphalevel == 12) // V_HUDTRANSDOUBLE
 			alphalevel = hudplusalpha[st_translucency];
 
 		if (alphalevel >= 10)
diff --git a/src/v_video.h b/src/v_video.h
index bcb39706ef26f6903a33d488242d4d23dce6f3ee..2831230a35d35bef057e37cc9436b3f9fec63414 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/version.h b/src/version.h
index 28fc71c36fca9dceea7f7aecc31f6436028b8d84..7a12fbbbe4638b5552510e23c09b6ba00c706b36 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,4 +1,4 @@
-#define SRB2VERSION "2.2.9"/* this must be the first line, for cmake !! */
+#define SRB2VERSION "2.2.10"/* this must be the first line, for cmake !! */
 
 // The Modification ID; must be obtained from a Master Server Admin ( https://mb.srb2.org/members/?key=ms_admin ).
 // DO NOT try to set this otherwise, or your modification will be unplayable through the Master Server.
@@ -9,7 +9,7 @@
 // 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 50
+#define MODVERSION 51
 
-// Define this as a prerelease version suffix
-// #define BETAVERSION "RC1"
+// Define this as a prerelease version suffix (pre#, RC#)
+// #define BETAVERSION "pre1"
diff --git a/src/vid_copy.s b/src/vid_copy.s
index 6a37883565f57023f5687d8c31e2de56d72b288a..8e43e23c1786d51e615446fda767d2ba7a7a8f83 100644
--- a/src/vid_copy.s
+++ b/src/vid_copy.s
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/w_wad.c b/src/w_wad.c
index e49e0ce82f9ffe24c06d757b61b2a69bbc10ee2a..0a8f630a804b2ea2fda9e83b902adb3af66295d1 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -361,6 +361,7 @@ static lumpinfo_t* ResGetLumpsStandalone (FILE* handle, UINT16* numlumps, const
 	lumpinfo->size = ftell(handle);
 	fseek(handle, 0, SEEK_SET);
 	strcpy(lumpinfo->name, lumpname);
+	lumpinfo->hash = quickncasehash(lumpname, 8);
 
 	// Allocate the lump's long name.
 	lumpinfo->longname = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
@@ -459,6 +460,7 @@ static lumpinfo_t* ResGetLumpsWad (FILE* handle, UINT16* nlmp, const char* filen
 			lump_p->compression = CM_NOCOMPRESSION;
 		memset(lump_p->name, 0x00, 9);
 		strncpy(lump_p->name, fileinfo->name, 8);
+		lump_p->hash = quickncasehash(lump_p->name, 8);
 
 		// Allocate the lump's long name.
 		lump_p->longname = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
@@ -634,6 +636,7 @@ static lumpinfo_t* ResGetLumpsZip (FILE* handle, UINT16* nlmp)
 
 		memset(lump_p->name, '\0', 9); // Making sure they're initialized to 0. Is it necessary?
 		strncpy(lump_p->name, trimname, min(8, dotpos - trimname));
+		lump_p->hash = quickncasehash(lump_p->name, 8);
 
 		lump_p->longname = Z_Calloc(dotpos - trimname + 1, PU_STATIC, NULL);
 		strlcpy(lump_p->longname, trimname, dotpos - trimname + 1);
@@ -1225,12 +1228,14 @@ UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump)
 {
 	UINT16 i;
 	static char uname[8 + 1];
+	UINT32 hash;
 
 	if (!TestValidLump(wad,0))
 		return INT16_MAX;
 
 	strlcpy(uname, name, sizeof uname);
 	strupr(uname);
+	hash = quickncasehash(uname, 8);
 
 	//
 	// scan forward
@@ -1241,7 +1246,7 @@ UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump)
 	{
 		lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
 		for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
-			if (!strncmp(lump_p->name, uname, sizeof(uname) - 1))
+			if (lump_p->hash == hash && !strncmp(lump_p->name, uname, sizeof(uname) - 1))
 				return i;
 	}
 
@@ -1444,15 +1449,20 @@ lumpnum_t W_CheckNumForLongName(const char *name)
 // TODO: Make it search through cache first, maybe...?
 lumpnum_t W_CheckNumForMap(const char *name)
 {
+	UINT32 hash = quickncasehash(name, 8);
 	UINT16 lumpNum, end;
 	UINT32 i;
+	lumpinfo_t *p;
 	for (i = numwadfiles - 1; i < numwadfiles; i--)
 	{
 		if (wadfiles[i]->type == RET_WAD)
 		{
 			for (lumpNum = 0; lumpNum < wadfiles[i]->numlumps; lumpNum++)
-				if (!strncmp(name, (wadfiles[i]->lumpinfo + lumpNum)->name, 8))
+			{
+				p = wadfiles[i]->lumpinfo + lumpNum;
+				if (p->hash == hash && !strncmp(name, p->name, 8))
 					return (i<<16) + lumpNum;
+			}
 		}
 		else if (W_FileHasFolders(wadfiles[i]))
 		{
@@ -1463,8 +1473,15 @@ lumpnum_t W_CheckNumForMap(const char *name)
 				continue;
 			// Now look for the specified map.
 			for (; lumpNum < end; lumpNum++)
-				if (!strnicmp(name, (wadfiles[i]->lumpinfo + lumpNum)->name, 8))
-					return (i<<16) + lumpNum;
+			{
+				p = wadfiles[i]->lumpinfo + lumpNum;
+				if (p->hash == hash && !strnicmp(name, p->name, 8))
+				{
+					const char *extension = strrchr(p->fullname, '.');
+					if (!(extension && stricmp(extension, ".wad")))
+						return (i<<16) + lumpNum;
+				}
+			}
 		}
 	}
 	return LUMPERROR;
diff --git a/src/w_wad.h b/src/w_wad.h
index a41ba1724a93efad3d583a1ea0d4b065f5dd3798..c4de55d7774eef96e69470cc8b056d4124d52017 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -67,6 +67,7 @@ typedef struct
 	unsigned long position; // filelump_t filepos
 	unsigned long disksize; // filelump_t size
 	char name[9];           // filelump_t name[] e.g. "LongEntr"
+	UINT32 hash;
 	char *longname;         //                   e.g. "LongEntryName"
 	char *fullname;         //                   e.g. "Folder/Subfolder/LongEntryName.extension"
 	char *diskpath;         // path to the file  e.g. "/usr/games/srb2/Addon/Folder/Subfolder/LongEntryName.extension"
diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc
index 0a280448b48b13d4dc5c09cc674e4213f87a9995..83948ac81978a8e77de4f127ad1dc37e6da1274a 100644
--- a/src/win32/Srb2win.rc
+++ b/src/win32/Srb2win.rc
@@ -76,8 +76,8 @@ END
 #include "../doomdef.h" // Needed for version string
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 2,2,9,0
- PRODUCTVERSION 2,2,9,0
+ FILEVERSION 2,2,10,0
+ PRODUCTVERSION 2,2,10,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -97,7 +97,7 @@ BEGIN
             VALUE "FileDescription", "Sonic Robo Blast 2\0"
             VALUE "FileVersion", VERSIONSTRING_RC
             VALUE "InternalName", "srb2\0"
-            VALUE "LegalCopyright", "Copyright 1998-2021 by Sonic Team Junior\0"
+            VALUE "LegalCopyright", "Copyright 1998-2022 by Sonic Team Junior\0"
             VALUE "LegalTrademarks", "Sonic the Hedgehog and related characters are trademarks of Sega.\0"
             VALUE "OriginalFilename", "srb2win.exe\0"
             VALUE "PrivateBuild", "\0"
@@ -128,4 +128,3 @@ END
 
 /////////////////////////////////////////////////////////////////////////////
 #endif    // not APSTUDIO_INVOKED
-
diff --git a/src/y_inter.c b/src/y_inter.c
index 288a821e6f33209408ce708b1666bc93aabee93d..34e58494f1b9b9cc1c04d068d2db43b1cccf2c7d 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2004-2021 by Sonic Team Junior.
+// Copyright (C) 2004-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/y_inter.h b/src/y_inter.h
index 871142858ba1df05f0163b19a9cf9efeeafe560a..74183066e4541298d6ec8d5988829c9763a8e253 100644
--- a/src/y_inter.h
+++ b/src/y_inter.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2004-2021 by Sonic Team Junior.
+// Copyright (C) 2004-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/z_zone.c b/src/z_zone.c
index 34ff3d37ef3a025beaa925228d5c6a18c68f2228..b949730e35bbc2e6bd342753571e6e6a0f1759f9 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2006      by Graue.
-// Copyright (C) 2006-2021 by Sonic Team Junior.
+// Copyright (C) 2006-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
diff --git a/src/z_zone.h b/src/z_zone.h
index 17f572a905b56597bf955ee28b53814c6108bf04..d7e1ed52860389d886c3fb436c1b9474f9c584a1 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2021 by Sonic Team Junior.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.