diff --git a/.gitattributes b/.gitattributes
index d45620912eebdefd549c4d30d9da82cb22cabc3e..7751149ac07713a953529c5e8ba579109d76356d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,12 @@
+#Source code
+/src/*.c text=auto
+/src/*.h text=auto
+/src/*.s text=auto
+/src/*.m text=auto
+/src/*.xpm text=auto
+/src/Makefile text=auto
+/src/Make*.cfg text=auto
+/src/CMakeLists.txt text=auto
 # Windows EOL
 *.cs -crlf -whitespace
 *.mk -crlf -whitespace
diff --git a/libs/libpng-src/pngconf.h b/libs/libpng-src/pngconf.h
index 23f8313c3772d95b2261b0731b37d53e6efa8e38..5cb126197331b4d4dbc68c90980e686bb7fea730 100644
--- a/libs/libpng-src/pngconf.h
+++ b/libs/libpng-src/pngconf.h
@@ -62,7 +62,6 @@
 
 #define PNG_NO_READ_iTXt
 #define PNG_NO_READ_APNG
-#define PNG_NO_READ_UNKNOWN_CHUNKS
 #define PNG_NO_READ_USER_TRANSFORM
 #define PNG_READ_BGR_SUPPORTED
 #define PNG_NO_READ_SWAP_ALPHA
diff --git a/libs/libpng-src/projects/libpng32.a b/libs/libpng-src/projects/libpng32.a
index 74cb54e8c24dd3a924ff491086fb92d670298a83..5d2917502b9a01f0d75ca824d495d141b7ad49c7 100644
Binary files a/libs/libpng-src/projects/libpng32.a and b/libs/libpng-src/projects/libpng32.a differ
diff --git a/libs/libpng-src/projects/libpng64.a b/libs/libpng-src/projects/libpng64.a
index bba4c9121e706e450e764f7dc89ad809d3e38a14..cddf938ddc7f1d138e2cfc5f2bf48e05d2b29db0 100644
Binary files a/libs/libpng-src/projects/libpng64.a and b/libs/libpng-src/projects/libpng64.a differ
diff --git a/srb2.png b/srb2.png
index 72a08f6648b8c8849d5804889977a412b35e6deb..3bbe2c3e66af543be6de806d8d893735e10e4062 100644
Binary files a/srb2.png and b/srb2.png differ
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 01e94485d85696736fa334ecbe40225ba77b66da..476bb1a1ea357b36c56894a36645e474e101ef73 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -152,7 +152,7 @@ ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS];
 static textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE] = {NULL};
 
 
-static consvar_t cv_showjoinaddress = {"showjoinaddress", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_showjoinaddress = {"showjoinaddress", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0, NULL}};
 consvar_t cv_playbackspeed = {"playbackspeed", "1", 0, playbackspeed_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -579,6 +579,7 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 	rsp->starpostnum = LONG(players[i].starpostnum);
 	rsp->starposttime = (tic_t)LONG(players[i].starposttime);
 	rsp->starpostangle = (angle_t)LONG(players[i].starpostangle);
+	rsp->starpostscale = (fixed_t)LONG(players[i].starpostscale);
 
 	rsp->maxlink = LONG(players[i].maxlink);
 	rsp->dashspeed = (fixed_t)LONG(players[i].dashspeed);
@@ -714,6 +715,7 @@ static void resynch_read_player(resynch_pak *rsp)
 	players[i].starpostnum = LONG(rsp->starpostnum);
 	players[i].starposttime = (tic_t)LONG(rsp->starposttime);
 	players[i].starpostangle = (angle_t)LONG(rsp->starpostangle);
+	players[i].starpostscale = (fixed_t)LONG(rsp->starpostscale);
 
 	players[i].maxlink = LONG(rsp->maxlink);
 	players[i].dashspeed = (fixed_t)LONG(rsp->dashspeed);
@@ -2415,7 +2417,7 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
 	// the remaining players.
 	if (G_IsSpecialStage(gamemap))
 	{
-		INT32 i, count, increment, spheres;
+		INT32 i, count, sincrement, spheres, rincrement, rings;
 
 		for (i = 0, count = 0; i < MAXPLAYERS; i++)
 		{
@@ -2425,18 +2427,35 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
 
 		count--;
 		spheres = players[playernum].spheres;
-		increment = spheres/count;
+		rings = players[playernum].rings;
+		sincrement = spheres/count;
+		rincrement = rings/count;
 
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
 			if (playeringame[i] && i != playernum)
 			{
-				if (spheres < increment)
+				if (spheres < 2*sincrement)
+				{
 					P_GivePlayerSpheres(&players[i], spheres);
+					spheres = 0;
+				}
 				else
-					P_GivePlayerSpheres(&players[i], increment);
+				{
+					P_GivePlayerSpheres(&players[i], sincrement);
+					spheres -= sincrement;
+				}
 
-				spheres -= increment;
+				if (rings < 2*rincrement)
+				{
+					P_GivePlayerRings(&players[i], rings);
+					rings = 0;
+				}
+				else
+				{
+					P_GivePlayerRings(&players[i], rincrement);
+					rings -= rincrement;
+				}
 			}
 		}
 	}
@@ -2946,13 +2965,13 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		CL_RemovePlayer(pnum, kickreason);
 }
 
-consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
-consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
+consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
+consvar_t cv_joinnextround = {"joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
 static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
 consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 static CV_PossibleValue_t resynchattempts_cons_t[] = {{0, "MIN"}, {20, "MAX"}, {0, NULL}};
-consvar_t cv_resynchattempts = {"resynchattempts", "10", 0, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL	};
-consvar_t cv_blamecfail = {"blamecfail", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
+consvar_t cv_resynchattempts = {"resynchattempts", "10", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL	};
+consvar_t cv_blamecfail = {"blamecfail", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
 
 // max file size to send to a player (in kilobytes)
 static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {51200, "MAX"}, {0, NULL}};
@@ -2993,11 +3012,6 @@ void D_ClientServerInit(void)
 	RegisterNetXCmd(XD_KICK, Got_KickCmd);
 	RegisterNetXCmd(XD_ADDPLAYER, Got_AddPlayer);
 #ifndef NONET
-	CV_RegisterVar(&cv_allownewplayer);
-	CV_RegisterVar(&cv_joinnextround);
-	CV_RegisterVar(&cv_showjoinaddress);
-	CV_RegisterVar(&cv_resynchattempts);
-	CV_RegisterVar(&cv_blamecfail);
 #ifdef DUMPCONSISTENCY
 	CV_RegisterVar(&cv_dumpconsistency);
 #endif
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index a2f140f330a2fad15e362cb7deb632f87ba667f6..d09d2aa48e024a5d7ce0451b26bf347663f83469 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -228,6 +228,7 @@ typedef struct
 	INT32 starpostnum;
 	tic_t starposttime;
 	angle_t starpostangle;
+	fixed_t starpostscale;
 
 	INT32 maxlink;
 	fixed_t dashspeed;
@@ -444,6 +445,7 @@ extern INT32 mapchangepending;
 // Points inside doomcom
 extern doomdata_t *netbuffer;
 
+extern consvar_t cv_showjoinaddress;
 extern consvar_t cv_playbackspeed;
 
 #define BASEPACKETSIZE      offsetof(doomdata_t, u)
diff --git a/src/d_main.c b/src/d_main.c
index eaeae4b10f09580b108e46fbc3e0ff80e56b8bdc..37637edd60c708a01034385c62bd9945b6ffd356 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -129,6 +129,7 @@ char srb2home[256] = ".";
 char srb2path[256] = ".";
 boolean usehome = true;
 const char *pandf = "%s" PATHSEP "%s";
+static char addonsdir[MAX_WADPATH];
 
 //
 // EVENT HANDLING
@@ -287,7 +288,7 @@ static void D_Display(void)
 				F_TitleScreenDrawer();
 				break;
 			}
-			// Intentional fall-through
+			/* FALLTHRU */
 		case GS_LEVEL:
 			if (!gametic)
 				break;
@@ -716,6 +717,7 @@ void D_StartTitle(void)
 	botskin = 0;
 	cv_debug = 0;
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 	lastmaploaded = 0;
 
 	// In case someone exits out at the same time they start a time attack run,
@@ -1038,7 +1040,6 @@ void D_SRB2Main(void)
 			// can't use sprintf since there is %u in savegamename
 			strcatbf(savegamename, srb2home, PATHSEP);
 
-			I_mkdir(srb2home, 0700);
 #else
 			snprintf(srb2home, sizeof srb2home, "%s", userhome);
 			snprintf(downloaddir, sizeof downloaddir, "%s", userhome);
@@ -1055,6 +1056,10 @@ void D_SRB2Main(void)
 		configfile[sizeof configfile - 1] = '\0';
 	}
 
+	// Create addons dir
+	snprintf(addonsdir, sizeof addonsdir, "%s%s%s", srb2home, PATHSEP, "addons");
+	I_mkdir(addonsdir, 0755);
+
 	// rand() needs seeded regardless of password
 	srand((unsigned int)time(NULL));
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 590543f007deaaba603854bcfeb12f54196badaa..7d5963ecc1ebc1601e36adee3743c8cdfb5cc420 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -556,9 +556,16 @@ void D_RegisterServerCommands(void)
 
 	// d_clisrv
 	CV_RegisterVar(&cv_maxplayers);
+	CV_RegisterVar(&cv_resynchattempts);
 	CV_RegisterVar(&cv_maxsend);
 	CV_RegisterVar(&cv_noticedownload);
 	CV_RegisterVar(&cv_downloadspeed);
+#ifndef NONET
+	CV_RegisterVar(&cv_allownewplayer);
+	CV_RegisterVar(&cv_joinnextround);
+	CV_RegisterVar(&cv_showjoinaddress);
+	CV_RegisterVar(&cv_blamecfail);
+#endif
 
 	COM_AddCommand("ping", Command_Ping_f);
 	CV_RegisterVar(&cv_nettimeout);
@@ -630,6 +637,8 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_screenshot_folder);
 	CV_RegisterVar(&cv_screenshot_colorprofile);
 	CV_RegisterVar(&cv_moviemode);
+	CV_RegisterVar(&cv_movie_option);
+	CV_RegisterVar(&cv_movie_folder);
 	// PNG variables
 	CV_RegisterVar(&cv_zlib_level);
 	CV_RegisterVar(&cv_zlib_memory);
@@ -1909,7 +1918,10 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 		precache = false;
 
 	if (resetplayer && !FLS)
+	{
 		emeralds = 0;
+		memset(&luabanks, 0, sizeof(luabanks));
+	}
 
 	if (modeattacking)
 	{
@@ -2707,14 +2719,6 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 		}
 	}
 
-	// Clear player score and rings if a spectator.
-	if (players[playernum].spectator)
-	{
-		players[playernum].score = players[playernum].rings = 0;
-		if (players[playernum].mo)
-			players[playernum].mo->health = 1;
-	}
-
 	// In tag, check to see if you still have a game.
 	if (G_TagGametype())
 		P_CheckSurvivors();
@@ -3601,7 +3605,7 @@ static void CoopLives_OnChange(void)
 	{
 		case 0:
 			CONS_Printf(M_GetText("Players can now respawn indefinitely.\n"));
-			return;
+			break;
 		case 1:
 			CONS_Printf(M_GetText("Lives are now per-player.\n"));
 			return;
@@ -4104,6 +4108,7 @@ void Command_ExitGame_f(void)
 	botskin = 0;
 	cv_debug = 0;
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 
 	if (dirmenu)
 		closefilemenu(true);
diff --git a/src/d_player.h b/src/d_player.h
index 5860cf1de4b05f86b7567c4d87c3699475513c71..69080bd9dc230450241fb0ca668a53c32d54889c 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -441,6 +441,7 @@ typedef struct player_s
 	INT32 starpostnum; // The number of the last starpost you hit
 	tic_t starposttime; // Your time when you hit the starpost
 	angle_t starpostangle; // Angle that the starpost is facing - you respawn facing this way
+	fixed_t starpostscale; // Scale of the player; if negative, player is gravflipped
 
 	/////////////////
 	// NiGHTS Stuff//
diff --git a/src/dehacked.c b/src/dehacked.c
index 5db61a5b5ea22eb508d097d1a6683d52bd5c3517..8334de61a86bd25a60998b0a0c258cd8e06000e7 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1181,6 +1181,20 @@ static void readlevelheader(MYFILE *f, INT32 num)
 				mapheaderinfo[num-1]->muspostbosspos = (UINT32)get_number(word2);
 			else if (fastcmp(word, "MUSICPOSTBOSSFADEIN"))
 				mapheaderinfo[num-1]->muspostbossfadein = (UINT32)get_number(word2);
+			else if (fastcmp(word, "FORCERESETMUSIC"))
+			{
+				// This is a weird one because "FALSE"/"NO" could either apply to "leave to default preference" (cv_resetmusic)
+				// or "force off". Let's assume it means "force off", and let an unspecified value mean "default preference"
+				if      (fastcmp(word2, "OFF") || word2[0] == 'F' || word2[0] == 'N')  i = 0;
+				else if (fastcmp(word2, "ON") || word2[0] == 'T' || word2[0] == 'Y')   i = 1;
+				else i = -1; // (fastcmp(word2, "DEFAULT"))
+
+				if (i >= -1 && i <= 1) // -1 to force off, 1 to force on, 0 to honor default.
+					// This behavior can be disabled with cv_resetmusicbyheader
+					mapheaderinfo[num-1]->musforcereset = (SINT8)i;
+				else
+					deh_warning("Level header %d: invalid forceresetmusic option %d", num, i);
+			}
 			else if (fastcmp(word, "FORCECHARACTER"))
 			{
 				strlcpy(mapheaderinfo[num-1]->forcecharacter, word2, SKINNAMESIZE+1);
@@ -2957,6 +2971,17 @@ static void readunlockable(MYFILE *f, INT32 num)
 	Z_Free(s);
 }
 
+static const char NIGHTSGRADE_LIST[] = {
+	'F', // GRADE_F
+	'E', // GRADE_E
+	'D', // GRADE_D
+	'C', // GRADE_C
+	'B', // GRADE_B
+	'A', // GRADE_A
+	'S', // GRADE_S
+	'\0'
+};
+
 #define PARAMCHECK(n) do { if (!params[n]) { deh_warning("Too few parameters, need %d", n); return; }} while (0)
 static void readcondition(UINT8 set, UINT32 id, char *word2)
 {
@@ -3058,7 +3083,21 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
 		PARAMCHECK(2); // one optional one
 
 		ty = UC_NIGHTSSCORE + offset;
-		re = atoi(params[2 + !!(params[3])]);
+		i = (params[3] ? 3 : 2);
+		if (fastncmp("GRADE_",params[i],6))
+		{
+			char *p = params[i]+6;
+			for (re = 0; NIGHTSGRADE_LIST[re]; re++)
+				if (*p == NIGHTSGRADE_LIST[re])
+					break;
+			if (!NIGHTSGRADE_LIST[re])
+			{
+				deh_warning("Invalid NiGHTS grade %s\n", params[i]);
+				return;
+			}
+		}
+		else
+			re = atoi(params[i]);
 
 		// Convert to map number if it appears to be one
 		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
@@ -4589,10 +4628,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SONIC3KBOSSEXPLOSION6",
 
 	"S_JETFUME1",
-	"S_JETFUME2",
 
 	// Boss 1
 	"S_EGGMOBILE_STND",
+	"S_EGGMOBILE_ROFL",
 	"S_EGGMOBILE_LATK1",
 	"S_EGGMOBILE_LATK2",
 	"S_EGGMOBILE_LATK3",
@@ -4602,7 +4641,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE_LATK7",
 	"S_EGGMOBILE_LATK8",
 	"S_EGGMOBILE_LATK9",
-	"S_EGGMOBILE_LATK10",
 	"S_EGGMOBILE_RATK1",
 	"S_EGGMOBILE_RATK2",
 	"S_EGGMOBILE_RATK3",
@@ -4612,7 +4650,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE_RATK7",
 	"S_EGGMOBILE_RATK8",
 	"S_EGGMOBILE_RATK9",
-	"S_EGGMOBILE_RATK10",
 	"S_EGGMOBILE_PANIC1",
 	"S_EGGMOBILE_PANIC2",
 	"S_EGGMOBILE_PANIC3",
@@ -4620,6 +4657,14 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE_PANIC5",
 	"S_EGGMOBILE_PANIC6",
 	"S_EGGMOBILE_PANIC7",
+	"S_EGGMOBILE_PANIC8",
+	"S_EGGMOBILE_PANIC9",
+	"S_EGGMOBILE_PANIC10",
+	"S_EGGMOBILE_PANIC11",
+	"S_EGGMOBILE_PANIC12",
+	"S_EGGMOBILE_PANIC13",
+	"S_EGGMOBILE_PANIC14",
+	"S_EGGMOBILE_PANIC15",
 	"S_EGGMOBILE_PAIN",
 	"S_EGGMOBILE_PAIN2",
 	"S_EGGMOBILE_DIE1",
@@ -4630,6 +4675,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE_FLEE2",
 	"S_EGGMOBILE_BALL",
 	"S_EGGMOBILE_TARGET",
+	"S_BOSSEGLZ1",
+	"S_BOSSEGLZ2",
 
 	// Boss 2
 	"S_EGGMOBILE2_STND",
@@ -4661,11 +4708,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Boss 3
 	"S_EGGMOBILE3_STND",
-	"S_EGGMOBILE3_LAUGH1",
-	"S_EGGMOBILE3_LAUGH2",
-	"S_EGGMOBILE3_LAUGH3",
-	"S_EGGMOBILE3_LAUGH4",
-	"S_EGGMOBILE3_LAUGH5",
+	"S_EGGMOBILE3_SHOCK",
 	"S_EGGMOBILE3_ATK1",
 	"S_EGGMOBILE3_ATK2",
 	"S_EGGMOBILE3_ATK3A",
@@ -4674,21 +4717,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE3_ATK3D",
 	"S_EGGMOBILE3_ATK4",
 	"S_EGGMOBILE3_ATK5",
-	"S_EGGMOBILE3_LAUGH6",
-	"S_EGGMOBILE3_LAUGH7",
-	"S_EGGMOBILE3_LAUGH8",
-	"S_EGGMOBILE3_LAUGH9",
-	"S_EGGMOBILE3_LAUGH10",
-	"S_EGGMOBILE3_LAUGH11",
-	"S_EGGMOBILE3_LAUGH12",
-	"S_EGGMOBILE3_LAUGH13",
-	"S_EGGMOBILE3_LAUGH14",
-	"S_EGGMOBILE3_LAUGH15",
-	"S_EGGMOBILE3_LAUGH16",
-	"S_EGGMOBILE3_LAUGH17",
-	"S_EGGMOBILE3_LAUGH18",
-	"S_EGGMOBILE3_LAUGH19",
-	"S_EGGMOBILE3_LAUGH20",
+	"S_EGGMOBILE3_ROFL",
 	"S_EGGMOBILE3_PAIN",
 	"S_EGGMOBILE3_PAIN2",
 	"S_EGGMOBILE3_DIE1",
@@ -4698,15 +4727,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE3_FLEE1",
 	"S_EGGMOBILE3_FLEE2",
 
-	// Boss 3 Propeller
-	"S_PROPELLER1",
-	"S_PROPELLER2",
-	"S_PROPELLER3",
-	"S_PROPELLER4",
-	"S_PROPELLER5",
-	"S_PROPELLER6",
-	"S_PROPELLER7",
-
 	// Boss 3 pinch
 	"S_FAKEMOBILE_INIT",
 	"S_FAKEMOBILE",
@@ -4719,6 +4739,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FAKEMOBILE_DIE1",
 	"S_FAKEMOBILE_DIE2",
 
+	"S_BOSSSEBH1",
+	"S_BOSSSEBH2",
+
 	// Boss 4
 	"S_EGGMOBILE4_STND",
 	"S_EGGMOBILE4_LATK1",
@@ -5122,16 +5145,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	"S_MSSHIELD_F1",
 	"S_MSSHIELD_F2",
-	"S_MSSHIELD_F3",
-	"S_MSSHIELD_F4",
-	"S_MSSHIELD_F5",
-	"S_MSSHIELD_F6",
-	"S_MSSHIELD_F7",
-	"S_MSSHIELD_F8",
-	"S_MSSHIELD_F9",
-	"S_MSSHIELD_F10",
-	"S_MSSHIELD_F11",
-	"S_MSSHIELD_F12",
 
 	// Ring
 	"S_RING",
@@ -5565,14 +5578,12 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_DRIPC1",
 	"S_DRIPC2",
 
-	// Coral 1
+	// Coral
 	"S_CORAL1",
-
-	// Coral 2
 	"S_CORAL2",
-
-	// Coral 3
 	"S_CORAL3",
+	"S_CORAL4",
+	"S_CORAL5",
 
 	// Blue Crystal
 	"S_BLUECRYSTAL1",
@@ -5580,6 +5591,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	// Kelp,
 	"S_KELP",
 
+	// Animated algae
+	"S_ANIMALGAETOP1",
+	"S_ANIMALGAETOP2",
+	"S_ANIMALGAESEG",
+
 	// DSZ Stalagmites
 	"S_DSZSTALAGMITE",
 	"S_DSZ2STALAGMITE",
@@ -5724,6 +5740,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_CACTI7",
 	"S_CACTI8",
 	"S_CACTI9",
+	"S_CACTI10",
+	"S_CACTI11",
+	"S_CACTITINYSEG",
+	"S_CACTISMALLSEG",
 
 	// Warning signs sprites
 	"S_ARIDSIGN_CAUTION",
@@ -7215,6 +7235,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_EGGTRAP",
 	"MT_BOSS3WAYPOINT",
 	"MT_BOSS9GATHERPOINT",
+	"MT_BOSSJUNK",
 
 	// Boss 1
 	"MT_EGGMOBILE",
@@ -7226,15 +7247,11 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	// Boss 2
 	"MT_EGGMOBILE2",
 	"MT_EGGMOBILE2_POGO",
-	"MT_BOSSTANK1",
-	"MT_BOSSTANK2",
-	"MT_BOSSSPIGOT",
 	"MT_GOOP",
 	"MT_GOOPTRAIL",
 
 	// Boss 3
 	"MT_EGGMOBILE3",
-	"MT_PROPELLER",
 	"MT_FAKEMOBILE",
 	"MT_SHOCK",
 
@@ -7445,11 +7462,15 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_SEAWEED", // DSZ Seaweed
 	"MT_WATERDRIP", // Dripping Water source
 	"MT_WATERDROP", // Water drop from dripping water
-	"MT_CORAL1", // Coral 1
-	"MT_CORAL2", // Coral 2
-	"MT_CORAL3", // Coral 3
+	"MT_CORAL1", // Coral
+	"MT_CORAL2",
+	"MT_CORAL3",
+	"MT_CORAL4",
+	"MT_CORAL5",
 	"MT_BLUECRYSTAL", // Blue Crystal
 	"MT_KELP", // Kelp
+	"MT_ANIMALGAETOP", // Animated algae top
+	"MT_ANIMALGAESEG", // Animated algae segment
 	"MT_DSZSTALAGMITE", // Deep Sea 1 Stalagmite
 	"MT_DSZ2STALAGMITE", // Deep Sea 2 Stalagmite
 	"MT_LIGHTBEAM", // DSZ Light beam
@@ -7509,6 +7530,10 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_CACTI7",
 	"MT_CACTI8",
 	"MT_CACTI9",
+	"MT_CACTI10",
+	"MT_CACTI11",
+	"MT_CACTITINYSEG",
+	"MT_CACTISMALLSEG",
 	"MT_ARIDSIGN_CAUTION",
 	"MT_ARIDSIGN_CACTI",
 	"MT_ARIDSIGN_SHARPTURN",
@@ -8474,15 +8499,6 @@ struct {
 	{"LF2_NOVISITNEEDED",LF2_NOVISITNEEDED},
 	{"LF2_WIDEICON",LF2_WIDEICON},
 
-	// NiGHTS grades
-	{"GRADE_F",GRADE_F},
-	{"GRADE_E",GRADE_E},
-	{"GRADE_D",GRADE_D},
-	{"GRADE_C",GRADE_C},
-	{"GRADE_B",GRADE_B},
-	{"GRADE_A",GRADE_A},
-	{"GRADE_S",GRADE_S},
-
 	// Emeralds
 	{"EMERALD1",EMERALD1},
 	{"EMERALD2",EMERALD2},
@@ -9304,6 +9320,19 @@ static fixed_t find_const(const char **rword)
 		free(word);
 		return 0;
 	}
+	else if (fastncmp("GRADE_",word,6))
+	{
+		char *p = word+6;
+		for (i = 0; NIGHTSGRADE_LIST[i]; i++)
+			if (*p == NIGHTSGRADE_LIST[i])
+			{
+				free(word);
+				return i;
+			}
+		const_warning("NiGHTS grade",word);
+		free(word);
+		return 0;
+	}
 	for (i = 0; INT_CONST[i].n; i++)
 		if (fastcmp(word,INT_CONST[i].n)) {
 			free(word);
@@ -9752,6 +9781,18 @@ static inline int lib_getenum(lua_State *L)
 		if (mathlib) return luaL_error(L, "skincolor '%s' could not be found.\n", word);
 		return 0;
 	}
+	else if (fastncmp("GRADE_",word,6))
+	{
+		p = word+6;
+		for (i = 0; NIGHTSGRADE_LIST[i]; i++)
+			if (*p == NIGHTSGRADE_LIST[i])
+			{
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "NiGHTS grade '%s' could not be found.\n", word);
+		return 0;
+	}
 	else if (fastncmp("MN_",word,3)) {
 		p = word+3;
 		for (i = 0; i < NUMMENUTYPES; i++)
diff --git a/src/doomdef.h b/src/doomdef.h
index 6e7db21432e24719c0c63eafc638637e31be114b..4a017436931be6f1cfa2d199ad7fbd6aa93f53fc 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -616,4 +616,8 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 ///      	SRB2CB itself ported this from PrBoom+
 #define NEWCLIP
 
+#ifndef HAVE_PNG
+#define NO_PNG_LUMPS
+#endif
+
 #endif // __DOOMDEF__
diff --git a/src/doomstat.h b/src/doomstat.h
index b6c376d1ce46ebea5bf83b86010110f98aaba201..57e37f72dc98dfbcbecdb225292e6bcc10c71db7 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -333,6 +333,8 @@ typedef struct
 	UINT32 muspostbosspos;      ///< Post-bossdeath position
 	UINT32 muspostbossfadein;   ///< Post-bossdeath fade-in milliseconds.
 
+	SINT8 musforcereset; ///< Force resetmusic (-1 for default; 0 for force off; 1 for force on)
+
 	// Lua stuff.
 	// (This is not ifdeffed so the map header structure can stay identical, just in case.)
 	UINT8 numCustomOptions;     ///< Internal. For Lua custom value support.
@@ -418,6 +420,10 @@ extern UINT16 emeralds;
 #define EMERALD7 64
 #define ALL7EMERALDS(v) ((v & (EMERALD1|EMERALD2|EMERALD3|EMERALD4|EMERALD5|EMERALD6|EMERALD7)) == (EMERALD1|EMERALD2|EMERALD3|EMERALD4|EMERALD5|EMERALD6|EMERALD7))
 
+// yes, even in non HAVE_BLUA
+#define NUM_LUABANKS 16 // please only make this number go up between versions, never down. you'll break saves otherwise. also, must fit in UINT8
+extern INT32 luabanks[NUM_LUABANKS];
+
 extern INT32 nummaprings; //keep track of spawned rings/coins
 
 /** Time attack information, currently a very small structure.
diff --git a/src/f_finale.c b/src/f_finale.c
index da042abeb05c9050b2815586df034bd2699eb457..ee22ecb2f6f8b3afe399bc0887c6649821ab4b6b 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1584,22 +1584,22 @@ void F_StartEnding(void)
 		UINT8 skinnum = players[consoleplayer].skin;
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
-		if (skins[skinnum].sprites[SPR2_XTRA].numframes >= 5)
+		if (skins[skinnum].sprites[SPR2_XTRA].numframes >= 7)
 		{
 			sprdef = &skins[skinnum].sprites[SPR2_XTRA];
 			// character head, skin specific
-			sprframe = &sprdef->spriteframes[2];
+			sprframe = &sprdef->spriteframes[4];
 			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
-			sprframe = &sprdef->spriteframes[3];
+			sprframe = &sprdef->spriteframes[5];
 			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
-			sprframe = &sprdef->spriteframes[4];
+			sprframe = &sprdef->spriteframes[6];
 			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
 		}
-		else // eh, yknow what? too lazy to put MISSINGs here. eggman wins if you don't give your character an ending firework display.
+		else // Show a star if your character doesn't have an ending firework display. (Basically the MISSINGs for this)
 		{
-			endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_LEVEL);
-			endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_LEVEL);
-			endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_LEVEL);
+			endfwrk[0] = W_CachePatchName("ENDFWRK3", PU_LEVEL);
+			endfwrk[1] = W_CachePatchName("ENDFWRK4", PU_LEVEL);
+			endfwrk[2] = W_CachePatchName("ENDFWRK5", PU_LEVEL);
 		}
 
 		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_LEVEL);
@@ -2465,6 +2465,11 @@ void F_TitleDemoTicker(void)
 // ==========
 //  CONTINUE
 // ==========
+
+static skin_t *contskins[2];
+static UINT8 cont_spr2[2][6];
+static UINT8 *contcolormaps[2];
+
 void F_StartContinue(void)
 {
 	I_Assert(!netgame && !multiplayer);
@@ -2488,7 +2493,44 @@ void F_StartContinue(void)
 	S_ChangeMusicInternal("_conti", false);
 	S_StopSounds();
 
-	timetonext = TICRATE*11;
+	contskins[0] = &skins[players[consoleplayer].skin];
+	cont_spr2[0][0] = P_GetSkinSprite2(contskins[0], SPR2_CNT1, NULL);
+	cont_spr2[0][2] = contskins[0]->contangle & 7;
+	contcolormaps[0] = R_GetTranslationColormap(players[consoleplayer].skin, players[consoleplayer].skincolor, GTC_CACHE);
+	cont_spr2[0][4] = contskins[0]->sprites[cont_spr2[0][0]].numframes;
+	cont_spr2[0][5] = max(1, contskins[0]->contspeed);
+
+	if (botskin)
+	{
+		INT32 secondplaya;
+
+		if (secondarydisplayplayer != consoleplayer)
+			secondplaya = secondarydisplayplayer;
+		else // HACK
+			secondplaya = 1;
+
+		contskins[1] = &skins[players[secondplaya].skin];
+		cont_spr2[1][0] = P_GetSkinSprite2(contskins[1], SPR2_CNT4, NULL);
+		cont_spr2[1][2] = (contskins[1]->contangle >> 3) & 7;
+		contcolormaps[1] = R_GetTranslationColormap(players[secondplaya].skin, players[secondplaya].skincolor, GTC_CACHE);
+		cont_spr2[1][4] = contskins[1]->sprites[cont_spr2[1][0]].numframes;
+		if (cont_spr2[1][0] == SPR2_CNT4)
+			cont_spr2[1][5] = 4; // sorry, this one is hardcoded
+		else
+			cont_spr2[1][5] = max(1, contskins[1]->contspeed);
+	}
+	else
+	{
+		contskins[1] = NULL;
+		contcolormaps[1] = NULL;
+		cont_spr2[1][0] = cont_spr2[1][2] = cont_spr2[1][4] = cont_spr2[1][5] = 0;
+	}
+
+	cont_spr2[0][1] = cont_spr2[0][3] =\
+	cont_spr2[1][1] = cont_spr2[1][3] = 0;
+
+	timetonext = (11*TICRATE)+11;
+	continuetime = 0;
 }
 
 //
@@ -2497,47 +2539,198 @@ void F_StartContinue(void)
 //
 void F_ContinueDrawer(void)
 {
-	patch_t *contsonic;
-	INT32 i, x = (BASEVIDWIDTH/2) + 4, ncontinues = players[consoleplayer].continues;
-	if (ncontinues > 20)
-		ncontinues = 20;
+	spritedef_t *sprdef;
+	spriteframe_t *sprframe;
+	patch_t *patch;
+	INT32 i, x = (BASEVIDWIDTH>>1), ncontinues = players[consoleplayer].continues;
+	char numbuf[9] = "CONTNUM*";
+	tic_t timeleft = (timetonext/TICRATE);
+	INT32 offsx = 0, offsy = 0, lift[2] = {0, 0};
 
-	if (imcontinuing)
-		contsonic = W_CachePatchName("CONT2", PU_CACHE);
-	else
-		contsonic = W_CachePatchName("CONT1", PU_CACHE);
+	if (continuetime >= 3*TICRATE)
+	{
+		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0);
+		return;
+	}
 
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
-	V_DrawCenteredString(BASEVIDWIDTH/2, 100, 0, "CONTINUE?");
 
-	// Draw a Sonic!
-	V_DrawScaledPatch((BASEVIDWIDTH - SHORT(contsonic->width))/2, 32, 0, contsonic);
+	if (timetonext >= (11*TICRATE)+10)
+		return;
+
+	V_DrawLevelTitle(x - (V_LevelNameWidth("CONTINUE")>>1), 16, 0, "CONTINUE");
+
+	// Two stars...
+	patch = W_CachePatchName("CONTSTAR", PU_CACHE);
+	V_DrawScaledPatch(x-32, 160, 0, patch);
+	V_DrawScaledPatch(x+32, 160, 0, patch);
+
+	// Time left!
+	if (timeleft > 9)
+	{
+		numbuf[7] = '1';
+		V_DrawScaledPatch(x - 10, 160, 0, W_CachePatchName(numbuf, PU_CACHE));
+		numbuf[7] = '0';
+		V_DrawScaledPatch(x + 10, 160, 0, W_CachePatchName(numbuf, PU_CACHE));
+	}
+	else
+	{
+		numbuf[7] = '0'+timeleft;
+		V_DrawScaledPatch(x, 160, 0, W_CachePatchName(numbuf, PU_CACHE));
+	}
+
+	// Draw the continue markers! Show continues.
+	if (ncontinues > 10)
+	{
+		if (!(continuetime & 1) || continuetime > 17)
+			V_DrawContinueIcon(x, 68, 0, players[consoleplayer].skin, players[consoleplayer].skincolor);
+		V_DrawScaledPatch(x+12, 68-2, 0, stlivex);
+		V_DrawRightAlignedString(x+36, 69-5, 0,
+			va("%d",(imcontinuing ? ncontinues-1 : ncontinues)));
+	}
+	else
+	{
+		x += (ncontinues/2) * 30;
+		if (!(ncontinues & 1))
+			x -= 15;
+		for (i = 0; i < ncontinues; ++i)
+		{
+			if (i == (ncontinues/2) && ((continuetime & 1) || continuetime > 17))
+				continue;
+			V_DrawContinueIcon(x - (i*30), 68, 0, players[consoleplayer].skin, players[consoleplayer].skincolor);
+		}
+		x = BASEVIDWIDTH>>1;
+	}
+
+	// Spotlight
+	V_DrawScaledPatch(x, 140, 0, W_CachePatchName("CONTSPOT", PU_CACHE));
 
-	// Draw the continue markers! Show continues minus one.
-	x -= ncontinues * 6;
-	for (i = 0; i < ncontinues; ++i)
-		V_DrawContinueIcon(x + (i*12), 140, 0, players[consoleplayer].skin, players[consoleplayer].skincolor);
+	// warping laser
+	if (continuetime)
+	{
+		INT32 w = min(continuetime, 28), brightness = (continuetime>>1) & 7;
+		if (brightness > 3)
+			brightness = 8-brightness;
+		V_DrawFadeFill(x-w, 0, w<<1, 140, 0, 0, (3+brightness));
+	}
 
-	V_DrawCenteredString(BASEVIDWIDTH/2, 168, 0, va("\x82*\x80" " %02d " "\x82*\x80", timetonext/TICRATE));
+	if (contskins[1])
+	{
+		if (continuetime > 15)
+		{
+			angle_t work = FixedAngle((10*(continuetime-15))<<FRACBITS)>>ANGLETOFINESHIFT;
+			offsy = FINESINE(work)<<1;
+			offsx = (27*FINECOSINE(work))>>1;
+		}
+		else
+			offsx = 27<<(FRACBITS-1);
+		lift[1] = continuetime-10;
+		if (lift[1] < 0)
+			lift[1] = 0;
+		else if (lift[1] > TICRATE+5)
+			lift[1] = TICRATE+5;
+	}
+
+	lift[0] = continuetime-5;
+	if (lift[0] < 0)
+		lift[0] = 0;
+	else if (lift[0] > TICRATE+5)
+		lift[0] = TICRATE+5;
+
+#define drawchar(dx, dy, n)	{\
+								sprdef = &contskins[n]->sprites[cont_spr2[n][0]];\
+								sprframe = &sprdef->spriteframes[cont_spr2[n][1]];\
+								patch = W_CachePatchNum(sprframe->lumppat[cont_spr2[n][2]], PU_CACHE);\
+								V_DrawFixedPatch((dx), (dy), FRACUNIT, (sprframe->flip & (1<<cont_spr2[n][2])) ? V_FLIP : 0, patch, contcolormaps[n]);\
+							}
+
+	if (offsy < 0)
+		drawchar((BASEVIDWIDTH<<(FRACBITS-1))-offsx, ((140-lift[0])<<FRACBITS)-offsy, 0);
+	if (contskins[1])
+		drawchar((BASEVIDWIDTH<<(FRACBITS-1))+offsx, ((140-lift[1])<<FRACBITS)+offsy, 1);
+	if (offsy >= 0)
+		drawchar((BASEVIDWIDTH<<(FRACBITS-1))-offsx, ((140-lift[0])<<FRACBITS)-offsy, 0);
+
+#undef drawchar
+
+	if (timetonext > (11*TICRATE))
+		V_DrawFadeScreen(31, timetonext-(11*TICRATE));
+	if (continuetime > ((3*TICRATE) - 10))
+		V_DrawFadeScreen(0, (continuetime - ((3*TICRATE) - 10)));
 }
 
 void F_ContinueTicker(void)
 {
 	if (!imcontinuing)
 	{
-		// note the setup to prevent 2x reloading
-		if (timetonext >= 0)
-			timetonext--;
-		if (timetonext == 0)
-			Command_ExitGame_f();
+		if (timetonext > 0)
+		{
+			if (!(--timetonext))
+			{
+				Command_ExitGame_f();
+				return;
+			}
+		}
 	}
 	else
 	{
-		// note the setup to prevent 2x reloading
-		if (continuetime >= 0)
-			continuetime--;
-		if (continuetime == 0)
+		if (++continuetime == 3*TICRATE)
+		{
 			G_Continue();
+			return;
+		}
+
+		if (continuetime > 5 && ((continuetime & 1) || continuetime > TICRATE) && (++cont_spr2[0][2]) >= 8)
+			cont_spr2[0][2] = 0;
+
+		if (continuetime > 10 && (!(continuetime & 1) || continuetime > TICRATE+5) && (++cont_spr2[1][2]) >= 8)
+			cont_spr2[1][2] = 0;
+
+		if (continuetime == (3*TICRATE)-10)
+			S_StartSound(NULL, sfx_cdfm56); // or 31
+		else if (continuetime == 5)
+		{
+			cont_spr2[0][0] = P_GetSkinSprite2(contskins[0], SPR2_CNT2, NULL);
+			cont_spr2[0][4] = contskins[0]->sprites[cont_spr2[0][0]].numframes;
+			cont_spr2[0][1] = cont_spr2[0][3] = 0;
+			cont_spr2[0][5] = 2;
+		}
+		else if (continuetime == TICRATE)
+		{
+			cont_spr2[0][0] = P_GetSkinSprite2(contskins[0], SPR2_CNT3, NULL);
+			cont_spr2[0][4] = contskins[0]->sprites[cont_spr2[0][0]].numframes;
+			cont_spr2[0][1] = cont_spr2[0][3] = 0;
+		}
+		else if (contskins[1])
+		{
+			if (continuetime == 10)
+			{
+				cont_spr2[1][0] = P_GetSkinSprite2(contskins[1], SPR2_CNT2, NULL);
+				cont_spr2[1][4] = contskins[1]->sprites[cont_spr2[1][0]].numframes;
+				cont_spr2[1][1] = cont_spr2[1][3] = 0;
+				cont_spr2[1][5] = 2;
+			}
+			else if (continuetime == TICRATE+5)
+			{
+				cont_spr2[1][0] = P_GetSkinSprite2(contskins[1], SPR2_CNT3, NULL);
+				cont_spr2[1][4] = contskins[1]->sprites[cont_spr2[1][0]].numframes;
+				cont_spr2[1][1] = cont_spr2[1][3] = 0;
+			}
+		}
+	}
+
+	if ((++cont_spr2[0][3]) >= cont_spr2[0][5])
+	{
+		cont_spr2[0][3] = 0;
+		if (++cont_spr2[0][1] >= cont_spr2[0][4])
+			cont_spr2[0][1] = 0;
+	}
+
+	if (contskins[1] && (++cont_spr2[1][3]) >= cont_spr2[1][5])
+	{
+		cont_spr2[1][3] = 0;
+		if (++cont_spr2[1][1] >= cont_spr2[1][4])
+			cont_spr2[1][1] = 0;
 	}
 }
 
@@ -2568,8 +2761,9 @@ boolean F_ContinueResponder(event_t *event)
 
 	keypressed = true;
 	imcontinuing = true;
-	continuetime = TICRATE;
-	S_StartSound(NULL, sfx_itemup);
+	S_StartSound(NULL, sfx_kc6b);
+	I_FadeSong(0, MUSICRATE, &S_StopMusic);
+
 	return true;
 }
 
diff --git a/src/filesrch.c b/src/filesrch.c
index 8f157bdd522c86af9cb16c647de8f802cb7f42d6..13d73b6f49845b5b693ff69080b2a779e1364b9d 100644
--- a/src/filesrch.c
+++ b/src/filesrch.c
@@ -751,7 +751,7 @@ boolean preparefilemenu(boolean samedepth)
 				}
 				else if (ext == EXT_TXT)
 				{
-					if (!strcmp(dent->d_name, "log.txt") || !strcmp(dent->d_name, "errorlog.txt"))
+					if (!strncmp(dent->d_name, "log-", 4) || !strcmp(dent->d_name, "errorlog.txt"))
 						ext |= EXT_LOADED;
 				}
 
diff --git a/src/g_game.c b/src/g_game.c
index d5faf68468cc2b1b0f01a09083210a6cb7dd2da6..a3025f9491f2b2c756229c275ae55916b5f82b7c 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -172,6 +172,7 @@ static boolean retrying = false;
 UINT8 stagefailed; // Used for GEMS BONUS? Also to see if you beat the stage.
 
 UINT16 emeralds;
+INT32 luabanks[NUM_LUABANKS]; // yes, even in non HAVE_BLUA
 UINT32 token; // Number of tokens collected in a level
 UINT32 tokenlist; // List of tokens collected
 boolean gottoken; // Did you get a token? Used for end of act
@@ -215,7 +216,7 @@ UINT16 spacetimetics = 11*TICRATE + (TICRATE/2);
 UINT16 extralifetics = 4*TICRATE;
 UINT16 nightslinktics = 2*TICRATE;
 
-INT32 gameovertics = 15*TICRATE;
+INT32 gameovertics = 11*TICRATE;
 
 UINT8 ammoremovaltics = 2*TICRATE;
 
@@ -2083,6 +2084,7 @@ static inline void G_PlayerFinishLevel(INT32 player)
 
 	p->mo->flags2 &= ~MF2_SHADOW; // cancel invisibility
 	P_FlashPal(p, 0, 0); // Resets
+	p->starpostscale = 0;
 	p->starpostangle = 0;
 	p->starposttime = 0;
 	p->starpostx = 0;
@@ -2098,7 +2100,7 @@ static inline void G_PlayerFinishLevel(INT32 player)
 // G_PlayerReborn
 // Called after a player dies. Almost everything is cleared and initialized.
 //
-void G_PlayerReborn(INT32 player)
+void G_PlayerReborn(INT32 player, boolean betweenmaps)
 {
 	player_t *p;
 	INT32 score;
@@ -2129,6 +2131,7 @@ void G_PlayerReborn(INT32 player)
 	INT16 starpostz;
 	INT32 starpostnum;
 	INT32 starpostangle;
+	fixed_t starpostscale;
 	fixed_t jumpfactor;
 	fixed_t height;
 	fixed_t spinheight;
@@ -2145,6 +2148,8 @@ void G_PlayerReborn(INT32 player)
 	boolean outofcoop;
 	INT16 bot;
 	SINT8 pity;
+	INT16 rings;
+	INT16 spheres;
 
 	score = players[player].score;
 	lives = players[player].lives;
@@ -2184,6 +2189,7 @@ void G_PlayerReborn(INT32 player)
 	starpostz = players[player].starpostz;
 	starpostnum = players[player].starpostnum;
 	starpostangle = players[player].starpostangle;
+	starpostscale = players[player].starpostscale;
 	jumpfactor = players[player].jumpfactor;
 	height = players[player].height;
 	spinheight = players[player].spinheight;
@@ -2199,6 +2205,17 @@ void G_PlayerReborn(INT32 player)
 	bot = players[player].bot;
 	pity = players[player].pity;
 
+	if (betweenmaps || !G_IsSpecialStage(gamemap))
+	{
+		rings = (ultimatemode ? 0 : mapheaderinfo[gamemap-1]->startrings);
+		spheres = 0;
+	}
+	else
+	{
+		rings = players[player].rings;
+		spheres = players[player].spheres;
+	}
+
 	p = &players[player];
 	memset(p, 0, sizeof (*p));
 
@@ -2239,6 +2256,7 @@ void G_PlayerReborn(INT32 player)
 	p->starpostz = starpostz;
 	p->starpostnum = starpostnum;
 	p->starpostangle = starpostangle;
+	p->starpostscale = starpostscale;
 	p->jumpfactor = jumpfactor;
 	p->height = height;
 	p->spinheight = spinheight;
@@ -2252,6 +2270,8 @@ void G_PlayerReborn(INT32 player)
 	if (bot)
 		p->bot = 1; // reset to AI-controlled
 	p->pity = pity;
+	p->rings = rings;
+	p->spheres = spheres;
 
 	// Don't do anything immediately
 	p->pflags |= PF_USEDOWN;
@@ -2259,12 +2279,33 @@ void G_PlayerReborn(INT32 player)
 	p->pflags |= PF_JUMPDOWN;
 
 	p->playerstate = PST_LIVE;
-	p->rings = p->spheres = 0; // 0 rings
 	p->panim = PA_IDLE; // standing animation
 
 	//if ((netgame || multiplayer) && !p->spectator) -- moved into P_SpawnPlayer to account for forced changes there
 		//p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent
 
+	// Check to make sure their color didn't change somehow...
+	if (G_GametypeHasTeams())
+	{
+		if (p->ctfteam == 1 && p->skincolor != skincolor_redteam)
+		{
+			if (p == &players[consoleplayer])
+				CV_SetValue(&cv_playercolor, skincolor_redteam);
+			else if (p == &players[secondarydisplayplayer])
+				CV_SetValue(&cv_playercolor2, skincolor_redteam);
+		}
+		else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam)
+		{
+			if (p == &players[consoleplayer])
+				CV_SetValue(&cv_playercolor, skincolor_blueteam);
+			else if (p == &players[secondarydisplayplayer])
+				CV_SetValue(&cv_playercolor2, skincolor_blueteam);
+		}
+	}
+
+	if (betweenmaps)
+		return;
+
 	if (p-players == consoleplayer)
 	{
 		if (mapmusflags & MUSIC_RELOADRESET)
@@ -2276,7 +2317,7 @@ void G_PlayerReborn(INT32 player)
 		}
 
 		// This is in S_Start, but this was not here previously.
-		// if (cv_resetmusic.value)
+		// if (RESETMUSIC)
 		// 	S_StopMusic();
 		S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
 	}
@@ -2284,9 +2325,6 @@ void G_PlayerReborn(INT32 player)
 	if (gametype == GT_COOP)
 		P_FindEmerald(); // scan for emeralds to hunt for
 
-	// Reset Nights score and max link to 0 on death
-	p->marescore = p->maxlink = 0;
-
 	// If NiGHTS, find lowest mare to start with.
 	p->mare = P_FindLowestMare();
 
@@ -2294,27 +2332,6 @@ void G_PlayerReborn(INT32 player)
 
 	if (p->mare == 255)
 		p->mare = 0;
-
-	p->marelap = p->marebonuslap = 0;
-
-	// Check to make sure their color didn't change somehow...
-	if (G_GametypeHasTeams())
-	{
-		if (p->ctfteam == 1 && p->skincolor != skincolor_redteam)
-		{
-			if (p == &players[consoleplayer])
-				CV_SetValue(&cv_playercolor, skincolor_redteam);
-			else if (p == &players[secondarydisplayplayer])
-				CV_SetValue(&cv_playercolor2, skincolor_redteam);
-		}
-		else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam)
-		{
-			if (p == &players[consoleplayer])
-				CV_SetValue(&cv_playercolor, skincolor_blueteam);
-			else if (p == &players[secondarydisplayplayer])
-				CV_SetValue(&cv_playercolor2, skincolor_blueteam);
-		}
-	}
 }
 
 //
@@ -2370,8 +2387,6 @@ void G_SpawnPlayer(INT32 playernum, boolean starpost)
 
 	P_SpawnPlayer(playernum);
 
-	players[playernum].rings = mapheaderinfo[gamemap-1]->startrings;
-
 	if (starpost) //Don't even bother with looking for a place to spawn.
 	{
 		P_MovePlayerToStarpost(playernum);
@@ -2594,7 +2609,7 @@ void G_DoReborn(INT32 playernum)
 
 	if (countdowntimeup || (!(netgame || multiplayer) && gametype == GT_COOP))
 		resetlevel = true;
-	else if (gametype == GT_COOP && (netgame || multiplayer))
+	else if (gametype == GT_COOP && (netgame || multiplayer) && !G_IsSpecialStage(gamemap))
 	{
 		boolean notgameover = true;
 
@@ -2657,6 +2672,7 @@ void G_DoReborn(INT32 playernum)
 			{
 				if (!playeringame[i])
 					continue;
+				players[i].starpostscale = 0;
 				players[i].starpostangle = 0;
 				players[i].starposttime = 0;
 				players[i].starpostx = 0;
@@ -2779,6 +2795,7 @@ void G_AddPlayer(INT32 playernum)
 			if (!(cv_coopstarposts.value && (gametype == GT_COOP) && (p->starpostnum < players[i].starpostnum)))
 				continue;
 
+			p->starpostscale = players[i].starpostscale;
 			p->starposttime = players[i].starposttime;
 			p->starpostx = players[i].starpostx;
 			p->starposty = players[i].starposty;
@@ -3782,7 +3799,29 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
 
 		// File end marker check
 		CHECKPOS
-		if (READUINT8(save_p) != 0x1d) BADSAVE;
+		switch (READUINT8(save_p))
+		{
+			case 0xb7:
+				{
+					UINT8 i, banksinuse;
+					CHECKPOS
+					banksinuse = READUINT8(save_p);
+					CHECKPOS
+					if (banksinuse > NUM_LUABANKS)
+						BADSAVE
+					for (i = 0; i < banksinuse; i++)
+					{
+						(void)READINT32(save_p);
+						CHECKPOS
+					}
+					if (READUINT8(save_p) != 0x1d)
+						BADSAVE
+				}
+			case 0x1d:
+				break;
+			default:
+				BADSAVE
+		}
 
 		// done
 		saved = FIL_WriteFile(backup, savebuffer, length);
@@ -3866,7 +3905,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
 			players[i].playerstate = PST_REBORN;
-			players[i].starpostangle = players[i].starpostnum = players[i].starposttime = 0;
+			players[i].starpostscale = players[i].starpostangle = players[i].starpostnum = players[i].starposttime = 0;
 			players[i].starpostx = players[i].starposty = players[i].starpostz = 0;
 
 			if (netgame || multiplayer)
@@ -4577,7 +4616,7 @@ void G_GhostTicker(void)
 				default:
 				case GHC_RETURNSKIN:
 					g->mo->skin = g->oldmo.skin;
-					// fallthru
+					/* FALLTHRU */
 				case GHC_NORMAL: // Go back to skin color
 					g->mo->color = g->oldmo.color;
 					break;
diff --git a/src/g_game.h b/src/g_game.h
index e161bc8ed4a7cf11eefe93bbf6715825426b582c..df1301dd7c392071a14fa66870c9f8fc7f099883 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -100,7 +100,7 @@ extern INT32 localaiming, localaiming2; // should be an angle_t but signed
 //
 void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo);
 void G_DoReborn(INT32 playernum);
-void G_PlayerReborn(INT32 player);
+void G_PlayerReborn(INT32 player, boolean betweenmaps);
 void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer,
 	boolean skipprecutscene, boolean FLS);
 char *G_BuildMapTitle(INT32 mapnum);
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 6bc2c712e3f5c593d24aed10dd643c7134d76d50..02df290b8c0634382a978a32d96c075b385d2d12 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -30,6 +30,7 @@
 #include "../z_zone.h"
 #include "../v_video.h"
 #include "../r_draw.h"
+#include "../p_setup.h"
 
 //Hurdler: 25/04/2000: used for new colormap code in hardware mode
 //static UINT8 *gr_colormap = NULL; // by default it must be NULL ! (because colormap tables are not initialized)
@@ -60,7 +61,6 @@ static const INT32 format2bpp[16] =
 	2, //14 GR_TEXFMT_AP_88
 };
 
-
 // This code was originally placed directly in HWR_DrawPatchInCache.
 // It is now split from it for my sanity! (and the sanity of others)
 // -- Monster Iestyn (13/02/19)
@@ -138,18 +138,37 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 			// Alam: SRB2 uses Mingw, HUGS
 			switch (bpp)
 			{
-				case 2 : texelu16 = (UINT16)((alpha<<8) | texel);
+				case 2 : // uhhhhhhhh..........
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+							 texel = ASTBlendPixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
+						 texelu16 = (UINT16)((alpha<<8) | texel);
 						 memcpy(dest, &texelu16, sizeof(UINT16));
 						 break;
 				case 3 : colortemp = V_GetColor(texel);
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+						 {
+							 RGBA_t rgbatexel;
+							 rgbatexel.rgba = *(UINT32 *)dest;
+							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 						 break;
 				case 4 : colortemp = V_GetColor(texel);
 						 colortemp.s.alpha = alpha;
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+						 {
+							 RGBA_t rgbatexel;
+							 rgbatexel.rgba = *(UINT32 *)dest;
+							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t));
 						 break;
 				// default is 1
-				default: *dest = texel;
+				default:
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+							 *dest = ASTBlendPixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
+						 else
+							 *dest = texel;
 						 break;
 			}
 
@@ -233,18 +252,37 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 			// Alam: SRB2 uses Mingw, HUGS
 			switch (bpp)
 			{
-				case 2 : texelu16 = (UINT16)((alpha<<8) | texel);
+				case 2 : // uhhhhhhhh..........
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+							 texel = ASTBlendPixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
+						 texelu16 = (UINT16)((alpha<<8) | texel);
 						 memcpy(dest, &texelu16, sizeof(UINT16));
 						 break;
 				case 3 : colortemp = V_GetColor(texel);
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+						 {
+							 RGBA_t rgbatexel;
+							 rgbatexel.rgba = *(UINT32 *)dest;
+							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 						 break;
 				case 4 : colortemp = V_GetColor(texel);
 						 colortemp.s.alpha = alpha;
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+						 {
+							 RGBA_t rgbatexel;
+							 rgbatexel.rgba = *(UINT32 *)dest;
+							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t));
 						 break;
 				// default is 1
-				default: *dest = texel;
+				default:
+						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
+							 *dest = ASTBlendPixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
+						 else
+							 *dest = texel;
 						 break;
 			}
 
@@ -331,16 +369,7 @@ static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
 	if (texture->width <= 0 || texture->height <= 0)
 		return;
 
-	/*if ((patch->style == AST_TRANSLUCENT) && (patch->alpha <= (10*255/11))) // Alpha style set to translucent? Is the alpha small enough for translucency?
-	{
-		if (patch->alpha < 255/11) // Is the patch way too translucent? Don't render then.
-			continue;
-		ColumnDrawerPointer = (patch->flip & 2) ? HWR_DrawTransFlippedColumnInCache : HWR_DrawTransColumnInCache;
-	}
-	else*/
-	{
-		ColumnDrawerPointer = (patch->flip & 2) ? HWR_DrawFlippedColumnInCache : HWR_DrawColumnInCache;
-	}
+	ColumnDrawerPointer = (patch->flip & 2) ? HWR_DrawFlippedColumnInCache : HWR_DrawColumnInCache;
 
 	x1 = patch->originx;
 	width = SHORT(realpatch->width);
@@ -420,6 +449,7 @@ static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
 static void HWR_ResizeBlock(INT32 originalwidth, INT32 originalheight,
 	GrTexInfo *grInfo)
 {
+#ifdef GLIDE_API_COMPATIBILITY
 	//   Build the full textures from patches.
 	static const GrLOD_t gr_lods[9] =
 	{
@@ -456,6 +486,9 @@ static void HWR_ResizeBlock(INT32 originalwidth, INT32 originalheight,
 
 	INT32     j,k;
 	INT32     max,min;
+#else
+	(void)grInfo;
+#endif
 
 	// find a power of 2 width/height
 	if (cv_grrounddown.value)
@@ -511,6 +544,7 @@ static void HWR_ResizeBlock(INT32 originalwidth, INT32 originalheight,
 	}
 	else
 	{
+#ifdef GLIDE_API_COMPATIBILITY
 		//size up to nearest power of 2
 		blockwidth = 1;
 		while (blockwidth < originalwidth)
@@ -528,9 +562,14 @@ static void HWR_ResizeBlock(INT32 originalwidth, INT32 originalheight,
 		if (blockheight > 2048)
 			blockheight = 2048;
 			//I_Error("3D GenerateTexture : too big");
+#else
+		blockwidth = originalwidth;
+		blockheight = originalheight;
+#endif
 	}
 
 	// do the boring LOD stuff.. blech!
+#ifdef GLIDE_API_COMPATIBILITY
 	if (blockwidth >= blockheight)
 	{
 		max = blockwidth;
@@ -562,6 +601,7 @@ static void HWR_ResizeBlock(INT32 originalwidth, INT32 originalheight,
 	if (blockwidth < blockheight)
 		j += 4;
 	grInfo->aspectRatioLog2 = gr_aspects[j].aspect;
+#endif
 
 	blocksize = blockwidth * blockheight;
 
@@ -650,7 +690,12 @@ static void HWR_GenerateTexture(INT32 texnum, GLTexture_t *grtex)
 	// Composite the columns together.
 	for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
 	{
+		size_t lumplength = W_LumpLengthPwad(patch->wad, patch->lump);
 		realpatch = W_CacheLumpNumPwad(patch->wad, patch->lump, PU_CACHE);
+#ifndef NO_PNG_LUMPS
+		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
+			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL, false);
+#endif
 		HWR_DrawTexturePatchInCache(&grtex->mipmap,
 		                     blockwidth, blockheight,
 		                     texture, patch,
@@ -679,6 +724,13 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 {
 	INT32 newwidth, newheight;
 
+#ifndef NO_PNG_LUMPS
+	// lump is a png so convert it
+	size_t len = W_LumpLengthPwad(grPatch->wadnum, grPatch->lumpnum);
+	if ((patch != NULL) && R_IsLumpPNG((const UINT8 *)patch, len))
+		patch = R_PNGToPatch((const UINT8 *)patch, len, NULL, true);
+#endif
+
 	// don't do it twice (like a cache)
 	if (grMipmap->width == 0)
 	{
@@ -756,11 +808,13 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 
 static size_t gr_numtextures;
 static GLTexture_t *gr_textures; // for ALL Doom textures
+static GLTexture_t *gr_textures2;
 
 void HWR_InitTextureCache(void)
 {
 	gr_numtextures = 0;
 	gr_textures = NULL;
+	gr_textures2 = NULL;
 }
 
 
@@ -799,7 +853,10 @@ void HWR_FreeTextureCache(void)
 	// texturecache info, we can free it
 	if (gr_textures)
 		free(gr_textures);
+	if (gr_textures2)
+		free(gr_textures2);
 	gr_textures = NULL;
+	gr_textures2 = NULL;
 	gr_numtextures = 0;
 }
 
@@ -817,6 +874,9 @@ void HWR_PrepLevelCache(size_t pnumtextures)
 	gr_textures = calloc(pnumtextures, sizeof (*gr_textures));
 	if (gr_textures == NULL)
 		I_Error("3D can't alloc gr_textures");
+	gr_textures2 = calloc(pnumtextures, sizeof (*gr_textures2));
+	if (gr_textures2 == NULL)
+		I_Error("3D can't alloc gr_textures2");
 }
 
 void HWR_SetPalette(RGBA_t *palette)
@@ -847,7 +907,7 @@ GLTexture_t *HWR_GetTexture(INT32 tex)
 	GLTexture_t *grtex;
 #ifdef PARANOIA
 	if ((unsigned)tex >= gr_numtextures)
-		I_Error(" HWR_GetTexture: tex >= numtextures\n");
+		I_Error("HWR_GetTexture: tex >= numtextures\n");
 #endif
 	grtex = &gr_textures[tex];
 
@@ -862,15 +922,39 @@ GLTexture_t *HWR_GetTexture(INT32 tex)
 	return grtex;
 }
 
+// HWR_RenderPlane and HWR_RenderPolyObjectPlane need this to get the flat dimensions from a patch.
+lumpnum_t gr_patchflat;
+
+static void HWR_LoadPatchFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
+{
+	UINT8 *flat;
+	patch_t *patch = (patch_t *)W_CacheLumpNum(flatlumpnum, PU_STATIC);
+	size_t lumplength = W_LumpLength(flatlumpnum);
+
+#ifndef NO_PNG_LUMPS
+	if (R_IsLumpPNG((UINT8 *)patch, lumplength))
+		patch = R_PNGToPatch((UINT8 *)patch, lumplength, NULL, false);
+#endif
+
+	grMipmap->width  = (UINT16)SHORT(patch->width);
+	grMipmap->height = (UINT16)SHORT(patch->height);
+
+	flat = Z_Malloc(grMipmap->width * grMipmap->height, PU_HWRCACHE, &grMipmap->grInfo.data);
+	memset(flat, TRANSPARENTPIXEL, grMipmap->width * grMipmap->height);
+
+	R_PatchToFlat(patch, flat);
+}
 
 static void HWR_CacheFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
 {
 	size_t size, pflatsize;
 
 	// setup the texture info
+#ifdef GLIDE_API_COMPATIBILITY
 	grMipmap->grInfo.smallLodLog2 = GR_LOD_LOG2_64;
 	grMipmap->grInfo.largeLodLog2 = GR_LOD_LOG2_64;
 	grMipmap->grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+#endif
 	grMipmap->grInfo.format = GR_TEXFMT_P_8;
 	grMipmap->flags = TF_WRAPXY|TF_CHROMAKEYED;
 
@@ -900,14 +984,19 @@ static void HWR_CacheFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
 			pflatsize = 64;
 			break;
 	}
-	grMipmap->width  = (UINT16)pflatsize;
-	grMipmap->height = (UINT16)pflatsize;
 
-	// the flat raw data needn't be converted with palettized textures
-	W_ReadLump(flatlumpnum, Z_Malloc(W_LumpLength(flatlumpnum),
-		PU_HWRCACHE, &grMipmap->grInfo.data));
-}
+	if (R_CheckIfPatch(flatlumpnum))
+		HWR_LoadPatchFlat(grMipmap, flatlumpnum);
+	else
+	{
+		grMipmap->width  = (UINT16)pflatsize;
+		grMipmap->height = (UINT16)pflatsize;
 
+		// the flat raw data needn't be converted with palettized textures
+		W_ReadLump(flatlumpnum, Z_Malloc(W_LumpLength(flatlumpnum),
+			PU_HWRCACHE, &grMipmap->grInfo.data));
+	}
+}
 
 // Download a Doom 'flat' to the hardware cache and make it ready for use
 void HWR_GetFlat(lumpnum_t flatlumpnum)
@@ -923,6 +1012,52 @@ void HWR_GetFlat(lumpnum_t flatlumpnum)
 
 	// The system-memory data can be purged now.
 	Z_ChangeTag(grmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
+
+	gr_patchflat = 0;
+	if (R_CheckIfPatch(flatlumpnum))
+		gr_patchflat = flatlumpnum;
+}
+
+static void HWR_LoadTextureFlat(GLMipmap_t *grMipmap, INT32 texturenum)
+{
+	UINT8 *flat;
+
+	// setup the texture info
+#ifdef GLIDE_API_COMPATIBILITY
+	grMipmap->grInfo.smallLodLog2 = GR_LOD_LOG2_64;
+	grMipmap->grInfo.largeLodLog2 = GR_LOD_LOG2_64;
+	grMipmap->grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+#endif
+	grMipmap->grInfo.format = GR_TEXFMT_P_8;
+	grMipmap->flags = TF_WRAPXY|TF_CHROMAKEYED;
+
+	grMipmap->width  = (UINT16)textures[texturenum]->width;
+	grMipmap->height = (UINT16)textures[texturenum]->height;
+
+	flat = Z_Malloc(grMipmap->width * grMipmap->height, PU_HWRCACHE, &grMipmap->grInfo.data);
+	memset(flat, TRANSPARENTPIXEL, grMipmap->width * grMipmap->height);
+
+	R_TextureToFlat(texturenum, flat);
+}
+
+void HWR_GetTextureFlat(INT32 texturenum)
+{
+	GLTexture_t *grtex;
+#ifdef PARANOIA
+	if ((unsigned)texturenum >= gr_numtextures)
+		I_Error("HWR_GetTextureFlat: texturenum >= numtextures\n");
+#endif
+	if (texturenum == 0 || texturenum == -1)
+		return;
+	grtex = &gr_textures2[texturenum];
+
+	if (!grtex->mipmap.grInfo.data && !grtex->mipmap.downloaded)
+		HWR_LoadTextureFlat(&grtex->mipmap, texturenum);
+
+	HWD.pfnSetTexture(&grtex->mipmap);
+
+	// The system-memory data can be purged now.
+	Z_ChangeTag(grtex->mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
 }
 
 //
diff --git a/src/hardware/hw_glide.h b/src/hardware/hw_glide.h
index 2625d58640bbc26d62ea18ca156b8019aee2c9bf..bf91229efa1ccde01f73436079cc009a12aa62ac 100644
--- a/src/hardware/hw_glide.h
+++ b/src/hardware/hw_glide.h
@@ -59,9 +59,11 @@ typedef FxI32 GrTextureFormat_t;
 
 typedef struct
 {
+#ifdef GLIDE_API_COMPATIBILITY
 	GrLOD_t           smallLodLog2;
 	GrLOD_t           largeLodLog2;
 	GrAspectRatio_t   aspectRatioLog2;
+#endif
 	GrTextureFormat_t format;
 	void              *data;
 } GrTexInfo;
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 9656e54e9b1d44df9bc921cbb544a03e8101a26d..c7b06edfd1f1455e5d399a0a51e58771fce88786 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -101,6 +101,7 @@ void HWR_FreeTextureCache(void);
 void HWR_FreeExtraSubsectors(void);
 
 void HWR_GetFlat(lumpnum_t flatlumpnum);
+void HWR_GetTextureFlat(INT32 texturenum);
 GLTexture_t *HWR_GetTexture(INT32 tex);
 void HWR_GetPatch(GLPatch_t *gpatch);
 void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap);
@@ -114,6 +115,8 @@ void HWR_GetFadeMask(lumpnum_t fademasklumpnum);
 // --------
 // hw_draw.c
 // --------
+extern lumpnum_t gr_patchflat;
+
 extern float gr_patch_scalex;
 extern float gr_patch_scaley;
 
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index a52d72869ee87e9c40fa6f6348d2d489da67f73c..1de20cad7a343a9311458aadbbaa5235e3f92a50 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -186,17 +186,16 @@ light_t *t_lspr[NUMSPRITES] =
 
 	// Boss 1, (Greenflower)
 	&lspr[NOLIGHT],     // SPR_EGGM
+	&lspr[NOLIGHT],     // SPR_EGLZ
 
 	// Boss 2, (Techno Hill)
 	&lspr[NOLIGHT],     // SPR_EGGN
-	&lspr[NOLIGHT],     // SPR_TNKA
-	&lspr[NOLIGHT],     // SPR_TNKB
-	&lspr[NOLIGHT],     // SPR_SPNK
+	&lspr[NOLIGHT],     // SPR_TANK
 	&lspr[NOLIGHT],     // SPR_GOOP
 
 	// Boss 3 (Deep Sea)
 	&lspr[NOLIGHT],     // SPR_EGGO
-	&lspr[NOLIGHT],     // SPR_PRPL
+	&lspr[NOLIGHT],     // SPR_SEBH
 	&lspr[NOLIGHT],     // SPR_FAKE
 
 	// Boss 4 (Castle Eggman)
@@ -1226,9 +1225,11 @@ static void HWR_SetLight(void)
 		lightmappatch.height = 128;
 		lightmappatch.mipmap.width = 128;
 		lightmappatch.mipmap.height = 128;
+#ifdef GLIDE_API_COMPATIBILITY
 		lightmappatch.mipmap.grInfo.smallLodLog2 = GR_LOD_LOG2_128;
 		lightmappatch.mipmap.grInfo.largeLodLog2 = GR_LOD_LOG2_128;
 		lightmappatch.mipmap.grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+#endif
 		lightmappatch.mipmap.flags = 0; //TF_WRAPXY; // DEBUG: view the overdraw !
 	}
 	HWD.pfnSetTexture(&lightmappatch.mipmap);
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index c600800fd9ac9e0710a0eecdf44d7e7bf8a68753..4a075d376bf232dfa8c4e452a7cfbb3f7d206ec2 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -70,9 +70,9 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing);
 #endif
 
 #ifdef SORTING
-void HWR_AddTransparentFloor(lumpnum_t lumpnum, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight,
+void HWR_AddTransparentFloor(lumpnum_t lumpnum, INT32 texturenum, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight,
                              INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap);
-void HWR_AddTransparentPolyobjectFloor(lumpnum_t lumpnum, polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
+void HWR_AddTransparentPolyobjectFloor(lumpnum_t lumpnum, INT32 texturenum, polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
                              INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, extracolormap_t *planecolormap);
 #else
 static void HWR_Add3DWater(lumpnum_t lumpnum, extrasubsector_t *xsub, fixed_t fixedheight,
@@ -522,7 +522,7 @@ static UINT8 HWR_FogBlockAlpha(INT32 light, UINT32 color) // Let's see if this c
 // HWR_RenderPlane  : Render a floor or ceiling convex polygon
 // -----------------+
 static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight,
-                           FBITFIELD PolyFlags, INT32 lightlevel, lumpnum_t lumpnum, sector_t *FOFsector, UINT8 alpha, boolean fogplane, extracolormap_t *planecolormap)
+                           FBITFIELD PolyFlags, INT32 lightlevel, lumpnum_t lumpnum, INT32 texturenum, sector_t *FOFsector, UINT8 alpha, boolean fogplane, extracolormap_t *planecolormap)
 {
 	polyvertex_t *  pv;
 	float           height; //constant y for all points on the convex flat polygon
@@ -530,8 +530,9 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	INT32             nrPlaneVerts;   //verts original define of convex flat polygon
 	INT32             i;
 	float           flatxref,flatyref;
-	float fflatsize;
+	float fflatwidth, fflatheight;
 	INT32 flatflag;
+	boolean texflat = true;
 	size_t len;
 	float scrollx = 0.0f, scrolly = 0.0f;
 	angle_t angle = 0;
@@ -540,6 +541,7 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 #ifdef ESLOPE
 	pslope_t *slope = NULL;
 #endif
+	patch_t *patch;
 
 	static FOutVector *planeVerts = NULL;
 	static UINT16 numAllocedPlaneVerts = 0;
@@ -580,9 +582,10 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	if (nrPlaneVerts < 3)   //not even a triangle ?
 		return;
 
-	if (nrPlaneVerts > (INT32)UINT16_MAX) // FIXME: exceeds plVerts size
+	// This check is so inconsistent between functions, it hurts.
+	if (nrPlaneVerts > INT16_MAX) // FIXME: exceeds plVerts size
 	{
-		CONS_Debug(DBG_RENDER, "polygon size of %d exceeds max value of %d vertices\n", nrPlaneVerts, UINT16_MAX);
+		CONS_Debug(DBG_RENDER, "polygon size of %d exceeds max value of %d vertices\n", nrPlaneVerts, INT16_MAX);
 		return;
 	}
 
@@ -599,38 +602,47 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	switch (len)
 	{
 		case 4194304: // 2048x2048 lump
-			fflatsize = 2048.0f;
-			flatflag = 2047;
+			fflatwidth = fflatheight = 2048.0f;
 			break;
 		case 1048576: // 1024x1024 lump
-			fflatsize = 1024.0f;
-			flatflag = 1023;
+			fflatwidth = fflatheight = 1024.0f;
 			break;
 		case 262144:// 512x512 lump
-			fflatsize = 512.0f;
-			flatflag = 511;
+			fflatwidth = fflatheight = 512.0f;
 			break;
 		case 65536: // 256x256 lump
-			fflatsize = 256.0f;
-			flatflag = 255;
+			fflatwidth = fflatheight = 256.0f;
 			break;
 		case 16384: // 128x128 lump
-			fflatsize = 128.0f;
-			flatflag = 127;
+			fflatwidth = fflatheight = 128.0f;
 			break;
 		case 1024: // 32x32 lump
-			fflatsize = 32.0f;
-			flatflag = 31;
+			fflatwidth = fflatheight = 32.0f;
 			break;
 		default: // 64x64 lump
-			fflatsize = 64.0f;
-			flatflag = 63;
+			fflatwidth = fflatheight = 64.0f;
 			break;
 	}
 
+	flatflag = ((INT32)fflatwidth)-1;
+
+	if (texturenum != 0 && texturenum != -1)
+	{
+		fflatwidth = textures[texturenum]->width;
+		fflatheight = textures[texturenum]->height;
+	}
+	else if (gr_patchflat && R_CheckIfPatch(gr_patchflat))		// Just in case?
+	{
+		patch = (patch_t *)W_CacheLumpNum(gr_patchflat, PU_STATIC);
+		fflatwidth = SHORT(patch->width);
+		fflatheight = SHORT(patch->height);
+	}
+	else
+		texflat = false;
+
 	// reference point for flat texture coord for each vertex around the polygon
-	flatxref = (float)(((fixed_t)pv->x & (~flatflag)) / fflatsize);
-	flatyref = (float)(((fixed_t)pv->y & (~flatflag)) / fflatsize);
+	flatxref = (float)(((fixed_t)pv->x & (~flatflag)) / fflatwidth);
+	flatyref = (float)(((fixed_t)pv->y & (~flatflag)) / fflatheight);
 
 	// transform
 	v3d = planeVerts;
@@ -639,14 +651,14 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->floor_xoffs)/fflatsize;
-			scrolly = FIXED_TO_FLOAT(FOFsector->floor_yoffs)/fflatsize;
+			scrollx = FIXED_TO_FLOAT(FOFsector->floor_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(FOFsector->floor_yoffs)/fflatheight;
 			angle = FOFsector->floorpic_angle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->ceiling_xoffs)/fflatsize;
-			scrolly = FIXED_TO_FLOAT(FOFsector->ceiling_yoffs)/fflatsize;
+			scrollx = FIXED_TO_FLOAT(FOFsector->ceiling_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(FOFsector->ceiling_yoffs)/fflatheight;
 			angle = FOFsector->ceilingpic_angle;
 		}
 	}
@@ -654,14 +666,14 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(gr_frontsector->floor_xoffs)/fflatsize;
-			scrolly = FIXED_TO_FLOAT(gr_frontsector->floor_yoffs)/fflatsize;
+			scrollx = FIXED_TO_FLOAT(gr_frontsector->floor_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gr_frontsector->floor_yoffs)/fflatheight;
 			angle = gr_frontsector->floorpic_angle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(gr_frontsector->ceiling_xoffs)/fflatsize;
-			scrolly = FIXED_TO_FLOAT(gr_frontsector->ceiling_yoffs)/fflatsize;
+			scrollx = FIXED_TO_FLOAT(gr_frontsector->ceiling_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gr_frontsector->ceiling_yoffs)/fflatheight;
 			angle = gr_frontsector->ceilingpic_angle;
 		}
 	}
@@ -680,17 +692,24 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	for (i = 0; i < nrPlaneVerts; i++,v3d++,pv++)
 	{
 		// Hurdler: add scrolling texture on floor/ceiling
-		v3d->sow = (float)((pv->x / fflatsize) - flatxref + scrollx);
-		v3d->tow = (float)(-(pv->y / fflatsize) + flatyref + scrolly);
-
-		//v3d->sow = (float)(pv->x / fflatsize);
-		//v3d->tow = (float)(pv->y / fflatsize);
+		if (texflat)
+		{
+			v3d->sow = (float)(pv->x / fflatwidth) + scrollx;
+			v3d->tow = -(float)(pv->y / fflatheight) + scrolly;
+		}
+		else
+		{
+			v3d->sow = (float)((pv->x / fflatwidth) - flatxref + scrollx);
+			v3d->tow = (float)(flatyref - (pv->y / fflatheight) + scrolly);
+		}
 
 		// Need to rotate before translate
 		if (angle) // Only needs to be done if there's an altered angle
 		{
 			tempxsow = FLOAT_TO_FIXED(v3d->sow);
 			tempytow = FLOAT_TO_FIXED(v3d->tow);
+			if (texflat)
+				tempytow = -tempytow;
 			v3d->sow = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINECOSINE(angle)) - FixedMul(tempytow, FINESINE(angle))));
 			v3d->tow = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINESINE(angle)) + FixedMul(tempytow, FINECOSINE(angle))));
 		}
@@ -3164,21 +3183,23 @@ static inline void HWR_AddPolyObjectSegs(void)
 
 #ifdef POLYOBJECTS_PLANES
 static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
-									FBITFIELD blendmode, UINT8 lightlevel, lumpnum_t lumpnum, sector_t *FOFsector,
+									FBITFIELD blendmode, UINT8 lightlevel, lumpnum_t lumpnum, INT32 texturenum, sector_t *FOFsector,
 									UINT8 alpha, extracolormap_t *planecolormap)
 {
 	float           height; //constant y for all points on the convex flat polygon
 	FOutVector      *v3d;
 	INT32             i;
 	float           flatxref,flatyref;
-	float fflatsize;
+	float fflatwidth, fflatheight;
 	INT32 flatflag;
+	boolean texflat = true;
 	size_t len;
 	float scrollx = 0.0f, scrolly = 0.0f;
 	angle_t angle = 0;
 	FSurfaceInfo    Surf;
 	fixed_t tempxsow, tempytow;
 	size_t nrPlaneVerts;
+	patch_t *patch;
 
 	static FOutVector *planeVerts = NULL;
 	static UINT16 numAllocedPlaneVerts = 0;
@@ -3209,38 +3230,47 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	switch (len)
 	{
 		case 4194304: // 2048x2048 lump
-			fflatsize = 2048.0f;
-			flatflag = 2047;
+			fflatwidth = fflatheight = 2048.0f;
 			break;
 		case 1048576: // 1024x1024 lump
-			fflatsize = 1024.0f;
-			flatflag = 1023;
+			fflatwidth = fflatheight = 1024.0f;
 			break;
 		case 262144:// 512x512 lump
-			fflatsize = 512.0f;
-			flatflag = 511;
+			fflatwidth = fflatheight = 512.0f;
 			break;
 		case 65536: // 256x256 lump
-			fflatsize = 256.0f;
-			flatflag = 255;
+			fflatwidth = fflatheight = 256.0f;
 			break;
 		case 16384: // 128x128 lump
-			fflatsize = 128.0f;
-			flatflag = 127;
+			fflatwidth = fflatheight = 128.0f;
 			break;
 		case 1024: // 32x32 lump
-			fflatsize = 32.0f;
-			flatflag = 31;
+			fflatwidth = fflatheight = 32.0f;
 			break;
 		default: // 64x64 lump
-			fflatsize = 64.0f;
-			flatflag = 63;
+			fflatwidth = fflatheight = 64.0f;
 			break;
 	}
 
+	flatflag = ((INT32)fflatwidth)-1;
+
+	if (texturenum != 0 && texturenum != -1)
+	{
+		fflatwidth = textures[texturenum]->width;
+		fflatheight = textures[texturenum]->height;
+	}
+	else if (gr_patchflat && R_CheckIfPatch(gr_patchflat))		// Just in case?
+	{
+		patch = (patch_t *)W_CacheLumpNum(gr_patchflat, PU_STATIC);
+		fflatwidth = SHORT(patch->width);
+		fflatheight = SHORT(patch->height);
+	}
+	else
+		texflat = false;
+
 	// reference point for flat texture coord for each vertex around the polygon
-	flatxref = (float)(((fixed_t)FIXED_TO_FLOAT(polysector->origVerts[0].x) & (~flatflag)) / fflatsize);
-	flatyref = (float)(((fixed_t)FIXED_TO_FLOAT(polysector->origVerts[0].y) & (~flatflag)) / fflatsize);
+	flatxref = (float)((polysector->origVerts[0].x & (~flatflag)) / fflatwidth);
+	flatyref = (float)((polysector->origVerts[0].y & (~flatflag)) / fflatheight);
 
 	// transform
 	v3d = planeVerts;
@@ -3249,14 +3279,14 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->floor_xoffs)/fflatsize;
-			scrolly = FIXED_TO_FLOAT(FOFsector->floor_yoffs)/fflatsize;
+			scrollx = FIXED_TO_FLOAT(FOFsector->floor_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(FOFsector->floor_yoffs)/fflatheight;
 			angle = FOFsector->floorpic_angle>>ANGLETOFINESHIFT;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->ceiling_xoffs)/fflatsize;
-			scrolly = FIXED_TO_FLOAT(FOFsector->ceiling_yoffs)/fflatsize;
+			scrollx = FIXED_TO_FLOAT(FOFsector->ceiling_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(FOFsector->ceiling_yoffs)/fflatheight;
 			angle = FOFsector->ceilingpic_angle>>ANGLETOFINESHIFT;
 		}
 	}
@@ -3264,14 +3294,14 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(gr_frontsector->floor_xoffs)/fflatsize;
-			scrolly = FIXED_TO_FLOAT(gr_frontsector->floor_yoffs)/fflatsize;
+			scrollx = FIXED_TO_FLOAT(gr_frontsector->floor_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gr_frontsector->floor_yoffs)/fflatheight;
 			angle = gr_frontsector->floorpic_angle>>ANGLETOFINESHIFT;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(gr_frontsector->ceiling_xoffs)/fflatsize;
-			scrolly = FIXED_TO_FLOAT(gr_frontsector->ceiling_yoffs)/fflatsize;
+			scrollx = FIXED_TO_FLOAT(gr_frontsector->ceiling_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gr_frontsector->ceiling_yoffs)/fflatheight;
 			angle = gr_frontsector->ceilingpic_angle>>ANGLETOFINESHIFT;
 		}
 	}
@@ -3294,15 +3324,26 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 
 	for (i = 0; i < (INT32)nrPlaneVerts; i++,v3d++)
 	{
-		// Hurdler: add scrolling texture on floor/ceiling
-		v3d->sow = (float)((FIXED_TO_FLOAT(polysector->origVerts[i].x) / fflatsize) - flatxref + scrollx); // Go from the polysector's original vertex locations
-		v3d->tow = (float)(flatyref - (FIXED_TO_FLOAT(polysector->origVerts[i].y) / fflatsize) + scrolly); // Means the flat is offset based on the original vertex locations
+		// Go from the polysector's original vertex locations
+		// Means the flat is offset based on the original vertex locations
+		if (texflat)
+		{
+			v3d->sow = (float)(FIXED_TO_FLOAT(polysector->origVerts[i].x) / fflatwidth) + scrollx;
+			v3d->tow = -(float)(FIXED_TO_FLOAT(polysector->origVerts[i].y) / fflatheight) + scrolly;
+		}
+		else
+		{
+			v3d->sow = (float)((FIXED_TO_FLOAT(polysector->origVerts[i].x) / fflatwidth) - flatxref + scrollx);
+			v3d->tow = (float)(flatyref - (FIXED_TO_FLOAT(polysector->origVerts[i].y) / fflatheight) + scrolly);
+		}
 
 		// Need to rotate before translate
 		if (angle) // Only needs to be done if there's an altered angle
 		{
 			tempxsow = FLOAT_TO_FIXED(v3d->sow);
 			tempytow = FLOAT_TO_FIXED(v3d->tow);
+			if (texflat)
+				tempytow = -tempytow;
 			v3d->sow = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINECOSINE(angle)) - FixedMul(tempytow, FINESINE(angle))));
 			v3d->tow = (FIXED_TO_FLOAT(-FixedMul(tempxsow, FINESINE(angle)) - FixedMul(tempytow, FINECOSINE(angle))));
 		}
@@ -3333,6 +3374,7 @@ static void HWR_AddPolyObjectPlanes(void)
 {
 	size_t i;
 	sector_t *polyobjsector;
+	INT32 light = 0;
 
 	// Polyobject Planes need their own function for drawing because they don't have extrasubsectors by themselves
 	// It should be okay because polyobjects should always be convex anyway
@@ -3351,19 +3393,23 @@ static void HWR_AddPolyObjectPlanes(void)
 			&& polyobjsector->floorheight >= gr_frontsector->floorheight
 			&& (viewz < polyobjsector->floorheight))
 		{
+			light = R_GetPlaneLight(gr_frontsector, polyobjsector->floorheight, true);
 			if (po_ptrs[i]->translucency > 0)
 			{
 				FSurfaceInfo Surf;
-				FBITFIELD blendmode = HWR_TranstableToAlpha(po_ptrs[i]->translucency, &Surf);
-				HWR_AddTransparentPolyobjectFloor(levelflats[polyobjsector->floorpic].lumpnum, po_ptrs[i], false, polyobjsector->floorheight,
-													polyobjsector->lightlevel, Surf.FlatColor.s.alpha, polyobjsector, blendmode, NULL);
+				FBITFIELD blendmode;
+				memset(&Surf, 0x00, sizeof(Surf));
+				blendmode = HWR_TranstableToAlpha(po_ptrs[i]->translucency, &Surf);
+				HWR_AddTransparentPolyobjectFloor(levelflats[polyobjsector->floorpic].lumpnum, levelflats[polyobjsector->floorpic].texturenum, po_ptrs[i], false, polyobjsector->floorheight,
+													(light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), Surf.FlatColor.s.alpha, polyobjsector, blendmode, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
 			}
 			else
 			{
 				HWR_GetFlat(levelflats[polyobjsector->floorpic].lumpnum);
+				HWR_GetTextureFlat(levelflats[polyobjsector->floorpic].texturenum);
 				HWR_RenderPolyObjectPlane(po_ptrs[i], false, polyobjsector->floorheight, PF_Occlude,
-										polyobjsector->lightlevel, levelflats[polyobjsector->floorpic].lumpnum,
-										polyobjsector, 255, NULL);
+										(light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), levelflats[polyobjsector->floorpic].lumpnum, levelflats[polyobjsector->floorpic].texturenum,
+										polyobjsector, 255, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
 			}
 		}
 
@@ -3371,21 +3417,23 @@ static void HWR_AddPolyObjectPlanes(void)
 			&& polyobjsector->ceilingheight <= gr_frontsector->ceilingheight
 			&& (viewz > polyobjsector->ceilingheight))
 		{
+			light = R_GetPlaneLight(gr_frontsector, polyobjsector->ceilingheight, true);
 			if (po_ptrs[i]->translucency > 0)
 			{
 				FSurfaceInfo Surf;
 				FBITFIELD blendmode;
 				memset(&Surf, 0x00, sizeof(Surf));
 				blendmode = HWR_TranstableToAlpha(po_ptrs[i]->translucency, &Surf);
-				HWR_AddTransparentPolyobjectFloor(levelflats[polyobjsector->ceilingpic].lumpnum, po_ptrs[i], true, polyobjsector->ceilingheight,
-				                                  polyobjsector->lightlevel, Surf.FlatColor.s.alpha, polyobjsector, blendmode, NULL);
+				HWR_AddTransparentPolyobjectFloor(levelflats[polyobjsector->ceilingpic].lumpnum, levelflats[polyobjsector->floorpic].texturenum, po_ptrs[i], true, polyobjsector->ceilingheight,
+				                                  (light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), Surf.FlatColor.s.alpha, polyobjsector, blendmode, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
 			}
 			else
 			{
 				HWR_GetFlat(levelflats[polyobjsector->ceilingpic].lumpnum);
+				HWR_GetTextureFlat(levelflats[polyobjsector->ceilingpic].texturenum);
 				HWR_RenderPolyObjectPlane(po_ptrs[i], true, polyobjsector->ceilingheight, PF_Occlude,
-				                          polyobjsector->lightlevel, levelflats[polyobjsector->floorpic].lumpnum,
-				                          polyobjsector, 255, NULL);
+				                          (light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), levelflats[polyobjsector->floorpic].lumpnum, levelflats[polyobjsector->floorpic].texturenum,
+				                          polyobjsector, 255, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
 			}
 		}
 	}
@@ -3536,11 +3584,12 @@ static void HWR_Subsector(size_t num)
 			if (sub->validcount != validcount)
 			{
 				HWR_GetFlat(levelflats[gr_frontsector->floorpic].lumpnum);
+				HWR_GetTextureFlat(levelflats[gr_frontsector->floorpic].texturenum);
 				HWR_RenderPlane(gr_frontsector, &extrasubsectors[num], false,
 					// Hack to make things continue to work around slopes.
 					locFloorHeight == cullFloorHeight ? locFloorHeight : gr_frontsector->floorheight,
 					// We now return you to your regularly scheduled rendering.
-					PF_Occlude, floorlightlevel, levelflats[gr_frontsector->floorpic].lumpnum, NULL, 255, false, floorcolormap);
+					PF_Occlude, floorlightlevel, levelflats[gr_frontsector->floorpic].lumpnum, levelflats[gr_frontsector->floorpic].texturenum, NULL, 255, false, floorcolormap);
 			}
 		}
 		else
@@ -3558,11 +3607,12 @@ static void HWR_Subsector(size_t num)
 			if (sub->validcount != validcount)
 			{
 				HWR_GetFlat(levelflats[gr_frontsector->ceilingpic].lumpnum);
+				HWR_GetTextureFlat(levelflats[gr_frontsector->ceilingpic].texturenum);
 				HWR_RenderPlane(NULL, &extrasubsectors[num], true,
 					// Hack to make things continue to work around slopes.
 					locCeilingHeight == cullCeilingHeight ? locCeilingHeight : gr_frontsector->ceilingheight,
 					// We now return you to your regularly scheduled rendering.
-					PF_Occlude, ceilinglightlevel, levelflats[gr_frontsector->ceilingpic].lumpnum,NULL, 255, false, ceilingcolormap);
+					PF_Occlude, ceilinglightlevel, levelflats[gr_frontsector->ceilingpic].lumpnum, levelflats[gr_frontsector->ceilingpic].texturenum, NULL, 255, false, ceilingcolormap);
 			}
 		}
 		else
@@ -3621,7 +3671,7 @@ static void HWR_Subsector(size_t num)
 					else
 						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG);
 
-					HWR_AddTransparentFloor(0,
+					HWR_AddTransparentFloor(0, 0,
 					                       &extrasubsectors[num],
 										   false,
 					                       *rover->bottomheight,
@@ -3640,6 +3690,7 @@ static void HWR_Subsector(size_t num)
 					               rover->alpha-1, rover->master->frontsector);
 #else
 					HWR_AddTransparentFloor(levelflats[*rover->bottompic].lumpnum,
+											levelflats[*rover->bottompic].texturenum,
 					                       &extrasubsectors[num],
 										   false,
 					                       *rover->bottomheight,
@@ -3651,8 +3702,9 @@ static void HWR_Subsector(size_t num)
 				else
 				{
 					HWR_GetFlat(levelflats[*rover->bottompic].lumpnum);
+					HWR_GetTextureFlat(levelflats[*rover->bottompic].texturenum);
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(NULL, &extrasubsectors[num], false, *rover->bottomheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, levelflats[*rover->bottompic].lumpnum,
+					HWR_RenderPlane(NULL, &extrasubsectors[num], false, *rover->bottomheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, levelflats[*rover->bottompic].lumpnum, levelflats[*rover->bottompic].texturenum,
 					                rover->master->frontsector, 255, false, *gr_frontsector->lightlist[light].extra_colormap);
 				}
 			}
@@ -3684,7 +3736,7 @@ static void HWR_Subsector(size_t num)
 					else
 						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG);
 
-					HWR_AddTransparentFloor(0,
+					HWR_AddTransparentFloor(0, 0,
 					                       &extrasubsectors[num],
 										   true,
 					                       *rover->topheight,
@@ -3703,6 +3755,7 @@ static void HWR_Subsector(size_t num)
 					                          rover->alpha-1, rover->master->frontsector);
 #else
 					HWR_AddTransparentFloor(levelflats[*rover->toppic].lumpnum,
+											levelflats[*rover->bottompic].texturenum,
 					                        &extrasubsectors[num],
 											true,
 					                        *rover->topheight,
@@ -3715,8 +3768,9 @@ static void HWR_Subsector(size_t num)
 				else
 				{
 					HWR_GetFlat(levelflats[*rover->toppic].lumpnum);
+					HWR_GetTextureFlat(levelflats[*rover->toppic].texturenum);
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(NULL, &extrasubsectors[num], true, *rover->topheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, levelflats[*rover->toppic].lumpnum,
+					HWR_RenderPlane(NULL, &extrasubsectors[num], true, *rover->topheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, levelflats[*rover->toppic].lumpnum, levelflats[*rover->toppic].texturenum,
 					                  rover->master->frontsector, 255, false, *gr_frontsector->lightlist[light].extra_colormap);
 				}
 			}
@@ -5045,6 +5099,7 @@ typedef struct
 	fixed_t fixedheight;
 	INT32 lightlevel;
 	lumpnum_t lumpnum;
+	INT32 texturenum;
 	INT32 alpha;
 	sector_t *FOFSector;
 	FBITFIELD blend;
@@ -5063,6 +5118,7 @@ typedef struct
 	fixed_t fixedheight;
 	INT32 lightlevel;
 	lumpnum_t lumpnum;
+	INT32 texturenum;
 	INT32 alpha;
 	sector_t *FOFSector;
 	FBITFIELD blend;
@@ -5093,7 +5149,7 @@ static INT32 drawcount = 0;
 #define MAX_TRANSPARENTFLOOR 512
 
 // This will likely turn into a copy of HWR_Add3DWater and replace it.
-void HWR_AddTransparentFloor(lumpnum_t lumpnum, extrasubsector_t *xsub, boolean isceiling,
+void HWR_AddTransparentFloor(lumpnum_t lumpnum, INT32 texturenum, extrasubsector_t *xsub, boolean isceiling,
 	fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap)
 {
 	static size_t allocedplanes = 0;
@@ -5112,6 +5168,7 @@ void HWR_AddTransparentFloor(lumpnum_t lumpnum, extrasubsector_t *xsub, boolean
 	planeinfo[numplanes].fixedheight = fixedheight;
 	planeinfo[numplanes].lightlevel = lightlevel;
 	planeinfo[numplanes].lumpnum = lumpnum;
+	planeinfo[numplanes].texturenum = texturenum;
 	planeinfo[numplanes].xsub = xsub;
 	planeinfo[numplanes].alpha = alpha;
 	planeinfo[numplanes].FOFSector = FOFSector;
@@ -5125,7 +5182,7 @@ void HWR_AddTransparentFloor(lumpnum_t lumpnum, extrasubsector_t *xsub, boolean
 
 // Adding this for now until I can create extrasubsector info for polyobjects
 // When that happens it'll just be done through HWR_AddTransparentFloor and HWR_RenderPlane
-void HWR_AddTransparentPolyobjectFloor(lumpnum_t lumpnum, polyobj_t *polysector, boolean isceiling,
+void HWR_AddTransparentPolyobjectFloor(lumpnum_t lumpnum, INT32 texturenum, polyobj_t *polysector, boolean isceiling,
 	fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, extracolormap_t *planecolormap)
 {
 	static size_t allocedpolyplanes = 0;
@@ -5144,6 +5201,7 @@ void HWR_AddTransparentPolyobjectFloor(lumpnum_t lumpnum, polyobj_t *polysector,
 	polyplaneinfo[numpolyplanes].fixedheight = fixedheight;
 	polyplaneinfo[numpolyplanes].lightlevel = lightlevel;
 	polyplaneinfo[numpolyplanes].lumpnum = lumpnum;
+	polyplaneinfo[numpolyplanes].texturenum = texturenum;
 	polyplaneinfo[numpolyplanes].polysector = polysector;
 	polyplaneinfo[numpolyplanes].alpha = alpha;
 	polyplaneinfo[numpolyplanes].FOFSector = FOFSector;
@@ -5305,9 +5363,12 @@ static void HWR_CreateDrawNodes(void)
 			gr_frontsector = NULL;
 
 			if (!(sortnode[sortindex[i]].plane->blend & PF_NoTexture))
+			{
 				HWR_GetFlat(sortnode[sortindex[i]].plane->lumpnum);
+				HWR_GetTextureFlat(sortnode[sortindex[i]].plane->texturenum);
+			}
 			HWR_RenderPlane(NULL, sortnode[sortindex[i]].plane->xsub, sortnode[sortindex[i]].plane->isceiling, sortnode[sortindex[i]].plane->fixedheight, sortnode[sortindex[i]].plane->blend, sortnode[sortindex[i]].plane->lightlevel,
-				sortnode[sortindex[i]].plane->lumpnum, sortnode[sortindex[i]].plane->FOFSector, sortnode[sortindex[i]].plane->alpha, sortnode[sortindex[i]].plane->fogplane, sortnode[sortindex[i]].plane->planecolormap);
+				sortnode[sortindex[i]].plane->lumpnum, sortnode[sortindex[i]].plane->texturenum, sortnode[sortindex[i]].plane->FOFSector, sortnode[sortindex[i]].plane->alpha, sortnode[sortindex[i]].plane->fogplane, sortnode[sortindex[i]].plane->planecolormap);
 		}
 		else if (sortnode[sortindex[i]].polyplane)
 		{
@@ -5315,9 +5376,12 @@ static void HWR_CreateDrawNodes(void)
 			gr_frontsector = NULL;
 
 			if (!(sortnode[sortindex[i]].polyplane->blend & PF_NoTexture))
+			{
 				HWR_GetFlat(sortnode[sortindex[i]].polyplane->lumpnum);
+				HWR_GetTextureFlat(sortnode[sortindex[i]].polyplane->texturenum);
+			}
 			HWR_RenderPolyObjectPlane(sortnode[sortindex[i]].polyplane->polysector, sortnode[sortindex[i]].polyplane->isceiling, sortnode[sortindex[i]].polyplane->fixedheight, sortnode[sortindex[i]].polyplane->blend, sortnode[sortindex[i]].polyplane->lightlevel,
-				sortnode[sortindex[i]].polyplane->lumpnum, sortnode[sortindex[i]].polyplane->FOFSector, sortnode[sortindex[i]].polyplane->alpha, sortnode[sortindex[i]].polyplane->planecolormap);
+				sortnode[sortindex[i]].polyplane->lumpnum, sortnode[sortindex[i]].polyplane->texturenum, sortnode[sortindex[i]].polyplane->FOFSector, sortnode[sortindex[i]].polyplane->alpha, sortnode[sortindex[i]].polyplane->planecolormap);
 		}
 		else if (sortnode[sortindex[i]].wall)
 		{
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index d4728315ad88f97e5f1f16ac16ed037a8f7376ce..cd1b957f0b36465edd69f0f9ec156ceac938ce40 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -26,6 +26,7 @@
 #include <string.h>
 #include <math.h>
 
+#include "../d_main.h"
 #include "../doomdef.h"
 #include "../doomstat.h"
 #include "../fastcmp.h"
@@ -70,6 +71,10 @@
  #endif
 #endif
 
+#ifndef errno
+#include "errno.h"
+#endif
+
 #define NUMVERTEXNORMALS 162
 float avertexnormals[NUMVERTEXNORMALS][3] = {
 {-0.525731f, 0.000000f, 0.850651f},
@@ -294,7 +299,8 @@ static md2_model_t *md2_readModel(const char *filename)
 	if (model == NULL)
 		return 0;
 
-	file = fopen(filename, "rb");
+	//Filename checking fixed ~Monster Iestyn and Golden
+	file = fopen(va("%s"PATHSEP"%s", srb2home, filename), "rb");
 	if (!file)
 	{
 		free(model);
@@ -523,7 +529,8 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 #endif
 #endif
 	png_FILE_p png_FILE;
-	char *pngfilename = va("md2/%s", filename);
+	//Filename checking fixed ~Monster Iestyn and Golden
+	char *pngfilename = va("%s"PATHSEP"md2"PATHSEP"%s", srb2home, filename);
 
 	FIL_ForceExtension(pngfilename, ".png");
 	png_FILE = fopen(pngfilename, "rb");
@@ -651,7 +658,8 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 	size_t pw, ph, size, ptr = 0;
 	INT32 ch, rep;
 	FILE *file;
-	char *pcxfilename = va("md2/%s", filename);
+	//Filename checking fixed ~Monster Iestyn and Golden
+	char *pcxfilename = va("%s"PATHSEP"md2"PATHSEP"%s", srb2home, filename);
 
 	FIL_ForceExtension(pcxfilename, ".pcx");
 	file = fopen(pcxfilename, "rb");
@@ -747,10 +755,12 @@ static void md2_loadTexture(md2_t *model)
 		grpatch->mipmap.width = (UINT16)w;
 		grpatch->mipmap.height = (UINT16)h;
 
+#ifdef GLIDE_API_COMPATIBILITY
 		// not correct!
 		grpatch->mipmap.grInfo.smallLodLog2 = GR_LOD_LOG2_256;
 		grpatch->mipmap.grInfo.largeLodLog2 = GR_LOD_LOG2_256;
 		grpatch->mipmap.grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+#endif
 	}
 	HWD.pfnSetTexture(&grpatch->mipmap);
 	HWR_UnlockCachedPatch(grpatch);
@@ -798,10 +808,12 @@ static void md2_loadBlendTexture(md2_t *model)
 		grpatch->mipmap.width = (UINT16)w;
 		grpatch->mipmap.height = (UINT16)h;
 
+#ifdef GLIDE_API_COMPATIBILITY
 		// not correct!
 		grpatch->mipmap.grInfo.smallLodLog2 = GR_LOD_LOG2_256;
 		grpatch->mipmap.grInfo.largeLodLog2 = GR_LOD_LOG2_256;
 		grpatch->mipmap.grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+#endif
 	}
 	HWD.pfnSetTexture(&grpatch->mipmap); // We do need to do this so that it can be cleared and knows to recreate it when necessary
 	HWR_UnlockCachedPatch(grpatch);
@@ -841,11 +853,12 @@ void HWR_InitMD2(void)
 	}
 
 	// read the md2.dat file
-	f = fopen("md2.dat", "rt");
+	//Filename checking fixed ~Monster Iestyn and Golden
+	f = fopen(va("%s"PATHSEP"%s", srb2home, "md2.dat"), "rt");
 
 	if (!f)
 	{
-		CONS_Printf("%s", M_GetText("Error while loading md2.dat\n"));
+		CONS_Printf("%s %s\n", M_GetText("Error while loading md2.dat:"), strerror(errno));
 		nomd2s = true;
 		return;
 	}
@@ -907,7 +920,8 @@ void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
 	CONS_Printf("AddPlayerMD2()...\n");
 
 	// read the md2.dat file
-	f = fopen("md2.dat", "rt");
+	//Filename checking fixed ~Monster Iestyn and Golden
+	f = fopen(va("%s"PATHSEP"%s", srb2home, "md2.dat"), "rt");
 
 	if (!f)
 	{
@@ -952,7 +966,8 @@ void HWR_AddSpriteMD2(size_t spritenum) // For MD2s that were added after startu
 		return;
 
 	// Read the md2.dat file
-	f = fopen("md2.dat", "rt");
+	//Filename checking fixed ~Monster Iestyn and Golden
+	f = fopen(va("%s"PATHSEP"%s", srb2home, "md2.dat"), "rt");
 
 	if (!f)
 	{
@@ -1198,7 +1213,7 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 	if (!md2 || !skin)
 		return 0;
 
-	if ((unsigned)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
+	if ((playersprite_t)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
 		return 0;
 
 	while (!(md2->model->spr2frames[spr2*2 + 1])
diff --git a/src/info.c b/src/info.c
index 18f0e838abca1dead9f6f37a7196d2e332f95335..da8022cd42cc68e866e07182fc86a73b9e488778 100644
--- a/src/info.c
+++ b/src/info.c
@@ -73,18 +73,17 @@ char sprnames[NUMSPRITES + 1][5] =
 	"JETF", // Boss jet fumes
 
 	// Boss 1 (Greenflower)
-	"EGGM",
+	"EGGM", // Boss 1
+	"EGLZ", // Boss 1 Junk
 
 	// Boss 2 (Techno Hill)
 	"EGGN", // Boss 2
-	"TNKA", // Boss 2 Tank 1
-	"TNKB", // Boss 2 Tank 2
-	"SPNK", // Boss 2 Spigot
+	"TANK", // Boss 2 Junk
 	"GOOP", // Boss 2 Goop
 
 	// Boss 3 (Deep Sea)
 	"EGGO", // Boss 3
-	"PRPL", // Boss 3 Propeller
+	"SEBH", // Boss 3 Junk
 	"FAKE", // Boss 3 Fakemobile
 
 	// Boss 4 (Castle Eggman)
@@ -213,11 +212,11 @@ char sprnames[NUMSPRITES + 1][5] =
 	"GARG", // Deep Sea Gargoyle
 	"SEWE", // Deep Sea Seaweed
 	"DRIP", // Dripping water
-	"CRL1", // Coral 1
-	"CRL2", // Coral 2
-	"CRL3", // Coral 3
+	"CORL", // Coral
 	"BCRY", // Blue Crystal
 	"KELP", // Kelp
+	"ALGA", // Animated algae top
+	"ALGB", // Animated algae segment
 	"DSTG", // DSZ Stalagmites
 	"LIBE", // DSZ Light beam
 
@@ -247,7 +246,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	// Arid Canyon Scenery
 	"BTBL", // Big tumbleweed
 	"STBL", // Small tumbleweed
-	"CACT", // Cacti sprites
+	"CACT", // Cacti
 	"WWSG", // Caution Sign
 	"WWS2", // Cacti Sign
 	"WWS3", // Sharp Turn Sign
@@ -577,8 +576,14 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"TALA",
 	"TALB",
 
+	"CNT1",
+	"CNT2",
+	"CNT3",
+	"CNT4",
+
 	"SIGN",
 	"LIFE",
+
 	"XTRA",
 };
 playersprite_t free_spr2 = SPR2_FIRSTFREESLOT;
@@ -674,8 +679,14 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	SPR2_TAL9, // SPR2_TALA,
 	SPR2_TAL0, // SPR2_TALB,
 
+	SPR2_WAIT, // SPR2_CNT1,
+	SPR2_FALL, // SPR2_CNT2,
+	SPR2_SPNG, // SPR2_CNT3,
+	SPR2_CNT1, // SPR2_CNT4,
+
 	0, // SPR2_SIGN,
 	0, // SPR2_LIFE,
+
 	0, // SPR2_XTRA (should never be referenced)
 };
 
@@ -728,7 +739,7 @@ state_t states[NUMSTATES] =
 
 	// CA_FLY/CA_SWIM
 	{SPR_PLAY, SPR2_FLY ,                 2, {NULL}, 0,  0, S_PLAY_FLY},  // S_PLAY_FLY
-	{SPR_PLAY, SPR2_SWIM,                 2, {NULL}, 0,  0, S_PLAY_SWIM}, // S_PLAY_SWIM
+	{SPR_PLAY, SPR2_SWIM,                 4, {NULL}, 0,  0, S_PLAY_SWIM}, // S_PLAY_SWIM
 	{SPR_PLAY, SPR2_TIRE,                12, {NULL}, 0,  0, S_PLAY_FLY_TIRED}, // S_PLAY_FLY_TIRED
 
 	// CA_GLIDEANDCLIMB
@@ -1179,49 +1190,58 @@ state_t states[NUMSTATES] =
 	{SPR_BOM3, FF_FULLBRIGHT|4, 3, {NULL}, 0, 0, S_SONIC3KBOSSEXPLOSION6}, // S_SONIC3KBOSSEXPLOSION5
 	{SPR_BOM3, FF_FULLBRIGHT|5, 4, {NULL}, 0, 0, S_NULL}, // S_SONIC3KBOSSEXPLOSION6
 
-	{SPR_JETF, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_JETFUME2}, // S_JETFUME1
-	{SPR_NULL, 0,             1, {NULL}, 0, 0, S_JETFUME1}, // S_JETFUME2
+	{SPR_JETF, FF_ANIMATE|FF_FULLBRIGHT, -1, {NULL}, 2, 1, S_NULL}, // S_JETFUME1
 
 	// Boss 1
 	{SPR_EGGM,  0,   1, {A_Boss1Chase},            0, 0, S_EGGMOBILE_STND},   // S_EGGMOBILE_STND
+	{SPR_EGGM, FF_ANIMATE|17, 35, {A_FaceTarget},  1, 2, S_EGGMOBILE_STND},   // S_EGGMOBILE_ROFL
 	{SPR_EGGM,  1,   3, {A_FaceTarget},            0, 0, S_EGGMOBILE_LATK2},  // S_EGGMOBILE_LATK1
 	{SPR_EGGM,  2,  15, {NULL},                    0, 0, S_EGGMOBILE_LATK3},  // S_EGGMOBILE_LATK2
-	{SPR_EGGM,  3,   2, {A_FaceTarget},            0, 0, S_EGGMOBILE_LATK4},  // S_EGGMOBILE_LATK3
-	{SPR_EGGM,  4,   1, {NULL},                    0, 0, S_EGGMOBILE_LATK5},  // S_EGGMOBILE_LATK4
-	{SPR_EGGM,  5,   1, {NULL},                    0, 0, S_EGGMOBILE_LATK6},  // S_EGGMOBILE_LATK5
-	{SPR_EGGM,  6,   1, {NULL},                    0, 0, S_EGGMOBILE_LATK7},  // S_EGGMOBILE_LATK6
-	{SPR_EGGM,  7,   1, {NULL},                    0, 0, S_EGGMOBILE_LATK8},  // S_EGGMOBILE_LATK7
-	{SPR_EGGM,  8,  45, {A_Boss1Laser},     MT_LASER, 0, S_EGGMOBILE_LATK9},  // S_EGGMOBILE_LATK8
-	{SPR_EGGM,  9,  10, {NULL},                    0, 0, S_EGGMOBILE_LATK10}, // S_EGGMOBILE_LATK9
-	{SPR_EGGM, 10,   2, {NULL},                    0, 0, S_EGGMOBILE_STND},   // S_EGGMOBILE_LATK10
-	{SPR_EGGM, 11,   3, {A_FaceTarget},            0, 0, S_EGGMOBILE_RATK2},  // S_EGGMOBILE_RATK1
-	{SPR_EGGM, 12,  15, {NULL},                    0, 0, S_EGGMOBILE_RATK3},  // S_EGGMOBILE_RATK2
-	{SPR_EGGM, 13,   2, {A_FaceTarget},            0, 0, S_EGGMOBILE_RATK4},  // S_EGGMOBILE_RATK3
-	{SPR_EGGM, 14,   1, {NULL},                    0, 0, S_EGGMOBILE_RATK5},  // S_EGGMOBILE_RATK4
-	{SPR_EGGM, 15,   1, {NULL},                    0, 0, S_EGGMOBILE_RATK6},  // S_EGGMOBILE_RATK5
-	{SPR_EGGM, 16,   1, {NULL},                    0, 0, S_EGGMOBILE_RATK7},  // S_EGGMOBILE_RATK6
-	{SPR_EGGM, 17,   1, {NULL},                    0, 0, S_EGGMOBILE_RATK8},  // S_EGGMOBILE_RATK7
-	{SPR_EGGM, 18,  45, {A_Boss1Laser},     MT_LASER, 1, S_EGGMOBILE_RATK9},  // S_EGGMOBILE_RATK8
-	{SPR_EGGM, 19,  10, {NULL},                    0, 0, S_EGGMOBILE_RATK10}, // S_EGGMOBILE_RATK9
-	{SPR_EGGM, 20,   2, {NULL},                    0, 0, S_EGGMOBILE_STND},   // S_EGGMOBILE_RATK10
-	{SPR_EGGM,  3,  12, {NULL},                    0, 0, S_EGGMOBILE_PANIC2}, // S_EGGMOBILE_PANIC1
-	{SPR_EGGM,  4,  45, {A_Boss1Laser},       MT_LASER, 2, S_EGGMOBILE_PANIC3}, // S_EGGMOBILE_PANIC2
-	{SPR_EGGM,  3,   8, {NULL},                    0, 0, S_EGGMOBILE_PANIC4}, // S_EGGMOBILE_PANIC3
-	{SPR_EGGM,  4,  45, {A_Boss1Laser},       MT_LASER, 2, S_EGGMOBILE_PANIC5 }, // S_EGGMOBILE_PANIC4
-	{SPR_EGGM,  3,  8,  {NULL},                    0, 0, S_EGGMOBILE_PANIC6}, // S_EGGMOBILE_PANIC5
-	{SPR_EGGM,  4,  45, {A_Boss1Laser},       MT_LASER, 2, S_EGGMOBILE_PANIC7 }, // S_EGGMOBILE_PANIC6
-	{SPR_EGGM,  0,  35,	{NULL},					0, 0, S_EGGMOBILE_STND },   // S_EGGMOBILE_PANIC7
-	{SPR_EGGM, 21,  24, {A_Pain},                  0, 0, S_EGGMOBILE_PAIN2},  // S_EGGMOBILE_PAIN
-	{SPR_EGGM, 21,  16, {A_SkullAttack},           1, 1, S_EGGMOBILE_STND},   // S_EGGMOBILE_PAIN2
-	{SPR_EGGM, 22,  2, {A_Fall},                   0, 0, S_EGGMOBILE_DIE2},   // S_EGGMOBILE_DIE1
-	{SPR_EGGM, 22,  2, {A_BossScream},             0, 0, S_EGGMOBILE_DIE3},   // S_EGGMOBILE_DIE2
-	{SPR_EGGM, 22,  0, {A_Repeat}, 17, S_EGGMOBILE_DIE2, S_EGGMOBILE_DIE4},   // S_EGGMOBILE_DIE3
-	{SPR_EGGM, 22,  -1, {A_BossDeath},             0, 0, S_NULL},             // S_EGGMOBILE_DIE4
-	{SPR_EGGM, 23,  2, {A_BossScream},             0, 0, S_EGGMOBILE_FLEE2},  // S_EGGMOBILE_FLEE1
-	{SPR_EGGM, 24,  2, {A_BossScream},             0, 0, S_EGGMOBILE_FLEE1},  // S_EGGMOBILE_FLEE2
+	{SPR_EGGM,  3,   2, {NULL},                    0, 0, S_EGGMOBILE_LATK4},  // S_EGGMOBILE_LATK3
+	{SPR_EGGM,  4,   2, {A_FaceTarget},            0, 0, S_EGGMOBILE_LATK5},  // S_EGGMOBILE_LATK4
+	{SPR_EGGM,  6,   0, {A_PrepareRepeat},        45, 0, S_EGGMOBILE_LATK6},  // S_EGGMOBILE_LATK5
+	{SPR_EGGM,  5,   1, {A_Boss1Laser},     MT_LASER, 0, S_EGGMOBILE_LATK7},  // S_EGGMOBILE_LATK6
+	{SPR_EGGM,  6,   1, {A_Boss1Laser},     MT_LASER, (1<<16), S_EGGMOBILE_LATK8},  // S_EGGMOBILE_LATK7
+	{SPR_EGGM,  5,   0, {A_Repeat}, 45, S_EGGMOBILE_LATK6, S_EGGMOBILE_LATK9},  // S_EGGMOBILE_LATK8
+	{SPR_EGGM,  8,  2, {NULL},                     0, 0, S_EGGMOBILE_ROFL}, // S_EGGMOBILE_LATK9
+	{SPR_EGGM,  9,   3, {A_FaceTarget},            0, 0, S_EGGMOBILE_RATK2},  // S_EGGMOBILE_RATK1
+	{SPR_EGGM, 10,  15, {NULL},                    0, 0, S_EGGMOBILE_RATK3},  // S_EGGMOBILE_RATK2
+	{SPR_EGGM, 11,   2, {NULL},                    0, 0, S_EGGMOBILE_RATK4},  // S_EGGMOBILE_RATK3
+	{SPR_EGGM, 12,   2, {A_FaceTarget},            0, 0, S_EGGMOBILE_RATK5},  // S_EGGMOBILE_RATK4
+	{SPR_EGGM, 14,   0, {A_PrepareRepeat},        45, 0, S_EGGMOBILE_RATK6},  // S_EGGMOBILE_RATK5
+	{SPR_EGGM, 13,   1, {A_Boss1Laser},     MT_LASER, 1, S_EGGMOBILE_RATK7},  // S_EGGMOBILE_RATK6
+	{SPR_EGGM, 14,   1, {A_Boss1Laser},     MT_LASER, 1|(1<<16), S_EGGMOBILE_RATK8},  // S_EGGMOBILE_RATK7
+	{SPR_EGGM, 13,   0, {A_Repeat}, 45, S_EGGMOBILE_RATK6, S_EGGMOBILE_RATK9},  // S_EGGMOBILE_RATK8
+	{SPR_EGGM, 16,  2, {NULL},                     0, 0, S_EGGMOBILE_ROFL}, // S_EGGMOBILE_RATK9
+	{SPR_EGGM,  0,  0, {A_PrepareRepeat},            45, 0, S_EGGMOBILE_PANIC2},  // S_EGGMOBILE_PANIC1
+	{SPR_EGGM, FF_ANIMATE|1, 16, {A_FaceTarget},      3, 4, S_EGGMOBILE_PANIC3},  // S_EGGMOBILE_PANIC2
+	{SPR_EGGM,  7,  1, {A_Boss1Laser},         MT_LASER, 2, S_EGGMOBILE_PANIC4},  // S_EGGMOBILE_PANIC3
+	{SPR_EGGM,  6,  1, {A_Boss1Laser},         MT_LASER, 2|(1<<16), S_EGGMOBILE_PANIC5},  // S_EGGMOBILE_PANIC4
+	{SPR_EGGM,  6,  0, {A_Repeat}, 45,  S_EGGMOBILE_PANIC3, S_EGGMOBILE_PANIC6},  // S_EGGMOBILE_PANIC5
+	{SPR_EGGM,  0,  0, {A_PrepareRepeat},            45, 0, S_EGGMOBILE_PANIC7},  // S_EGGMOBILE_PANIC6
+	{SPR_EGGM, FF_ANIMATE|9, 16, {A_FaceTarget},      3, 4, S_EGGMOBILE_PANIC8},  // S_EGGMOBILE_PANIC7
+	{SPR_EGGM, 15,  1, {A_Boss1Laser},         MT_LASER, 2, S_EGGMOBILE_PANIC9},  // S_EGGMOBILE_PANIC8
+	{SPR_EGGM, 14,  1, {A_Boss1Laser},         MT_LASER, 2|(1<<16), S_EGGMOBILE_PANIC10},  // S_EGGMOBILE_PANIC9
+	{SPR_EGGM, 14,  0, {A_Repeat}, 45,  S_EGGMOBILE_PANIC8, S_EGGMOBILE_PANIC11}, // S_EGGMOBILE_PANIC10
+	{SPR_EGGM,  0,  0, {A_PrepareRepeat},            45, 0, S_EGGMOBILE_PANIC12}, // S_EGGMOBILE_PANIC11
+	{SPR_EGGM, FF_ANIMATE|1, 16, {A_FaceTarget},      3, 4, S_EGGMOBILE_PANIC13}, // S_EGGMOBILE_PANIC12
+	{SPR_EGGM,  7,  1, {A_Boss1Laser},         MT_LASER, 2, S_EGGMOBILE_PANIC14}, // S_EGGMOBILE_PANIC13
+	{SPR_EGGM,  6,  1, {A_Boss1Laser},         MT_LASER, 2|(1<<16), S_EGGMOBILE_PANIC15}, // S_EGGMOBILE_PANIC14
+	{SPR_EGGM,  6,  0, {A_Repeat}, 45,    S_EGGMOBILE_PANIC13, S_EGGMOBILE_ROFL}, // S_EGGMOBILE_PANIC15
+	{SPR_EGGM, 19,  24, {A_Pain},                  0, 0, S_EGGMOBILE_PAIN2},  // S_EGGMOBILE_PAIN
+	{SPR_EGGM, 19,  16, {A_SkullAttack},           3, 1, S_EGGMOBILE_STND},   // S_EGGMOBILE_PAIN2
+	{SPR_EGGM, 20,  2, {A_Fall},                  17, 0, S_EGGMOBILE_DIE2},   // S_EGGMOBILE_DIE1
+	{SPR_EGGM, 20,  2, {A_BossScream},             0, 0, S_EGGMOBILE_DIE3},   // S_EGGMOBILE_DIE2
+	{SPR_EGGM, 20,  0, {A_Repeat}, 17, S_EGGMOBILE_DIE2, S_EGGMOBILE_DIE4},   // S_EGGMOBILE_DIE3
+	{SPR_EGGM, 20,  -1, {A_BossDeath},             0, 0, S_NULL},             // S_EGGMOBILE_DIE4
+	{SPR_EGGM, 21,  2, {A_BossScream},             0, 0, S_EGGMOBILE_FLEE2},  // S_EGGMOBILE_FLEE1
+	{SPR_EGGM, 22,  2, {A_BossScream},             0, 0, S_EGGMOBILE_FLEE1},  // S_EGGMOBILE_FLEE2
 	{SPR_UNID,  1,  1, {A_UnidusBall},             2, 0, S_EGGMOBILE_BALL},   // S_EGGMOBILE_BALL
 	{SPR_NULL,  0,  1, {A_FocusTarget},            0, 0, S_EGGMOBILE_TARGET}, // S_EGGMOBILE_TARGET
 
+	{SPR_EGLZ, 0, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSEGLZ1
+	{SPR_EGLZ, 1, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSEGLZ2
+
 	// Boss 2
 	{SPR_EGGN, 0, -1,              {NULL},           0,          0, S_NULL},             // S_EGGMOBILE2_STND
 	{SPR_EGGN, 1, 4,               {NULL},           0,          0, S_EGGMOBILE2_POGO2}, // S_EGGMOBILE2_POGO1
@@ -1240,9 +1260,9 @@ state_t states[NUMSTATES] =
 	{SPR_EGGN, 6, 2,       {A_BossScream},           0,          0, S_EGGMOBILE2_FLEE2}, // S_EGGMOBILE2_FLEE1
 	{SPR_EGGN, 7, 2,       {A_BossScream},           0,          0, S_EGGMOBILE2_FLEE1}, // S_EGGMOBILE2_FLEE2
 
-	{SPR_TNKA, 0, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSTANK1
-	{SPR_TNKB, 0, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSTANK2
-	{SPR_SPNK, 0, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSSPIGOT
+	{SPR_TANK, 0, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSTANK1
+	{SPR_TANK, 1, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSTANK2
+	{SPR_TANK, 2, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSSPIGOT
 
 	// Boss 2 Goop
 	{SPR_GOOP,            0,  2, {A_SpawnObjectRelative}, 0, MT_GOOPTRAIL, S_GOOP2}, // S_GOOP1
@@ -1252,34 +1272,16 @@ state_t states[NUMSTATES] =
 
 	// Boss 3
 	{SPR_EGGO,  0,   1, {NULL},                    0, 0, S_EGGMOBILE3_STND},    // S_EGGMOBILE3_STND
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH2},  // S_EGGMOBILE3_LAUGH1
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH3},  // S_EGGMOBILE3_LAUGH2
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH4},  // S_EGGMOBILE3_LAUGH3
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH5},  // S_EGGMOBILE3_LAUGH4
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_ATK1},  // S_EGGMOBILE3_LAUGH5
-	{SPR_EGGO,  1,   2, {NULL},                    0, 0, S_EGGMOBILE3_ATK2},    // S_EGGMOBILE3_ATK1
+	{SPR_EGGO,   FF_ANIMATE, 24, {NULL}, 1, 2, S_EGGMOBILE3_ATK2},  // S_EGGMOBILE3_SHOCK
+	{SPR_EGGO, 6|FF_ANIMATE, 24, {NULL}, 1, 2, S_EGGMOBILE3_ATK2}, // S_EGGMOBILE3_ATK1
 	{SPR_EGGO,  2,   2, {NULL},                    0, 0, S_EGGMOBILE3_ATK3A},   // S_EGGMOBILE3_ATK2
 	{SPR_EGGO,  3,   2, {A_BossFireShot}, MT_TORPEDO, 2, S_EGGMOBILE3_ATK3B},   // S_EGGMOBILE3_ATK3A
 	{SPR_EGGO,  3,   2, {A_BossFireShot}, MT_TORPEDO, 4, S_EGGMOBILE3_ATK3C},   // S_EGGMOBILE3_ATK3B
 	{SPR_EGGO,  3,   2, {A_BossFireShot}, MT_TORPEDO, 3, S_EGGMOBILE3_ATK3D},   // S_EGGMOBILE3_ATK3C
 	{SPR_EGGO,  3,   2, {A_BossFireShot}, MT_TORPEDO, 5, S_EGGMOBILE3_ATK4},    // S_EGGMOBILE3_ATK3D
 	{SPR_EGGO,  4,   2, {NULL},                    0, 0, S_EGGMOBILE3_ATK5},    // S_EGGMOBILE3_ATK4
-	{SPR_EGGO,  5,   2, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH6},  // S_EGGMOBILE3_ATK5
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH7},  // S_EGGMOBILE3_LAUGH6
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH8},  // S_EGGMOBILE3_LAUGH7
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH9},  // S_EGGMOBILE3_LAUGH8
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH10}, // S_EGGMOBILE3_LAUGH9
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH11}, // S_EGGMOBILE3_LAUGH10
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH12}, // S_EGGMOBILE3_LAUGH11
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH13}, // S_EGGMOBILE3_LAUGH12
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH14}, // S_EGGMOBILE3_LAUGH13
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH15}, // S_EGGMOBILE3_LAUGH14
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH16}, // S_EGGMOBILE3_LAUGH15
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH17}, // S_EGGMOBILE3_LAUGH16
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH18}, // S_EGGMOBILE3_LAUGH17
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH19}, // S_EGGMOBILE3_LAUGH18
-	{SPR_EGGO,  6,   4, {NULL},                    0, 0, S_EGGMOBILE3_LAUGH20}, // S_EGGMOBILE3_LAUGH19
-	{SPR_EGGO,  7,   4, {NULL},                    0, 0, S_EGGMOBILE3_STND},    // S_EGGMOBILE3_LAUGH20
+	{SPR_EGGO,  5,   2, {NULL},                    0, 0, S_EGGMOBILE3_ROFL},  // S_EGGMOBILE3_ATK5
+	{SPR_EGGO,  6|FF_ANIMATE, 60, {NULL}, 1, 2, S_EGGMOBILE3_STND},  // S_EGGMOBILE3_ROFL
 	{SPR_EGGO,  8,   1, {A_Boss3TakeDamage},       0, 0, S_EGGMOBILE3_PAIN2},   // S_EGGMOBILE3_PAIN
 	{SPR_EGGO,  8,  23, {A_Pain},                  0, 0, S_EGGMOBILE3_STND},    // S_EGGMOBILE3_PAIN2
 	{SPR_EGGO,  9,   2, {A_Fall},                  0, 0, S_EGGMOBILE3_DIE2},    // S_EGGMOBILE3_DIE1
@@ -1289,17 +1291,8 @@ state_t states[NUMSTATES] =
 	{SPR_EGGO, 10,   2, {A_BossScream},            0, 0, S_EGGMOBILE3_FLEE2},   // S_EGGMOBILE3_FLEE1
 	{SPR_EGGO, 11,   2, {A_BossScream},            0, 0, S_EGGMOBILE3_FLEE1},   // S_EGGMOBILE3_FLEE2
 
-	// Boss 3 Propeller
-	{SPR_PRPL, 0, 1, {NULL}, 0, 0, S_PROPELLER2}, // S_PROPELLER1
-	{SPR_PRPL, 1, 1, {NULL}, 0, 0, S_PROPELLER3}, // S_PROPELLER2
-	{SPR_PRPL, 2, 1, {NULL}, 0, 0, S_PROPELLER4}, // S_PROPELLER3
-	{SPR_PRPL, 3, 1, {NULL}, 0, 0, S_PROPELLER5}, // S_PROPELLER4
-	{SPR_PRPL, 4, 1, {NULL}, 0, 0, S_PROPELLER6}, // S_PROPELLER5
-	{SPR_PRPL, 5, 1, {NULL}, 0, 0, S_PROPELLER7}, // S_PROPELLER6
-	{SPR_PRPL, 6, 1, {NULL}, 0, 0, S_PROPELLER1}, // S_PROPELLER7
-
 	// Boss 3 Pinch
-	{SPR_FAKE, 0,  1, {A_BossJetFume},            1, 0, S_FAKEMOBILE},       // S_FAKEMOBILE_INIT
+	{SPR_FAKE, 0,  1, {NULL},                     0, 0, S_FAKEMOBILE},       // S_FAKEMOBILE_INIT
 	{SPR_FAKE, 0,  1, {A_Boss3Path},              0, 0, S_FAKEMOBILE},       // S_FAKEMOBILE
 	{SPR_FAKE, 0, 22, {NULL},                     0, 0, S_FAKEMOBILE_ATK2},  // S_FAKEMOBILE_ATK1
 	{SPR_FAKE, 0,  2, {NULL},                     0, 0, S_FAKEMOBILE_ATK3A}, // S_FAKEMOBILE_ATK2
@@ -1307,33 +1300,36 @@ state_t states[NUMSTATES] =
 	{SPR_FAKE, 0,  2, {A_BossFireShot}, MT_TORPEDO2, 4, S_FAKEMOBILE_ATK3C}, // S_FAKEMOBILE_ATK3B
 	{SPR_FAKE, 0,  2, {A_BossFireShot}, MT_TORPEDO2, 3, S_FAKEMOBILE_ATK3D}, // S_FAKEMOBILE_ATK3C
 	{SPR_FAKE, 0,  2, {A_BossFireShot}, MT_TORPEDO2, 5, S_FAKEMOBILE},       // S_FAKEMOBILE_ATK3D
-	{SPR_FAKE, 0,  1, {NULL},                     0, 0, S_FAKEMOBILE_DIE2},  // S_FAKEMOBILE_DIE1
+	{SPR_FAKE, 1,  1, {NULL},                     0, 0, S_FAKEMOBILE_DIE2},  // S_FAKEMOBILE_DIE1
 	{SPR_NULL, 0,  1, {NULL},                     0, 0, S_FAKEMOBILE_DIE1},  // S_FAKEMOBILE_DIE2
 
+	{SPR_SEBH, 0, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSSEBH1
+	{SPR_SEBH, 1, 35, {NULL}, 0, 0, S_NULL}, // S_BOSSSEBH2
+
 	// Boss 4
 	{SPR_EGGP, 0, -1, {NULL},           0,          0, S_NULL},              // S_EGGMOBILE4_STND
 	{SPR_EGGP, 1,  3, {NULL},           0,          0, S_EGGMOBILE4_LATK2},  // S_EGGMOBILE4_LATK1
 	{SPR_EGGP, 2, 15, {NULL},           0,          0, S_EGGMOBILE4_LATK3},  // S_EGGMOBILE4_LATK2
 	{SPR_EGGP, 3,  2, {NULL},           0,          0, S_EGGMOBILE4_LATK4},  // S_EGGMOBILE4_LATK3
-	{SPR_EGGP, 4,  4, {NULL},           0,          0, S_EGGMOBILE4_LATK5},  // S_EGGMOBILE4_LATK4
-	{SPR_EGGP, 4, 50, {A_Boss4Reverse}, sfx_mswing, 0, S_EGGMOBILE4_LATK6},  // S_EGGMOBILE4_LATK5
-	{SPR_EGGP, 5,  2, {NULL},           0,          0, S_EGGMOBILE4_STND},   // S_EGGMOBILE4_LATK6
-	{SPR_EGGP, 6,  3, {NULL},           0,          0, S_EGGMOBILE4_RATK2},  // S_EGGMOBILE4_RATK1
-	{SPR_EGGP, 7, 15, {NULL},           0,          0, S_EGGMOBILE4_RATK3},  // S_EGGMOBILE4_RATK2
-	{SPR_EGGP, 8,  2, {NULL},           0,          0, S_EGGMOBILE4_RATK4},  // S_EGGMOBILE4_RATK3
-	{SPR_EGGP, 9,  4, {NULL},           0,          0, S_EGGMOBILE4_RATK5},  // S_EGGMOBILE4_RATK4
-	{SPR_EGGP, 9,150, {A_Boss4SpeedUp}, sfx_mswing, 0, S_EGGMOBILE4_RATK6},  // S_EGGMOBILE4_RATK5
-	{SPR_EGGP,10,  2, {NULL},           0,          0, S_EGGMOBILE4_STND},   // S_EGGMOBILE4_RATK6
-	{SPR_EGGP, 0, 20, {A_Boss4Raise},   sfx_doord1, 0, S_EGGMOBILE4_RAISE2}, // S_EGGMOBILE4_RAISE1
-	{SPR_EGGP,13|FF_ANIMATE, -1, {NULL},        1,        10, S_NULL},             // S_EGGMOBILE4_RAISE2
-	{SPR_EGGP,11,  0, {A_Boss4Reverse}, sfx_alarm, sfx_s3k60, S_EGGMOBILE4_PAIN2}, // S_EGGMOBILE4_PAIN1
-	{SPR_EGGP,11, 24, {A_Pain},                 0,         0, S_EGGMOBILE4_STND},  // S_EGGMOBILE4_PAIN2
-	{SPR_EGGP,12,  2, {A_Fall},         0,          0, S_EGGMOBILE4_DIE2},   // S_EGGMOBILE4_DIE1
-	{SPR_EGGP,12,  2, {A_BossScream},   0,          0, S_EGGMOBILE4_DIE3},   // S_EGGMOBILE4_DIE2
-	{SPR_EGGP,12,  0, {A_Repeat},   17, S_EGGMOBILE4_DIE2, S_EGGMOBILE4_DIE4},   // S_EGGMOBILE4_DIE3
-	{SPR_EGGP,12, -1, {A_BossDeath},    0,          0, S_NULL},              // S_EGGMOBILE4_DIE4
-	{SPR_EGGP,13,  2, {A_BossScream},   0,          0, S_EGGMOBILE4_FLEE2},  // S_EGGMOBILE4_FLEE1
-	{SPR_EGGP,14,  2, {A_BossScream},   0,          0, S_EGGMOBILE4_FLEE1},  // S_EGGMOBILE4_FLEE2
+	{SPR_EGGP, 4,  2, {NULL},           0,          0, S_EGGMOBILE4_LATK5},  // S_EGGMOBILE4_LATK4
+	{SPR_EGGP, 5, 50, {A_Boss4Reverse}, sfx_mswing, 0, S_EGGMOBILE4_LATK6},  // S_EGGMOBILE4_LATK5
+	{SPR_EGGP, 6,  2, {NULL},           0,          0, S_EGGMOBILE4_STND},   // S_EGGMOBILE4_LATK6
+	{SPR_EGGP, 7,  3, {NULL},           0,          0, S_EGGMOBILE4_RATK2},  // S_EGGMOBILE4_RATK1
+	{SPR_EGGP, 8, 15, {NULL},           0,          0, S_EGGMOBILE4_RATK3},  // S_EGGMOBILE4_RATK2
+	{SPR_EGGP, 9,  2, {NULL},           0,          0, S_EGGMOBILE4_RATK4},  // S_EGGMOBILE4_RATK3
+	{SPR_EGGP,10,  2, {NULL},           0,          0, S_EGGMOBILE4_RATK5},  // S_EGGMOBILE4_RATK4
+	{SPR_EGGP,11,150, {A_Boss4SpeedUp}, sfx_mswing, 0, S_EGGMOBILE4_RATK6},  // S_EGGMOBILE4_RATK5
+	{SPR_EGGP,12,  2, {NULL},           0,          0, S_EGGMOBILE4_STND},   // S_EGGMOBILE4_RATK6
+	{SPR_EGGP,13, 20, {A_Boss4Raise},   sfx_doord1, 0, S_EGGMOBILE4_RAISE2}, // S_EGGMOBILE4_RAISE1
+	{SPR_EGGP,15|FF_ANIMATE, -1, {NULL},        1,        10, S_NULL},             // S_EGGMOBILE4_RAISE2
+	{SPR_EGGP,13,  0, {A_Boss4Reverse}, sfx_alarm, sfx_s3k60, S_EGGMOBILE4_PAIN2}, // S_EGGMOBILE4_PAIN1
+	{SPR_EGGP,13, 24, {A_Pain},                 0,         0, S_EGGMOBILE4_STND},  // S_EGGMOBILE4_PAIN2
+	{SPR_EGGP,14,  2, {A_Fall},         0,          0, S_EGGMOBILE4_DIE2},   // S_EGGMOBILE4_DIE1
+	{SPR_EGGP,14,  2, {A_BossScream},   0,          0, S_EGGMOBILE4_DIE3},   // S_EGGMOBILE4_DIE2
+	{SPR_EGGP,14,  0, {A_Repeat},   17, S_EGGMOBILE4_DIE2, S_EGGMOBILE4_DIE4},   // S_EGGMOBILE4_DIE3
+	{SPR_EGGP,14, -1, {A_BossDeath},    0,          0, S_NULL},              // S_EGGMOBILE4_DIE4
+	{SPR_EGGP,15,  2, {A_BossScream},   0,          0, S_EGGMOBILE4_FLEE2},  // S_EGGMOBILE4_FLEE1
+	{SPR_EGGP,16,  2, {A_BossScream},   0,          0, S_EGGMOBILE4_FLEE1},  // S_EGGMOBILE4_FLEE2
 	{SPR_BMCE, 0, -1, {NULL},           0,          0, S_NULL},              // S_EGGMOBILE4_MACE
 	{SPR_BMCE, 0,  2, {A_BossScream},   1, 0, S_EGGMOBILE4_MACE_DIE2},  // S_EGGMOBILE4_MACE_DIE1
 	{SPR_NULL, 0,  2, {A_BossScream},   1, 0, S_EGGMOBILE4_MACE_DIE3},  // S_EGGMOBILE4_MACE_DIE2
@@ -1732,18 +1728,8 @@ state_t states[NUMSTATES] =
 	{SPR_METL, 11,  1, {A_BossScream},         0, 0, S_METALSONIC_FLEE2}, // S_METALSONIC_FLEE1
 	{SPR_METL, 11,  7, {NULL},                 0, 0, S_METALSONIC_FLEE1}, // S_METALSONIC_FLEE2
 
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 0, 1, {NULL}, 0, 0, S_MSSHIELD_F2},  // S_MSSHIELD_F1
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 1, 1, {NULL}, 0, 0, S_MSSHIELD_F3},  // S_MSSHIELD_F2
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 2, 1, {NULL}, 0, 0, S_MSSHIELD_F4},  // S_MSSHIELD_F3
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 3, 1, {NULL}, 0, 0, S_MSSHIELD_F5},  // S_MSSHIELD_F4
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 4, 1, {NULL}, 0, 0, S_MSSHIELD_F6},  // S_MSSHIELD_F5
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 5, 1, {NULL}, 0, 0, S_MSSHIELD_F7},  // S_MSSHIELD_F6
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 6, 1, {NULL}, 0, 0, S_MSSHIELD_F8},  // S_MSSHIELD_F7
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 7, 1, {NULL}, 0, 0, S_MSSHIELD_F9},  // S_MSSHIELD_F8
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 8, 1, {NULL}, 0, 0, S_MSSHIELD_F10}, // S_MSSHIELD_F9
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30| 9, 1, {NULL}, 0, 0, S_MSSHIELD_F11}, // S_MSSHIELD_F10
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30|10, 1, {NULL}, 0, 0, S_MSSHIELD_F12}, // S_MSSHIELD_F11
-	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30|11, 1, {NULL}, 0, 0, S_MSSHIELD_F1},  // S_MSSHIELD_F12
+	{SPR_MSCF, FF_FULLBRIGHT|FF_TRANS30|FF_ANIMATE, -1, {NULL}, 11, 1, S_NULL},  // S_MSSHIELD_F1
+	{SPR_MSCF, FF_FULLBRIGHT|FF_ANIMATE|12, -1, {NULL}, 8, 2, S_NULL},  // S_MSSHIELD_F2
 
 	// Ring
 	{SPR_RING, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 23, 1, S_RING}, // S_RING
@@ -2178,14 +2164,12 @@ state_t states[NUMSTATES] =
 	{SPR_DRIP, FF_TRANS30|4,         1, {NULL},                  0, 0, S_DRIPC2}, // S_DRIPC1
 	{SPR_DRIP, FF_TRANS30|5,         1, {NULL},                  0, 0,   S_NULL}, // S_DRIPC2
 
-	// Coral 1
-	{SPR_CRL1, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CORAL1
-
-	// Coral 2
-	{SPR_CRL2, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CORAL2
-
-	// Coral 3
-	{SPR_CRL3, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CORAL3
+	// Coral
+	{SPR_CORL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CORAL1
+	{SPR_CORL, 1, -1, {NULL}, 0, 0, S_NULL}, // S_CORAL2
+	{SPR_CORL, 2, -1, {NULL}, 0, 0, S_NULL}, // S_CORAL3
+	{SPR_CORL, 3, -1, {NULL}, 0, 0, S_NULL}, // S_CORAL4
+	{SPR_CORL, 4, -1, {NULL}, 0, 0, S_NULL}, // S_CORAL5
 
 	// Blue Crystal
 	{SPR_BCRY, FF_TRANS30, -1, {NULL}, 0, 0, S_NULL}, // S_BLUECRYSTAL1
@@ -2193,6 +2177,11 @@ state_t states[NUMSTATES] =
 	// Kelp
 	{SPR_KELP, 0, -1, {NULL}, 0, 0, S_NULL}, // S_KELP
 
+	// Animated algae
+	{SPR_ALGA, 0, 1, {A_ConnectToGround}, MT_ANIMALGAESEG, 0, S_ANIMALGAETOP2}, // S_ANIMALGAETOP1
+	{SPR_ALGA, 0|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 11, 4, S_NULL},          // S_ANIMALGAETOP2
+	{SPR_ALGB, 0|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 11, 4, S_NULL},          // S_ANIMALGAESEG
+
 	// DSZ Stalagmites
 	{SPR_DSTG, 0, -1, {NULL}, 0, 0, S_NULL}, // S_DSZSTALAGMITE
 	{SPR_DSTG, 1, -1, {NULL}, 0, 0, S_NULL}, // S_DSZ2STALAGMITE
@@ -2337,16 +2326,20 @@ state_t states[NUMSTATES] =
 	{SPR_STBL, 6, 5, {NULL}, 0, 0, S_LITTLETUMBLEWEED_ROLL8}, // S_LITTLETUMBLEWEED_ROLL7
 	{SPR_STBL, 7, 5, {NULL}, 0, 0, S_LITTLETUMBLEWEED_ROLL1}, // S_LITTLETUMBLEWEED_ROLL8
 
-	// Cacti Sprites
-	{SPR_CACT, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI1
-	{SPR_CACT, 1, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI2
-	{SPR_CACT, 2, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI3
-	{SPR_CACT, 3, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI4
+	// Cacti
+	{SPR_CACT, 0, -1, {A_ConnectToGround}, MT_CACTITINYSEG, 0, S_NULL}, // S_CACTI1
+	{SPR_CACT, 1, -1, {A_ConnectToGround}, MT_CACTISMALLSEG, 0, S_NULL}, // S_CACTI2
+	{SPR_CACT, 2, -1, {A_ConnectToGround}, MT_CACTITINYSEG, 0, S_NULL}, // S_CACTI3
+	{SPR_CACT, 3, -1, {A_ConnectToGround}, MT_CACTISMALLSEG, 0, S_NULL}, // S_CACTI4
 	{SPR_CACT, 4, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI5
 	{SPR_CACT, 5, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI6
 	{SPR_CACT, 6, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI7
 	{SPR_CACT, 7, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI8
 	{SPR_CACT, 8, -1, {NULL}, 0, 0, S_NULL}, // S_CACTI9
+	{SPR_CACT, 9, -1, {A_ConnectToGround}, MT_CACTITINYSEG, 0, S_NULL}, // S_CACTI10
+	{SPR_CACT, 10, -1, {A_ConnectToGround}, MT_CACTISMALLSEG, 0, S_NULL}, // S_CACTI11
+	{SPR_CACT, 11, -1, {NULL}, 0, 0, S_NULL}, // S_CACTITINYSEG
+	{SPR_CACT, 12, -1, {NULL}, 0, 0, S_NULL}, // S_CACTISMALLSEG
 
 	// Warning Signs
 	{SPR_WWSG, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_ARIDSIGN_CAUTION
@@ -5144,6 +5137,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_BOSSJUNK
+		-1,             // doomednum
+		S_BOSSEGLZ1,    // spawnstate
+		1,              // 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
+		8*FRACUNIT,     // radius
+		64*FRACUNIT,    // height
+		2,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_EGGMOBILE
 		200,               // doomednum
 		S_EGGMOBILE_STND,  // spawnstate
@@ -5333,87 +5353,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_EGGMOBILE2_POGO5 // raisestate
 	},
 
-	{           // MT_BOSSTANK1
-		-1,             // doomednum
-		S_BOSSTANK1,    // spawnstate
-		1,              // 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
-		8*FRACUNIT,     // radius
-		64*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		1,              // damage
-		sfx_None,       // activesound
-		MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIPHEIGHT, // flags
-		S_NULL          // raisestate
-	},
-
-	{           // MT_BOSSTANK2
-		-1,             // doomednum
-		S_BOSSTANK2,    // spawnstate
-		1,              // 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
-		8*FRACUNIT,     // radius
-		64*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		1,              // damage
-		sfx_None,       // activesound
-		MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIPHEIGHT, // flags
-		S_NULL          // raisestate
-	},
-
-	{           // MT_BOSSSPIGOT
-		-1,             // doomednum
-		S_BOSSSPIGOT,   // spawnstate
-		1,              // 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
-		8*FRACUNIT,     // radius
-		24*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		1,              // damage
-		sfx_None,       // activesound
-		MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIPHEIGHT, // flags
-		S_NULL          // raisestate
-	},
-
 	{           // MT_GOOP
 		-1,             // doomednum
 		S_GOOP1,        // spawnstate
@@ -5477,10 +5416,10 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,                  // reactiontime
 		sfx_None,           // attacksound
 		S_EGGMOBILE3_PAIN,  // painstate
-		MT_PROPELLER,       // painchance
+		MT_NULL,            // painchance
 		sfx_dmpain,         // painsound
 		S_NULL,             // meleestate
-		S_EGGMOBILE3_LAUGH1,// missilestate
+		S_EGGMOBILE3_SHOCK, // missilestate
 		S_EGGMOBILE3_DIE1,  // deathstate
 		S_EGGMOBILE3_FLEE1, // xdeathstate
 		sfx_s3kb4,          // deathsound
@@ -5492,34 +5431,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		3,                  // damage
 		sfx_telept,         // activesound
 		MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY|MF_BOSS|MF_NOCLIPHEIGHT, // flags
-		S_EGGMOBILE3_LAUGH20 // raisestate
-	},
-
-	{           // MT_PROPELLER
-		-1,             // doomednum
-		S_PROPELLER1,   // 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
-		1,              // speed
-		4*FRACUNIT,     // radius
-		4*FRACUNIT,     // height
-		0,              // display offset
-		4,              // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT, // flags
-		S_NULL          // raisestate
+		S_EGGMOBILE3_ROFL   // raisestate
 	},
 
 	{           // MT_FAKEMOBILE
@@ -5531,7 +5443,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,                  // reactiontime
 		sfx_None,           // attacksound
 		S_NULL,             // painstate
-		MT_PROPELLER,       // painchance
+		MT_NULL,            // painchance
 		sfx_s3k7b,          // painsound
 		S_NULL,             // meleestate
 		S_FAKEMOBILE_ATK1,  // missilestate
@@ -6592,8 +6504,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		16*FRACUNIT,     // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		1,              // damage
@@ -6673,8 +6585,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_ncitem,     // deathsound
 		1,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		16*FRACUNIT,     // radius
+		30*FRACUNIT,    // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
@@ -9211,7 +9123,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // mass
 		20,             // damage
 		sfx_None,       // activesound
-		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP, // flags
 		S_NULL          // raisestate
 	},
 
@@ -10129,7 +10041,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY,  // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -10156,7 +10068,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SPECIAL|MF_SCENERY,     // flags
+		MF_SPECIAL|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -10177,13 +10089,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		29*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -10204,13 +10116,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		30*FRACUNIT,    // radius
+		53*FRACUNIT,    // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -10231,13 +10143,67 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		28*FRACUNIT,    // radius
+		41*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CORAL4
+		1014,           // doomednum
+		S_CORAL4,       // 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
+		56*FRACUNIT,    // radius
+		112*FRACUNIT,   // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CORAL5
+		1015,           // doomednum
+		S_CORAL5,       // 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
+		56*FRACUNIT,    // radius
+		112*FRACUNIT,   // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -10264,13 +10230,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
 	{           // MT_KELP
 		1007,           // doomednum
-		S_KELP, // spawnstate
+		S_KELP,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10285,19 +10251,73 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,     // radius
-		292*FRACUNIT,    // height
+		16*FRACUNIT,    // radius
+		292*FRACUNIT,   // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_ANIMALGAETOP
+		1013,            // doomednum
+		S_ANIMALGAETOP1, // 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
+		48*FRACUNIT,     // radius
+		120*FRACUNIT,    // height
+		0,               // display offset
+		4,               // mass
+		0,               // damage
+		sfx_None,        // activesound
+		MF_NOCLIP|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_ANIMALGAESEG
+		-1,             // doomednum
+		S_ANIMALGAESEG, // 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
+		48*FRACUNIT,    // radius
+		120*FRACUNIT,   // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SCENERY|MF_NOBLOCKMAP,     // flags
+		MF_NOCLIP|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
 	{           // MT_DSZSTALAGMITE
 		1008,           // doomednum
-		S_DSZSTALAGMITE, // spawnstate
+		S_DSZSTALAGMITE,// spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10313,12 +10333,12 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		8*FRACUNIT,     // radius
-		116*FRACUNIT,    // height
+		116*FRACUNIT,   // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SCENERY|MF_SOLID,     // flags
+		MF_SCENERY|MF_SOLID, // flags
 		S_NULL          // raisestate
 	},
 
@@ -10340,18 +10360,18 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		8*FRACUNIT,     // radius
-		116*FRACUNIT,    // height
+		116*FRACUNIT,   // height
 		0,              // display offset
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SCENERY|MF_SOLID,     // flags
+		MF_SCENERY|MF_SOLID, // flags
 		S_NULL          // raisestate
 	},
 
 	{           // MT_LIGHTBEAM
 		1010,           // doomednum
-		S_LIGHTBEAM1, // spawnstate
+		S_LIGHTBEAM1,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -10366,7 +10386,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,     // radius
+		16*FRACUNIT,    // radius
 		16*FRACUNIT,    // height
 		0,              // display offset
 		4,              // mass
@@ -10432,7 +10452,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_FLAMEPARTICLE
 		-1,             // doomednum
-		S_FLAMEPARTICLE, // spawnstate
+		S_FLAMEPARTICLE,// spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11554,13 +11574,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		13*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SCENERY|MF_PAIN|MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
 		S_NULL          // raisestate
 	},
 
@@ -11581,13 +11601,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
+		15*FRACUNIT,    // radius
+		52*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SCENERY|MF_PAIN|MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
 		S_NULL          // raisestate
 	},
 
@@ -11608,13 +11628,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		32*FRACUNIT,    // height
+		13*FRACUNIT,    // radius
+		24*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SCENERY|MF_PAIN|MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
 		S_NULL          // raisestate
 	},
 
@@ -11635,13 +11655,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,    // radius
-		80*FRACUNIT,    // height
+		15*FRACUNIT,    // radius
+		52*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SCENERY|MF_PAIN|MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
 		S_NULL          // raisestate
 	},
 
@@ -11665,7 +11685,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		32*FRACUNIT,    // radius
 		96*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
@@ -11692,7 +11712,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		20*FRACUNIT,    // radius
 		128*FRACUNIT,   // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
@@ -11717,9 +11737,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		24*FRACUNIT,    // radius
-		224*FRACUNIT,    // height
+		224*FRACUNIT,   // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
@@ -11744,9 +11764,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		24*FRACUNIT,    // radius
-		256*FRACUNIT,    // height
+		256*FRACUNIT,   // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
@@ -11773,13 +11793,121 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		48*FRACUNIT,    // radius
 		96*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_SOLID|MF_SCENERY|MF_PAIN, // flags
 		S_NULL          // raisestate
 	},
 
+	{           // MT_CACTI10
+		1230,           // doomednum
+		S_CACTI10,       // 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
+		13*FRACUNIT,    // radius
+		28*FRACUNIT,    // height
+		0,              // display offset
+		DMG_SPIKE,      // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_PAIN|MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CACTI11
+		1231,           // doomednum
+		S_CACTI11,       // 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
+		15*FRACUNIT,    // radius
+		60*FRACUNIT,    // height
+		0,              // display offset
+		DMG_SPIKE,      // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_PAIN|MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CACTITINYSEG
+		-1,             // doomednum
+		S_CACTITINYSEG, // 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
+		13*FRACUNIT,    // radius
+		28*FRACUNIT,    // height
+		0,              // display offset
+		DMG_SPIKE,      // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_SCENERY|MF_PAIN|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CACTISMALLSEG
+		-1,              // doomednum
+		S_CACTISMALLSEG, // 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
+		15*FRACUNIT,     // radius
+		60*FRACUNIT,     // height
+		0,               // display offset
+		DMG_SPIKE,       // mass
+		0,               // damage
+		sfx_None,        // activesound
+		MF_NOTHINK|MF_SCENERY|MF_PAIN|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_ARIDSIGN_CAUTION
 		1212,           // doomednum
 		S_ARIDSIGN_CAUTION,       // spawnstate
diff --git a/src/info.h b/src/info.h
index 593c1fb7cdd60c372162e3de51e1fad444367c3b..74e4b87a26576a3bc1f68dcd2efbc4507b635488 100644
--- a/src/info.h
+++ b/src/info.h
@@ -319,18 +319,17 @@ typedef enum sprite
 	SPR_JETF, // Boss jet fumes
 
 	// Boss 1 (Greenflower)
-	SPR_EGGM,
+	SPR_EGGM, // Boss 1
+	SPR_EGLZ, // Boss 1 Junk
 
 	// Boss 2 (Techno Hill)
 	SPR_EGGN, // Boss 2
-	SPR_TNKA, // Boss 2 Tank 1
-	SPR_TNKB, // Boss 2 Tank 2
-	SPR_SPNK, // Boss 2 Spigot
+	SPR_TANK, // Boss 2 Junk
 	SPR_GOOP, // Boss 2 Goop
 
 	// Boss 3 (Deep Sea)
 	SPR_EGGO, // Boss 3
-	SPR_PRPL, // Boss 3 Propeller
+	SPR_SEBH, // Boss 3 Junk
 	SPR_FAKE, // Boss 3 Fakemobile
 
 	// Boss 4 (Castle Eggman)
@@ -459,11 +458,11 @@ typedef enum sprite
 	SPR_GARG, // Deep Sea Gargoyle
 	SPR_SEWE, // Deep Sea Seaweed
 	SPR_DRIP, // Dripping water
-	SPR_CRL1, // Coral 1
-	SPR_CRL2, // Coral 2
-	SPR_CRL3, // Coral 3
+	SPR_CORL, // Coral
 	SPR_BCRY, // Blue Crystal
 	SPR_KELP, // Kelp
+	SPR_ALGA, // Animated algae top
+	SPR_ALGB, // Animated algae segment
 	SPR_DSTG, // DSZ Stalagmites
 	SPR_LIBE, // DSZ Light beam
 
@@ -493,7 +492,7 @@ typedef enum sprite
 	// Arid Canyon Scenery
 	SPR_BTBL, // Big tumbleweed
 	SPR_STBL, // Small tumbleweed
-	SPR_CACT, // Cacti sprites
+	SPR_CACT, // Cacti
 	SPR_WWSG, // Caution Sign
 	SPR_WWS2, // Cacti Sign
 	SPR_WWS3, // Sharp Turn Sign
@@ -833,9 +832,15 @@ typedef enum playersprite
 	SPR2_TALA,
 	SPR2_TALB,
 
+	SPR2_CNT1, // continue disappointment
+	SPR2_CNT2, // continue lift
+	SPR2_CNT3, // continue spin
+	SPR2_CNT4, // continue "soooooooniiic!" tugging
+
 	SPR2_SIGN, // end sign head
 	SPR2_LIFE, // life monitor icon
-	SPR2_XTRA, // stuff that isn't in-game - keep this last in the list
+
+	SPR2_XTRA, // stuff that isn't in-map - "would this ever need an md2 or variable length animation?"
 
 	SPR2_FIRSTFREESLOT,
 	SPR2_LASTFREESLOT = 0x7f,
@@ -1333,10 +1338,10 @@ typedef enum state
 	S_SONIC3KBOSSEXPLOSION6,
 
 	S_JETFUME1,
-	S_JETFUME2,
 
 	// Boss 1
 	S_EGGMOBILE_STND,
+	S_EGGMOBILE_ROFL,
 	S_EGGMOBILE_LATK1,
 	S_EGGMOBILE_LATK2,
 	S_EGGMOBILE_LATK3,
@@ -1346,7 +1351,6 @@ typedef enum state
 	S_EGGMOBILE_LATK7,
 	S_EGGMOBILE_LATK8,
 	S_EGGMOBILE_LATK9,
-	S_EGGMOBILE_LATK10,
 	S_EGGMOBILE_RATK1,
 	S_EGGMOBILE_RATK2,
 	S_EGGMOBILE_RATK3,
@@ -1356,7 +1360,6 @@ typedef enum state
 	S_EGGMOBILE_RATK7,
 	S_EGGMOBILE_RATK8,
 	S_EGGMOBILE_RATK9,
-	S_EGGMOBILE_RATK10,
 	S_EGGMOBILE_PANIC1,
 	S_EGGMOBILE_PANIC2,
 	S_EGGMOBILE_PANIC3,
@@ -1364,6 +1367,14 @@ typedef enum state
 	S_EGGMOBILE_PANIC5,
 	S_EGGMOBILE_PANIC6,
 	S_EGGMOBILE_PANIC7,
+	S_EGGMOBILE_PANIC8,
+	S_EGGMOBILE_PANIC9,
+	S_EGGMOBILE_PANIC10,
+	S_EGGMOBILE_PANIC11,
+	S_EGGMOBILE_PANIC12,
+	S_EGGMOBILE_PANIC13,
+	S_EGGMOBILE_PANIC14,
+	S_EGGMOBILE_PANIC15,
 	S_EGGMOBILE_PAIN,
 	S_EGGMOBILE_PAIN2,
 	S_EGGMOBILE_DIE1,
@@ -1375,6 +1386,9 @@ typedef enum state
 	S_EGGMOBILE_BALL,
 	S_EGGMOBILE_TARGET,
 
+	S_BOSSEGLZ1,
+	S_BOSSEGLZ2,
+
 	// Boss 2
 	S_EGGMOBILE2_STND,
 	S_EGGMOBILE2_POGO1,
@@ -1405,11 +1419,7 @@ typedef enum state
 
 	// Boss 3
 	S_EGGMOBILE3_STND,
-	S_EGGMOBILE3_LAUGH1,
-	S_EGGMOBILE3_LAUGH2,
-	S_EGGMOBILE3_LAUGH3,
-	S_EGGMOBILE3_LAUGH4,
-	S_EGGMOBILE3_LAUGH5,
+	S_EGGMOBILE3_SHOCK,
 	S_EGGMOBILE3_ATK1,
 	S_EGGMOBILE3_ATK2,
 	S_EGGMOBILE3_ATK3A,
@@ -1418,21 +1428,7 @@ typedef enum state
 	S_EGGMOBILE3_ATK3D,
 	S_EGGMOBILE3_ATK4,
 	S_EGGMOBILE3_ATK5,
-	S_EGGMOBILE3_LAUGH6,
-	S_EGGMOBILE3_LAUGH7,
-	S_EGGMOBILE3_LAUGH8,
-	S_EGGMOBILE3_LAUGH9,
-	S_EGGMOBILE3_LAUGH10,
-	S_EGGMOBILE3_LAUGH11,
-	S_EGGMOBILE3_LAUGH12,
-	S_EGGMOBILE3_LAUGH13,
-	S_EGGMOBILE3_LAUGH14,
-	S_EGGMOBILE3_LAUGH15,
-	S_EGGMOBILE3_LAUGH16,
-	S_EGGMOBILE3_LAUGH17,
-	S_EGGMOBILE3_LAUGH18,
-	S_EGGMOBILE3_LAUGH19,
-	S_EGGMOBILE3_LAUGH20,
+	S_EGGMOBILE3_ROFL,
 	S_EGGMOBILE3_PAIN,
 	S_EGGMOBILE3_PAIN2,
 	S_EGGMOBILE3_DIE1,
@@ -1442,15 +1438,6 @@ typedef enum state
 	S_EGGMOBILE3_FLEE1,
 	S_EGGMOBILE3_FLEE2,
 
-	// Boss 3 Propeller
-	S_PROPELLER1,
-	S_PROPELLER2,
-	S_PROPELLER3,
-	S_PROPELLER4,
-	S_PROPELLER5,
-	S_PROPELLER6,
-	S_PROPELLER7,
-
 	// Boss 3 Pinch
 	S_FAKEMOBILE_INIT,
 	S_FAKEMOBILE,
@@ -1463,6 +1450,9 @@ typedef enum state
 	S_FAKEMOBILE_DIE1,
 	S_FAKEMOBILE_DIE2,
 
+	S_BOSSSEBH1,
+	S_BOSSSEBH2,
+
 	// Boss 4
 	S_EGGMOBILE4_STND,
 	S_EGGMOBILE4_LATK1,
@@ -1866,16 +1856,6 @@ typedef enum state
 
 	S_MSSHIELD_F1,
 	S_MSSHIELD_F2,
-	S_MSSHIELD_F3,
-	S_MSSHIELD_F4,
-	S_MSSHIELD_F5,
-	S_MSSHIELD_F6,
-	S_MSSHIELD_F7,
-	S_MSSHIELD_F8,
-	S_MSSHIELD_F9,
-	S_MSSHIELD_F10,
-	S_MSSHIELD_F11,
-	S_MSSHIELD_F12,
 
 	// Ring
 	S_RING,
@@ -2309,14 +2289,12 @@ typedef enum state
 	S_DRIPC1,
 	S_DRIPC2,
 
-	// Coral 1
+	// Coral
 	S_CORAL1,
-
-	// Coral 2
 	S_CORAL2,
-
-	// Coral 3
 	S_CORAL3,
+	S_CORAL4,
+	S_CORAL5,
 
 	// Blue Crystal
 	S_BLUECRYSTAL1,
@@ -2324,6 +2302,11 @@ typedef enum state
 	// Kelp,
 	S_KELP,
 
+	// Animated algae
+	S_ANIMALGAETOP1,
+	S_ANIMALGAETOP2,
+	S_ANIMALGAESEG,
+
 	// DSZ Stalagmites
 	S_DSZSTALAGMITE,
 	S_DSZ2STALAGMITE,
@@ -2458,7 +2441,7 @@ typedef enum state
 	S_LITTLETUMBLEWEED_ROLL7,
 	S_LITTLETUMBLEWEED_ROLL8,
 
-	// Cacti Sprites
+	// Cacti
 	S_CACTI1,
 	S_CACTI2,
 	S_CACTI3,
@@ -2468,8 +2451,12 @@ typedef enum state
 	S_CACTI7,
 	S_CACTI8,
 	S_CACTI9,
+	S_CACTI10,
+	S_CACTI11,
+	S_CACTITINYSEG,
+	S_CACTISMALLSEG,
 
-	// Warning signs sprites
+	// Warning signs
 	S_ARIDSIGN_CAUTION,
 	S_ARIDSIGN_CACTI,
 	S_ARIDSIGN_SHARPTURN,
@@ -3981,6 +3968,7 @@ typedef enum mobj_type
 	MT_EGGTRAP,
 	MT_BOSS3WAYPOINT,
 	MT_BOSS9GATHERPOINT,
+	MT_BOSSJUNK,
 
 	// Boss 1
 	MT_EGGMOBILE,
@@ -3992,15 +3980,11 @@ typedef enum mobj_type
 	// Boss 2
 	MT_EGGMOBILE2,
 	MT_EGGMOBILE2_POGO,
-	MT_BOSSTANK1,
-	MT_BOSSTANK2,
-	MT_BOSSSPIGOT,
 	MT_GOOP,
 	MT_GOOPTRAIL,
 
 	// Boss 3
 	MT_EGGMOBILE3,
-	MT_PROPELLER,
 	MT_FAKEMOBILE,
 	MT_SHOCK,
 
@@ -4211,11 +4195,15 @@ typedef enum mobj_type
 	MT_SEAWEED, // DSZ Seaweed
 	MT_WATERDRIP, // Dripping Water source
 	MT_WATERDROP, // Water drop from dripping water
-	MT_CORAL1, // Coral 1
-	MT_CORAL2, // Coral 2
-	MT_CORAL3, // Coral 3
+	MT_CORAL1, // Coral 
+	MT_CORAL2,
+	MT_CORAL3,
+	MT_CORAL4,
+	MT_CORAL5,
 	MT_BLUECRYSTAL, // Blue Crystal
 	MT_KELP, // Kelp
+	MT_ANIMALGAETOP, // Animated algae top
+	MT_ANIMALGAESEG, // Animated algae segment
 	MT_DSZSTALAGMITE, // Deep Sea 1 Stalagmite
 	MT_DSZ2STALAGMITE, // Deep Sea 2 Stalagmite
 	MT_LIGHTBEAM, // DSZ Light beam
@@ -4266,15 +4254,19 @@ typedef enum mobj_type
 	// Arid Canyon Scenery
 	MT_BIGTUMBLEWEED,
 	MT_LITTLETUMBLEWEED,
-	MT_CACTI1,
-	MT_CACTI2,
-	MT_CACTI3,
-	MT_CACTI4,
-	MT_CACTI5, // Harmful Cactus 1
-	MT_CACTI6, // Harmful Cactus 2
-	MT_CACTI7, // Harmful Cactus 3
-	MT_CACTI8, // Harmful Cactus 4
-	MT_CACTI9, // Harmful Cactus 5
+	MT_CACTI1, // Tiny Red Flower Cactus
+	MT_CACTI2, // Small Red Flower Cactus
+	MT_CACTI3, // Tiny Blue Flower Cactus
+	MT_CACTI4, // Small Blue Flower Cactus
+	MT_CACTI5, // Prickly Pear
+	MT_CACTI6, // Barrel Cactus
+	MT_CACTI7, // Tall Barrel Cactus
+	MT_CACTI8, // Armed Cactus
+	MT_CACTI9, // Ball Cactus
+	MT_CACTI10, // Tiny Cactus
+	MT_CACTI11, // Small Cactus
+	MT_CACTITINYSEG, // Tiny Cactus Segment
+	MT_CACTISMALLSEG, // Small Cactus Segment
 	MT_ARIDSIGN_CAUTION, // Caution Sign
 	MT_ARIDSIGN_CACTI, // Cacti Sign
 	MT_ARIDSIGN_SHARPTURN, // Sharp Turn Sign
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 3c136a43695b66fdafb3ae2cb29f8ef3ff18dbe5..a69e8a188d0030a2893ce4e4eac8237130edafc9 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -182,6 +182,8 @@ static const struct {
 	{META_CAMERA,       "camera_t"},
 
 	{META_ACTION,       "action"},
+
+	{META_LUABANKS,     "luabanks[]"},
 	{NULL,              NULL}
 };
 
@@ -228,6 +230,18 @@ static int lib_isPlayerAdmin(lua_State *L)
 	return 1;
 }
 
+static int lib_reserveLuabanks(lua_State *L)
+{
+	static boolean reserved = false;
+	if (!lua_lumploading)
+		return luaL_error(L, "luabanks[] cannot be reserved from within a hook or coroutine!");
+	if (reserved)
+		return luaL_error(L, "luabanks[] has already been reserved! Only one savedata-enabled mod at a time may use this feature.");
+	reserved = true;
+	LUA_PushUserdata(L, &luabanks, META_LUABANKS);
+	return 1;
+}
+
 // M_RANDOM
 //////////////
 
@@ -2736,6 +2750,7 @@ static luaL_Reg lib[] = {
 	{"chatprintf", lib_chatprintf},
 	{"userdataType", lib_userdataType},
 	{"IsPlayerAdmin", lib_isPlayerAdmin},
+	{"reserveLuabanks", lib_reserveLuabanks},
 
 	// m_random
 	{"P_RandomFixed",lib_pRandomFixed},
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 77f37f8ecc90b26b3f43894ac2f29a3e93f4f9ec..8ef0bafcf3801a306234d5898bd1c228aa6d4fd1 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -18,6 +18,7 @@
 #include "p_mobj.h"
 #include "p_local.h"
 #include "z_zone.h"
+#include "doomstat.h" // luabanks[]
 
 #include "lua_script.h"
 #include "lua_libs.h"
@@ -146,7 +147,7 @@ static int lib_getSpr2default(lua_State *L)
 		return luaL_error(L, "spr2defaults[] invalid index");
 
 	if (i >= free_spr2)
-		return 0;
+		return luaL_error(L, "spr2defaults[] index %d out of range (%d - %d)", i, 0, free_spr2-1);
 
 	lua_pushinteger(L, spr2defaults[i]);
 	return 1;
@@ -1026,6 +1027,61 @@ static int sfxinfo_num(lua_State *L)
 	return 1;
 }
 
+//////////////
+// LUABANKS //
+//////////////
+
+static int lib_getluabanks(lua_State *L)
+{
+	UINT8 i;
+
+	lua_remove(L, 1); // don't care about luabanks[] dummy userdata.
+
+	if (lua_isnumber(L, 1))
+		i = lua_tonumber(L, 1);
+	else
+		return luaL_error(L, "luabanks[] invalid index");
+
+	if (i >= NUM_LUABANKS)
+		luaL_error(L, "luabanks[] index %d out of range (%d - %d)", i, 0, NUM_LUABANKS-1);
+
+	lua_pushinteger(L, luabanks[i]);
+	return 1;
+}
+
+static int lib_setluabanks(lua_State *L)
+{
+	UINT8 i;
+	INT32 j = 0;
+
+	if (hud_running)
+		return luaL_error(L, "Do not alter luabanks[] in HUD rendering code!");
+
+	lua_remove(L, 1); // don't care about luabanks[] dummy userdata.
+
+	if (lua_isnumber(L, 1))
+		i = lua_tonumber(L, 1);
+	else
+		return luaL_error(L, "luabanks[] invalid index");
+
+	if (i >= NUM_LUABANKS)
+		luaL_error(L, "luabanks[] index %d out of range (%d - %d)", i, 0, NUM_LUABANKS-1);
+
+	if (lua_isnumber(L, 2))
+		j = lua_tonumber(L, 2);
+	else
+		return luaL_error(L, "luabanks[] invalid set");
+
+	luabanks[i] = j;
+	return 0;
+}
+
+static int lib_luabankslen(lua_State *L)
+{
+	lua_pushinteger(L, NUM_LUABANKS);
+	return 1;
+}
+
 //////////////////////////////
 //
 // Now push all these functions into the Lua state!
@@ -1147,6 +1203,18 @@ int LUA_InfoLib(lua_State *L)
 	lua_pushvalue(L, -1);
 	lua_setglobal(L, "S_sfx");
 	lua_setglobal(L, "sfxinfo");
+
+	luaL_newmetatable(L, META_LUABANKS);
+		lua_pushcfunction(L, lib_getluabanks);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, lib_setluabanks);
+		lua_setfield(L, -2, "__newindex");
+
+		lua_pushcfunction(L, lib_luabankslen);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
 	return 0;
 }
 
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 827c1d7987713ae0440fb31665226c839748fa71..7609971ce65bae48f1595c2737a817dd03e20f60 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -67,6 +67,8 @@ extern lua_State *gL;
 
 #define META_ACTION "ACTIONF_T*"
 
+#define META_LUABANKS "LUABANKS[]*"
+
 boolean luaL_checkboolean(lua_State *L, int narg);
 
 int LUA_EnumLib(lua_State *L);
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 74b2599219bc203f39352632eb2c2985a04eb4e7..dbb69b7e26dd4129600a7915a7a8426a6cd23c54 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -2017,6 +2017,8 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushinteger(L, header->muspostbosspos);
 	else if (fastcmp(field,"muspostbossfadein"))
 		lua_pushinteger(L, header->muspostbossfadein);
+	else if (fastcmp(field,"musforcereset"))
+		lua_pushinteger(L, header->musforcereset);
 	else if (fastcmp(field,"forcecharacter"))
 		lua_pushstring(L, header->forcecharacter);
 	else if (fastcmp(field,"weather"))
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index addd707e16da8e86254cedb7393788091678744f..dd9959afbdf3300bc150438e2e5cedce2f40d298 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -262,6 +262,8 @@ static int player_get(lua_State *L)
 		lua_pushinteger(L, plr->starposttime);
 	else if (fastcmp(field,"starpostangle"))
 		lua_pushangle(L, plr->starpostangle);
+	else if (fastcmp(field,"starpostscale"))
+		lua_pushfixed(L, plr->starpostscale);
 	else if (fastcmp(field,"angle_pos"))
 		lua_pushangle(L, plr->angle_pos);
 	else if (fastcmp(field,"old_angle_pos"))
@@ -570,6 +572,8 @@ static int player_set(lua_State *L)
 		plr->starposttime = (tic_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"starpostangle"))
 		plr->starpostangle = luaL_checkangle(L, 3);
+	else if (fastcmp(field,"starpostscale"))
+		plr->starpostscale = luaL_checkfixed(L, 3);
 	else if (fastcmp(field,"angle_pos"))
 		plr->angle_pos = luaL_checkangle(L, 3);
 	else if (fastcmp(field,"old_angle_pos"))
diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c
index a28f6a359556da233d71c6b598264101e56bb4e8..7f68905aa1ec748aa700fb7ced2441d0538d78d1 100644
--- a/src/lua_skinlib.c
+++ b/src/lua_skinlib.c
@@ -52,6 +52,8 @@ enum skin {
 	skin_supercolor,
 	skin_prefoppositecolor,
 	skin_highresscale,
+	skin_contspeed,
+	skin_contangle,
 	skin_soundsid,
 	skin_availability
 };
@@ -88,6 +90,8 @@ static const char *const skin_opt[] = {
 	"supercolor",
 	"prefoppositecolor",
 	"highresscale",
+	"contspeed",
+	"contangle",
 	"soundsid",
 	"availability",
 	NULL};
@@ -199,6 +203,12 @@ static int skin_get(lua_State *L)
 	case skin_highresscale:
 		lua_pushinteger(L, skin->highresscale);
 		break;
+	case skin_contspeed:
+		lua_pushinteger(L, skin->contspeed);
+		break;
+	case skin_contangle:
+		lua_pushinteger(L, skin->contangle);
+		break;
 	case skin_soundsid:
 		LUA_PushUserdata(L, skin->soundsid, META_SOUNDSID);
 		break;
diff --git a/src/m_cheat.c b/src/m_cheat.c
index da449b2f7a0713286d85a753895a6024a2b209cf..bb757839aeb140105b3d06f335a19691bffc2099 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -831,6 +831,12 @@ void Command_Savecheckpoint_f(void)
 	players[consoleplayer].starposty = players[consoleplayer].mo->y>>FRACBITS;
 	players[consoleplayer].starpostz = players[consoleplayer].mo->floorz>>FRACBITS;
 	players[consoleplayer].starpostangle = players[consoleplayer].mo->angle;
+	players[consoleplayer].starpostscale = players[consoleplayer].mo->destscale;
+	if (players[consoleplayer].mo->flags2 & MF2_OBJECTFLIP)
+	{
+		players[consoleplayer].starpostscale *= -1;
+		players[consoleplayer].starpostz += players[consoleplayer].mo->height;
+	}
 
 	CONS_Printf(M_GetText("Temporary checkpoint created at %d, %d, %d\n"), players[consoleplayer].starpostx, players[consoleplayer].starposty, players[consoleplayer].starpostz);
 }
diff --git a/src/m_menu.c b/src/m_menu.c
index 49e54c153bb3c4087bde786d9811c1c967c0e493..e1b2b5c90db0cf223b21f4506bd542146d9f0c2e 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -125,7 +125,7 @@ const char *quitmsg[NUM_QUITMESSAGES];
 
 // Stuff for customizing the player select screen Tails 09-22-2003
 description_t description[MAXSKINS];
-INT16 char_on = -1, startchar = 1;
+INT16 char_on = -1, startchar = 0;
 static char *char_notes = NULL;
 static fixed_t char_scroll = 0;
 
@@ -272,6 +272,7 @@ static void M_ServerOptions(INT32 choice);
 #ifndef NONET
 static void M_StartServerMenu(INT32 choice);
 static void M_ConnectMenu(INT32 choice);
+static void M_ConnectMenuModChecks(INT32 choice);
 static void M_Refresh(INT32 choice);
 static void M_Connect(INT32 choice);
 static void M_ChooseRoom(INT32 choice);
@@ -282,9 +283,10 @@ menu_t MP_MainDef;
 // Split into multiple parts due to size
 // Controls
 menu_t OP_ChangeControlsDef;
-menu_t OP_MPControlsDef, OP_CameraControlsDef, OP_MiscControlsDef;
+menu_t OP_MPControlsDef, OP_MiscControlsDef;
 menu_t OP_P1ControlsDef, OP_P2ControlsDef, OP_MouseOptionsDef;
 menu_t OP_Mouse2OptionsDef, OP_Joystick1Def, OP_Joystick2Def;
+menu_t OP_CameraOptionsDef, OP_Camera2OptionsDef;
 static void M_VideoModeMenu(INT32 choice);
 static void M_Setup1PControlsMenu(INT32 choice);
 static void M_Setup2PControlsMenu(INT32 choice);
@@ -891,12 +893,12 @@ static menuitem_t MP_SplitServerMenu[] =
 
 static menuitem_t MP_MainMenu[] =
 {
-	{IT_HEADER, NULL, "Host a game", NULL, 0},
-	{IT_STRING|IT_CALL,       NULL, "Internet/LAN...",       M_StartServerMenu,      12},
-	{IT_STRING|IT_CALL,       NULL, "Splitscreen...",        M_StartSplitServerMenu, 22},
-	{IT_HEADER, NULL, "Join a game", NULL, 40},
-	{IT_STRING|IT_CALL,       NULL, "Server browser...",     M_ConnectMenu,          52},
-	{IT_STRING|IT_KEYHANDLER, NULL, "Specify IPv4 address:", M_HandleConnectIP,      62},
+	{IT_HEADER, NULL, "Join a game", NULL, 0},
+	{IT_STRING|IT_CALL,       NULL, "Server browser...",     M_ConnectMenuModChecks,          12},
+	{IT_STRING|IT_KEYHANDLER, NULL, "Specify IPv4 address:", M_HandleConnectIP,      22},
+	{IT_HEADER, NULL, "Host a game", NULL, 54},
+	{IT_STRING|IT_CALL,       NULL, "Internet/LAN...",       M_StartServerMenu,      66},
+	{IT_STRING|IT_CALL,       NULL, "Splitscreen...",        M_StartSplitServerMenu, 76},
 	{IT_HEADER, NULL, "Player setup", NULL, 94},
 	{IT_STRING|IT_CALL,       NULL, "Player 1...",           M_SetupMultiPlayer,    106},
 	{IT_STRING|IT_CALL,       NULL, "Player 2... ",          M_SetupMultiPlayer2,   116},
@@ -955,7 +957,7 @@ enum
 
 static menuitem_t MP_RoomMenu[] =
 {
-	{IT_STRING | IT_CALL, NULL, "<Offline Mode>", M_ChooseRoom,   9},
+	{IT_STRING | IT_CALL, NULL, "<Unlisted Mode>", M_ChooseRoom,   9},
 	{IT_DISABLED,         NULL, "",               M_ChooseRoom,  18},
 	{IT_DISABLED,         NULL, "",               M_ChooseRoom,  27},
 	{IT_DISABLED,         NULL, "",               M_ChooseRoom,  36},
@@ -1009,13 +1011,11 @@ static menuitem_t OP_P1ControlsMenu[] =
 	{IT_SUBMENU | IT_STRING, NULL, "Mouse Options...", &OP_MouseOptionsDef, 20},
 	{IT_SUBMENU | IT_STRING, NULL, "Gamepad Options...", &OP_Joystick1Def  ,  30},
 
-	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam , 50},
-	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam , 60},
-	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 70},
+	{IT_SUBMENU | IT_STRING, NULL, "Camera Options...", &OP_CameraOptionsDef,	50},
 
-	//{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog,  90},
-	{IT_STRING  | IT_CVAR, NULL, "Character angle", &cv_directionchar,  90},
-	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake,  100},
+	//{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog,  100},
+	{IT_STRING  | IT_CVAR, NULL, "Character angle", &cv_directionchar,  70},
+	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake,  80},
 };
 
 static menuitem_t OP_P2ControlsMenu[] =
@@ -1024,13 +1024,11 @@ static menuitem_t OP_P2ControlsMenu[] =
 	{IT_SUBMENU | IT_STRING, NULL, "Second Mouse Options...", &OP_Mouse2OptionsDef, 20},
 	{IT_SUBMENU | IT_STRING, NULL, "Second Gamepad Options...", &OP_Joystick2Def  ,  30},
 
-	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam2 , 50},
-	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam2 , 60},
-	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 70},
+	{IT_SUBMENU | IT_STRING, NULL, "Camera Options...", &OP_Camera2OptionsDef,	50},
 
-	//{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog2,  90},
-	{IT_STRING  | IT_CVAR, NULL, "Character angle", &cv_directionchar2,  90},
-	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake2,  100},
+	//{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog2,  100},
+	{IT_STRING  | IT_CVAR, NULL, "Character angle", &cv_directionchar2,  70},
+	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake2,  80},
 };
 
 static menuitem_t OP_ChangeControlsMenu[] =
@@ -1159,6 +1157,34 @@ static menuitem_t OP_Mouse2OptionsMenu[] =
 	                      NULL, "Mouse Y Sensitivity",    &cv_mouseysens2,      80},
 };
 
+static menuitem_t OP_CameraOptionsMenu[] =
+{
+	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam , 10},
+	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam , 20},
+	{IT_STRING  | IT_CVAR, NULL, "Orbital Looking"  , &cv_cam_orbit , 30},
+	{IT_STRING	|	IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam_adjust, 40},
+
+	{IT_STRING	|	IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_dist, 60},
+	{IT_STRING	| IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_height, 70},
+	{IT_STRING	|	IT_CVAR	| IT_CV_FLOATSLIDER, NULL, "Camera Speed", &cv_cam_speed, 80},
+
+	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 100},
+};
+
+static menuitem_t OP_Camera2OptionsMenu[] =
+{
+	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam2 , 10},
+	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam2 , 20},
+	{IT_STRING  | IT_CVAR, NULL, "Orbital Looking"  , &cv_cam2_orbit , 30},
+	{IT_STRING	|	IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam2_adjust, 40},
+
+	{IT_STRING	|	IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam2_dist, 60},
+	{IT_STRING	| IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam2_height, 70},
+	{IT_STRING	|	IT_CVAR	| IT_CV_FLOATSLIDER, NULL, "Camera Speed", &cv_cam2_speed, 80},
+
+	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 100},
+};
+
 static menuitem_t OP_VideoOptionsMenu[] =
 {
 	{IT_HEADER, NULL, "Screen", NULL, 0},
@@ -1329,6 +1355,12 @@ static menuitem_t OP_SoundOptionsMenu[] =
 #define OPENMPT_MENUOFFSET 0
 #endif
 
+#ifdef HAVE_MIXERX
+#define MIXERX_MENUOFFSET 81
+#else
+#define MIXERX_MENUOFFSET 0
+#endif
+
 static menuitem_t OP_SoundAdvancedMenu[] =
 {
 #ifdef HAVE_OPENMPT
@@ -1340,12 +1372,15 @@ static menuitem_t OP_SoundAdvancedMenu[] =
 	{IT_HEADER, NULL, "MIDI Settings", NULL, OPENMPT_MENUOFFSET+10},
 	{IT_STRING | IT_CVAR, NULL, "MIDI Player", &cv_midiplayer, OPENMPT_MENUOFFSET+22},
 	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "FluidSynth Sound Font File", &cv_midisoundfontpath, OPENMPT_MENUOFFSET+34},
-	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "TiMidity++ Config Folder", &cv_miditimiditypath, OPENMPT_MENUOFFSET+61}
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "TiMidity++ Config Folder", &cv_miditimiditypath, OPENMPT_MENUOFFSET+61},
 #endif
+
+	{IT_HEADER, NULL, "Miscellaneous", NULL, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+10},
+	{IT_STRING | IT_CVAR, NULL, "Let Levels Force Reset Music", &cv_resetmusicbyheader, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+22},
 };
 
 #undef OPENMPT_MENUOFFSET
-
+#undef MIXERX_MENUOFFSET
 #endif
 
 static menuitem_t OP_DataOptionsMenu[] =
@@ -1360,36 +1395,39 @@ static menuitem_t OP_ScreenshotOptionsMenu[] =
 {
 	{IT_HEADER, NULL, "General", NULL, 0},
 	{IT_STRING|IT_CVAR, NULL, "Use color profile", &cv_screenshot_colorprofile,     6},
-	{IT_STRING|IT_CVAR, NULL, "Storage Location",  &cv_screenshot_option,          11},
-	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_screenshot_folder, 16},
 
-	{IT_HEADER, NULL, "Screenshots (F8)", NULL, 30},
-	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memory,                36},
-	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_level,                 41},
-	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategy,              46},
-	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bits,           51},
-
-	{IT_HEADER, NULL, "Movie Mode (F9)", NULL, 60},
-	{IT_STRING|IT_CVAR, NULL, "Capture Mode",      &cv_moviemode,                  66},
-
-	{IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize,               71},
-	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_gif_downscale,              76},
-
-	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memorya,               71},
-	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela,                76},
-	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategya,             81},
-	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bitsa,          86},
+	{IT_HEADER, NULL, "Screenshots (F8)", NULL, 16},
+	{IT_STRING|IT_CVAR, NULL, "Storage Location",  &cv_screenshot_option,          22},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_screenshot_folder, 27},
+	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memory,                42},
+	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_level,                 47},
+	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategy,              52},
+	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bits,           57},
+
+	{IT_HEADER, NULL, "Movie Mode (F9)", NULL, 64},
+	{IT_STRING|IT_CVAR, NULL, "Storage Location",  &cv_movie_option,              70},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_movie_folder, 	  75},
+	{IT_STRING|IT_CVAR, NULL, "Capture Mode",      &cv_moviemode,                 90},
+
+	{IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize,              95},
+	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_gif_downscale,             100},
+
+	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memorya,              95},
+	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela,               100},
+	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategya,            105},
+	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bitsa,         110},
 };
 
 enum
 {
 	op_screenshot_colorprofile = 1,
-	op_screenshot_folder = 3,
-	op_screenshot_capture = 10,
-	op_screenshot_gif_start = 11,
-	op_screenshot_gif_end = 12,
-	op_screenshot_apng_start = 13,
-	op_screenshot_apng_end = 16,
+	op_screenshot_folder = 4,
+	op_movie_folder = 11,
+	op_screenshot_capture = 12,
+	op_screenshot_gif_start = 13,
+	op_screenshot_gif_end = 14,
+	op_screenshot_apng_start = 15,
+	op_screenshot_apng_end = 18,
 };
 
 static menuitem_t OP_EraseDataMenu[] =
@@ -1884,6 +1922,13 @@ menu_t OP_JoystickSetDef =
 	0,
 	NULL
 };
+menu_t OP_CameraOptionsDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_P1CAMERA << 12),
+	"M_CONTRO", OP_CameraOptionsMenu, &OP_P1ControlsDef, 35, 30);
+menu_t OP_Camera2OptionsDef = DEFAULTMENUSTYLE(
+	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6) + (MN_OP_P2CAMERA << 12),
+	"M_CONTRO", OP_Camera2OptionsMenu, &OP_P2ControlsDef, 35, 30);
+
 
 menu_t OP_VideoOptionsDef =
 {
@@ -2207,6 +2252,12 @@ void Addons_option_Onchange(void)
 		(cv_addons_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED);
 }
 
+void Moviemode_option_Onchange(void)
+{
+	OP_ScreenshotOptionsMenu[op_movie_folder].status =
+		(cv_movie_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED);
+}
+
 // ==========================================================================
 // END ORGANIZATION STUFF.
 // ==========================================================================
@@ -2745,13 +2796,27 @@ static void M_ChangeCvar(INT32 choice)
 	    ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_INVISSLIDER)
 	    ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_NOMOD))
 	{
-		CV_SetValue(cv,cv->value+(choice));
+		if (cv->flags & CV_FLOAT && (currentMenu->menuitems[itemOn].status & IT_CV_FLOATSLIDER) == IT_CV_FLOATSLIDER)
+		{
+			char s[20];
+			sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f));
+			CV_Set(cv,s);
+		}
+		else
+			CV_SetValue(cv,cv->value+(choice));
 	}
 	else if (cv->flags & CV_FLOAT)
 	{
-		char s[20];
-		sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f));
-		CV_Set(cv,s);
+		if (currentMenu->menuitems[itemOn].status & IT_CV_INTEGERSTEP)
+		{
+			CV_SetValue(cv,FIXED_TO_FLOAT(cv->value)+(choice));
+		}
+		else
+		{
+			char s[20];
+			sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f));
+			CV_Set(cv,s);
+		}
 	}
 	else
 		CV_AddValue(cv,choice);
@@ -3641,7 +3706,12 @@ static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv, boolean ontop)
 
 	for (i = 0; cv->PossibleValue[i+1].strvalue; i++);
 
-	if ((range = atoi(cv->defaultvalue)) != cv->value)
+	if (cv->flags & CV_FLOAT)
+		range = (INT32)(atof(cv->defaultvalue)*FRACUNIT);
+	else
+		range = atoi(cv->defaultvalue);
+
+	if (range != cv->value)
 	{
 		range = ((range - cv->PossibleValue[0].value) * 100 /
 		 (cv->PossibleValue[i].value - cv->PossibleValue[0].value));
@@ -4517,6 +4587,9 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
 			if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU)
 				return false;
 
+			if (G_IsSpecialStage(mapnum+1))
+				return false;
+
 			if (gt == GT_COOP && (mapheaderinfo[mapnum]->typeoflevel & TOL_COOP))
 				return true;
 
@@ -5610,7 +5683,7 @@ static boolean M_AddonsRefresh(void)
 		{
 			S_StartSound(NULL, sfx_lose);
 			if (refreshdirmenu & REFRESHDIR_MAX)
-				message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nIf you want to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
+				message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nIf you wish to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 			else
 				message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
 		}
@@ -6892,6 +6965,7 @@ static void M_StartTutorial(INT32 choice)
 	tutorialmode = true; // turn on tutorial mode
 
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 	M_ClearMenus(true);
 	gamecomplete = false;
 	cursaveslot = 0;
@@ -7058,7 +7132,7 @@ static void M_DrawLoadGameData(void)
 			}
 		}
 
-		y -= 13;
+		y -= 4;
 
 		// character heads, lives, and continues
 		{
@@ -7067,7 +7141,7 @@ static void M_DrawLoadGameData(void)
 			patch_t *patch;
 			UINT8 *colormap = NULL;
 
-			INT32 tempx = (x+40)<<FRACBITS, tempy = y<<FRACBITS, flip = 0, calc;
+			INT32 tempx = (x+40)<<FRACBITS, flip = 0;
 
 			// botskin first
 			if (savegameinfo[savetodraw].botskin)
@@ -7082,13 +7156,13 @@ static void M_DrawLoadGameData(void)
 
 				V_DrawFixedPatch(
 					tempx + (18<<FRACBITS),
-					tempy -  (4<<FRACBITS),
+					y<<FRACBITS,
 					charbotskin->highresscale,
 					0, patch, colormap);
 
 				Z_Free(colormap);
 
-				tempx -= (15<<FRACBITS);
+				tempx -= (20<<FRACBITS);
 				flip = V_FLIP;
 			}
 skipbot:
@@ -7101,17 +7175,15 @@ skipbot:
 				goto skipsign;
 			sprframe = &sprdef->spriteframes[0];
 			patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
-			if ((calc = SHORT(patch->topoffset) - 42) > 0)
-				tempy += ((4+calc)<<FRACBITS);
 
 			V_DrawFixedPatch(
 				tempx,
-				tempy,
+				y<<FRACBITS,
 				charskin->highresscale,
 				flip, patch, colormap);
 
 skipsign:
-			y += 25;
+			y += 16;
 
 			tempx = x + 10;
 			if (savegameinfo[savetodraw].lives != INFLIVES
@@ -7300,7 +7372,29 @@ static void M_ReadSavegameInfo(UINT32 slot)
 
 	// File end marker check
 	CHECKPOS
-	if (READUINT8(save_p) != 0x1d) BADSAVE;
+	switch (READUINT8(save_p))
+	{
+		case 0xb7:
+			{
+				UINT8 i, banksinuse;
+				CHECKPOS
+				banksinuse = READUINT8(save_p);
+				CHECKPOS
+				if (banksinuse > NUM_LUABANKS)
+					BADSAVE
+				for (i = 0; i < banksinuse; i++)
+				{
+					(void)READINT32(save_p);
+					CHECKPOS
+				}
+				if (READUINT8(save_p) != 0x1d)
+					BADSAVE
+			}
+		case 0x1d:
+			break;
+		default:
+			BADSAVE
+	}
 
 	// done
 	Z_Free(savebuffer);
@@ -8486,8 +8580,8 @@ static void M_NightsAttack(INT32 choice)
 	M_PatchSkinNameTable();
 
 	G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
-	M_SetupNextMenu(&SP_NightsAttackDef);
 	titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
+	M_SetupNextMenu(&SP_NightsAttackDef);
 	if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
 		CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
 	else
@@ -8502,6 +8596,7 @@ static void M_ChooseNightsAttack(INT32 choice)
 	char nameofdemo[256];
 	(void)choice;
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 	M_ClearMenus(true);
 	modeattacking = ATTACKING_NIGHTS;
 
@@ -8526,6 +8621,7 @@ static void M_ChooseTimeAttack(INT32 choice)
 	char nameofdemo[256];
 	(void)choice;
 	emeralds = 0;
+	memset(&luabanks, 0, sizeof(luabanks));
 	M_ClearMenus(true);
 	modeattacking = ATTACKING_RECORD;
 
@@ -8879,7 +8975,7 @@ static void M_DrawConnectMenu(void)
 	// Room name
 	if (ms_RoomId < 0)
 		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_room].alphaKey,
-		                         V_YELLOWMAP, (itemOn == mp_connect_room) ? "<Select to change>" : "<Offline Mode>");
+		                         V_YELLOWMAP, (itemOn == mp_connect_room) ? "<Select to change>" : "<Unlisted Mode>");
 	else
 		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_room].alphaKey,
 		                         V_YELLOWMAP, room_list[menuRoomIndex].name);
@@ -9029,11 +9125,32 @@ static void M_ConnectMenu(INT32 choice)
 
 	// first page of servers
 	serverlistpage = 0;
-	M_SetupNextMenu(&MP_ConnectDef);
+	if (ms_RoomId < 0)
+	{
+		M_RoomMenu(0); // Select a room instead of staring at an empty list
+		// This prevents us from returning to the modified game alert.
+		currentMenu->prevMenu = &MP_MainDef;
+	}
+	else
+		M_SetupNextMenu(&MP_ConnectDef);
 	itemOn = 0;
 	M_Refresh(0);
 }
 
+static void M_ConnectMenuModChecks(INT32 choice)
+{
+	(void)choice;
+	// okay never mind we want to COMMUNICATE to the player pre-emptively instead of letting them try and then get confused when it doesn't work
+
+	if (modifiedgame)
+	{
+		M_StartMessage(M_GetText("Add-ons are currently loaded.\n\nYou will only be able to join a server if\nit has the same ones loaded in the same order, which may be unlikely.\n\nIf you wish to play on other servers,\nrestart the game to clear existing add-ons.\n\n(Press a key)\n"),M_ConnectMenu,MM_EVENTHANDLER);
+		return;
+	}
+
+	M_ConnectMenu(-1);
+}
+
 static UINT32 roomIds[NUM_LIST_ROOMS];
 
 static void M_RoomMenu(INT32 choice)
@@ -9088,7 +9205,16 @@ static void M_ChooseRoom(INT32 choice)
 	}
 
 	serverlistpage = 0;
-	M_SetupNextMenu(currentMenu->prevMenu);
+	/*
+	We were on the Multiplayer menu? That means that we must have been trying to
+	view the server browser, but we hadn't selected a room yet. So we need to go
+	to the browser next, not back there.
+	*/
+	if (currentMenu->prevMenu == &MP_MainDef)
+		M_SetupNextMenu(&MP_ConnectDef);
+	else
+		M_SetupNextMenu(currentMenu->prevMenu);
+
 	if (currentMenu == &MP_ConnectDef)
 		M_Refresh(0);
 }
@@ -9147,7 +9273,7 @@ static void M_DrawServerMenu(void)
 		M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight/2, "Server settings", true, false);
 		if (ms_RoomId < 0)
 			V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey,
-			                         V_YELLOWMAP, (itemOn == mp_server_room) ? "<Select to change>" : "<Offline Mode>");
+			                         V_YELLOWMAP, (itemOn == mp_server_room) ? "<Select to change>" : "<Unlisted Mode>");
 		else
 			V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey,
 			                         V_YELLOWMAP, room_list[menuRoomIndex].name);
@@ -9254,7 +9380,7 @@ static void M_StartServerMenu(INT32 choice)
 // CONNECT VIA IP
 // ==============
 
-static char setupm_ip[16];
+static char setupm_ip[28];
 
 // Draw the funky Connect IP menu. Tails 11-19-2002
 // So much work for such a little thing!
@@ -9266,30 +9392,26 @@ static void M_DrawMPMainMenu(void)
 	// use generic drawer for cursor, items and title
 	M_DrawGenericMenu();
 
-#if MAXPLAYERS == 32
-	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+12,
-		((itemOn == 1) ? V_YELLOWMAP : 0), "(2-32 players)");
-#else
-Update the maxplayers label...
-#endif
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+66,
+		((itemOn == 4) ? V_YELLOWMAP : 0), va("(2-%d players)", MAXPLAYERS));
 
-	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+22,
-		((itemOn == 2) ? V_YELLOWMAP : 0), "(2 players)");
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+76,
+		((itemOn == 5) ? V_YELLOWMAP : 0), "(2 players)");
 
 	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+116,
 		((itemOn == 8) ? V_YELLOWMAP : 0), "(splitscreen)");
 
-	y += 62;
+	y += 22;
 
 	V_DrawFill(x+5, y+4+5, /*16*8 + 6,*/ BASEVIDWIDTH - 2*(x+5), 8+6, 159);
 
 	// draw name string
-	V_DrawString(x+8,y+12, V_MONOSPACE, setupm_ip);
+	V_DrawString(x+8,y+12, V_ALLOWLOWERCASE, setupm_ip);
 
 	// draw text cursor for name
-	if (itemOn == 5 //0
+	if (itemOn == 2 //0
 	    && skullAnimCounter < 4)   //blink cursor
-		V_DrawCharacter(x+8+V_StringWidth(setupm_ip, V_MONOSPACE),y+12,'_',false);
+		V_DrawCharacter(x+8+V_StringWidth(setupm_ip, V_ALLOWLOWERCASE),y+12,'_',false);
 }
 
 // Tails 11-19-2002
@@ -9360,10 +9482,11 @@ static void M_HandleConnectIP(INT32 choice)
 
 		default:
 			l = strlen(setupm_ip);
-			if (l >= 16-1)
+			if (l >= 28-1)
 				break;
 
-			if (choice == 46 || (choice >= 48 && choice <= 57)) // Rudimentary number and period enforcing
+			// Rudimentary number and period enforcing - also allows letters so hostnames can be used instead
+			if ((choice >= '-' && choice <= ':') || (choice >= 'A' && choice <= 'Z') || (choice >= 'a' && choice <= 'z'))
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_ip[l] = (char)choice;
diff --git a/src/m_menu.h b/src/m_menu.h
index 38be5a347293efe4d6559cde0a845fba6f741ebf..05962d2b19072eda900642223db6f042f7790c8e 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -72,10 +72,12 @@ typedef enum
 	MN_OP_P1MOUSE,
 	MN_OP_P1JOYSTICK,
 	MN_OP_JOYSTICKSET, // OP_JoystickSetDef shared with P2
+	MN_OP_P1CAMERA,
 
 	MN_OP_P2CONTROLS,
 	MN_OP_P2MOUSE,
 	MN_OP_P2JOYSTICK,
+	MN_OP_P2CAMERA,
 
 	MN_OP_VIDEO,
 	MN_OP_VIDEOMODE,
@@ -241,6 +243,8 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt);
 #define IT_CV_NOPRINT     1536
 #define IT_CV_NOMOD       2048
 #define IT_CV_INVISSLIDER 2560
+#define IT_CV_INTEGERSTEP 4096			// if IT_CV_NORMAL and cvar is CV_FLOAT, modify it by 1 instead of 0.0625
+#define IT_CV_FLOATSLIDER	4608			// IT_CV_SLIDER, value modified by 0.0625 instead of 1 (for CV_FLOAT cvars)
 
 //call/submenu specific
 // There used to be a lot more here but ...
@@ -400,6 +404,9 @@ void Screenshot_option_Onchange(void);
 // Addons menu updating
 void Addons_option_Onchange(void);
 
+// Moviemode menu updating
+void Moviemode_option_Onchange(void);
+
 // These defines make it a little easier to make menus
 #define DEFAULTMENUSTYLE(id, header, source, prev, x, y)\
 {\
diff --git a/src/m_misc.c b/src/m_misc.c
index 22e19df73d567000facee5d666af31c579ec86c5..aaaf30d67ff4263967f693f8126ff6b72b1b6f8c 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -108,6 +108,9 @@ consvar_t cv_screenshot_colorprofile = {"screenshot_colorprofile", "Yes", CV_SAV
 static CV_PossibleValue_t moviemode_cons_t[] = {{MM_GIF, "GIF"}, {MM_APNG, "aPNG"}, {MM_SCREENSHOT, "Screenshots"}, {0, NULL}};
 consvar_t cv_moviemode = {"moviemode_mode", "GIF", CV_SAVE|CV_CALL, moviemode_cons_t, Moviemode_mode_Onchange, 0, NULL, NULL, 0, 0, NULL};
 
+consvar_t cv_movie_option = {"movie_option", "Default", CV_SAVE|CV_CALL, screenshot_cons_t, Moviemode_option_Onchange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_movie_folder = {"movie_folder", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 static CV_PossibleValue_t zlib_mem_level_t[] = {
 	{1, "(Min Memory) 1"},
 	{2, "2"}, {3, "3"}, {4, "4"}, {5, "5"}, {6, "6"}, {7, "7"},
@@ -1124,19 +1127,25 @@ static inline moviemode_t M_StartMovieGIF(const char *pathname)
 void M_StartMovie(void)
 {
 #if NUMSCREENS > 2
-	const char *pathname = ".";
+	char pathname[MAX_WADPATH];
 
 	if (moviemode)
 		return;
 
-	if (cv_screenshot_option.value == 0)
-		pathname = usehome ? srb2home : srb2path;
-	else if (cv_screenshot_option.value == 1)
-		pathname = srb2home;
-	else if (cv_screenshot_option.value == 2)
-		pathname = srb2path;
-	else if (cv_screenshot_option.value == 3 && *cv_screenshot_folder.string != '\0')
-		pathname = cv_screenshot_folder.string;
+	if (cv_movie_option.value == 0)
+		strcpy(pathname, usehome ? srb2home : srb2path);
+	else if (cv_movie_option.value == 1)
+		strcpy(pathname, srb2home);
+	else if (cv_movie_option.value == 2)
+		strcpy(pathname, srb2path);
+	else if (cv_movie_option.value == 3 && *cv_movie_folder.string != '\0')
+		strcpy(pathname, cv_movie_folder.string);
+
+	if (cv_movie_option.value != 3)
+	{
+		strcat(pathname, PATHSEP"movies"PATHSEP);
+		I_mkdir(pathname, 0755);
+	}
 
 	if (rendermode == render_none)
 		I_Error("Can't make a movie without a render system\n");
@@ -1474,7 +1483,8 @@ void M_ScreenShot(void)
 void M_DoScreenShot(void)
 {
 #if NUMSCREENS > 2
-	const char *freename = NULL, *pathname = ".";
+	const char *freename = NULL;
+	char pathname[MAX_WADPATH];
 	boolean ret = false;
 	UINT8 *linear = NULL;
 
@@ -1486,13 +1496,19 @@ void M_DoScreenShot(void)
 		return;
 
 	if (cv_screenshot_option.value == 0)
-		pathname = usehome ? srb2home : srb2path;
+		strcpy(pathname, usehome ? srb2home : srb2path);
 	else if (cv_screenshot_option.value == 1)
-		pathname = srb2home;
+		strcpy(pathname, srb2home);
 	else if (cv_screenshot_option.value == 2)
-		pathname = srb2path;
+		strcpy(pathname, srb2path);
 	else if (cv_screenshot_option.value == 3 && *cv_screenshot_folder.string != '\0')
-		pathname = cv_screenshot_folder.string;
+		strcpy(pathname, cv_screenshot_folder.string);
+
+	if (cv_screenshot_option.value != 3)
+	{
+		strcat(pathname, PATHSEP"screenshots"PATHSEP);
+		I_mkdir(pathname, 0755);
+	}
 
 #ifdef USE_PNG
 	freename = Newsnapshotfile(pathname,"png");
diff --git a/src/m_misc.h b/src/m_misc.h
index 6ac92dbcc085324c5c5bcba48c8f22fd2824c85f..7038e3e48d94cbed3c1bbb9ee71be9f35e33a942 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -30,7 +30,7 @@ typedef enum {
 extern moviemode_t moviemode;
 
 extern consvar_t cv_screenshot_option, cv_screenshot_folder, cv_screenshot_colorprofile;
-extern consvar_t cv_moviemode;
+extern consvar_t cv_moviemode, cv_movie_folder, cv_movie_option;
 extern consvar_t cv_zlib_memory, cv_zlib_level, cv_zlib_strategy, cv_zlib_window_bits;
 extern consvar_t cv_zlib_memorya, cv_zlib_levela, cv_zlib_strategya, cv_zlib_window_bitsa;
 extern consvar_t cv_apng_delay;
diff --git a/src/m_swap.h b/src/m_swap.h
index 2d42f6138efda6115d1d0527877ddb55a983d79a..3b50dc623737435bda8bd04eac6b50511e9cb626 100644
--- a/src/m_swap.h
+++ b/src/m_swap.h
@@ -14,29 +14,39 @@
 #ifndef __M_SWAP__
 #define __M_SWAP__
 
-#include "endian.h"
-
 // Endianess handling.
 // WAD files are stored little endian.
+#include "endian.h"
+
+// Little to big endian
 #ifdef SRB2_BIG_ENDIAN
 
-#define SHORT(x) ((INT16)(\
-(((UINT16)(x) & (UINT16)0x00ffU) << 8) \
-| \
-(((UINT16)(x) & (UINT16)0xff00U) >> 8))) \
+	#define SHORT(x) ((INT16)(\
+	(((UINT16)(x) & (UINT16)0x00ffU) << 8) \
+	| \
+	(((UINT16)(x) & (UINT16)0xff00U) >> 8))) \
 
-#define LONG(x) ((INT32)(\
-(((UINT32)(x) & (UINT32)0x000000ffUL) << 24) \
-| \
-(((UINT32)(x) & (UINT32)0x0000ff00UL) <<  8) \
-| \
-(((UINT32)(x) & (UINT32)0x00ff0000UL) >>  8) \
-| \
-(((UINT32)(x) & (UINT32)0xff000000UL) >> 24)))
+	#define LONG(x) ((INT32)(\
+	(((UINT32)(x) & (UINT32)0x000000ffUL) << 24) \
+	| \
+	(((UINT32)(x) & (UINT32)0x0000ff00UL) <<  8) \
+	| \
+	(((UINT32)(x) & (UINT32)0x00ff0000UL) >>  8) \
+	| \
+	(((UINT32)(x) & (UINT32)0xff000000UL) >> 24)))
+
+#else
+	#define SHORT(x) ((INT16)(x))
+	#define LONG(x)	((INT32)(x))
+#endif
 
+// Big to little endian
+#ifdef SRB2_LITTLE_ENDIAN
+	#define BIGENDIAN_LONG(x) ((INT32)(((x)>>24)&0xff)|(((x)<<8)&0xff0000)|(((x)>>8)&0xff00)|(((x)<<24)&0xff000000))
+	#define BIGENDIAN_SHORT(x) ((INT16)(((x)>>8)|((x)<<8)))
 #else
-#define SHORT(x) ((INT16)(x))
-#define LONG(x)	((INT32)(x))
+	#define BIGENDIAN_LONG(x) ((INT32)(x))
+	#define BIGENDIAN_SHORT(x) ((INT16)(x))
 #endif
 
 #endif
diff --git a/src/p_enemy.c b/src/p_enemy.c
index b60e5e68b2df8b65e751fb8dec0d5f0bab76763f..bc3665237d326e7045cad7a9c305d6c96f554fbb 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -3006,16 +3006,19 @@ void A_Boss7FireMissiles(mobj_t *actor)
 //		0 - Boss 1 Left side
 //		1 - Boss 1 Right side
 //		2 - Triple laser
-//		>3 - Boss 1 Middle
+//		3 - Boss 1 Middle
+//		>=3 - Generic middle
 //
 void A_Boss1Laser(mobj_t *actor)
 {
 	fixed_t x, y, z, floorz, speed;
 	INT32 locvar1 = var1;
-	INT32 locvar2 = var2;
+	INT32 locvar2 = (var2 & 65535);
+	INT32 upperend = (var2>>16);
 	INT32 i;
 	angle_t angle;
 	mobj_t *point;
+	tic_t dur;
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_Boss1Laser", actor))
@@ -3024,19 +3027,24 @@ void A_Boss1Laser(mobj_t *actor)
 	if (!actor->target)
 		return;
 
+	if ((upperend & 1) && (actor->extravalue2 > 1))
+		actor->extravalue2--;
+
+	dur = actor->extravalue2;
+
 	switch (locvar2)
 	{
 		case 0:
-			x = actor->x + P_ReturnThrustX(actor, actor->angle+ANGLE_90, FixedMul(43*FRACUNIT, actor->scale));
-			y = actor->y + P_ReturnThrustY(actor, actor->angle+ANGLE_90, FixedMul(43*FRACUNIT, actor->scale));
+			x = actor->x + P_ReturnThrustX(actor, actor->angle+ANGLE_90, FixedMul(44*FRACUNIT, actor->scale));
+			y = actor->y + P_ReturnThrustY(actor, actor->angle+ANGLE_90, FixedMul(44*FRACUNIT, actor->scale));
 			if (actor->eflags & MFE_VERTICALFLIP)
 				z = actor->z + actor->height - FixedMul(56*FRACUNIT, actor->scale) - mobjinfo[locvar1].height;
 			else
 				z = actor->z + FixedMul(56*FRACUNIT, actor->scale);
 			break;
 		case 1:
-			x = actor->x + P_ReturnThrustX(actor, actor->angle-ANGLE_90, FixedMul(43*FRACUNIT, actor->scale));
-			y = actor->y + P_ReturnThrustY(actor, actor->angle-ANGLE_90, FixedMul(43*FRACUNIT, actor->scale));
+			x = actor->x + P_ReturnThrustX(actor, actor->angle-ANGLE_90, FixedMul(44*FRACUNIT, actor->scale));
+			y = actor->y + P_ReturnThrustY(actor, actor->angle-ANGLE_90, FixedMul(44*FRACUNIT, actor->scale));
 			if (actor->eflags & MFE_VERTICALFLIP)
 				z = actor->z + actor->height - FixedMul(56*FRACUNIT, actor->scale) - mobjinfo[locvar1].height;
 			else
@@ -3051,6 +3059,11 @@ void A_Boss1Laser(mobj_t *actor)
 			A_Boss1Laser(actor);
 			return;
 			break;
+		case 3:
+			x = actor->x + P_ReturnThrustX(actor, actor->angle, FixedMul(42*FRACUNIT, actor->scale));
+			y = actor->y + P_ReturnThrustY(actor, actor->angle, FixedMul(42*FRACUNIT, actor->scale));
+			z = actor->z + actor->height/2;
+			break;
 		default:
 			x = actor->x;
 			y = actor->y;
@@ -3058,7 +3071,7 @@ void A_Boss1Laser(mobj_t *actor)
 			break;
 	}
 
-	if (!(actor->flags2 & MF2_FIRING) && actor->tics > 1)
+	if (!(actor->flags2 & MF2_FIRING) && dur > 1)
 	{
 		actor->angle = R_PointToAngle2(x, y, actor->target->x, actor->target->y);
 		if (mobjinfo[locvar1].seesound)
@@ -3067,7 +3080,7 @@ void A_Boss1Laser(mobj_t *actor)
 		{
 			point = P_SpawnMobj(x + P_ReturnThrustX(actor, actor->angle, actor->radius), y + P_ReturnThrustY(actor, actor->angle, actor->radius), actor->z - actor->height / 2, MT_EGGMOBILE_TARGET);
 			point->angle = actor->angle;
-			point->fuse = actor->tics+1;
+			point->fuse = dur+1;
 			P_SetTarget(&point->target, actor->target);
 			P_SetTarget(&actor->target, point);
 		}
@@ -3076,9 +3089,9 @@ void A_Boss1Laser(mobj_t *actor)
 	else if (actor->target && !(actor->spawnpoint && actor->spawnpoint->options & MTF_AMBUSH))
 		actor->angle = R_PointToAngle2(x, y, actor->target->x, actor->target->y);*/
 
-	if (actor->spawnpoint && actor->spawnpoint->options & MTF_AMBUSH)
-		angle = FixedAngle(FixedDiv(actor->tics*160*FRACUNIT, actor->state->tics*FRACUNIT) + 10*FRACUNIT);
-	else
+	/*if (actor->spawnpoint && actor->spawnpoint->options & MTF_AMBUSH)
+		angle = FixedAngle(FixedDiv(dur*160*FRACUNIT, actor->state->tics*FRACUNIT) + 10*FRACUNIT);
+	else*/
 		angle = R_PointToAngle2(z + (mobjinfo[locvar1].height>>1), 0, actor->target->z, R_PointToDist2(x, y, actor->target->x, actor->target->y));
 
 	point = P_SpawnMobj(x, y, z, locvar1);
@@ -3112,7 +3125,7 @@ void A_Boss1Laser(mobj_t *actor)
 		point->fuse = TICRATE;
 	}
 
-	if (actor->tics > 1)
+	if (dur > 1)
 		actor->flags2 |= MF2_FIRING;
 	else
 		actor->flags2 &= ~MF2_FIRING;
@@ -3256,6 +3269,7 @@ void A_Boss4Raise(mobj_t *actor)
 //		0 - Fly at the player
 //		1 - Fly away from the player
 //		2 - Strafe in relation to the player
+//      3 - Dynamic mode - don't get too close to walls
 // var2:
 //		0 - Fly horizontally and vertically
 //		1 - Fly horizontal-only (momz = 0)
@@ -3286,16 +3300,83 @@ void A_SkullAttack(mobj_t *actor)
 		S_StartSound(actor, actor->info->activesound);
 	A_FaceTarget(actor);
 
+	dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y);
+
 	if (locvar1 == 1)
 		actor->angle += ANGLE_180;
 	else if (locvar1 == 2)
 		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;
+		INT32 i, j, k;
+		boolean allow;
+		angle_t testang;
+
+		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;
+
+		if (P_RandomChance(FRACUNIT/2)) // port priority 1?
+		{
+			i = 9;
+			j = 27;
+		}
+		else
+		{
+			i = 27;
+			j = 9;
+		}
+
+#define dostuff(q) check = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_NULL);\
+			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_RandomChance(FRACUNIT/2)) // port priority 2?
+		{
+			for (k = 0; k < 9; k++)
+			{
+				dostuff(i+k)
+				dostuff(i-k)
+				dostuff(j+k)
+				dostuff(j-k)
+			}
+		}
+		else
+		{
+			for (k = 0; k < 9; k++)
+			{
+				dostuff(i-k)
+				dostuff(i+k)
+				dostuff(j-k)
+				dostuff(j+k)
+			}
+		}
+		actor->angle = testang;
+
+#undef dostuff
+
+		mobjinfo[MT_NULL].spawnstate = oldspawnstate;
+		mobjinfo[MT_NULL].flags = oldflags;
+		mobjinfo[MT_NULL].radius = oldradius;
+		mobjinfo[MT_NULL].height = oldheight;
+	}
 
 	an = actor->angle >> ANGLETOFINESHIFT;
 
 	actor->momx = FixedMul(speed, FINECOSINE(an));
 	actor->momy = FixedMul(speed, FINESINE(an));
-	dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y);
 	dist = dist / speed;
 
 	if (dist < 1)
@@ -3445,11 +3526,13 @@ void A_Pain(mobj_t *actor)
 //
 // Description: Changes a dying object's flags to reflect its having fallen to the ground.
 //
-// var1 = unused
+// var1 = value to set repeat to if nonzero
 // var2 = unused
 //
 void A_Fall(mobj_t *actor)
 {
+	INT32 locvar1 = var1;
+
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_Fall", actor))
 		return;
@@ -3462,6 +3545,9 @@ void A_Fall(mobj_t *actor)
 
 	// So change this if corpse objects
 	// are meant to be obstacles.
+
+	if (locvar1)
+		actor->extravalue2 = locvar1;
 }
 
 #define LIVESBOXDISPLAYPLAYER // Use displayplayer instead of closest player
@@ -3857,6 +3943,72 @@ bossjustdie:
 	else if (P_MobjWasRemoved(mo))
 		return;
 #endif
+
+	// Spawn your junk
+	switch (mo->type)
+	{
+		default:
+			break;
+		case MT_EGGMOBILE: // twin laser pods
+			{
+				mo2 = P_SpawnMobjFromMobj(mo,
+					P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+					P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+					32<<FRACBITS, MT_BOSSJUNK);
+				mo2->angle = mo->angle;
+				P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
+				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+				P_SetMobjState(mo2, S_BOSSEGLZ1);
+
+				mo2 = P_SpawnMobjFromMobj(mo,
+					P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
+					P_ReturnThrustY(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
+					32<<FRACBITS, MT_BOSSJUNK);
+				mo2->angle = mo->angle;
+				P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
+				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+				P_SetMobjState(mo2, S_BOSSEGLZ2);
+			}
+			break;
+		case MT_EGGMOBILE2: // twin tanks + spigot
+			{
+				mo2 = P_SpawnMobjFromMobj(mo,
+					P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+					P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+					32<<FRACBITS, MT_BOSSJUNK);
+				mo2->angle = mo->angle;
+				P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
+				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+				P_SetMobjState(mo2, S_BOSSTANK1);
+
+				mo2 = P_SpawnMobjFromMobj(mo,
+					P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
+					P_ReturnThrustY(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
+					32<<FRACBITS, MT_BOSSJUNK);
+				mo2->angle = mo->angle;
+				P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
+				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+				P_SetMobjState(mo2, S_BOSSTANK2);
+
+				mo2 = P_SpawnMobjFromMobj(mo, 0, 0,
+					mobjinfo[MT_EGGMOBILE2].height + (32<<FRACBITS),
+					MT_BOSSJUNK);
+				mo2->angle = mo->angle;
+				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+				mo2->momz += mo->momz;
+				P_SetMobjState(mo2, S_BOSSSPIGOT);
+			}
+			break;
+		case MT_EGGMOBILE3:
+			{
+				mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_BOSSJUNK);
+				mo2->angle = mo->angle;
+				P_SetMobjState(mo2, S_BOSSSEBH1);
+			}
+			break;
+	}
+
+	// now do another switch case for escaping
 	switch (mo->type)
 	{
 		case MT_BLACKEGGMAN:
@@ -3954,7 +4106,7 @@ bossjustdie:
 			mo->movedir = 0;
 			mo->extravalue1 = 35;
 			mo->flags2 |= MF2_BOSSFLEE;
-			mo->momz = 2*mo->scale;
+			mo->momz = P_MobjFlip(mo)*2*mo->scale;
 
 			if (mo->target)
 			{
@@ -3972,50 +4124,6 @@ bossjustdie:
 			break;
 		}
 	}
-
-	if (mo->type == MT_EGGMOBILE2)
-	{
-		mo2 = P_SpawnMobj(mo->x + P_ReturnThrustX(mo, mo->angle - ANGLE_90, FixedMul(32*FRACUNIT, mo->scale)),
-			mo->y + P_ReturnThrustY(mo, mo->angle - ANGLE_90, FixedMul(32*FRACUNIT, mo->scale)),
-			mo->z + mo->height/2 + ((mo->eflags & MFE_VERTICALFLIP)? FixedMul(8*FRACUNIT, mo->scale)-mobjinfo[MT_BOSSTANK1].height : -FixedMul(8*FRACUNIT, mo->scale)), MT_BOSSTANK1); // Right tank
-		mo2->angle = mo->angle;
-		mo2->destscale = mo->scale;
-		P_SetScale(mo2, mo2->destscale);
-		if (mo->eflags & MFE_VERTICALFLIP)
-		{
-			mo2->eflags |= MFE_VERTICALFLIP;
-			mo2->flags2 |= MF2_OBJECTFLIP;
-		}
-		P_InstaThrust(mo2, mo2->angle - ANGLE_90, FixedMul(4*FRACUNIT, mo2->scale));
-		P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-
-		mo2 = P_SpawnMobj(mo->x + P_ReturnThrustX(mo, mo->angle + ANGLE_90, FixedMul(32*FRACUNIT, mo->scale)),
-			mo->y + P_ReturnThrustY(mo, mo->angle + ANGLE_90, FixedMul(32*FRACUNIT, mo->scale)),
-			mo->z + mo->height/2 + ((mo->eflags & MFE_VERTICALFLIP)? FixedMul(8*FRACUNIT, mo->scale)-mobjinfo[MT_BOSSTANK2].height : -FixedMul(8*FRACUNIT, mo->scale)), MT_BOSSTANK2); // Left tank
-		mo2->angle = mo->angle;
-		mo2->destscale = mo->scale;
-		P_SetScale(mo2, mo2->destscale);
-		if (mo->eflags & MFE_VERTICALFLIP)
-		{
-			mo2->eflags |= MFE_VERTICALFLIP;
-			mo2->flags2 |= MF2_OBJECTFLIP;
-		}
-		P_InstaThrust(mo2, mo2->angle + ANGLE_90, FixedMul(4*FRACUNIT, mo2->scale));
-		P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-
-		mo2 = P_SpawnMobj(mo->x, mo->y,
-			mo->z + ((mo->eflags & MFE_VERTICALFLIP)? mobjinfo[MT_BOSSSPIGOT].height-FixedMul(32*FRACUNIT,mo->scale): mo->height + FixedMul(32*FRACUNIT, mo->scale)), MT_BOSSSPIGOT);
-		mo2->angle = mo->angle;
-		mo2->destscale = mo->scale;
-		P_SetScale(mo2, mo2->destscale);
-		if (mo->eflags & MFE_VERTICALFLIP)
-		{
-			mo2->eflags |= MFE_VERTICALFLIP;
-			mo2->flags2 |= MF2_OBJECTFLIP;
-		}
-		P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-		return;
-	}
 }
 
 // Function: A_CustomPower
@@ -6358,6 +6466,7 @@ void A_MixUp(mobj_t *actor)
 		INT32 starpostnum;
 		tic_t starposttime;
 		angle_t starpostangle;
+		fixed_t starpostscale;
 
 		INT32 mflags2;
 
@@ -6405,6 +6514,7 @@ void A_MixUp(mobj_t *actor)
 		starposty = players[one].starposty;
 		starpostz = players[one].starpostz;
 		starpostangle = players[one].starpostangle;
+		starpostscale = players[one].starpostscale;
 		starpostnum = players[one].starpostnum;
 		starposttime = players[one].starposttime;
 
@@ -6413,15 +6523,11 @@ void A_MixUp(mobj_t *actor)
 		P_MixUp(players[one].mo, players[two].mo->x, players[two].mo->y, players[two].mo->z, players[two].mo->angle,
 				players[two].starpostx, players[two].starposty, players[two].starpostz,
 				players[two].starpostnum, players[two].starposttime, players[two].starpostangle,
-				players[two].mo->flags2);
-
-		players[one].drawangle = players[two].drawangle;
+				players[two].starpostscale, players[two].drawangle, players[two].mo->flags2);
 
 		P_MixUp(players[two].mo, x, y, z, angle, starpostx, starposty, starpostz,
 				starpostnum, starposttime, starpostangle,
-				mflags2);
-
-		players[two].drawangle = drawangle;
+				starpostscale, drawangle, mflags2);
 
 		//carry set after mixup.  Stupid P_ResetPlayer() takes away some of the stuff we look for...
 		//but not all of it!  So we need to make sure they aren't set wrong or anything.
@@ -6448,6 +6554,7 @@ void A_MixUp(mobj_t *actor)
 		INT32 starpostnum[MAXPLAYERS];
 		tic_t starposttime[MAXPLAYERS];
 		angle_t starpostangle[MAXPLAYERS];
+		fixed_t starpostscale[MAXPLAYERS];
 
 		INT32 flags2[MAXPLAYERS];
 
@@ -6485,6 +6592,7 @@ void A_MixUp(mobj_t *actor)
 				starpostnum[counter] = players[i].starpostnum;
 				starposttime[counter] = players[i].starposttime;
 				starpostangle[counter] = players[i].starpostangle;
+				starpostscale[counter] = players[i].starpostscale;
 
 				flags2[counter] = players[i].mo->flags2;
 
@@ -6525,9 +6633,7 @@ void A_MixUp(mobj_t *actor)
 				P_MixUp(players[i].mo, position[teleportfrom][0], position[teleportfrom][1], position[teleportfrom][2], anglepos[teleportfrom][0],
 					spposition[teleportfrom][0], spposition[teleportfrom][1], spposition[teleportfrom][2],
 					starpostnum[teleportfrom], starposttime[teleportfrom], starpostangle[teleportfrom],
-					flags2[teleportfrom]);
-
-				players[i].drawangle = anglepos[teleportfrom][1];
+					starpostscale[teleportfrom], anglepos[teleportfrom][1], flags2[teleportfrom]);
 
 				//...carry after.  same reasoning.
 				players[i].powers[pw_carry] = transcarry[teleportfrom];
@@ -8702,7 +8808,7 @@ void A_SetObjectFlags2(mobj_t *actor)
 //
 // var1:
 //		0 - Triple jet fume pattern
-//		1 - Boss 3's propeller
+//		1 - Unused (formerly Boss 3's propeller)
 //		2 - Metal Sonic jet fume
 //		3 - Boss 4 jet flame
 // var2 = unused
@@ -8762,7 +8868,7 @@ void A_BossJetFume(mobj_t *actor)
 
 		P_SetTarget(&actor->tracer, filler);
 	}
-	else if (locvar1 == 1) // Boss 3 propeller
+	/*else if (locvar1 == 1) // Boss 3 propeller
 	{
 		fixed_t jetx, jety, jetz;
 
@@ -8782,14 +8888,14 @@ void A_BossJetFume(mobj_t *actor)
 		filler->angle = actor->angle - ANGLE_180;
 
 		P_SetTarget(&actor->tracer, filler);
-	}
+	}*/
 	else if (locvar1 == 2) // Metal Sonic jet fumes
 	{
 		filler = P_SpawnMobj(actor->x, actor->y, actor->z, MT_JETFUME1);
 		P_SetTarget(&filler->target, actor);
 		filler->fuse = 59;
 		P_SetTarget(&actor->tracer, filler);
-		filler->destscale = actor->scale/2;
+		filler->destscale = actor->scale/3;
 		P_SetScale(filler, filler->destscale);
 		if (actor->eflags & MFE_VERTICALFLIP)
 			filler->flags2 |= MF2_OBJECTFLIP;
diff --git a/src/p_inter.c b/src/p_inter.c
index 0030e8e58e5a827ea57efe595680f86e9da9c25d..bfdec3e2338ddfa36233c1b21266be8b715e3984 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1427,6 +1427,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						players[i].starposty = player->mo->y>>FRACBITS;
 						players[i].starpostz = special->z>>FRACBITS;
 						players[i].starpostangle = special->angle;
+						players[i].starpostscale = player->mo->destscale;
+						if (special->flags2 & MF2_OBJECTFLIP)
+						{
+							players[i].starpostscale *= -1;
+							players[i].starpostz += special->height>>FRACBITS;
+						}
 						players[i].starpostnum = special->health;
 
 						if (cv_coopstarposts.value == 2 && (players[i].playerstate == PST_DEAD || players[i].spectator) && P_GetLives(&players[i]))
@@ -1443,6 +1449,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				player->starposty = toucher->y>>FRACBITS;
 				player->starpostz = special->z>>FRACBITS;
 				player->starpostangle = special->angle;
+				player->starpostscale = player->mo->destscale;
+				if (special->flags2 & MF2_OBJECTFLIP)
+				{
+					player->starpostscale *= -1;
+					player->starpostz += special->height>>FRACBITS;
+				}
 				player->starpostnum = special->health;
 				S_StartSound(toucher, special->info->painsound);
 			}
@@ -1583,6 +1595,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			// Buenos Dias Mandy
 			P_SetPlayerMobjState(toucher, S_PLAY_STUN);
 			player->pflags &= ~PF_APPLYAUTOBRAKE;
+			P_ResetPlayer(player);
 			player->drawangle = special->angle + ANGLE_180;
 			P_InstaThrust(toucher, special->angle, FixedMul(3*special->info->speed, special->scale/2));
 			toucher->z += P_MobjFlip(toucher);
@@ -1688,13 +1701,15 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				return;
 			if (mariomode)
 				return;
+			if (special->state-states != S_EXTRALARGEBUBBLE)
+				return; // Don't grab the bubble during its spawn animation
 			else if (toucher->eflags & MFE_VERTICALFLIP)
 			{
-				if (special->z+special->height < toucher->z + toucher->height / 3
-				 || special->z+special->height > toucher->z + (toucher->height*2/3))
+				if (special->z+special->height < toucher->z
+					|| special->z+special->height > toucher->z + (toucher->height*2/3))
 					return; // Only go in the mouth
 			}
-			else if (special->z < toucher->z + toucher->height / 3
+			else if (special->z < toucher->z
 				|| special->z > toucher->z + (toucher->height*2/3))
 				return; // Only go in the mouth
 
@@ -2469,8 +2484,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 				else if (P_IsLocalPlayer(target->player))
 					gameovermus = true;
 
-				if (gameovermus)
-					P_PlayJingle(target->player, JT_GOVER); // Yousa dead now, Okieday? Tails 03-14-2000
+				if (gameovermus) // Yousa dead now, Okieday? Tails 03-14-2000
+					S_ChangeMusicEx("_gover", 0, 0, 0, (2*MUSICRATE) - (MUSICRATE/25), 0); // 1.96 seconds
+					//P_PlayJingle(target->player, JT_GOVER); // can't be used because incompatible with track fadeout
 
 				if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && numgameovers < maxgameovers)
 				{
@@ -2594,6 +2610,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 
 		case MT_EGGMOBILE3:
 			{
+				mobj_t *mo2;
 				thinker_t *th;
 				UINT32 i = 0; // to check how many clones we've removed
 
@@ -2614,6 +2631,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 					mo->scalespeed = (mo->scale - mo->destscale)/(2*TICRATE);
 					mo->momz = mo->info->speed;
 					mo->angle = FixedAngle((P_RandomKey(36)*10)<<FRACBITS);
+
+					mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_BOSSJUNK);
+					mo2->angle = mo->angle;
+					P_SetMobjState(mo2, S_BOSSSEBH2);
+
 					if (++i == 2) // we've already removed 2 of these, let's stop now
 						break;
 					else
@@ -3418,7 +3440,8 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			return false;
 
 		// Make sure that boxes cannot be popped by enemies, red rings, etc.
-		if (target->flags & MF_MONITOR && ((!source || !source->player || source->player->bot) || (inflictor && !inflictor->player)))
+		if (target->flags & MF_MONITOR && ((!source || !source->player || source->player->bot)
+		|| (inflictor && inflictor->type >= MT_REDRING && inflictor->type <= MT_GRENADERING)))
 			return false;
 	}
 
@@ -3500,7 +3523,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			return true;
 		}
 
-		if (G_IsSpecialStage(gamemap))
+		if (G_IsSpecialStage(gamemap) && !(damagetype & DMG_DEATHMASK))
 		{
 			P_SpecialStageDamage(player, inflictor, source);
 			return true;
@@ -3524,10 +3547,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 
 		// Instant-Death
 		if (damagetype & DMG_DEATHMASK)
-		{
 			P_KillPlayer(player, source, damage);
-			player->rings = player->spheres = 0;
-		}
 		else if (metalrecording)
 		{
 			if (!inflictor)
diff --git a/src/p_local.h b/src/p_local.h
index 3a0146e541fc9f3737239df62a3139db3f7a3258..4a58650272b7c8ba20c69ef3db283872c958fa57 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -115,10 +115,10 @@ typedef struct camera_s
 
 extern camera_t camera, camera2;
 extern consvar_t cv_cam_dist, cv_cam_still, cv_cam_height;
-extern consvar_t cv_cam_speed, cv_cam_rotate, cv_cam_rotspeed;
+extern consvar_t cv_cam_speed, cv_cam_rotate, cv_cam_rotspeed, cv_cam_orbit, cv_cam_adjust;
 
 extern consvar_t cv_cam2_dist, cv_cam2_still, cv_cam2_height;
-extern consvar_t cv_cam2_speed, cv_cam2_rotate, cv_cam2_rotspeed;
+extern consvar_t cv_cam2_speed, cv_cam2_rotate, cv_cam2_rotspeed, cv_cam2_orbit, cv_cam2_adjust;
 
 extern fixed_t t_cam_dist, t_cam_height, t_cam_rotate;
 extern fixed_t t_cam2_dist, t_cam2_height, t_cam2_rotate;
@@ -509,7 +509,7 @@ extern INT32 ceilmovesound;
 void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle,
 			INT16 starpostx, INT16 starposty, INT16 starpostz,
 			INT32 starpostnum, tic_t starposttime, angle_t starpostangle,
-			INT32 flags2);
+			fixed_t starpostscale, angle_t drawangle, INT32 flags2);
 boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, boolean flash, boolean dontstopmove);
 boolean P_SetMobjStateNF(mobj_t *mobj, statenum_t state);
 boolean P_CheckMissileSpawn(mobj_t *th);
diff --git a/src/p_map.c b/src/p_map.c
index 15fa97c8f1481423193abbccd011273712331218..01c41131b83df9a8ee268c91f2cd89faae6fd843 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -345,17 +345,14 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		if (horizspeed)
 		{
 			object->player->drawangle = spring->angle;
-			if (vertispeed || (object->player->cmd.forwardmove == 0 && object->player->cmd.sidemove == 0))
-			{
-				object->angle = spring->angle;
+			object->angle = spring->angle;
 
-				if (!demoplayback || P_AnalogMove(object->player))
-				{
-					if (object->player == &players[consoleplayer])
-						localangle = spring->angle;
-					else if (object->player == &players[secondarydisplayplayer])
-						localangle2 = spring->angle;
-				}
+			if (!demoplayback || P_AnalogMove(object->player))
+			{
+				if (object->player == &players[consoleplayer])
+					localangle = spring->angle;
+				else if (object->player == &players[secondarydisplayplayer])
+					localangle2 = spring->angle;
 			}
 		}
 
@@ -1022,7 +1019,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			return true; // underneath
 		if (tmthing->flags & MF_SHOOTABLE && thing->health > 0)
 		{
-			UINT8 damagetype = (thing->info->mass & 0xFF);
+			UINT32 damagetype = (thing->info->mass & 0xFF);
 			if (!damagetype && thing->flags & MF_FIRE) // BURN!
 				damagetype = DMG_FIRE;
 			if (P_DamageMobj(tmthing, thing, thing, 1, damagetype) && (damagetype = (thing->info->mass>>8)))
@@ -1039,7 +1036,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			return true; // underneath
 		if (thing->flags & MF_SHOOTABLE && tmthing->health > 0)
 		{
-			UINT8 damagetype = (tmthing->info->mass & 0xFF);
+			UINT32 damagetype = (tmthing->info->mass & 0xFF);
 			if (!damagetype && tmthing->flags & MF_FIRE) // BURN!
 				damagetype = DMG_FIRE;
 			if (P_DamageMobj(thing, tmthing, tmthing, 1, damagetype) && (damagetype = (tmthing->info->mass>>8)))
@@ -2884,11 +2881,8 @@ static boolean P_ThingHeightClip(mobj_t *thing)
 			thing->z = thing->ceilingz - thing->height;
 	}
 
-	if (thing->z != oldz)
-	{
-		if (thing->player)
-			P_PlayerHitFloor(thing->player, !onfloor);
-	}
+	if (P_MobjFlip(thing)*(thing->z - oldz) > 0 && thing->player)
+		P_PlayerHitFloor(thing->player, !onfloor);
 
 	// debug: be sure it falls to the floor
 	thing->eflags &= ~MFE_ONGROUND;
@@ -3202,7 +3196,7 @@ static boolean P_IsClimbingValid(player_t *player, angle_t angle)
 				&& glidesector->sector->ceilingpic == skyflatnum)
 				return false;
 
-			if ((player->mo->z + FixedMul(16*FRACUNIT,player->mo->scale) < ceilingz)
+			if ((player->mo->z + FixedMul(16*FRACUNIT,player->mo->scale) < floorz)
 				|| (player->mo->z >= ceilingz))
 				floorclimb = true;
 		}
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 1ee90d250acff6f79da69a528b303e25eba03948..5dfb7a5abe5582b05976af039cac9e968525ddb6 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2381,22 +2381,42 @@ boolean P_CheckDeathPitCollide(mobj_t *mo)
 
 boolean P_CheckSolidLava(mobj_t *mo, ffloor_t *rover)
 {
+	fixed_t topheight;
+
 	I_Assert(mo != NULL);
 	I_Assert(!P_MobjWasRemoved(mo));
 
+	// not a lava block with solid planes
+	if (!(rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3
+		&& !(rover->master->flags & ML_BLOCKMONSTERS)))
+		return false;
+
+	// is solid from the sides
+	if (rover->master->flags & ML_EFFECT3)
+		return true;
+
+	if (mo->eflags & MFE_VERTICALFLIP)
 	{
-		fixed_t topheight =
+		topheight =
 	#ifdef ESLOPE
-			*rover->t_slope ? P_GetZAt(*rover->t_slope, mo->x, mo->y) :
+			*rover->b_slope ? P_GetZAt(*rover->b_slope, mo->x, mo->y) :
 	#endif
-			*rover->topheight;
+			*rover->bottomheight;
 
-		if (rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3
-			&& !(rover->master->flags & ML_BLOCKMONSTERS)
-			&& ((rover->master->flags & ML_EFFECT3) || mo->z-mo->momz > topheight - FixedMul(16*FRACUNIT, mo->scale)))
+		if (mo->z+mo->height-mo->momz < topheight + FixedMul(16*FRACUNIT, mo->scale))
 				return true;
+		return false;
 	}
 
+	topheight =
+	#ifdef ESLOPE
+		*rover->t_slope ? P_GetZAt(*rover->t_slope, mo->x, mo->y) :
+	#endif
+		*rover->topheight;
+
+	if (mo->z-mo->momz > topheight - FixedMul(16*FRACUNIT, mo->scale))
+		return true;
+
 	return false;
 }
 
@@ -2954,7 +2974,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 		}
 		// Get up if you fell.
 		if (mo->player->panim == PA_PAIN)
-			P_SetPlayerMobjState(mo, S_PLAY_STND);
+			P_SetPlayerMobjState(mo, S_PLAY_WALK);
 
 #ifdef ESLOPE
 		if (!mo->standingslope && (mo->eflags & MFE_VERTICALFLIP ? tmceilingslope : tmfloorslope)) {
@@ -3760,7 +3780,7 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled
 
 	if (thiscam->momx || thiscam->momy)
 	{
-		if (!P_TryCameraMove(thiscam->x + thiscam->momx, thiscam->y + thiscam->momy, thiscam))
+		if (!P_TryCameraMove(thiscam->x + thiscam->momx, thiscam->y + thiscam->momy, thiscam)) // Thanks for the greatly improved camera, Lach -- Sev
 		{ // Never fails for 2D mode.
 			mobj_t dummy;
 			dummy.thinker.function.acp1 = (actionf_p1)P_MobjThinker;
@@ -3770,9 +3790,22 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled
 			dummy.z = thiscam->z;
 			dummy.height = thiscam->height;
 			if (!resetcalled && !(player->pflags & PF_NOCLIP) && !P_CheckSight(&dummy, player->mo)) // TODO: "P_CheckCameraSight" instead.
+			{
 				P_ResetCamera(player, thiscam);
+				resetcalled = true;
+			}
 			else
+			{
+				fixed_t camspeed = P_AproxDistance(thiscam->momx, thiscam->momy);
+
 				P_SlideCameraMove(thiscam);
+
+				if (!resetcalled && P_AproxDistance(thiscam->momx, thiscam->momy) == camspeed)
+					{
+						P_ResetCamera(player, thiscam);
+						resetcalled = true;
+					}
+			}
 			if (resetcalled) // Okay this means the camera is fully reset.
 				return true;
 		}
@@ -3981,6 +4014,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	}
 	else
 	{
+#if 0 // i don't know why this is here, it's causing a few undesired state glitches, and disabling it doesn't appear to negatively affect the game, but i don't want it gone permanently just in case some obscure bug crops up
 		if (!(mobj->player->powers[pw_carry] == CR_NIGHTSMODE)) // used for drilling
 			mobj->player->pflags &= ~PF_STARTJUMP;
 		mobj->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
@@ -3990,6 +4024,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 			mobj->player->powers[pw_tailsfly] = 0;
 			P_SetPlayerMobjState(mobj, S_PLAY_WALK);
 		}
+#endif
 		mobj->eflags &= ~MFE_JUSTHITFLOOR;
 	}
 
@@ -4359,12 +4394,6 @@ static void P_Boss3Thinker(mobj_t *mobj)
 	if (mobj->flags2 & MF2_FRET)
 		mobj->movedir = 1;
 
-	if (!mobj->tracer)
-	{
-		var1 = 1;
-		A_BossJetFume(mobj);
-	}
-
 	if (mobj->health <= 0)
 		return;
 	/*
@@ -4493,7 +4522,7 @@ static void P_Boss3Thinker(mobj_t *mobj)
 			if (mobj->health <= mobj->info->damage) // pinch phase
 				mobj->movecount--; // limited number of shots before diving again
 			if (mobj->movecount)
-				P_SetMobjState(mobj, mobj->info->missilestate);
+				P_SetMobjState(mobj, mobj->info->missilestate+1);
 		}
 	}
 	else if (mobj->threshold >= 0) // Traveling mode
@@ -4592,6 +4621,15 @@ static void P_Boss3Thinker(mobj_t *mobj)
 					ang += (ANGLE_MAX/64);
 				}
 				S_StartSound(mobj, sfx_fizzle);
+
+				// look for a new target
+				P_BossTargetPlayer(mobj, false);
+
+				if (mobj->target && mobj->target->player)
+				{
+					A_FaceTarget(mobj);
+					P_SetMobjState(mobj, mobj->info->missilestate);
+				}
 			}
 			else if (mobj->flags2 & (MF2_STRONGBOX|MF2_CLASSICPUSH)) // just hit the bottom of your tube
 			{
@@ -5527,8 +5565,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 				mobj->tracer->destscale = FRACUNIT + (4*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2);
 				P_SetScale(mobj->tracer, mobj->tracer->destscale);
 			}
-			else
-				mobj->tracer->frame &= ~FF_TRANSMASK; // this causes a flicker but honestly i like it this way
+
 			P_TeleportMove(mobj->tracer, mobj->x, mobj->y, mobj->z + mobj->height/2 - mobj->tracer->height/2);
 			mobj->tracer->momx = mobj->momx;
 			mobj->tracer->momy = mobj->momy;
@@ -5645,12 +5682,12 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 				if (mobj->health > mobj->info->damage)
 				{
-					P_SetScale(missile, FRACUNIT/2);
+					P_SetScale(missile, FRACUNIT/3);
 					missile->color = SKINCOLOR_GOLD; // sonic cd electric power
 				}
 				else
 				{
-					P_SetScale(missile, FRACUNIT/4);
+					P_SetScale(missile, FRACUNIT/5);
 					missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power
 				}
 				missile->destscale = missile->scale*2;
@@ -5940,9 +5977,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 					P_SetTarget(&mobj->tracer, shield);
 					P_SetTarget(&shield->target, mobj);
 					shield->height -= 20*FRACUNIT; // different offset...
-					shield->color = SKINCOLOR_MAGENTA;
-					shield->colorized = true;
-					P_SetMobjState(shield, S_FIRS1);
+					P_SetMobjState(shield, S_MSSHIELD_F2);
 					//P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); -- why does this happen twice? see case 2...
 				}
 				mobj->fuse = 4*TICRATE;
@@ -7093,9 +7128,7 @@ void P_MobjThinker(mobj_t *mobj)
 
 		switch (mobj->type)
 		{
-			case MT_BOSSTANK1:
-			case MT_BOSSTANK2:
-			case MT_BOSSSPIGOT:
+			case MT_BOSSJUNK:
 				mobj->flags2 ^= MF2_DONTDRAW;
 				break;
 			case MT_MACEPOINT:
@@ -7681,12 +7714,22 @@ void P_MobjThinker(mobj_t *mobj)
 		switch (mobj->type)
 		{
 			case MT_EGGMOBILE:
-				if (mobj->health < mobj->info->damage+1 && leveltime & 1 && mobj->health > 0)
-					P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SMOKE);
+				if (mobj->health < mobj->info->damage+1 && leveltime & 2)
+				{
+					fixed_t rad = mobj->radius>>FRACBITS;
+					fixed_t hei = mobj->height>>FRACBITS;
+					mobj_t *particle = P_SpawnMobjFromMobj(mobj,
+						P_RandomRange(rad, -rad)<<FRACBITS,
+						P_RandomRange(rad, -rad)<<FRACBITS,
+						P_RandomRange(hei/2, hei)<<FRACBITS,
+						MT_SMOKE);
+					P_SetObjectMomZ(particle, 2<<FRACBITS, false);
+					particle->momz += mobj->momz;
+				}
 				if (mobj->flags2 & MF2_SKULLFLY)
 #if 1
 					P_SpawnGhostMobj(mobj);
-#else
+#else // all the way back from final demo... MT_THOK isn't even the same size anymore!
 				{
 					mobj_t *spawnmobj;
 					spawnmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->painchance);
@@ -7697,12 +7740,48 @@ void P_MobjThinker(mobj_t *mobj)
 				P_Boss1Thinker(mobj);
 				break;
 			case MT_EGGMOBILE2:
+				if (mobj->health < mobj->info->damage+1 && leveltime & 2)
+				{
+					fixed_t rad = mobj->radius>>FRACBITS;
+					fixed_t hei = mobj->height>>FRACBITS;
+					mobj_t *particle = P_SpawnMobjFromMobj(mobj,
+						P_RandomRange(rad, -rad)<<FRACBITS,
+						P_RandomRange(rad, -rad)<<FRACBITS,
+						P_RandomRange(hei/2, hei)<<FRACBITS,
+						MT_SMOKE);
+					P_SetObjectMomZ(particle, 2<<FRACBITS, false);
+					particle->momz += mobj->momz;
+				}
 				P_Boss2Thinker(mobj);
 				break;
 			case MT_EGGMOBILE3:
+				if (mobj->health < mobj->info->damage+1 && leveltime & 2)
+				{
+					fixed_t rad = mobj->radius>>FRACBITS;
+					fixed_t hei = mobj->height>>FRACBITS;
+					mobj_t *particle = P_SpawnMobjFromMobj(mobj,
+						P_RandomRange(rad, -rad)<<FRACBITS,
+						P_RandomRange(rad, -rad)<<FRACBITS,
+						P_RandomRange(hei/2, hei)<<FRACBITS,
+						MT_SMOKE);
+					P_SetObjectMomZ(particle, 2<<FRACBITS, false);
+					particle->momz += mobj->momz;
+				}
 				P_Boss3Thinker(mobj);
 				break;
 			case MT_EGGMOBILE4:
+				if (mobj->health < mobj->info->damage+1 && leveltime & 2)
+				{
+					fixed_t rad = mobj->radius>>FRACBITS;
+					fixed_t hei = mobj->height>>FRACBITS;
+					mobj_t *particle = P_SpawnMobjFromMobj(mobj,
+						P_RandomRange(rad, -rad)<<FRACBITS,
+						P_RandomRange(rad, -rad)<<FRACBITS,
+						P_RandomRange(hei/2, hei)<<FRACBITS,
+						MT_SMOKE);
+					P_SetObjectMomZ(particle, 2<<FRACBITS, false);
+					particle->momz += mobj->momz;
+				}
 				P_Boss4Thinker(mobj);
 				break;
 			case MT_FANG:
@@ -8318,30 +8397,6 @@ void P_MobjThinker(mobj_t *mobj)
 					mobj->fuse++;
 				}
 				break;
-			case MT_PROPELLER:
-				{
-					fixed_t jetx, jety;
-
-					if (!mobj->target // if you have no target
-					|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
-					{ // then remove yourself as well!
-						P_RemoveMobj(mobj);
-						return;
-					}
-
-					jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale));
-					jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale));
-
-					P_UnsetThingPosition(mobj);
-					mobj->x = jetx;
-					mobj->y = jety;
-					mobj->z = mobj->target->z + FixedMul(17*FRACUNIT, mobj->target->scale);
-					mobj->angle = mobj->target->angle - ANGLE_180;
-					mobj->floorz = mobj->z;
-					mobj->ceilingz = mobj->z+mobj->height;
-					P_SetThingPosition(mobj);
-				}
-				break;
 			case MT_JETFLAME:
 				{
 					if (!mobj->target // if you have no target
@@ -8405,6 +8460,17 @@ void P_MobjThinker(mobj_t *mobj)
 					}
 					else
 					{
+
+						fixed_t basex = mobj->cusval, basey = mobj->cvmem;
+
+						if (mobj->spawnpoint && mobj->spawnpoint->options & (MTF_AMBUSH|MTF_OBJECTSPECIAL))
+						{
+							angle_t sideang = mobj->movedir + ((mobj->spawnpoint->options & MTF_AMBUSH) ? ANGLE_90 : -ANGLE_90);
+							fixed_t oscillate = FixedMul(FINESINE(((leveltime*ANG1)>>(ANGLETOFINESHIFT+2)) & FINEMASK), 250*mobj->scale);
+							basex += P_ReturnThrustX(mobj, sideang, oscillate);
+							basey += P_ReturnThrustY(mobj, sideang, oscillate);
+						}
+
 						mobj->z = mobj->threshold + FixedMul(FINESINE(((leveltime + mobj->movecount)*ANG2>>(ANGLETOFINESHIFT-2)) & FINEMASK), 8*mobj->scale);
 						if (mobj->state != &states[mobj->info->meleestate])
 						{
@@ -8433,8 +8499,8 @@ void P_MobjThinker(mobj_t *mobj)
 									if (players[i].mo->z + players[i].mo->height < mobj->z - 8*mobj->scale)
 										continue;
 									compdist = P_AproxDistance(
-										players[i].mo->x + players[i].mo->momx - mobj->cusval,
-										players[i].mo->y + players[i].mo->momy - mobj->cvmem);
+										players[i].mo->x + players[i].mo->momx - basex,
+										players[i].mo->y + players[i].mo->momy - basey);
 									if (compdist >= dist)
 										continue;
 									dist = compdist;
@@ -8448,14 +8514,14 @@ void P_MobjThinker(mobj_t *mobj)
 									mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
 
 									if (P_AproxDistance(
-										mobj->x - mobj->cusval,
-										mobj->y - mobj->cvmem)
+										mobj->x - basex,
+										mobj->y - basey)
 										< mobj->scale)
 										S_StartSound(mobj, mobj->info->seesound);
 
 									P_TeleportMove(mobj,
-										(15*(mobj->x>>4)) + (mobj->cusval>>4) + P_ReturnThrustX(mobj, mobj->angle, SPECTATORRADIUS>>4),
-										(15*(mobj->y>>4)) + (mobj->cvmem>>4) + P_ReturnThrustY(mobj, mobj->angle, SPECTATORRADIUS>>4),
+										(15*(mobj->x>>4)) + (basex>>4) + P_ReturnThrustX(mobj, mobj->angle, SPECTATORRADIUS>>4),
+										(15*(mobj->y>>4)) + (basey>>4) + P_ReturnThrustY(mobj, mobj->angle, SPECTATORRADIUS>>4),
 										mobj->z);
 								}
 								else
@@ -8478,18 +8544,12 @@ void P_MobjThinker(mobj_t *mobj)
 
 							if (!didmove)
 							{
-								if (P_AproxDistance(
-										mobj->x - mobj->cusval,
-										mobj->y - mobj->cvmem)
-										< mobj->scale)
-									P_TeleportMove(mobj,
-										mobj->cusval,
-										mobj->cvmem,
-										mobj->z);
+								if (P_AproxDistance(mobj->x - basex, mobj->y - basey) < mobj->scale)
+									P_TeleportMove(mobj, basex, basey, mobj->z);
 								else
 									P_TeleportMove(mobj,
-										(15*(mobj->x>>4)) + (mobj->cusval>>4),
-										(15*(mobj->y>>4)) + (mobj->cvmem>>4),
+										(15*(mobj->x>>4)) + (basex>>4),
+										(15*(mobj->y>>4)) + (basey>>4),
 										mobj->z);
 							}
 						}
@@ -9038,9 +9098,9 @@ void P_MobjThinker(mobj_t *mobj)
 	{
 		if (mobj->state->action.acp1 == (actionf_p1)A_Boss1Laser)
 		{
-			var1 = mobj->state->var1;
-			var2 = mobj->state->var2;
-			mobj->state->action.acp1(mobj);
+			/*var1 = mobj->state->var1;
+			var2 = mobj->state->var2 & 65535;
+			mobj->state->action.acp1(mobj);*/
 		}
 		else if (leveltime & 1) // Fire mode
 		{
@@ -10387,7 +10447,7 @@ void P_SpawnPlayer(INT32 playernum)
 	mobj_t *mobj;
 
 	if (p->playerstate == PST_REBORN)
-		G_PlayerReborn(playernum);
+		G_PlayerReborn(playernum, false);
 
 	// spawn as spectator determination
 	if (!G_GametypeHasSpectators())
@@ -10395,9 +10455,9 @@ void P_SpawnPlayer(INT32 playernum)
 		p->spectator = p->outofcoop =
 		(((multiplayer || netgame) && gametype == GT_COOP) // only question status in coop
 		&& ((leveltime > 0
-		&& ((G_IsSpecialStage(gamemap) && (maptol & TOL_NIGHTS)) // late join special stage
+		&& ((G_IsSpecialStage(gamemap)) // late join special stage
 		|| (cv_coopstarposts.value == 2 && (p->jointime < 1 || p->outofcoop)))) // late join or die in new coop
-		|| (((cv_cooplives.value == 1) || !P_GetLives(p)) && p->lives <= 0))); // game over and can't redistribute lives
+		|| (!P_GetLives(p) && p->lives <= 0))); // game over and can't redistribute lives
 	}
 	else
 	{
@@ -10464,7 +10524,6 @@ void P_SpawnPlayer(INT32 playernum)
 	P_SetupStateAnimation(mobj, mobj->state);
 
 	mobj->health = 1;
-	p->rings = p->spheres = 0;
 	p->playerstate = PST_LIVE;
 
 	p->bonustime = false;
@@ -10521,10 +10580,6 @@ void P_AfterPlayerSpawn(INT32 playernum)
 	else
 		p->viewz = p->mo->z + p->viewheight;
 
-	if (p->powers[pw_carry] != CR_NIGHTSMODE)
-		P_SetPlayerMobjState(p->mo, S_PLAY_STND);
-	p->pflags &= ~PF_SPINNING;
-
 	if (playernum == consoleplayer)
 	{
 		// wake up the status bar
@@ -10609,6 +10664,8 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
 			mobj->eflags |= MFE_VERTICALFLIP;
 			mobj->flags2 |= MF2_OBJECTFLIP;
 		}
+		if (mthing->options & MTF_AMBUSH)
+			P_SetPlayerMobjState(mobj, S_PLAY_FALL);
 	}
 	else
 		z = floor;
@@ -10627,7 +10684,12 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
 	P_SetThingPosition(mobj);
 
 	mobj->z = z;
-	if (mobj->z == mobj->floorz)
+	if (mobj->flags2 & MF2_OBJECTFLIP)
+	{
+		if (mobj->z + mobj->height == mobj->ceilingz)
+			mobj->eflags |= MFE_ONGROUND;
+	}
+	else if (mobj->z == mobj->floorz)
 		mobj->eflags |= MFE_ONGROUND;
 
 	mobj->angle = angle;
@@ -10663,17 +10725,29 @@ void P_MovePlayerToStarpost(INT32 playernum)
 	sector->ceilingheight;
 
 	z = p->starpostz << FRACBITS;
-	if (z < floor)
+
+	P_SetScale(mobj, (mobj->destscale = abs(p->starpostscale)));
+
+	if (p->starpostscale < 0)
+	{
+		mobj->flags2 |= MF2_OBJECTFLIP;
+		if (z >= ceiling)
+		{
+			mobj->eflags |= MFE_ONGROUND;
+			z = ceiling;
+		}
+		z -= mobj->height;
+	}
+	else if (z <= floor)
+	{
+		mobj->eflags |= MFE_ONGROUND;
 		z = floor;
-	else if (z > ceiling - mobjinfo[MT_PLAYER].height)
-		z = ceiling - mobjinfo[MT_PLAYER].height;
+	}
 
 	mobj->floorz = floor;
 	mobj->ceilingz = ceiling;
 
 	mobj->z = z;
-	if (mobj->z == mobj->floorz)
-		mobj->eflags |= MFE_ONGROUND;
 
 	mobj->angle = p->starpostangle;
 
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 7c073b1514762b74ab6d352d2cdbebb755670ff1..69c942236df283f5825f2ce8a1f8b081daa12d61 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -189,6 +189,7 @@ static void P_NetArchivePlayers(void)
 		WRITEINT16(save_p, players[i].starpostz);
 		WRITEINT32(save_p, players[i].starpostnum);
 		WRITEANGLE(save_p, players[i].starpostangle);
+		WRITEFIXED(save_p, players[i].starpostscale);
 
 		WRITEANGLE(save_p, players[i].angle_pos);
 		WRITEANGLE(save_p, players[i].old_angle_pos);
@@ -397,6 +398,7 @@ static void P_NetUnArchivePlayers(void)
 		players[i].starpostz = READINT16(save_p);
 		players[i].starpostnum = READINT32(save_p);
 		players[i].starpostangle = READANGLE(save_p);
+		players[i].starpostscale = READFIXED(save_p);
 
 		players[i].angle_pos = READANGLE(save_p);
 		players[i].old_angle_pos = READANGLE(save_p);
@@ -4117,12 +4119,54 @@ static inline boolean P_NetUnArchiveMisc(void)
 	return true;
 }
 
+static inline void P_ArchiveLuabanksAndConsistency(void)
+{
+	UINT8 i, banksinuse = NUM_LUABANKS;
+
+	while (banksinuse && !luabanks[banksinuse-1])
+		banksinuse--; // get the last used bank
+
+	if (banksinuse)
+	{
+		WRITEUINT8(save_p, 0xb7); // luabanks marker
+		WRITEUINT8(save_p, banksinuse);
+		for (i = 0; i < banksinuse; i++)
+			WRITEINT32(save_p, luabanks[i]);
+	}
+
+	WRITEUINT8(save_p, 0x1d); // consistency marker
+}
+
+static inline boolean P_UnArchiveLuabanksAndConsistency(void)
+{
+	switch (READUINT8(save_p))
+	{
+		case 0xb7:
+			{
+				UINT8 i, banksinuse = READUINT8(save_p);
+				if (banksinuse > NUM_LUABANKS)
+					return false;
+				for (i = 0; i < banksinuse; i++)
+					luabanks[i] = READINT32(save_p);
+				if (READUINT8(save_p) != 0x1d)
+					return false;
+			}
+		case 0x1d:
+			break;
+		default:
+			return false;
+	}
+
+	return true;
+}
+
 void P_SaveGame(void)
 {
 	P_ArchiveMisc();
 	P_ArchivePlayer();
 
-	WRITEUINT8(save_p, 0x1d); // consistency marker
+	// yes, even in non HAVE_BLUA
+	P_ArchiveLuabanksAndConsistency();
 }
 
 void P_SaveNetGame(void)
@@ -4161,7 +4205,7 @@ void P_SaveNetGame(void)
 	LUA_Archive();
 #endif
 
-	WRITEUINT8(save_p, 0x1d); // consistency marker
+	P_ArchiveLuabanksAndConsistency();
 }
 
 boolean P_LoadGame(INT16 mapoverride)
@@ -4173,8 +4217,7 @@ boolean P_LoadGame(INT16 mapoverride)
 	P_UnArchiveSPGame(mapoverride);
 	P_UnArchivePlayer();
 
-	// Savegame end marker
-	if (READUINT8(save_p) != 0x1d)
+	if (!P_UnArchiveLuabanksAndConsistency())
 		return false;
 
 	// Only do this after confirming savegame is ok
@@ -4215,5 +4258,5 @@ boolean P_LoadNetGame(void)
 	// precipitation when loading a netgame save. Instead, precip has to be spawned here.
 	// This is done in P_NetUnArchiveSpecials now.
 
-	return READUINT8(save_p) == 0x1d;
+	return P_UnArchiveLuabanksAndConsistency();
 }
diff --git a/src/p_setup.c b/src/p_setup.c
index d0cd14b22d3011a64e26a23e44be00739420adeb..c83c8cd5cefaefecbb763c15f7a5742d64ea8cf5 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -221,6 +221,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->muspostbosstrack = 0;
 	mapheaderinfo[num]->muspostbosspos = 0;
 	mapheaderinfo[num]->muspostbossfadein = 0;
+	mapheaderinfo[num]->musforcereset = -1;
 	mapheaderinfo[num]->forcecharacter[0] = '\0';
 	mapheaderinfo[num]->weather = 0;
 	mapheaderinfo[num]->skynum = 1;
@@ -573,6 +574,11 @@ INT32 P_AddLevelFlat(const char *flatname, levelflat_t *levelflat)
 
 		// store the flat lump number
 		levelflat->lumpnum = R_GetFlatNumForName(flatname);
+		levelflat->texturenum = R_CheckTextureNumForName(flatname);
+		levelflat->lasttexturenum = levelflat->texturenum;
+
+		levelflat->baselumpnum = LUMPERROR;
+		levelflat->basetexturenum = -1;
 
 #ifndef ZDEBUG
 		CONS_Debug(DBG_SETUP, "flat #%03d: %s\n", atoi(sizeu1(numlevelflats)), levelflat->name);
@@ -617,6 +623,11 @@ INT32 P_AddLevelFlatRuntime(const char *flatname)
 
 		// store the flat lump number
 		levelflat->lumpnum = R_GetFlatNumForName(flatname);
+		levelflat->texturenum = R_CheckTextureNumForName(flatname);
+		levelflat->lasttexturenum = levelflat->texturenum;
+
+		levelflat->baselumpnum = LUMPERROR;
+		levelflat->basetexturenum = -1;
 
 #ifndef ZDEBUG
 		CONS_Debug(DBG_SETUP, "flat #%03d: %s\n", atoi(sizeu1(numlevelflats)), levelflat->name);
@@ -1480,6 +1491,7 @@ static void P_LoadRawSideDefs2(void *data)
 			case 425: // Calls P_SetMobjState on calling mobj
 			case 434: // Custom Power
 			case 442: // Calls P_SetMobjState on mobjs of a given type in the tagged sectors
+			case 461: // Spawns an object on the map based on texture offsets
 			{
 				char process[8*3+1];
 				memset(process,0,8*3+1);
@@ -2234,6 +2246,8 @@ static void P_LevelInitStuff(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
+		G_PlayerReborn(i, true);
+
 		if (canresetlives && (netgame || multiplayer) && playeringame[i] && (gametype == GT_COMPETITION || players[i].lives <= 0))
 		{
 			// In Co-Op, replenish a user's lives if they are depleted.
@@ -2241,41 +2255,18 @@ static void P_LevelInitStuff(void)
 		}
 
 		// obliteration station...
-		players[i].rings = players[i].spheres =\
-		 players[i].xtralife = players[i].deadtimer =\
-		 players[i].numboxes = players[i].totalring =\
-		 players[i].laps = players[i].aiming =\
-		 players[i].losstime = players[i].timeshit =\
-		 players[i].marescore = players[i].lastmarescore =\
-		 players[i].maxlink = players[i].startedtime =\
-		 players[i].finishedtime = players[i].finishedspheres =\
-		 players[i].finishedrings = players[i].lastmare =\
-		 players[i].lastmarelap = players[i].lastmarebonuslap =\
-		 players[i].totalmarelap = players[i].totalmarebonuslap =\
-		 players[i].marebegunat = players[i].textvar =\
-		 players[i].texttimer = players[i].linkcount =\
-		 players[i].linktimer = players[i].flyangle =\
-		 players[i].anotherflyangle = players[i].nightstime =\
-		 players[i].oldscale = players[i].mare = players[i].marelap =\
-		 players[i].marebonuslap = players[i].lapbegunat =\
-		 players[i].lapstartedtime = players[i].totalmarescore =\
-		 players[i].realtime = players[i].exiting = 0;
-
-		// i guess this could be part of the above but i feel mildly uncomfortable implicitly casting
-		players[i].gotcontinue = false;
-
-		// aha, the first evidence this shouldn't be a memset!
+		players[i].numboxes = players[i].totalring =\
+		 players[i].laps = players[i].marescore = players[i].lastmarescore =\
+		 players[i].mare = players[i].exiting = 0;
+
 		players[i].drillmeter = 40*20;
 
-		P_ResetPlayer(&players[i]);
 		// hit these too
-		players[i].pflags &= ~(PF_GAMETYPEOVER|PF_TRANSFERTOCLOSEST);
-
-		// unset ALL the pointers. P_SetTarget isn't needed here because if this
-		// function is being called we're just going to clobber the data anyways
-		players[i].mo = players[i].followmobj = players[i].awayviewmobj =\
-		players[i].capsule = players[i].axis1 = players[i].axis2 = players[i].drone = NULL;
+		players[i].pflags &= ~(PF_GAMETYPEOVER);
 	}
+
+	if (botingame)
+		CV_SetValue(&cv_analog2, true);
 }
 
 //
@@ -2692,7 +2683,7 @@ boolean P_SetupLevel(boolean skipprecip)
 		S_StartSound(NULL, sfx_s3kaf);
 
 		// Fade music! Time it to S3KAF: 0.25 seconds is snappy.
-		if (cv_resetmusic.value ||
+		if (RESETMUSIC ||
 			strnicmp(S_MusicName(),
 				(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7))
 			S_FadeOutStopMusic(MUSICRATE/4); //FixedMul(FixedDiv(F_GetWipeLength(wipedefs[wipe_speclevel_towhite])*NEWTICRATERATIO, NEWTICRATE), MUSICRATE)
@@ -2725,7 +2716,7 @@ boolean P_SetupLevel(boolean skipprecip)
 
 	// Fade out music here. Deduct 2 tics so the fade volume actually reaches 0.
 	// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
-	if (!titlemapinaction && (cv_resetmusic.value ||
+	if (!titlemapinaction && (RESETMUSIC ||
 		strnicmp(S_MusicName(),
 			(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7)))
 		S_FadeMusic(0, FixedMul(
diff --git a/src/p_setup.h b/src/p_setup.h
index 7e8a5d7e6bd31185d48c936eb95e7b33cf847413..7e3a149eb882965e436dbc4ff4327d0fcefa8a00 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -37,12 +37,19 @@ typedef struct
 {
 	char name[9]; // resource name from wad
 	lumpnum_t lumpnum; // lump number of the flat
+	INT32 texturenum, lasttexturenum; // texture number of the flat
+	UINT16 width, height;
+	fixed_t topoffset, leftoffset;
 
 	// for flat animation
 	lumpnum_t baselumpnum;
+	INT32 basetexturenum;
 	INT32 animseq; // start pos. in the anim sequence
 	INT32 numpics;
 	INT32 speed;
+
+	// for patchflats
+	UINT8 *flatpatch;
 } levelflat_t;
 
 extern size_t numlevelflats;
diff --git a/src/p_spec.c b/src/p_spec.c
index 37a1652f0d77cc7f8b262d80769cd998cc241657..256ca3453f0017311392d0b7d7d3043f7fe576a6 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -205,8 +205,8 @@ void P_InitPicAnims(void)
 			if ((W_CheckNumForName(animdefs[i].startname)) == LUMPERROR)
 				continue;
 
-			lastanim->picnum = R_FlatNumForName(animdefs[i].endname);
-			lastanim->basepic = R_FlatNumForName(animdefs[i].startname);
+			lastanim->picnum = R_GetFlatNumForName(animdefs[i].endname);
+			lastanim->basepic = R_GetFlatNumForName(animdefs[i].startname);
 		}
 
 		lastanim->istexture = animdefs[i].istexture;
@@ -464,7 +464,19 @@ static inline void P_FindAnimatedFlat(INT32 animnum)
 	for (i = 0; i < numlevelflats; i++, foundflats++)
 	{
 		// is that levelflat from the flat anim sequence ?
-		if (foundflats->lumpnum >= startflatnum && foundflats->lumpnum <= endflatnum)
+		if ((anims[animnum].istexture) && (foundflats->texturenum != 0 && foundflats->texturenum != -1)
+			&& ((UINT16)foundflats->texturenum >= startflatnum && (UINT16)foundflats->texturenum <= endflatnum))
+		{
+			foundflats->basetexturenum = startflatnum;
+			foundflats->animseq = foundflats->texturenum - startflatnum;
+			foundflats->numpics = endflatnum - startflatnum + 1;
+			foundflats->speed = anims[animnum].speed;
+
+			CONS_Debug(DBG_SETUP, "animflat: #%03d name:%.8s animseq:%d numpics:%d speed:%d\n",
+					atoi(sizeu1(i)), foundflats->name, foundflats->animseq,
+					foundflats->numpics,foundflats->speed);
+		}
+		else if (foundflats->lumpnum >= startflatnum && foundflats->lumpnum <= endflatnum)
 		{
 			foundflats->baselumpnum = startflatnum;
 			foundflats->animseq = foundflats->lumpnum - startflatnum;
@@ -488,10 +500,7 @@ void P_SetupLevelFlatAnims(void)
 
 	// the original game flat anim sequences
 	for (i = 0; anims[i].istexture != -1; i++)
-	{
-		if (!anims[i].istexture)
-			P_FindAnimatedFlat(i);
-	}
+		P_FindAnimatedFlat(i);
 }
 
 //
@@ -3953,6 +3962,39 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
+		case 461: // Spawns an object on the map based on texture offsets
+			{
+				const mobjtype_t type = (mobjtype_t)(sides[line->sidenum[0]].toptexture);
+				mobj_t *mobj;
+
+				fixed_t x, y, z;
+				x = sides[line->sidenum[0]].textureoffset;
+				y = sides[line->sidenum[0]].rowoffset;
+				z = line->frontsector->floorheight;
+
+				if (line->flags & ML_NOCLIMB) // If noclimb is set, spawn randomly within a range
+				{
+					if (line->sidenum[1] != 0xffff) // Make sure the linedef has a back side
+					{
+						x = P_RandomRange(sides[line->sidenum[0]].textureoffset>>FRACBITS, sides[line->sidenum[1]].textureoffset>>FRACBITS)<<FRACBITS;
+						y = P_RandomRange(sides[line->sidenum[0]].rowoffset>>FRACBITS, sides[line->sidenum[1]].rowoffset>>FRACBITS)<<FRACBITS;
+						z = P_RandomRange(line->frontsector->floorheight>>FRACBITS, line->frontsector->ceilingheight>>FRACBITS)<<FRACBITS;
+					}
+					else
+					{
+						CONS_Alert(CONS_WARNING,"Linedef Type %d - Spawn Object: Linedef is set for random range but has no back side.\n", line->special);
+						break;
+					}
+				}
+
+				mobj = P_SpawnMobj(x, y, z, type);
+				if (mobj)
+					CONS_Debug(DBG_GAMELOGIC, "Linedef Type %d - Spawn Object: %d spawned at (%d, %d, %d)\n", line->special, mobj->type, mobj->x>>FRACBITS, mobj->y>>FRACBITS, mobj->z>>FRACBITS); //TODO: Convert mobj->type to a string somehow.
+				else
+					CONS_Alert(CONS_ERROR,"Linedef Type %d - Spawn Object: Object did not spawn!\n", line->special);
+			}
+			break;
+
 #ifdef POLYOBJECTS
 		case 480: // Polyobj_DoorSlide
 		case 481: // Polyobj_DoorSwing
@@ -4942,7 +4984,7 @@ DoneSection2:
 						CONS_Printf(M_GetText("%s started lap %u\n"), player_names[player-players], (UINT32)player->laps+1);
 
 					// Reset starposts (checkpoints) info
-					player->starpostangle = player->starposttime = player->starpostnum = 0;
+					player->starpostscale = player->starpostangle = player->starposttime = player->starpostnum = 0;
 					player->starpostx = player->starposty = player->starpostz = 0;
 					P_ResetStarposts();
 
@@ -5636,9 +5678,12 @@ void P_UpdateSpecials(void)
 	{
 		if (foundflats->speed) // it is an animated flat
 		{
+			// update the levelflat texture number
+			if (foundflats->basetexturenum != -1)
+				foundflats->texturenum = foundflats->basetexturenum + ((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
 			// update the levelflat lump number
-			foundflats->lumpnum = foundflats->baselumpnum +
-				((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
+			else if (foundflats->baselumpnum != LUMPERROR)
+				foundflats->lumpnum = foundflats->baselumpnum + ((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
 		}
 	}
 }
diff --git a/src/p_telept.c b/src/p_telept.c
index e80dd04284778301e8210e45349be8d66a2e8a3d..632b81e0488bc3ebeb1306365f2f7e2a476e248f 100644
--- a/src/p_telept.c
+++ b/src/p_telept.c
@@ -33,7 +33,7 @@
 void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle,
 			INT16 starpostx, INT16 starposty, INT16 starpostz,
 			INT32 starpostnum, tic_t starposttime, angle_t starpostangle,
-			INT32 flags2)
+			fixed_t starpostscale, angle_t drawangle, INT32 flags2)
 {
 	const INT32 takeflags2 = MF2_TWOD|MF2_OBJECTFLIP;
 
@@ -89,8 +89,11 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle,
 		thing->player->starpostz = starpostz;
 		thing->player->starposttime = starposttime;
 		thing->player->starpostangle = starpostangle;
+		thing->player->starpostscale = starpostscale;
 		thing->player->starpostnum = starpostnum;
 
+		thing->player->drawangle = drawangle;
+
 		// Reset map starposts for the player's new info.
 		P_ResetStarposts();
 		P_ClearStarPost(starpostnum);
diff --git a/src/p_user.c b/src/p_user.c
index 58ba00fc94390314fa95213ef7bf96b45fd98d7d..88751c1430c247c12783a0957539c5ad9b686e4c 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1109,7 +1109,7 @@ boolean P_PlayerCanDamage(player_t *player, mobj_t *thing)
 	}
 	else if (P_MobjFlip(player->mo)*(topheight - (thing->z + thing->height/2)) < 0)
 	{
-		if (player->charability == CA_FLY && player->panim == PA_ABILITY && (P_MobjFlip(player->mo)*(player->mo->momz - thing->momz) > 0))
+		if (player->charability == CA_FLY && player->panim == PA_ABILITY && !(player->mo->eflags & MFE_UNDERWATER) && (P_MobjFlip(player->mo)*(player->mo->momz - thing->momz) > 0))
 			return true;
 	}
 
@@ -2217,148 +2217,153 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				player->pflags &= ~PF_SPINNING;
 		}
 
-		if (player->pflags & PF_SPINNING)
+		if (player->pflags & PF_BOUNCING)
 		{
-			if (player->mo->state-states != S_PLAY_ROLL && !(player->pflags & PF_STARTDASH))
+			if (dorollstuff && player->mo->state-states != S_PLAY_BOUNCE_LANDING)
 			{
-				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-				S_StartSound(player->mo, sfx_spin);
+				P_MobjCheckWater(player->mo);
+				player->mo->momz *= -1;
+				P_DoAbilityBounce(player, true);
+				if (player->scoreadd)
+					player->scoreadd--;
 			}
+			clipmomz = false;
 		}
-		else if (player->pflags & PF_GLIDING) // ground gliding
+		else
 		{
-			if (dorollstuff)
+			if (player->pflags & PF_SPINNING)
 			{
-				player->skidtime = TICRATE;
-				player->mo->tics = -1;
+				if (player->mo->state-states != S_PLAY_ROLL && !(player->pflags & PF_STARTDASH))
+				{
+					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+					S_StartSound(player->mo, sfx_spin);
+				}
 			}
-			else if (!player->skidtime)
-				player->pflags &= ~PF_GLIDING;
-		}
-		else if (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
-		{
-			if (player->mo->state-states != S_PLAY_MELEE_LANDING)
+			else if (player->pflags & PF_GLIDING) // ground gliding
 			{
-				mobjtype_t type = player->revitem;
-				P_SetPlayerMobjState(player->mo, S_PLAY_MELEE_LANDING);
-				player->mo->tics = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
-				S_StartSound(player->mo, sfx_s3k8b);
-				player->pflags |= PF_FULLSTASIS;
-
-				// hearticles
-				if (type)
+				if (dorollstuff)
 				{
-					UINT8 i = 0;
-					angle_t throwang = -(2*ANG30);
-					fixed_t xo = P_ReturnThrustX(player->mo, player->drawangle, 16*player->mo->scale);
-					fixed_t yo = P_ReturnThrustY(player->mo, player->drawangle, 16*player->mo->scale);
-					fixed_t zo = 6*player->mo->scale;
-					fixed_t mu = FixedMul(player->maxdash, player->mo->scale);
-					fixed_t mu2 = FixedHypot(player->mo->momx, player->mo->momy);
-					fixed_t ev;
-					mobj_t *missile = NULL;
-					if (mu2 < mu)
-						mu2 = mu;
-					ev = (50*FRACUNIT - (mu/25))/50;
-					while (i < 5)
-					{
-						missile = P_SpawnMobjFromMobj(player->mo, xo, yo, zo, type);
-						P_SetTarget(&missile->target, player->mo);
-						missile->angle = throwang + player->drawangle;
-						P_Thrust(missile, player->drawangle + ANGLE_90,
-							P_ReturnThrustY(missile, throwang, mu)); // side to side component
-						P_Thrust(missile, player->drawangle, mu2); // forward component
-						P_SetObjectMomZ(missile, (4 + ((i&1)<<1))*FRACUNIT, true);
-						missile->momz += player->mo->pmomz;
-						missile->fuse = TICRATE/2;
-						missile->extravalue2 = ev;
-
-						i++;
-						throwang += ANG30;
-					}
-					if (mobjinfo[type].seesound && missile)
-						S_StartSound(missile, missile->info->seesound);
+					player->skidtime = TICRATE;
+					player->mo->tics = -1;
 				}
+				else if (!player->skidtime)
+					player->pflags &= ~PF_GLIDING;
 			}
-		}
-		else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
-			;
-		else if (player->panim != PA_IDLE && player->panim != PA_WALK && player->panim != PA_RUN && player->panim != PA_DASH)
-		{
-			if (player->cmomx || player->cmomy)
+			else if (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
 			{
-				if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
-					P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
-				else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
-				&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
-					P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
-				else if ((player->rmomx || player->rmomy)
-				&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
-					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-				else if (!player->rmomx && !player->rmomy && player->panim != PA_IDLE)
-					P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+				if (player->mo->state-states != S_PLAY_MELEE_LANDING)
+				{
+					mobjtype_t type = player->revitem;
+					P_SetPlayerMobjState(player->mo, S_PLAY_MELEE_LANDING);
+					player->mo->tics = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
+					S_StartSound(player->mo, sfx_s3k8b);
+					player->pflags |= PF_FULLSTASIS;
+
+					// hearticles
+					if (type)
+					{
+						UINT8 i = 0;
+						angle_t throwang = -(2*ANG30);
+						fixed_t xo = P_ReturnThrustX(player->mo, player->drawangle, 16*player->mo->scale);
+						fixed_t yo = P_ReturnThrustY(player->mo, player->drawangle, 16*player->mo->scale);
+						fixed_t zo = 6*player->mo->scale;
+						fixed_t mu = FixedMul(player->maxdash, player->mo->scale);
+						fixed_t mu2 = FixedHypot(player->mo->momx, player->mo->momy);
+						fixed_t ev;
+						mobj_t *missile = NULL;
+						if (mu2 < mu)
+							mu2 = mu;
+						ev = (50*FRACUNIT - (mu/25))/50;
+						while (i < 5)
+						{
+							missile = P_SpawnMobjFromMobj(player->mo, xo, yo, zo, type);
+							P_SetTarget(&missile->target, player->mo);
+							missile->angle = throwang + player->drawangle;
+							P_Thrust(missile, player->drawangle + ANGLE_90,
+								P_ReturnThrustY(missile, throwang, mu)); // side to side component
+							P_Thrust(missile, player->drawangle, mu2); // forward component
+							P_SetObjectMomZ(missile, (4 + ((i&1)<<1))*FRACUNIT, true);
+							missile->momz += player->mo->pmomz;
+							missile->fuse = TICRATE/2;
+							missile->extravalue2 = ev;
+
+							i++;
+							throwang += ANG30;
+						}
+						if (mobjinfo[type].seesound && missile)
+							S_StartSound(missile, missile->info->seesound);
+					}
+				}
 			}
-			else
+			else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
+				;
+			else if (player->panim != PA_IDLE && player->panim != PA_WALK && player->panim != PA_RUN && player->panim != PA_DASH)
 			{
-				if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
-					P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
-				else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
-				&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
-					P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
-				else if ((player->mo->momx || player->mo->momy)
-				&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
-					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
-				else if (!player->mo->momx && !player->mo->momy && player->panim != PA_IDLE)
-					P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+				if (player->cmomx || player->cmomy)
+				{
+					if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
+						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+					else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
+					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
+						P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+					else if ((player->rmomx || player->rmomy)
+					&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
+						P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+					else if (!player->rmomx && !player->rmomy && player->panim != PA_IDLE)
+						P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+				}
+				else
+				{
+					if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
+						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+					else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
+					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
+						P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+					else if ((player->mo->momx || player->mo->momy)
+					&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
+						P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+					else if (!player->mo->momx && !player->mo->momy && player->panim != PA_IDLE)
+						P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+				}
 			}
-		}
-
-		if (!(player->pflags & PF_GLIDING))
-			player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
-		player->pflags &= ~(PF_STARTJUMP|PF_THOKKED|PF_CANCARRY/*|PF_GLIDING*/);
-		player->secondjump = 0;
-		player->glidetime = 0;
-		player->climbing = 0;
-		player->powers[pw_tailsfly] = 0;
 
-		if (player->pflags & PF_SHIELDABILITY)
-		{
-			player->pflags &= ~PF_SHIELDABILITY;
+			if (!(player->pflags & PF_GLIDING))
+				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
+			player->pflags &= ~(PF_STARTJUMP|PF_THOKKED|PF_CANCARRY/*|PF_GLIDING*/);
+			player->secondjump = 0;
+			player->glidetime = 0;
+			player->climbing = 0;
+			player->powers[pw_tailsfly] = 0;
 
-			if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) // Elemental shield's stomp attack.
+			if (player->pflags & PF_SHIELDABILITY)
 			{
-				if (player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)) // play a blunt sound
-					S_StartSound(player->mo, sfx_s3k4c);
-				else // create a fire pattern on the ground
+				player->pflags &= ~PF_SHIELDABILITY;
+
+				if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) // Elemental shield's stomp attack.
 				{
-					S_StartSound(player->mo, sfx_s3k47);
-					P_ElementalFire(player, true);
+					if (player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)) // play a blunt sound
+						S_StartSound(player->mo, sfx_s3k4c);
+					else // create a fire pattern on the ground
+					{
+						S_StartSound(player->mo, sfx_s3k47);
+						P_ElementalFire(player, true);
+					}
+					P_SetObjectMomZ(player->mo,
+					(player->mo->eflags & MFE_UNDERWATER)
+					? 6*FRACUNIT/5
+					: 5*FRACUNIT/2,
+					false);
+					P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+					player->mo->momx = player->mo->momy = 0;
+					clipmomz = false;
+				}
+				else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) // Bubble shield's bounce attack.
+				{
+					P_DoBubbleBounce(player);
+					clipmomz = false;
 				}
-				P_SetObjectMomZ(player->mo,
-				(player->mo->eflags & MFE_UNDERWATER)
-				? 6*FRACUNIT/5
-				: 5*FRACUNIT/2,
-				false);
-				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
-				player->mo->momx = player->mo->momy = 0;
-				clipmomz = false;
-			}
-			else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) // Bubble shield's bounce attack.
-			{
-				P_DoBubbleBounce(player);
-				clipmomz = false;
 			}
 		}
-
-		if (player->pflags & PF_BOUNCING)
-		{
-			P_MobjCheckWater(player->mo);
-			player->mo->momz *= -1;
-			P_DoAbilityBounce(player, true);
-			if (player->scoreadd)
-				player->scoreadd--;
-			clipmomz = false;
-		}
 	}
 
 	return clipmomz;
@@ -2964,22 +2969,19 @@ static void P_DoBubbleBreath(player_t *player)
 		P_SetScale(bubble, bubble->destscale);
 	}
 
-	if (player->powers[pw_carry] == CR_NIGHTSMODE) // NiGHTS Super doesn't spawn flight bubbles
-		return;
-
 	// Tails stirs up the water while flying in it
 	if (player->powers[pw_tailsfly] && (leveltime & 1) && player->charability != CA_SWIM)
 	{
-		fixed_t radius = (3*player->mo->radius)>>1;
+		fixed_t radius = player->mo->radius;
 		angle_t fa = ((leveltime%45)*FINEANGLES/8) & FINEMASK;
 		fixed_t stirwaterx = FixedMul(FINECOSINE(fa),radius);
 		fixed_t stirwatery = FixedMul(FINESINE(fa),radius);
 		fixed_t stirwaterz;
 
 		if (player->mo->eflags & MFE_VERTICALFLIP)
-			stirwaterz = player->mo->z + player->mo->height - FixedDiv(player->mo->height,3*FRACUNIT/2);
+			stirwaterz = player->mo->z + player->mo->height - (4<<FRACBITS);
 		else
-			stirwaterz = player->mo->z + FixedDiv(player->mo->height,3*FRACUNIT/2);
+			stirwaterz = player->mo->z + (4<<FRACBITS);
 
 		bubble = P_SpawnMobj(
 			player->mo->x + stirwaterx,
@@ -4886,56 +4888,185 @@ void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range)
 //
 static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 {
-	mobj_t *lockon = NULL;
+	mobj_t *lockonthok = NULL, *lockonshield = NULL, *visual = NULL;
 
 	if (player->pflags & PF_JUMPSTASIS)
 		return;
 
-	if ((player->charability == CA_HOMINGTHOK) && !player->homing && (player->pflags & PF_JUMPED) && (!(player->pflags & PF_THOKKED) || (player->charflags & SF_MULTIABILITY)) && (lockon = P_LookForEnemies(player, true, false)))
+	if ((player->charability == CA_HOMINGTHOK) && !player->homing && (player->pflags & PF_JUMPED) && (!(player->pflags & PF_THOKKED) || (player->charflags & SF_MULTIABILITY)) && (lockonthok = P_LookForEnemies(player, true, false)))
 	{
 		if (P_IsLocalPlayer(player)) // Only display it on your own view.
 		{
-			mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-			P_SetTarget(&visual->target, lockon);
+			visual = P_SpawnMobj(lockonthok->x, lockonthok->y, lockonthok->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
+			P_SetTarget(&visual->target, lockonthok);
 		}
 	}
 
-	if (cmd->buttons & BT_USE && !(player->pflags & PF_JUMPDOWN) && !player->exiting && !P_PlayerInPain(player))
+	//////////////////
+	//SHIELD ACTIVES//
+	//& SUPER FLOAT!//
+	//////////////////
+
+	if ((player->pflags & PF_JUMPED) && !player->exiting && !P_PlayerInPain(player))
 	{
-		if (player->mo->tracer && player->powers[pw_carry] == CR_MACESPIN)
-		{}
-		else if (onground || player->climbing || (player->mo->tracer && player->powers[pw_carry]))
-		{}
-		else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_WHIRLWIND
-		&& !(player->pflags & PF_JUMPED)
-		&& !(player->pflags & PF_USEDOWN))
-			P_DoJumpShield(player);
-		else if (!(player->pflags & PF_SLIDING) && ((gametype != GT_CTF) || (!player->gotflag)))
+		if (onground || player->climbing || player->powers[pw_carry])
+			;
+		else if (gametype == GT_CTF && player->gotflag)
+			;
+		else if (player->pflags & (PF_GLIDING|PF_SLIDING|PF_SHIELDABILITY)) // If the player has used an ability previously
+			;
+		else if ((player->powers[pw_shield] & SH_NOSTACK) && !player->powers[pw_super] && !(player->pflags & PF_USEDOWN)
+			&& ((!(player->pflags & PF_THOKKED) || ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP && player->secondjump == UINT8_MAX)))) // thokked is optional if you're bubblewrapped
 		{
+			if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT)
+			{
+				if ((lockonshield = P_LookForEnemies(player, false, false)))
+				{
+					if (P_IsLocalPlayer(player)) // Only display it on your own view.
+					{
+						boolean dovis = true;
+						if (lockonshield == lockonthok)
+						{
+							if (leveltime & 2)
+								dovis = false;
+							else if (visual)
+								P_RemoveMobj(visual);
+						}
+						if (dovis)
+						{
+							visual = P_SpawnMobj(lockonshield->x, lockonshield->y, lockonshield->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
+							P_SetTarget(&visual->target, lockonshield);
+							P_SetMobjStateNF(visual, visual->info->spawnstate+1);
+						}
+					}
+				}
+			}
+			if (cmd->buttons & BT_USE // Spin button effects
+	#ifdef HAVE_BLUA
+			&& !LUAh_ShieldSpecial(player)
+	#endif
+				)
+			{
+				// Force stop
+				if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
+				{
+					player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+					player->mo->momx = player->mo->momy = player->mo->momz = 0;
+					S_StartSound(player->mo, sfx_ngskid);
+				}
+				else
+				{
+					switch (player->powers[pw_shield] & SH_NOSTACK)
+					{
+						// Whirlwind jump/Thunder jump
+						case SH_WHIRLWIND:
+						case SH_THUNDERCOIN:
+							P_DoJumpShield(player);
+							break;
+						// Armageddon pow
+						case SH_ARMAGEDDON:
+							player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+							P_BlackOw(player);
+							break;
+						// Attraction blast
+						case SH_ATTRACT:
+							player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+							player->homing = 2;
+							P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockonshield));
+							if (lockonshield)
+								{
+									player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockonshield->x, lockonshield->y);
+										player->pflags &= ~PF_NOJUMPDAMAGE;
+										P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+										S_StartSound(player->mo, sfx_s3k40);
+										player->homing = 3*TICRATE;
+								}
+								else
+									S_StartSound(player->mo, sfx_s3ka6);
+								break;
+							// Elemental stomp/Bubble bounce
+							case SH_ELEMENTAL:
+							case SH_BUBBLEWRAP:
+								{
+									boolean elem = ((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL);
+									player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+									if (elem)
+									{
+										player->pflags |= PF_NOJUMPDAMAGE;
+										P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+										S_StartSound(player->mo, sfx_s3k43);
+									}
+									else
+									{
+										player->pflags &= ~PF_NOJUMPDAMAGE;
+										P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+										S_StartSound(player->mo, sfx_s3k44);
+									}
+									player->secondjump = 0;
+									player->mo->momx = player->mo->momy = 0;
+									P_SetObjectMomZ(player->mo, -24*FRACUNIT, false);
+									break;
+								}
+							// Flame burst
+							case SH_FLAMEAURA:
+								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+								P_Thrust(player->mo, player->mo->angle, FixedMul(30*FRACUNIT - FixedSqrt(FixedDiv(player->speed, player->mo->scale)), player->mo->scale));
+								player->drawangle = player->mo->angle;
+								S_StartSound(player->mo, sfx_s3k43);
+							default:
+								break;
+					}
+				}
+			}
+		}
+		else if ((cmd->buttons & BT_USE))
+		{
+			if (!(player->pflags & PF_USEDOWN) && P_SuperReady(player))
+			{
+				// If you can turn super and aren't already,
+				// and you don't have a shield, do it!
+				P_DoSuperTransformation(player, false);
+			}
+			else
 #ifdef HAVE_BLUA
 			if (!LUAh_JumpSpinSpecial(player))
 #endif
 			switch (player->charability)
 			{
-				case CA_TELEKINESIS:
-					if (player->pflags & PF_JUMPED)
+				case CA_THOK:
+					if (player->powers[pw_super]) // Super Sonic float
 					{
-						if (!(player->pflags & PF_THOKKED) || (player->charflags & SF_MULTIABILITY))
+						if ((player->speed > 5*player->mo->scale) // FixedMul(5<<FRACBITS, player->mo->scale), but scale is FRACUNIT-based
+						&& (P_MobjFlip(player->mo)*player->mo->momz <= 0))
 						{
-							P_Telekinesis(player,
-								-FixedMul(player->actionspd, player->mo->scale), // -ve thrust (pulling towards player)
-								FixedMul(384*FRACUNIT, player->mo->scale));
+							if (player->panim != PA_RUN && player->panim != PA_WALK)
+							{
+								if (player->speed >= FixedMul(player->runspeed, player->mo->scale))
+									P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
+								else
+									P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
+							}
+
+							player->mo->momz = 0;
+							player->pflags &= ~(PF_STARTJUMP|PF_SPINNING);
 						}
 					}
 					break;
-				case CA_AIRDRILL:
-					if (player->pflags & PF_JUMPED)
+				case CA_TELEKINESIS:
+					if (!(player->pflags & (PF_THOKKED|PF_USEDOWN)) || (player->charflags & SF_MULTIABILITY))
 					{
-						if (player->pflags & PF_THOKKED) // speed up falling down
-						{
-							if (player->secondjump < 42)
-								player->secondjump ++;
-						}
+						P_Telekinesis(player,
+							-FixedMul(player->actionspd, player->mo->scale), // -ve thrust (pulling towards player)
+							FixedMul(384*FRACUNIT, player->mo->scale));
+					}
+					break;
+				case CA_TWINSPIN:
+					if ((player->charability2 == CA2_MELEE) && (!(player->pflags & (PF_THOKKED|PF_USEDOWN)) || player->charflags & SF_MULTIABILITY))
+					{
+						player->pflags |= PF_THOKKED;
+						S_StartSound(player->mo, sfx_s3k42);
+						player->mo->frame = 0;
+						P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
 					}
 					break;
 				default:
@@ -4948,6 +5079,9 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 	{
 		if (player->pflags & PF_JUMPED)
 		{
+			if (cmd->buttons & BT_USE && player->secondjump < 42) // speed up falling down
+				player->secondjump++;
+
 			if (player->flyangle > 0 && player->pflags & PF_THOKKED)
 			{
 				player->flyangle--;
@@ -4966,6 +5100,11 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		}
 	}
 
+	///////////////
+	// CHARACTER //
+	// ABILITIES!//
+	///////////////
+
 	if (cmd->buttons & BT_JUMP && !player->exiting && !P_PlayerInPain(player))
 	{
 #ifdef HAVE_BLUA
@@ -5035,7 +5174,6 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						}
 
 						P_InstaThrust(player->mo, player->mo->angle, FixedMul(actionspd, player->mo->scale));
-						player->drawangle = player->mo->angle;
 
 						if (maptol & TOL_2D)
 						{
@@ -5050,11 +5188,11 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 						if (player->charability == CA_HOMINGTHOK)
 						{
-							P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
-							if (lockon)
+							P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockonthok));
+							if (lockonthok)
 							{
 								P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
+								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockonthok->x, lockonthok->y);
 								player->homing = 3*TICRATE;
 							}
 							else
@@ -5066,6 +5204,8 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							player->pflags &= ~PF_NOJUMPDAMAGE;
 						}
 
+						player->drawangle = player->mo->angle;
+
 						if (player->mo->info->attacksound && !player->spectator)
 							S_StartSound(player->mo, player->mo->info->attacksound); // Play the THOK sound
 
@@ -5212,6 +5352,60 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 			P_DoJumpShield(player);
 	}
 
+	// HOMING option.
+	if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT // Sonic 3D Blast.
+	&& player->pflags & PF_SHIELDABILITY)
+	{
+		if (player->homing && player->mo->tracer)
+		{
+			if (!P_HomingAttack(player->mo, player->mo->tracer))
+			{
+				P_SetObjectMomZ(player->mo, 6*FRACUNIT, false);
+				if (player->mo->eflags & MFE_UNDERWATER)
+					player->mo->momz = FixedMul(player->mo->momz, FRACUNIT/3);
+				player->homing = 0;
+			}
+		}
+
+		// If you're not jumping, then you obviously wouldn't be homing.
+		if (!(player->pflags & PF_JUMPED))
+			player->homing = 0;
+	}
+	else if (player->charability == CA_HOMINGTHOK) // Sonic Adventure.
+	{
+		// If you've got a target, chase after it!
+		if (player->homing && player->mo->tracer)
+		{
+			P_SpawnThokMobj(player);
+
+			// But if you don't, then stop homing.
+			if (!P_HomingAttack(player->mo, player->mo->tracer))
+			{
+				if (player->mo->eflags & MFE_UNDERWATER)
+					P_SetObjectMomZ(player->mo, FixedDiv(457*FRACUNIT,72*FRACUNIT), false);
+				else
+					P_SetObjectMomZ(player->mo, 10*FRACUNIT, false);
+
+				player->mo->momx = player->mo->momy = player->homing = 0;
+
+				if (player->mo->tracer->flags2 & MF2_FRET)
+					P_InstaThrust(player->mo, player->mo->angle, -(player->speed>>3));
+
+				if (!(player->mo->tracer->flags & MF_BOSS))
+					player->pflags &= ~PF_THOKKED;
+
+				P_SetPlayerMobjState(player->mo, S_PLAY_SPRING);
+				player->pflags |= PF_NOJUMPDAMAGE;
+			}
+		}
+
+		// If you're not jumping, then you obviously wouldn't be homing.
+		if (!(player->pflags & PF_JUMPED))
+			player->homing = 0;
+	}
+	else
+		player->homing = 0;
+
 	if (cmd->buttons & BT_JUMP)
 	{
 		player->pflags |= PF_JUMPDOWN;
@@ -5238,7 +5432,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		player->pflags &= ~PF_JUMPDOWN;
 
 		// Repeat abilities, but not double jump!
-		if (player->secondjump == 1 && player->charability != CA_DOUBLEJUMP)
+		if (player->secondjump == 1 && player->charability != CA_DOUBLEJUMP && player->charability != CA_AIRDRILL)
 		{
 			if (player->charflags & SF_MULTIABILITY)
 			{
@@ -7891,7 +8085,7 @@ static void P_MovePlayer(player_t *player)
 	}
 	else if (player->pflags & PF_BOUNCING)
 	{
-		if (!(player->pflags & PF_JUMPDOWN) || (onground && P_MobjFlip(player->mo)*player->mo->momz <= 0)) // If not holding the jump button OR on flat ground
+		if (!(player->pflags & PF_JUMPDOWN)) // If not holding the jump button
 		{
 			P_ResetPlayer(player); // down, stop bouncing.
 			player->pflags |= PF_THOKKED;
@@ -7991,16 +8185,22 @@ static void P_MovePlayer(player_t *player)
 					if (P_MobjFlip(player->mo)*player->mo->momz < FixedMul(5*actionspd, player->mo->scale))
 						P_SetObjectMomZ(player->mo, actionspd/2, true);
 
+					P_SetPlayerMobjState(player->mo, player->mo->state->nextstate);
+
 					player->fly1--;
 				}
 			}
 
 			// Tails Put-Put noise
-			if (player->charability == CA_FLY && player->bot != 1 && leveltime % 10 == 0 && !player->spectator)
+			if (player->charability == CA_FLY
+				&& player->bot != 1
+				&& !(player->mo->eflags & MFE_UNDERWATER)
+				&& leveltime % 10 == 0
+				&& !player->spectator)
 				S_StartSound(player->mo, sfx_putput);
 
 			// Descend
-			if (cmd->buttons & BT_USE && !(player->pflags & PF_STASIS) && !player->exiting)
+			if (cmd->buttons & BT_USE && !(player->pflags & PF_STASIS) && !player->exiting && !(player->mo->eflags & MFE_GOOWATER))
 				if (P_MobjFlip(player->mo)*player->mo->momz > -FixedMul(5*actionspd, player->mo->scale))
 					P_SetObjectMomZ(player->mo, -actionspd/2, true);
 
@@ -8008,11 +8208,12 @@ static void P_MovePlayer(player_t *player)
 		else
 		{
 			// Tails-gets-tired Stuff
-			if (player->panim == PA_ABILITY)
+			if (player->panim == PA_ABILITY && player->mo->state-states != S_PLAY_FLY_TIRED)
 				P_SetPlayerMobjState(player->mo, S_PLAY_FLY_TIRED);
 
 			if (player->charability == CA_FLY && (leveltime % 10 == 0)
 				&& player->mo->state-states == S_PLAY_FLY_TIRED
+				&& !(player->mo->eflags & MFE_UNDERWATER)
 				&& !player->spectator)
 				S_StartSound(player->mo, sfx_pudpud);
 		}
@@ -8117,178 +8318,6 @@ static void P_MovePlayer(player_t *player)
 			localangle2 = player->mo->angle;
 	}
 
-	//////////////////
-	//SHIELD ACTIVES//
-	//& SUPER FLOAT!//
-	//////////////////
-
-	if (player->pflags & PF_JUMPED && !player->exiting && player->mo->health)
-	{
-		mobj_t *lockon = NULL;
-		if (!player->powers[pw_super] && player->powers[pw_shield] == SH_ATTRACT && !(player->pflags & PF_THOKKED))
-		{
-			if ((lockon = P_LookForEnemies(player, false, false)))
-			{
-				if (P_IsLocalPlayer(player)) // Only display it on your own view.
-				{
-					mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-					P_SetTarget(&visual->target, lockon);
-					P_SetMobjStateNF(visual, visual->info->spawnstate+1);
-				}
-			}
-		}
-		if (cmd->buttons & BT_USE) // Spin button effects
-		{
-			if (player->powers[pw_super]) // Super can't use shield actives, only passives
-			{
-				if ((player->charability == CA_THOK) // Super Sonic float
-				&& (player->speed > 5*player->mo->scale) // FixedMul(5<<FRACBITS, player->mo->scale), but scale is FRACUNIT-based
-				&& (P_MobjFlip(player->mo)*player->mo->momz <= 0))
-				{
-					if (player->panim != PA_RUN && player->panim != PA_WALK)
-					{
-						if (player->speed >= FixedMul(player->runspeed, player->mo->scale))
-							P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
-						else
-							P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
-					}
-
-					player->mo->momz = 0;
-					player->pflags &= ~(PF_STARTJUMP|PF_SPINNING);
-				}
-			}
-			else
-#ifdef HAVE_BLUA
-			if (!LUAh_ShieldSpecial(player))
-#endif
-			{
-				if (!(player->pflags & (PF_USEDOWN|PF_GLIDING|PF_SLIDING|PF_SHIELDABILITY)) // If the player is not holding down BT_USE, or having used an ability previously
-					&& (!(player->powers[pw_shield] & SH_NOSTACK) || !(player->pflags & PF_THOKKED) || ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP && player->secondjump == UINT8_MAX))) // thokked is optional if you're bubblewrapped/turning super
-				{
-					// Force stop
-					if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
-					{
-						player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-						player->mo->momx = player->mo->momy = player->mo->momz = 0;
-						S_StartSound(player->mo, sfx_ngskid);
-					}
-					else
-					{
-						switch (player->powers[pw_shield] & SH_NOSTACK)
-						{
-							// Super!
-							case SH_NONE:
-								if (P_SuperReady(player))
-									P_DoSuperTransformation(player, false);
-								break;
-							// Whirlwind jump/Thunder jump
-							case SH_WHIRLWIND:
-							case SH_THUNDERCOIN:
-								P_DoJumpShield(player);
-								break;
-							// Armageddon pow
-							case SH_ARMAGEDDON:
-								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-								P_BlackOw(player);
-								break;
-							// Attraction blast
-							case SH_ATTRACT:
-								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-								player->homing = 2;
-								P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
-								if (lockon)
-								{
-									player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
-									player->pflags &= ~PF_NOJUMPDAMAGE;
-									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-									S_StartSound(player->mo, sfx_s3k40);
-									player->homing = 3*TICRATE;
-								}
-								else
-									S_StartSound(player->mo, sfx_s3ka6);
-								break;
-							// Elemental stomp/Bubble bounce
-							case SH_ELEMENTAL:
-							case SH_BUBBLEWRAP:
-								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-								player->pflags &= ~PF_NOJUMPDAMAGE;
-								P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-								player->secondjump = 0;
-								player->mo->momx = player->mo->momy = 0;
-								P_SetObjectMomZ(player->mo, -24*FRACUNIT, false);
-								S_StartSound(player->mo,
-									((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL)
-									? sfx_s3k43
-									: sfx_s3k44);
-								break;
-							// Flame burst
-							case SH_FLAMEAURA:
-								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-								P_Thrust(player->mo, player->mo->angle, FixedMul(30*FRACUNIT - FixedSqrt(FixedDiv(player->speed, player->mo->scale)), player->mo->scale));
-								player->drawangle = player->mo->angle;
-								S_StartSound(player->mo, sfx_s3k43);
-							default:
-								break;
-						}
-					}
-				}
-			}
-		}
-	}
-
-	// HOMING option.
-	if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT // Sonic 3D Blast.
-	&& player->pflags & PF_SHIELDABILITY)
-	{
-		if (player->homing && player->mo->tracer)
-		{
-			if (!P_HomingAttack(player->mo, player->mo->tracer))
-			{
-				P_SetObjectMomZ(player->mo, 6*FRACUNIT, false);
-				if (player->mo->eflags & MFE_UNDERWATER)
-					player->mo->momz = FixedMul(player->mo->momz, FRACUNIT/3);
-				player->homing = 0;
-			}
-		}
-
-		// If you're not jumping, then you obviously wouldn't be homing.
-		if (!(player->pflags & PF_JUMPED))
-			player->homing = 0;
-	}
-	else if (player->charability == CA_HOMINGTHOK) // Sonic Adventure.
-	{
-		// If you've got a target, chase after it!
-		if (player->homing && player->mo->tracer)
-		{
-			P_SpawnThokMobj(player);
-
-			// But if you don't, then stop homing.
-			if (!P_HomingAttack(player->mo, player->mo->tracer))
-			{
-				if (player->mo->eflags & MFE_UNDERWATER)
-					P_SetObjectMomZ(player->mo, FixedDiv(457*FRACUNIT,72*FRACUNIT), false);
-				else
-					P_SetObjectMomZ(player->mo, 10*FRACUNIT, false);
-
-				player->mo->momx = player->mo->momy = player->homing = 0;
-
-				if (player->mo->tracer->flags2 & MF2_FRET)
-					P_InstaThrust(player->mo, player->mo->angle, -(player->speed>>3));
-
-				if (!(player->mo->tracer->flags & MF_BOSS))
-					player->pflags &= ~PF_THOKKED;
-
-				// P_SetPlayerMobjState(player->mo, S_PLAY_SPRING); -- Speed didn't like it, RIP
-			}
-		}
-
-		// If you're not jumping, then you obviously wouldn't be homing.
-		if (!(player->pflags & PF_JUMPED))
-			player->homing = 0;
-	}
-	else
-		player->homing = 0;
-
 	if (player->climbing == 1)
 		P_DoClimbing(player);
 
@@ -8332,7 +8361,7 @@ static void P_MovePlayer(player_t *player)
 
 		// Less height while spinning. Good for spinning under things...?
 		if ((player->mo->state == &states[player->mo->info->painstate])
-		|| ((player->pflags & PF_JUMPED) && !(player->pflags & PF_NOJUMPDAMAGE && player->charflags & SF_NOJUMPSPIN))
+		|| ((player->pflags & PF_JUMPED) && !(player->pflags & PF_NOJUMPDAMAGE))
 		|| (player->pflags & PF_SPINNING)
 		|| player->powers[pw_tailsfly] || player->pflags & PF_GLIDING
 		|| (player->charability == CA_FLY && player->mo->state-states == S_PLAY_FLY_TIRED))
@@ -8843,7 +8872,7 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 // Looks for something you can hit - Used for homing attack
 // If nonenemies is true, includes monitors and springs!
 // If bullet is true, you can look up and the distance is further,
-// but your total angle span you can look is limited to compensate.
+// but your total angle span you can look is limited to compensate. (Also, allows monitors.)
 //
 mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 {
@@ -8853,6 +8882,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 	const fixed_t maxdist = FixedMul((bullet ? RING_DIST*2 : RING_DIST), player->mo->scale);
 	const angle_t span = (bullet ? ANG30 : ANGLE_90);
 	fixed_t dist, closestdist = 0;
+	const mobjflag_t nonenemiesdisregard = (bullet ? 0 : MF_MONITOR)|MF_SPRING;
 
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
@@ -8872,7 +8902,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 		if (mo->flags2 & MF2_FRET)
 			continue;
 
-		if (!nonenemies && mo->flags & (MF_MONITOR|MF_SPRING))
+		if (!nonenemies && mo->flags & nonenemiesdisregard)
 			continue;
 
 		if (!bullet && mo->type == MT_DETON) // Don't be STUPID, Sonic!
@@ -9027,19 +9057,22 @@ boolean P_GetLives(player_t *player)
 	INT32 i, maxlivesplayer = -1, livescheck = 1;
 	if (!(netgame || multiplayer)
 	|| (gametype != GT_COOP)
-	|| (cv_cooplives.value == 1)
 	|| (player->lives == INFLIVES))
 		return true;
 
-	if ((cv_cooplives.value == 2 || cv_cooplives.value == 0) && player->lives > 0)
-		return true;
-
 	if (cv_cooplives.value == 0) // infinite lives
 	{
-		player->lives++;
+		if (player->lives < 1)
+			player->lives = 1;
 		return true;
 	}
 
+	if ((cv_cooplives.value == 2 || cv_cooplives.value == 1) && player->lives > 0)
+		return true;
+
+	if (cv_cooplives.value == 1)
+		return false;
+
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		if (!playeringame[i])
@@ -9146,7 +9179,7 @@ static void P_DeathThink(player_t *player)
 	// continue logic
 	if (!(netgame || multiplayer) && player->lives <= 0)
 	{
-		if (player->deadtimer > TICRATE && (cmd->buttons & BT_USE || cmd->buttons & BT_JUMP) && player->continues > 0)
+		if (player->deadtimer > (3*TICRATE) && (cmd->buttons & BT_USE || cmd->buttons & BT_JUMP) && player->continues > 0)
 			G_UseContinue();
 		else if (player->deadtimer >= gameovertics)
 			G_UseContinue(); // Even if we don't have one this handles ending the game
@@ -9170,12 +9203,12 @@ static void P_DeathThink(player_t *player)
 	// Force respawn if idle for more than 30 seconds in shooter modes.
 	if (player->deadtimer > 30*TICRATE && !G_PlatformGametype())
 		player->playerstate = PST_REBORN;
-	else if ((player->lives > 0 || j != MAXPLAYERS) && !G_IsSpecialStage(gamemap)) // Don't allow "click to respawn" in special stages!
+	else if ((player->lives > 0 || j != MAXPLAYERS) && !(!(netgame || multiplayer) && G_IsSpecialStage(gamemap))) // Don't allow "click to respawn" in special stages!
 	{
 		if (gametype == GT_COOP && (netgame || multiplayer) && cv_coopstarposts.value == 2)
 		{
 			P_ConsiderAllGone();
-			if ((player->deadtimer > 5*TICRATE) || ((cmd->buttons & BT_JUMP) && (player->deadtimer > TICRATE)))
+			if ((player->deadtimer > TICRATE<<1) || ((cmd->buttons & BT_JUMP) && (player->deadtimer > TICRATE)))
 			{
 				//player->spectator = true;
 				player->outofcoop = true;
@@ -9191,16 +9224,11 @@ static void P_DeathThink(player_t *player)
 					player->playerstate = PST_REBORN;
 				else switch(gametype) {
 					case GT_COOP:
-						if (player->deadtimer > TICRATE)
-							player->playerstate = PST_REBORN;
-						break;
 					case GT_COMPETITION:
+					case GT_RACE:
 						if (player->deadtimer > TICRATE)
 							player->playerstate = PST_REBORN;
 						break;
-					case GT_RACE:
-						player->playerstate = PST_REBORN;
-						break;
 					default:
 						if (player->deadtimer > cv_respawntime.value*TICRATE)
 							player->playerstate = PST_REBORN;
@@ -9209,7 +9237,7 @@ static void P_DeathThink(player_t *player)
 			}
 
 			// Single player auto respawn
-			if (!(netgame || multiplayer) && player->deadtimer > 5*TICRATE)
+			if (!(netgame || multiplayer) && player->deadtimer > TICRATE<<1)
 				player->playerstate = PST_REBORN;
 		}
 	}
@@ -9309,12 +9337,16 @@ consvar_t cv_cam_still = {"cam_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL,
 consvar_t cv_cam_speed = {"cam_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotate = {"cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotspeed = {"cam_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_orbit = {"cam_orbit", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_adjust = {"cam_adjust", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_dist = {"cam2_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_height = {"cam2_height", "25", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_still = {"cam2_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_speed = {"cam2_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_rotate = {"cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_rotspeed = {"cam2_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_orbit = {"cam2_orbit", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_adjust = {"cam2_adjust", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 fixed_t t_cam_dist = -42;
 fixed_t t_cam_height = -42;
@@ -9368,9 +9400,9 @@ void P_ResetCamera(player_t *player, camera_t *thiscam)
 boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcalled)
 {
 	angle_t angle = 0, focusangle = 0, focusaiming = 0;
-	fixed_t x, y, z, dist, checkdist, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight;
+	fixed_t x, y, z, dist, distxy, distz, checkdist, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight, slopez = 0;
 	INT32 camrotate;
-	boolean camstill, cameranoclip;
+	boolean camstill, cameranoclip, camorbit;
 	mobj_t *mo;
 	subsector_t *newsubsec;
 	fixed_t f1, f2;
@@ -9451,6 +9483,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		// force defaults because we have a camera look section
 		camspeed = (INT32)(atof(cv_cam_speed.defaultvalue) * FRACUNIT);
 		camstill = (!stricmp(cv_cam_still.defaultvalue, "off")) ? false : true;
+		camorbit = (!stricmp(cv_cam_orbit.defaultvalue, "off")) ? false : true;
 		camrotate = atoi(cv_cam_rotate.defaultvalue);
 		camdist = FixedMul((INT32)(atof(cv_cam_dist.defaultvalue) * FRACUNIT), mo->scale);
 		camheight = FixedMul((INT32)(atof(cv_cam_height.defaultvalue) * FRACUNIT), FixedMul(player->camerascale, mo->scale));
@@ -9459,6 +9492,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	{
 		camspeed = cv_cam_speed.value;
 		camstill = cv_cam_still.value;
+		camorbit = cv_cam_orbit.value;
 		camrotate = cv_cam_rotate.value;
 		camdist = FixedMul(cv_cam_dist.value, mo->scale);
 		camheight = FixedMul(cv_cam_height.value, FixedMul(player->camerascale, mo->scale));
@@ -9467,6 +9501,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	{
 		camspeed = cv_cam2_speed.value;
 		camstill = cv_cam2_still.value;
+		camorbit = cv_cam2_orbit.value;
 		camrotate = cv_cam2_rotate.value;
 		camdist = FixedMul(cv_cam2_dist.value, mo->scale);
 		camheight = FixedMul(cv_cam2_height.value, FixedMul(player->camerascale, mo->scale));
@@ -9576,13 +9611,50 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 			dist <<= 1;
 	}
 
+
+
 	checkdist = (dist = FixedMul(dist, player->camerascale));
 
 	if (checkdist < 128*FRACUNIT)
 		checkdist = 128*FRACUNIT;
 
-	x = mo->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
-	y = mo->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	if (!(twodlevel || (mo->flags2 & MF2_TWOD)) && !(player->powers[pw_carry] == CR_NIGHTSMODE)) // This block here is like 90% Lach's work, thanks bud
+	{
+		if ((thiscam == &camera && cv_cam_adjust.value) || (thiscam == &camera2 && cv_cam2_adjust.value))
+		{
+			if (!(mo->eflags & MFE_JUSTHITFLOOR) && (P_IsObjectOnGround(mo)) // Check that player is grounded
+			&& thiscam->ceilingz - thiscam->floorz >= P_GetPlayerHeight(player)) // Check that camera's sector is large enough for the player to fit into, at least
+			{
+				if (mo->eflags & MFE_VERTICALFLIP) // if player is upside-down
+				{
+					//z = min(z, thiscam->ceilingz); // solution 1: change new z coordinate to be at LEAST its ground height
+					slopez += min(thiscam->ceilingz - mo->z, 0); // solution 2: change new z coordinate by the difference between camera's ground and top of player
+				}
+				else // player is not upside-down
+				{
+					//z = max(z, thiscam->floorz); // solution 1: change new z coordinate to be at LEAST its ground height
+					slopez += max(thiscam->floorz - mo->z - mo->height, 0); // solution 2: change new z coordinate by the difference between camera's ground and top of player
+				}
+			}
+		}
+	}
+
+	if (camorbit) //Sev here, I'm guessing this is where orbital cam lives
+	{
+		if (rendermode == render_opengl)
+			distxy = FixedMul(dist, FINECOSINE((focusaiming>>ANGLETOFINESHIFT) & FINEMASK));
+		else
+			distxy = dist;
+		distz = -FixedMul(dist, FINESINE((focusaiming>>ANGLETOFINESHIFT) & FINEMASK)) + slopez;
+	}
+	else
+	{
+		distxy = dist;
+		distz = slopez;
+	}
+
+	x = mo->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+	y = mo->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
 
 #if 0
 	if (twodlevel || (mo->flags2 & MF2_TWOD))
@@ -9619,9 +9691,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	pviewheight = FixedMul(41*player->height/48, mo->scale);
 
 	if (mo->eflags & MFE_VERTICALFLIP)
-		z = mo->z + mo->height - pviewheight - camheight;
+		z = mo->z + mo->height - pviewheight - camheight + distz;
 	else
-		z = mo->z + pviewheight + camheight;
+		z = mo->z + pviewheight + camheight + distz;
 
 	// move camera down to move under lower ceilings
 	newsubsec = R_IsPointInSubsector(((mo->x>>FRACBITS) + (thiscam->x>>FRACBITS))<<(FRACBITS-1), ((mo->y>>FRACBITS) + (thiscam->y>>FRACBITS))<<(FRACBITS-1));
@@ -9934,6 +10006,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 
 	return (x == thiscam->x && y == thiscam->y && z == thiscam->z && angle == thiscam->aiming);
+
 }
 
 boolean P_SpectatorJoinGame(player_t *player)
@@ -10585,7 +10658,8 @@ static void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 	angle_t horizangle = player->drawangle;
 	fixed_t zoffs = 0;
 	fixed_t backwards = -1*FRACUNIT;
-	boolean doroll = (player->panim == PA_ROLL || player->panim == PA_JUMP);
+	boolean doswim = (player->panim == PA_ABILITY && (player->mo->eflags & MFE_UNDERWATER));
+	boolean doroll = (player->panim == PA_ROLL || (player->panim == PA_JUMP && !(player->charflags & SF_NOJUMPSPIN)) || doswim);
 	angle_t rollangle;
 	boolean panimchange;
 	INT32 ticnum = 0;
@@ -10612,17 +10686,25 @@ static void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 			if (testval < FRACUNIT)
 				testval = FRACUNIT;
 		}
-		if (smilesonground && !player->mo->reactiontime)
+
+		if (doswim)
+			zdist = player->mo->momz<<1;
+		else if (smilesonground && !player->mo->reactiontime)
 			zdist = (player->mo->z - tails->threshold);
 		else
 			zdist = player->mo->momz;
+
 		rollangle = R_PointToAngle2(0, 0, testval, -P_MobjFlip(player->mo)*zdist);
-		zoffs = 3*FRACUNIT + 12*FINESINE(rollangle >> ANGLETOFINESHIFT);
-		backwards = -12*FINECOSINE(rollangle >> ANGLETOFINESHIFT);
+
+		if (!doswim)
+		{
+			zoffs = 3*FRACUNIT + 12*FINESINE(rollangle >> ANGLETOFINESHIFT);
+			backwards = -12*FINECOSINE(rollangle >> ANGLETOFINESHIFT);
+		}
 	}
 	else if (player->panim == PA_RUN)
 		backwards = -5*FRACUNIT;
-	else if (player->panim == PA_SPRING)
+	else if (player->panim == PA_SPRING || player->panim == PA_JUMP)
 	{
 		zoffs += 4*FRACUNIT;
 		backwards /= 2;
@@ -10644,7 +10726,7 @@ static void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 		zoffs = -7*FRACUNIT;
 		backwards = -9*FRACUNIT;
 	}
-	else if (player->mo->sprite2 == SPR2_FLY || player->mo->sprite2 == SPR2_TIRE)
+	else if (player->panim == PA_ABILITY)
 		backwards = -5*FRACUNIT;
 
 	// sprite...
@@ -10661,7 +10743,7 @@ static void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 		else
 			chosenstate = S_TAILSOVERLAY_0DEGREES;
 	}
-	else if (player->panim == PA_SPRING)
+	else if (player->panim == PA_SPRING || player->panim == PA_JUMP)
 		chosenstate = S_TAILSOVERLAY_MINUS60DEGREES;
 	else if (player->panim == PA_FALL || player->mo->state-states == S_PLAY_RIDE)
 		chosenstate = S_TAILSOVERLAY_PLUS60DEGREES;
@@ -10684,6 +10766,8 @@ static void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 	}
 	else if (player->mo->sprite2 == SPR2_FLY)
 		chosenstate = S_TAILSOVERLAY_FLY;
+	else if (player->mo->sprite2 == SPR2_SWIM)
+		chosenstate = S_TAILSOVERLAY_FLY;
 	else if (player->mo->sprite2 == SPR2_TIRE)
 		chosenstate = S_TAILSOVERLAY_TIRE;
 	else if (player->panim == PA_ABILITY2)
@@ -10709,8 +10793,10 @@ static void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 		}
 	}
 
+#if 0
 	if (player->fly1 != 0 && player->powers[pw_tailsfly] != 0 && !smilesonground)
 		P_SetMobjState(tails, chosenstate);
+#endif
 
 	// animation...
 	if (player->panim == PA_SPRING || player->panim == PA_FALL || player->mo->state-states == S_PLAY_RIDE)
@@ -10725,7 +10811,7 @@ static void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 	else if (player->mo->state-states == S_PLAY_GASP)
 		tails->tics = -1;
 	else if (player->mo->sprite2 == SPR2_TIRE)
-		ticnum = 4;
+		ticnum = (doswim ? 2 : 4);
 	else if (player->panim != PA_IDLE)
 		ticnum = player->mo->tics;
 
@@ -10948,7 +11034,8 @@ void P_PlayerThink(player_t *player)
 
 	if (player->exiting == 2 || countdown2 == 2)
 	{
-		if (cv_playersforexit.value) // Count to be sure everyone's exited
+		UINT8 numneeded = (G_IsSpecialStage(gamemap) ? 4 : cv_playersforexit.value);
+		if (numneeded) // Count to be sure everyone's exited
 		{
 			INT32 i, total = 0, exiting = 0;
 
@@ -10964,7 +11051,7 @@ void P_PlayerThink(player_t *player)
 					exiting++;
 			}
 
-			if (!total || ((4*exiting)/total) >= cv_playersforexit.value)
+			if (!total || ((4*exiting)/total) >= numneeded)
 			{
 				if (server)
 					SendNetXCmd(XD_EXITLEVEL, NULL, 0);
@@ -11011,8 +11098,6 @@ void P_PlayerThink(player_t *player)
 	{
 		if (gametype != GT_COOP)
 			player->score = 0;
-		player->mo->health = 1;
-		player->rings = player->spheres = 0;
 	}
 	else if ((netgame || multiplayer) && player->lives <= 0 && gametype != GT_COOP)
 	{
@@ -11252,8 +11337,8 @@ void P_PlayerThink(player_t *player)
 		{
 			boolean currentlyonground = P_IsObjectOnGround(player->mo);
 
-			if (!player->powers[pw_carry]
-			&& ((player->pflags & (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE)) == (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE))
+			if (!player->powers[pw_carry] && !player->powers[pw_nocontrol]
+			&& ((player->pflags & (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE|PF_STASIS)) == (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE))
 			&& !(cmd->forwardmove || cmd->sidemove)
 			&& (player->rmomx || player->rmomy)
 			&& (!player->capsule || (player->capsule->reactiontime != (player-players)+1)))
@@ -11264,7 +11349,7 @@ void P_PlayerThink(player_t *player)
 				if (!currentlyonground)
 					acceleration /= 2;
 				// fake skidding! see P_SkidStuff for reference on conditionals
-				else if (!player->skidtime && !(player->mo->eflags & MFE_GOOWATER) && !(player->pflags & (PF_JUMPED|PF_SPINNING|PF_SLIDING)) && !(player->charflags & SF_NOSKID) && P_AproxDistance(player->mo->momx, player->mo->momy) >= FixedMul(player->runspeed/2, player->mo->scale))
+				else if (!player->skidtime && !(player->mo->eflags & MFE_GOOWATER) && !(player->pflags & (PF_JUMPED|PF_SPINNING|PF_SLIDING)) && !(player->charflags & SF_NOSKID) && P_AproxDistance(player->mo->momx, player->mo->momy) >= FixedMul(player->runspeed, player->mo->scale)) // modified from player->runspeed/2 'cuz the skid was just TOO frequent ngl
 				{
 					if (player->mo->state-states != S_PLAY_SKID)
 						P_SetPlayerMobjState(player->mo, S_PLAY_SKID);
@@ -11752,7 +11837,7 @@ void P_PlayerAfterThink(player_t *player)
 				{
 					if ((tails->z + tails->height + player->mo->height + FixedMul(FRACUNIT, player->mo->scale)) <= tails->ceilingz
 						&& (tails->eflags & MFE_VERTICALFLIP)) // Reverse gravity check for the carrier - Flame
-						player->mo->z = tails->z + tails->height + FixedMul(FRACUNIT, player->mo->scale);
+						player->mo->z = tails->z + tails->height + 12*player->mo->scale;
 					else
 						player->powers[pw_carry] = CR_NONE;
 				}
@@ -11760,7 +11845,7 @@ void P_PlayerAfterThink(player_t *player)
 				{
 					if ((tails->z - player->mo->height - FixedMul(FRACUNIT, player->mo->scale)) >= tails->floorz
 						&& !(tails->eflags & MFE_VERTICALFLIP)) // Correct gravity check for the carrier - Flame
-						player->mo->z = tails->z - player->mo->height - FixedMul(FRACUNIT, player->mo->scale);
+						player->mo->z = tails->z - player->mo->height - 12*player->mo->scale;
 					else
 						player->powers[pw_carry] = CR_NONE;
 				}
@@ -11769,7 +11854,7 @@ void P_PlayerAfterThink(player_t *player)
 					player->powers[pw_carry] = CR_NONE;
 				else
 				{
-					P_TryMove(player->mo, tails->x, tails->y, true);
+					P_TryMove(player->mo, tails->x + P_ReturnThrustX(tails, tails->player->drawangle, 4*FRACUNIT), tails->y + P_ReturnThrustY(tails, tails->player->drawangle, 4*FRACUNIT), true);
 					player->mo->momx = tails->momx;
 					player->mo->momy = tails->momy;
 					player->mo->momz = tails->momz;
@@ -11795,6 +11880,8 @@ void P_PlayerAfterThink(player_t *player)
 				{
 					if (player->mo->state-states != S_PLAY_RIDE)
 						P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+					if ((tails->skin && ((skin_t *)(tails->skin))->sprites[SPR2_SWIM].numframes) && (tails->eflags & MFE_UNDERWATER))
+						tails->player->powers[pw_tailsfly] = 0;
 				}
 				else
 					P_SetTarget(&player->mo->tracer, NULL);
diff --git a/src/r_bsp.c b/src/r_bsp.c
index d521d9f4d4943a6ea5e341738a20e2100dadb91b..23e751420e30a8a2393fbce60712ef799eca2887 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -1088,9 +1088,9 @@ static void R_Subsector(size_t num)
 			{
 				light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight);
 				ffloor[numffloors].plane = R_FindPlane(polysec->floorheight, polysec->floorpic,
-					polysec->lightlevel, polysec->floor_xoffs, polysec->floor_yoffs,
+					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->floor_xoffs, polysec->floor_yoffs,
 					polysec->floorpic_angle-po->angle,
-					NULL, NULL, po
+					(light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po
 #ifdef ESLOPE
 					, NULL // will ffloors be slopable eventually?
 #endif
@@ -1115,10 +1115,10 @@ static void R_Subsector(size_t num)
 				&& polysec->ceilingheight <= ceilingcenterz
 				&& (viewz > polysec->ceilingheight))
 			{
-				light = R_GetPlaneLight(frontsector, polysec->ceilingheight, viewz < polysec->ceilingheight);
+				light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight);
 				ffloor[numffloors].plane = R_FindPlane(polysec->ceilingheight, polysec->ceilingpic,
-					polysec->lightlevel, polysec->ceiling_xoffs, polysec->ceiling_yoffs, polysec->ceilingpic_angle-po->angle,
-					NULL, NULL, po
+					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->ceiling_xoffs, polysec->ceiling_yoffs, polysec->ceilingpic_angle-po->angle,
+					(light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po
 #ifdef ESLOPE
 					, NULL // will ffloors be slopable eventually?
 #endif
diff --git a/src/r_data.c b/src/r_data.c
index 6889bdddebf587ce22b3526066fca43aec364ceb..8594c85954d8133145a371643e7cca62a16bbc5f 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -23,6 +23,7 @@
 #include "z_zone.h"
 #include "p_setup.h" // levelflats
 #include "v_video.h" // pMasterPalette
+#include "byteptr.h"
 #include "dehacked.h"
 
 #ifdef _WIN32
@@ -40,6 +41,28 @@
 #include <errno.h>
 #endif
 
+#ifdef HAVE_PNG
+
+#ifndef _MSC_VER
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+#endif
+
+#ifndef _LFS64_LARGEFILE
+#define _LFS64_LARGEFILE
+#endif
+
+#ifndef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 0
+#endif
+
+#include "png.h"
+#ifndef PNG_READ_SUPPORTED
+#undef HAVE_PNG
+#endif
+#endif
+
 //
 // Texture definition.
 // Each texture is composed of one or more patches,
@@ -98,12 +121,11 @@ INT32 numtextures = 0; // total number of textures found,
 // size of following tables
 
 texture_t **textures = NULL;
+textureflat_t *texflats = NULL;
 static UINT32 **texturecolumnofs; // column offset lookup table for each texture
 static UINT8 **texturecache; // graphics data for each generated full-size texture
 
-// texture width is a power of 2, so it can easily repeat along sidedefs using a simple mask
-INT32 *texturewidthmask;
-
+INT32 *texturewidth;
 fixed_t *textureheight; // needed for texture pegging
 
 INT32 *texturetranslation;
@@ -220,15 +242,110 @@ static inline void R_DrawFlippedColumnInCache(column_t *patch, UINT8 *cache, tex
 	}
 }
 
+UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha)
+{
+	RGBA_t output;
+	if (style == AST_TRANSLUCENT)
+	{
+		if (alpha == 0)
+			output.rgba = background.rgba;
+		else if (alpha == 0xFF)
+			output.rgba = foreground.rgba;
+		else if (alpha < 0xFF)
+		{
+			UINT8 beta = (0xFF - alpha);
+			output.s.red = ((background.s.red * beta) + (foreground.s.red * alpha)) / 0xFF;
+			output.s.green = ((background.s.green * beta) + (foreground.s.green * alpha)) / 0xFF;
+			output.s.blue = ((background.s.blue * beta) + (foreground.s.blue * alpha)) / 0xFF;
+		}
+		// write foreground pixel alpha
+		// if there's no pixel in here
+		if (!background.rgba)
+			output.s.alpha = foreground.s.alpha;
+	}
+#define clamp(c) max(min(c, 0xFF), 0x00);
+	else
+	{
+		float falpha = ((float)alpha / 256.0f);
+		float fr = ((float)foreground.s.red * falpha);
+		float fg = ((float)foreground.s.green * falpha);
+		float fb = ((float)foreground.s.blue * falpha);
+		if (style == AST_ADD)
+		{
+			output.s.red = clamp((int)(background.s.red + fr));
+			output.s.green = clamp((int)(background.s.green + fg));
+			output.s.blue = clamp((int)(background.s.blue + fb));
+		}
+		else if (style == AST_SUBTRACT)
+		{
+			output.s.red = clamp((int)(background.s.red - fr));
+			output.s.green = clamp((int)(background.s.green - fg));
+			output.s.blue = clamp((int)(background.s.blue - fb));
+		}
+		else if (style == AST_REVERSESUBTRACT)
+		{
+			output.s.red = clamp((int)((-background.s.red) + fr));
+			output.s.green = clamp((int)((-background.s.green) + fg));
+			output.s.blue = clamp((int)((-background.s.blue) + fb));
+		}
+		else if (style == AST_MODULATE)
+		{
+			fr = ((float)foreground.s.red / 256.0f);
+			fg = ((float)foreground.s.green / 256.0f);
+			fb = ((float)foreground.s.blue / 256.0f);
+			output.s.red = clamp((int)(background.s.red * fr));
+			output.s.green = clamp((int)(background.s.green * fg));
+			output.s.blue = clamp((int)(background.s.blue * fb));
+		}
+		// just copy the pixel
+		else if (style == AST_COPY)
+			output.rgba = foreground.rgba;
+
+		output.s.alpha = 0xFF;
+		return output.rgba;
+	}
+#undef clamp
+	return 0;
+}
+
+UINT8 ASTBlendPixel_8bpp(UINT8 background, UINT8 foreground, int style, UINT8 alpha)
+{
+	if ((style == AST_TRANSLUCENT) && (alpha <= (10*255/11))) // Alpha style set to translucent? Is the alpha small enough for translucency?
+	{
+		UINT8 *mytransmap;
+		if (alpha < 255/11) // Is the patch way too translucent? Don't render then.
+			return background;
+		// The equation's not exact but it works as intended. I'll call it a day for now.
+		mytransmap = transtables + ((8*(alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT);
+		if (background != 0xFF)
+			return *(mytransmap + (background<<8) + foreground);
+	}
+	// just copy the pixel
+	else if (style == AST_COPY)
+		return foreground;
+	// use ASTBlendPixel for all other blend modes
+	// and find the nearest colour in the palette
+	else if (style != AST_TRANSLUCENT)
+	{
+		RGBA_t texel;
+		RGBA_t bg = V_GetColor(background);
+		RGBA_t fg = V_GetColor(foreground);
+		texel.rgba = ASTBlendPixel(bg, fg, style, alpha);
+		return NearestColor(texel.s.red, texel.s.green, texel.s.blue);
+	}
+	// fallback if all above fails, somehow
+	// return the background pixel
+	return background;
+}
+
 //
-// R_DrawTransColumnInCache
+// R_DrawBlendColumnInCache
 // Draws a translucent column into the cache, applying a half-cooked equation to get a proper translucency value (Needs code in R_GenerateTexture()).
 //
-static inline void R_DrawTransColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+static inline void R_DrawBlendColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
 {
 	INT32 count, position;
 	UINT8 *source, *dest;
-	UINT8 *mytransmap = transtables + ((8*(originPatch->alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT); // The equation's not exact but it works as intended. I'll call it a day for now.
 	INT32 topdelta, prevdelta = -1;
 	INT32 originy = originPatch->originy;
 
@@ -258,7 +375,8 @@ static inline void R_DrawTransColumnInCache(column_t *patch, UINT8 *cache, texpa
 		if (count > 0)
 		{
 			for (; dest < cache + position + count; source++, dest++)
-				if (*dest != 0xFF) *dest = *(mytransmap + ((*dest)<<8) + (*source));
+				if (*dest != 0xFF)
+					*dest = ASTBlendPixel_8bpp(*dest, *source, originPatch->style, originPatch->alpha);
 		}
 
 		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
@@ -269,11 +387,10 @@ static inline void R_DrawTransColumnInCache(column_t *patch, UINT8 *cache, texpa
 // R_DrawTransColumnInCache
 // Similar to the one above except that the column is inverted.
 //
-static inline void R_DrawTransFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+static inline void R_DrawBlendFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
 {
 	INT32 count, position;
 	UINT8 *source, *dest;
-	UINT8 *mytransmap = transtables + ((8*(originPatch->alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT); // The equation's not exact but it works as intended. I'll call it a day for now.
 	INT32 topdelta, prevdelta = -1;
 	INT32 originy = originPatch->originy;
 
@@ -302,7 +419,8 @@ static inline void R_DrawTransFlippedColumnInCache(column_t *patch, UINT8 *cache
 		if (count > 0)
 		{
 			for (; dest < cache + position + count; --source, dest++)
-				if (*dest != 0xFF) *dest = *(mytransmap + ((*dest)<<8) + (*source));
+				if (*dest != 0xFF)
+					*dest = ASTBlendPixel_8bpp(*dest, *source, originPatch->style, originPatch->alpha);
 		}
 
 		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
@@ -315,7 +433,7 @@ static inline void R_DrawTransFlippedColumnInCache(column_t *patch, UINT8 *cache
 // Allocate space for full size texture, either single patch or 'composite'
 // Build the full textures from patches.
 // The texture caching system is a little more hungry of memory, but has
-// been simplified for the sake of highcolor, dynamic ligthing, & speed.
+// been simplified for the sake of highcolor (lol), dynamic ligthing, & speed.
 //
 // This is not optimised, but it's supposed to be executed only once
 // per level, when enough memory is available.
@@ -332,6 +450,10 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	column_t *patchcol;
 	UINT32 *colofs;
 
+	UINT16 wadnum;
+	lumpnum_t lumpnum;
+	size_t lumplength;
+
 	I_Assert(texnum <= (size_t)numtextures);
 	texture = textures[texnum];
 	I_Assert(texture != NULL);
@@ -346,7 +468,19 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	{
 		boolean holey = false;
 		patch = texture->patches;
-		realpatch = W_CacheLumpNumPwad(patch->wad, patch->lump, PU_CACHE);
+
+		wadnum = patch->wad;
+		lumpnum = patch->lump;
+		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+		realpatch = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+
+#ifndef NO_PNG_LUMPS
+		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
+		{
+			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL, false);
+			goto multipatch;
+		}
+#endif
 
 		// Check the patch for holes.
 		if (texture->width > SHORT(realpatch->width) || texture->height > SHORT(realpatch->height))
@@ -376,7 +510,7 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 		{
 			texture->holes = true;
 			texture->flip = patch->flip;
-			blocksize = W_LumpLengthPwad(patch->wad, patch->lump);
+			blocksize = lumplength;
 			block = Z_Calloc(blocksize, PU_STATIC, // will change tag at end of this function
 				&texturecache[texnum]);
 			M_Memcpy(block, realpatch, blocksize);
@@ -403,6 +537,9 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	}
 
 	// multi-patch textures (or 'composite')
+#ifndef NO_PNG_LUMPS
+	multipatch:
+#endif
 	texture->holes = false;
 	texture->flip = 0;
 	blocksize = (texture->width * 4) + (texture->width * texture->height);
@@ -422,18 +559,20 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
 	{
 		static void (*ColumnDrawerPointer)(column_t *, UINT8 *, texpatch_t *, INT32, INT32); // Column drawing function pointer.
-		if ((patch->style == AST_TRANSLUCENT) && (patch->alpha <= (10*255/11))) // Alpha style set to translucent? Is the alpha small enough for translucency?
-		{
-			if (patch->alpha < 255/11) // Is the patch way too translucent? Don't render then.
-				continue;
-			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawTransFlippedColumnInCache : R_DrawTransColumnInCache;
-		}
+		if (patch->style != AST_COPY)
+			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawBlendFlippedColumnInCache : R_DrawBlendColumnInCache;
 		else
-		{
 			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawFlippedColumnInCache : R_DrawColumnInCache;
-		}
 
-		realpatch = W_CacheLumpNumPwad(patch->wad, patch->lump, PU_CACHE);
+		wadnum = patch->wad;
+		lumpnum = patch->lump;
+		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+		realpatch = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+#ifndef NO_PNG_LUMPS
+		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
+			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL, false);
+#endif
+
 		x1 = patch->originx;
 		width = SHORT(realpatch->width);
 		height = SHORT(realpatch->height);
@@ -509,10 +648,14 @@ void R_CheckTextureCache(INT32 tex)
 UINT8 *R_GetColumn(fixed_t tex, INT32 col)
 {
 	UINT8 *data;
+	INT32 width = texturewidth[tex];
 
-	col &= texturewidthmask[tex];
-	data = texturecache[tex];
+	if (width & (width - 1))
+		col = (UINT32)col % width;
+	else
+		col &= (width - 1);
 
+	data = texturecache[tex];
 	if (!data)
 		data = R_GenerateTexture(tex);
 
@@ -550,7 +693,7 @@ void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *index);
 #define TX_END "TX_END"
 void R_LoadTextures(void)
 {
-	INT32 i, k, w;
+	INT32 i, w;
 	UINT16 j;
 	UINT16 texstart, texend, texturesLumpPos;
 	patch_t *patchlump;
@@ -567,6 +710,7 @@ void R_LoadTextures(void)
 		}
 		Z_Free(texturetranslation);
 		Z_Free(textures);
+		Z_Free(texflats);
 	}
 
 	// Load patches and textures.
@@ -627,15 +771,16 @@ void R_LoadTextures(void)
 	// 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);
+	texflats = Z_Calloc((numtextures * sizeof(*texflats)), PU_STATIC, NULL);
 
 	// Allocate texture column offset table.
 	texturecolumnofs = (void *)((UINT8 *)textures + (numtextures * sizeof(void *)));
 	// Allocate texture referencing cache.
-	texturecache	 = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 2));
-	// Allocate texture width mask table.
-	texturewidthmask = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 3));
-	// Allocate texture height mask table.
-	textureheight	= (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 4));
+	texturecache     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 2));
+	// Allocate texture width table.
+	texturewidth     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 3));
+	// Allocate texture height table.
+	textureheight    = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 4));
 	// Create translation table for global animation.
 	texturetranslation = Z_Malloc((numtextures + 1) * sizeof(*texturetranslation), PU_STATIC, NULL);
 
@@ -673,20 +818,39 @@ void R_LoadTextures(void)
 		// Work through each lump between the markers in the WAD.
 		for (j = 0; j < (texend - texstart); j++)
 		{
+			UINT16 wadnum = (UINT16)w;
+			lumpnum_t lumpnum = texstart + j;
+			size_t lumplength;
+
 			if (wadfiles[w]->type == RET_PK3)
 			{
-				if (W_IsLumpFolder((UINT16)w, texstart + j)) // Check if lump is a folder
+				if (W_IsLumpFolder(wadnum, lumpnum)) // Check if lump is a folder
 					continue; // If it is then SKIP IT
 			}
-			patchlump = W_CacheLumpNumPwad((UINT16)w, texstart + j, PU_CACHE);
+
+			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+			patchlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
 
 			//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
 			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
 
 			// Set texture properties.
-			M_Memcpy(texture->name, W_CheckNameForNumPwad((UINT16)w, texstart + j), sizeof(texture->name));
-			texture->width = SHORT(patchlump->width);
-			texture->height = SHORT(patchlump->height);
+			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
+
+#ifndef NO_PNG_LUMPS
+			if (R_IsLumpPNG((UINT8 *)patchlump, lumplength))
+			{
+				INT16 width, height;
+				R_PNGDimensions((UINT8 *)patchlump, &width, &height, lumplength);
+				texture->width = width;
+				texture->height = height;
+			}
+			else
+#endif
+			{
+				texture->width = SHORT(patchlump->width);
+				texture->height = SHORT(patchlump->height);
+			}
 			texture->patchcount = 1;
 			texture->holes = false;
 			texture->flip = 0;
@@ -701,11 +865,7 @@ void R_LoadTextures(void)
 
 			Z_Unlock(patchlump);
 
-			k = 1;
-			while (k << 1 <= texture->width)
-				k <<= 1;
-
-			texturewidthmask[i] = k - 1;
+			texturewidth[i] = texture->width;
 			textureheight[i] = texture->height << FRACBITS;
 			i++;
 		}
@@ -848,8 +1008,16 @@ static texpatch_t *R_ParsePatch(boolean actuallyLoadPatch)
 				{
 					Z_Free(texturesToken);
 					texturesToken = M_GetToken(NULL);
-					if(stricmp(texturesToken, "TRANSLUCENT")==0)
+					if (stricmp(texturesToken, "TRANSLUCENT")==0)
 						style = AST_TRANSLUCENT;
+					else if (stricmp(texturesToken, "ADD")==0)
+						style = AST_ADD;
+					else if (stricmp(texturesToken, "SUBTRACT")==0)
+						style = AST_SUBTRACT;
+					else if (stricmp(texturesToken, "REVERSESUBTRACT")==0)
+						style = AST_REVERSESUBTRACT;
+					else if (stricmp(texturesToken, "MODULATE")==0)
+						style = AST_MODULATE;
 				}
 				else if (stricmp(texturesToken, "FLIPX")==0)
 					flip |= 1;
@@ -1097,7 +1265,7 @@ int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum)
 	texturesToken = M_GetToken(texturesText);
 	while (texturesToken != NULL)
 	{
-		if (stricmp(texturesToken, "WALLTEXTURE")==0)
+		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
 		{
 			numTexturesInLump++;
 			Z_Free(texturesToken);
@@ -1105,7 +1273,7 @@ int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum)
 		}
 		else
 		{
-			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\", got \"%s\"",texturesToken);
+			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
 		}
 		texturesToken = M_GetToken(NULL);
 	}
@@ -1146,21 +1314,21 @@ void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *texindex)
 	texturesToken = M_GetToken(texturesText);
 	while (texturesToken != NULL)
 	{
-		if (stricmp(texturesToken, "WALLTEXTURE")==0)
+		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
 		{
 			Z_Free(texturesToken);
 			// Get the new texture
 			newTexture = R_ParseTexture(true);
 			// Store the new texture
 			textures[*texindex] = newTexture;
-			texturewidthmask[*texindex] = newTexture->width - 1;
+			texturewidth[*texindex] = newTexture->width;
 			textureheight[*texindex] = newTexture->height << FRACBITS;
 			// Increment i back in R_LoadTextures()
 			(*texindex)++;
 		}
 		else
 		{
-			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\", got \"%s\"",texturesToken);
+			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
 		}
 		texturesToken = M_GetToken(NULL);
 	}
@@ -1267,6 +1435,41 @@ lumpnum_t R_GetFlatNumForName(const char *name)
 		lump = LUMPERROR;
 	}
 
+	// Detect textures
+	if (lump == LUMPERROR)
+	{
+		// Scan wad files backwards so patched textures take preference.
+		for (i = numwadfiles - 1; i >= 0; i--)
+		{
+			switch (wadfiles[i]->type)
+			{
+			case RET_WAD:
+				if ((start = W_CheckNumForNamePwad("TX_START", (UINT16)i, 0)) == INT16_MAX)
+					continue;
+				if ((end = W_CheckNumForNamePwad("TX_END", (UINT16)i, start)) == INT16_MAX)
+					continue;
+				break;
+			case RET_PK3:
+				if ((start = W_CheckNumForFolderStartPK3("Textures/", i, 0)) == INT16_MAX)
+					continue;
+				if ((end = W_CheckNumForFolderEndPK3("Textures/", i, start)) == INT16_MAX)
+					continue;
+				break;
+			default:
+				continue;
+			}
+
+			// Now find lump with specified name in that range.
+			lump = W_CheckNumForNamePwad(name, (UINT16)i, start);
+			if (lump < end)
+			{
+				lump += (i<<16); // found it, in our constraints
+				break;
+			}
+			lump = LUMPERROR;
+		}
+	}
+
 	if (lump == LUMPERROR)
 	{
 		if (strcmp(name, SKYFLATNAME))
@@ -1615,7 +1818,6 @@ extracolormap_t *R_ColormapForName(char *name)
 //
 static double deltas[256][3], map[256][3];
 
-static UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
 static int RoundUp(double number);
 
 lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
@@ -2027,7 +2229,7 @@ extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *ex
 
 // Thanks to quake2 source!
 // utils3/qdata/images.c
-static UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b)
+UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b)
 {
 	int dr, dg, db;
 	int distortion, bestdistortion = 256 * 256 * 4, bestcolor = 0, i;
@@ -2306,3 +2508,535 @@ void R_PrecacheLevel(void)
 			"texturememory: %s k\n"
 			"spritememory:  %s k\n", sizeu1(flatmemory>>10), sizeu2(texturememory>>10), sizeu3(spritememory>>10));
 }
+
+// https://github.com/coelckers/prboom-plus/blob/master/prboom2/src/r_patch.c#L350
+boolean R_CheckIfPatch(lumpnum_t lump)
+{
+	size_t size;
+	INT16 width, height;
+	patch_t *patch;
+	boolean result;
+
+	size = W_LumpLength(lump);
+
+	// minimum length of a valid Doom patch
+	if (size < 13)
+		return false;
+
+	patch = (patch_t *)W_CacheLumpNum(lump, PU_STATIC);
+
+	width = SHORT(patch->width);
+	height = SHORT(patch->height);
+
+	result = (height > 0 && height <= 16384 && width > 0 && width <= 16384 && width < (INT16)(size / 4));
+
+	if (result)
+	{
+		// The dimensions seem like they might be valid for a patch, so
+		// check the column directory for extra security. All columns
+		// must begin after the column directory, and none of them must
+		// point past the end of the patch.
+		INT16 x;
+
+		for (x = 0; x < width; x++)
+		{
+			UINT32 ofs = LONG(patch->columnofs[x]);
+
+			// Need one byte for an empty column (but there's patches that don't know that!)
+			if (ofs < (UINT32)width * 4 + 8 || ofs >= (UINT32)size)
+			{
+				result = false;
+				break;
+			}
+		}
+	}
+
+	return result;
+}
+
+void R_PatchToFlat(patch_t *patch, UINT8 *flat)
+{
+	fixed_t col, ofs;
+	column_t *column;
+	UINT8 *desttop, *dest, *deststop;
+	UINT8 *source;
+
+	desttop = flat;
+	deststop = desttop + (SHORT(patch->width) * SHORT(patch->height));
+
+	for (col = 0; col < SHORT(patch->width); col++, desttop++)
+	{
+		INT32 topdelta, prevdelta = -1;
+		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[col]));
+
+		while (column->topdelta != 0xff)
+		{
+			topdelta = column->topdelta;
+			if (topdelta <= prevdelta)
+				topdelta += prevdelta;
+			prevdelta = topdelta;
+
+			dest = desttop + (topdelta * SHORT(patch->width));
+			source = (UINT8 *)(column) + 3;
+			for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
+			{
+				*dest = source[ofs];
+				dest += SHORT(patch->width);
+			}
+			column = (column_t *)((UINT8 *)column + column->length + 4);
+		}
+	}
+}
+
+#ifndef NO_PNG_LUMPS
+boolean R_IsLumpPNG(const UINT8 *d, size_t s)
+{
+	if (s < 67) // http://garethrees.org/2007/11/14/pngcrush/
+		return false;
+	// Check for PNG file signature using memcmp
+	// As it may be faster on CPUs with slow unaligned memory access
+	// Ref: http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature
+	return (memcmp(&d[0], "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0);
+}
+
+#ifdef HAVE_PNG
+
+#if PNG_LIBPNG_VER_DLLNUM < 14
+typedef PNG_CONST png_byte *png_const_bytep;
+#endif
+typedef struct {
+	png_const_bytep buffer;
+	png_uint_32 bufsize;
+	png_uint_32 current_pos;
+} png_io_t;
+
+static void PNG_IOReader(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+	png_io_t *f = png_get_io_ptr(png_ptr);
+	if (length > (f->bufsize - f->current_pos))
+		png_error(png_ptr, "PNG_IOReader: buffer overrun");
+	memcpy(data, f->buffer + f->current_pos, length);
+	f->current_pos += length;
+}
+
+typedef struct
+{
+	char name[4];
+	void *data;
+	size_t size;
+} png_chunk_t;
+
+static png_byte *chunkname = NULL;
+static png_chunk_t chunk;
+
+static int PNG_ChunkReader(png_structp png_ptr, png_unknown_chunkp chonk)
+{
+	(void)png_ptr;
+	if (!memcmp(chonk->name, chunkname, 4))
+	{
+		memcpy(chunk.name, chonk->name, 4);
+		chunk.size = chonk->size;
+		chunk.data = Z_Malloc(chunk.size, PU_STATIC, NULL);
+		memcpy(chunk.data, chonk->data, chunk.size);
+		return 1;
+	}
+	return 0;
+}
+
+static void PNG_error(png_structp PNG, png_const_charp pngtext)
+{
+	CONS_Debug(DBG_RENDER, "libpng error at %p: %s", PNG, pngtext);
+	//I_Error("libpng error at %p: %s", PNG, pngtext);
+}
+
+static void PNG_warn(png_structp PNG, png_const_charp pngtext)
+{
+	CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
+}
+
+static png_bytep *PNG_Read(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoffset, INT16 *leftoffset, size_t size)
+{
+	png_structp png_ptr;
+	png_infop png_info_ptr;
+	png_uint_32 width, height;
+	int bit_depth, color_type;
+	png_uint_32 y;
+#ifdef PNG_SETJMP_SUPPORTED
+#ifdef USE_FAR_KEYWORD
+	jmp_buf jmpbuf;
+#endif
+#endif
+
+	png_io_t png_io;
+	png_bytep *row_pointers;
+
+	png_byte grAb_chunk[5] = {'g', 'r', 'A', 'b', (png_byte)'\0'};
+	png_voidp *user_chunk_ptr;
+
+	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
+	if (!png_ptr)
+	{
+		CONS_Debug(DBG_RENDER, "PNG_Load: Error on initialize libpng\n");
+		return NULL;
+	}
+
+	png_info_ptr = png_create_info_struct(png_ptr);
+	if (!png_info_ptr)
+	{
+		CONS_Debug(DBG_RENDER, "PNG_Load: Error on allocate for libpng\n");
+		png_destroy_read_struct(&png_ptr, NULL, NULL);
+		return NULL;
+	}
+
+#ifdef USE_FAR_KEYWORD
+	if (setjmp(jmpbuf))
+#else
+	if (setjmp(png_jmpbuf(png_ptr)))
+#endif
+	{
+		//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
+		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
+		return NULL;
+	}
+#ifdef USE_FAR_KEYWORD
+	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
+#endif
+
+	// set our own read_function
+	png_io.buffer = (png_const_bytep)png;
+	png_io.bufsize = size;
+	png_io.current_pos = 0;
+	png_set_read_fn(png_ptr, &png_io, PNG_IOReader);
+
+	memset(&chunk, 0x00, sizeof(png_chunk_t));
+	chunkname = grAb_chunk; // I want to read a grAb chunk
+
+	user_chunk_ptr = png_get_user_chunk_ptr(png_ptr);
+	png_set_read_user_chunk_fn(png_ptr, user_chunk_ptr, PNG_ChunkReader);
+	png_set_keep_unknown_chunks(png_ptr, 2, chunkname, 1);
+
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+	png_set_user_limits(png_ptr, 2048, 2048);
+#endif
+
+	png_read_info(png_ptr, png_info_ptr);
+	png_get_IHDR(png_ptr, png_info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
+
+	if (bit_depth == 16)
+		png_set_strip_16(png_ptr);
+
+	if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+		png_set_gray_to_rgb(png_ptr);
+	else if (color_type == PNG_COLOR_TYPE_PALETTE)
+		png_set_palette_to_rgb(png_ptr);
+
+	if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_tRNS))
+		png_set_tRNS_to_alpha(png_ptr);
+	else if (color_type != PNG_COLOR_TYPE_RGB_ALPHA && color_type != PNG_COLOR_TYPE_GRAY_ALPHA)
+	{
+#if PNG_LIBPNG_VER < 10207
+		png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
+#else
+		png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+#endif
+	}
+
+	png_read_update_info(png_ptr, png_info_ptr);
+
+	// Read the image
+	row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
+	for (y = 0; y < height; y++)
+		row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, png_info_ptr));
+	png_read_image(png_ptr, row_pointers);
+
+	// Read grAB chunk
+	if ((topoffset || leftoffset) && (chunk.data != NULL))
+	{
+		INT32 *offsets = (INT32 *)chunk.data;
+		// read left offset
+		if (leftoffset != NULL)
+			*leftoffset = (INT16)BIGENDIAN_LONG(*offsets);
+		offsets++;
+		// read top offset
+		if (topoffset != NULL)
+			*topoffset = (INT16)BIGENDIAN_LONG(*offsets);
+	}
+
+	// bye
+	png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
+	if (chunk.data)
+		Z_Free(chunk.data);
+
+	*w = (INT32)width;
+	*h = (INT32)height;
+	return row_pointers;
+}
+
+// Convert a PNG to a raw image.
+static UINT8 *PNG_RawConvert(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoffset, INT16 *leftoffset, size_t size)
+{
+	UINT8 *flat;
+	png_uint_32 x, y;
+	png_bytep *row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, size);
+	png_uint_32 width = *w, height = *h;
+
+	if (!row_pointers)
+		I_Error("PNG_RawConvert: conversion failed");
+
+	// Convert the image to 8bpp
+	flat = Z_Malloc(width * height, PU_LEVEL, NULL);
+	memset(flat, TRANSPARENTPIXEL, width * height);
+	for (y = 0; y < height; y++)
+	{
+		png_bytep row = row_pointers[y];
+		for (x = 0; x < width; x++)
+		{
+			png_bytep px = &(row[x * 4]);
+			if ((UINT8)px[3])
+				flat[((y * width) + x)] = NearestColor((UINT8)px[0], (UINT8)px[1], (UINT8)px[2]);
+		}
+	}
+	free(row_pointers);
+
+	return flat;
+}
+
+// Convert a PNG to a flat.
+UINT8 *R_PNGToFlat(levelflat_t *levelflat, UINT8 *png, size_t size)
+{
+	return PNG_RawConvert(png, &levelflat->width, &levelflat->height, NULL, NULL, size);
+}
+
+// Convert a PNG to a patch.
+static unsigned char imgbuf[1<<26];
+patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize, boolean transparency)
+{
+	UINT16 width, height;
+	INT16 topoffset = 0, leftoffset = 0;
+	UINT8 *raw = PNG_RawConvert(png, &width, &height, &topoffset, &leftoffset, size);
+
+	UINT32 x, y;
+	UINT8 *img;
+	UINT8 *imgptr = imgbuf;
+	UINT8 *colpointers, *startofspan;
+
+	if (!raw)
+		I_Error("R_PNGToPatch: conversion failed");
+
+	// Write image size and offset
+	WRITEINT16(imgptr, width);
+	WRITEINT16(imgptr, height);
+	WRITEINT16(imgptr, leftoffset);
+	WRITEINT16(imgptr, topoffset);
+
+	// Leave placeholder to column pointers
+	colpointers = imgptr;
+	imgptr += width*4;
+
+	// Write columns
+	for (x = 0; x < width; x++)
+	{
+		int lastStartY = 0;
+		int spanSize = 0;
+		startofspan = NULL;
+
+		//printf("%d ", x);
+		// Write column pointer (@TODO may be wrong)
+		WRITEINT32(colpointers, imgptr - imgbuf);
+
+		// Write pixels
+		for (y = 0; y < height; y++)
+		{
+			UINT8 paletteIndex = raw[((y * width) + x)];
+			boolean opaque = transparency ? (paletteIndex != TRANSPARENTPIXEL) : true;
+
+			// End span if we have a transparent pixel
+			if (!opaque)
+			{
+				if (startofspan)
+					WRITEUINT8(imgptr, 0);
+				startofspan = NULL;
+				continue;
+			}
+
+			// Start new column if we need to
+			if (!startofspan || spanSize == 255)
+			{
+				int writeY = y;
+
+				// If we reached the span size limit, finish the previous span
+				if (startofspan)
+					WRITEUINT8(imgptr, 0);
+
+				if (y > 254)
+				{
+					// Make sure we're aligned to 254
+					if (lastStartY < 254)
+					{
+						WRITEUINT8(imgptr, 254);
+						WRITEUINT8(imgptr, 0);
+						imgptr += 2;
+						lastStartY = 254;
+					}
+
+					// Write stopgap empty spans if needed
+					writeY = y - lastStartY;
+
+					while (writeY > 254)
+					{
+						WRITEUINT8(imgptr, 254);
+						WRITEUINT8(imgptr, 0);
+						imgptr += 2;
+						writeY -= 254;
+					}
+				}
+
+				startofspan = imgptr;
+				WRITEUINT8(imgptr, writeY);///@TODO calculate starting y pos
+				imgptr += 2;
+				spanSize = 0;
+
+				lastStartY = y;
+			}
+
+			// Write the pixel
+			WRITEUINT8(imgptr, paletteIndex);
+			spanSize++;
+			startofspan[1] = spanSize;
+		}
+
+		if (startofspan)
+			WRITEUINT8(imgptr, 0);
+
+		WRITEUINT8(imgptr, 0xFF);
+	}
+
+	size = imgptr-imgbuf;
+	img = Z_Malloc(size, PU_STATIC, NULL);
+	memcpy(img, imgbuf, size);
+
+	Z_Free(raw);
+
+	if (destsize != NULL)
+		*destsize = size;
+	return (patch_t *)img;
+}
+
+boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size)
+{
+	png_structp png_ptr;
+	png_infop png_info_ptr;
+	png_uint_32 w, h;
+	int bit_depth, color_type;
+#ifdef PNG_SETJMP_SUPPORTED
+#ifdef USE_FAR_KEYWORD
+	jmp_buf jmpbuf;
+#endif
+#endif
+
+	png_io_t png_io;
+
+	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
+		PNG_error, PNG_warn);
+	if (!png_ptr)
+	{
+		CONS_Debug(DBG_RENDER, "PNG_Load: Error on initialize libpng\n");
+		return false;
+	}
+
+	png_info_ptr = png_create_info_struct(png_ptr);
+	if (!png_info_ptr)
+	{
+		CONS_Debug(DBG_RENDER, "PNG_Load: Error on allocate for libpng\n");
+		png_destroy_read_struct(&png_ptr, NULL, NULL);
+		return false;
+	}
+
+#ifdef USE_FAR_KEYWORD
+	if (setjmp(jmpbuf))
+#else
+	if (setjmp(png_jmpbuf(png_ptr)))
+#endif
+	{
+		//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
+		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
+		return false;
+	}
+#ifdef USE_FAR_KEYWORD
+	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
+#endif
+
+	// set our own read_function
+	png_io.buffer = (png_bytep)png;
+	png_io.bufsize = size;
+	png_io.current_pos = 0;
+	png_set_read_fn(png_ptr, &png_io, PNG_IOReader);
+
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+	png_set_user_limits(png_ptr, 2048, 2048);
+#endif
+
+	png_read_info(png_ptr, png_info_ptr);
+
+	png_get_IHDR(png_ptr, png_info_ptr, &w, &h, &bit_depth, &color_type,
+	 NULL, NULL, NULL);
+
+	// okay done. stop.
+	png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
+
+	*width = (INT32)w;
+	*height = (INT32)h;
+	return true;
+}
+#endif
+#endif
+
+void R_TextureToFlat(size_t tex, UINT8 *flat)
+{
+	texture_t *texture = textures[tex];
+
+	fixed_t col, ofs;
+	column_t *column;
+	UINT8 *desttop, *dest, *deststop;
+	UINT8 *source;
+
+	desttop = flat;
+	deststop = desttop + (texture->width * texture->height);
+
+	for (col = 0; col < texture->width; col++, desttop++)
+	{
+		column = (column_t *)R_GetColumn(tex, col);
+		if (!texture->holes)
+		{
+			dest = desttop;
+			source = (UINT8 *)(column);
+			for (ofs = 0; dest < deststop && ofs < texture->height; ofs++)
+			{
+				if (source[ofs] != TRANSPARENTPIXEL)
+					*dest = source[ofs];
+				dest += texture->width;
+			}
+		}
+		else
+		{
+			INT32 topdelta, prevdelta = -1;
+			while (column->topdelta != 0xff)
+			{
+				topdelta = column->topdelta;
+				if (topdelta <= prevdelta)
+					topdelta += prevdelta;
+				prevdelta = topdelta;
+
+				dest = desttop + (topdelta * texture->width);
+				source = (UINT8 *)(column) + 3;
+				for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
+				{
+					if (source[ofs] != TRANSPARENTPIXEL)
+						*dest = source[ofs];
+					dest += texture->width;
+				}
+				column = (column_t *)((UINT8 *)column + column->length + 4);
+			}
+		}
+	}
+}
diff --git a/src/r_data.h b/src/r_data.h
index b6b0a16a15fad21fb90aa0325721d177a1c6eccc..c2fd284ff016810d5062ebe6523f5eedcb9df025 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -16,13 +16,19 @@
 
 #include "r_defs.h"
 #include "r_state.h"
+#include "p_setup.h" // levelflats
 
 #ifdef __GNUG__
 #pragma interface
 #endif
 
 // Possible alpha types for a patch.
-enum patchalphastyle {AST_COPY, AST_TRANSLUCENT}; // , AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY};
+enum patchalphastyle {AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY};
+
+UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha);
+UINT8 ASTBlendPixel_8bpp(UINT8 background, UINT8 foreground, int style, UINT8 alpha);
+
+UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
 
 // moved here for r_sky.c (texpatch_t is used)
 
@@ -55,12 +61,17 @@ typedef struct
 	texpatch_t patches[0];
 } texture_t;
 
+typedef struct
+{
+	UINT8 *flat;
+	INT16 width, height;
+} textureflat_t;
+
 // all loaded and prepared textures from the start of the game
 extern texture_t **textures;
+extern textureflat_t *texflats;
 
-// texture width is a power of 2, so it can easily repeat along sidedefs using a simple mask
-extern INT32 *texturewidthmask;
-
+extern INT32 *texturewidth;
 extern fixed_t *textureheight; // needed for texture pegging
 
 extern INT16 color8to16[256]; // remap color index to highcolor
@@ -88,7 +99,6 @@ void R_PrecacheLevel(void);
 // Floor/ceiling opaque texture tiles,
 // lookup by name. For animation?
 lumpnum_t R_GetFlatNumForName(const char *name);
-#define R_FlatNumForName(x) R_GetFlatNumForName(x)
 
 // Called by P_Ticker for switches and animations,
 // returns the texture number for the texture name.
@@ -148,6 +158,20 @@ const char *R_NameForColormap(extracolormap_t *extra_colormap);
 #define R_PutRgbaRGB(r, g, b) (R_PutRgbaR(r) + R_PutRgbaG(g) + R_PutRgbaB(b))
 #define R_PutRgbaRGBA(r, g, b, a) (R_PutRgbaRGB(r, g, b) + R_PutRgbaA(a))
 
+boolean R_CheckIfPatch(lumpnum_t lump);
+UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
+
+void R_PatchToFlat(patch_t *patch, UINT8 *flat);
+void R_TextureToFlat(size_t tex, UINT8 *flat);
+
+#ifndef NO_PNG_LUMPS
+boolean R_IsLumpPNG(const UINT8 *d, size_t s);
+
+UINT8 *R_PNGToFlat(levelflat_t *levelflat, UINT8 *png, size_t size);
+patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize, boolean transparency);
+boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size);
+#endif
+
 extern INT32 numtextures;
 
 #endif
diff --git a/src/r_draw.c b/src/r_draw.c
index d8b720caf9e1a18e7824a8bd442dad3e353197a2..1754403c44141a2ca599bddcc28d0ca6ed90ca80 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -99,6 +99,8 @@ INT32 dc_numlights = 0, dc_maxlights, dc_texheight;
 INT32 ds_y, ds_x1, ds_x2;
 lighttable_t *ds_colormap;
 fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep;
+UINT16 ds_flatwidth, ds_flatheight;
+boolean ds_powersoftwo;
 
 UINT8 *ds_source; // start of a 64*64 tile image
 UINT8 *ds_transmap; // one of the translucency tables
@@ -557,9 +559,16 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 
 		// White!
 		if (skinnum == TC_BOSS)
-			dest_colormap[31] = 0;
+		{
+			for (i = 0; i < 16; i++)
+				dest_colormap[31-i] = i;
+		}
 		else if (skinnum == TC_METALSONIC)
-			dest_colormap[159] = 0;
+		{
+			for (i = 0; i < 6; i++)
+				dest_colormap[Color_Index[SKINCOLOR_BLUE-1][12-i]] = Color_Index[SKINCOLOR_BLUE-1][i];
+			dest_colormap[159] = dest_colormap[253] = dest_colormap[254] = 0;
+		}
 		return;
 	}
 	else if (color == SKINCOLOR_NONE)
diff --git a/src/r_draw.h b/src/r_draw.h
index 82498eb11e22d30569e69b2baca059d1acbeb40e..3c142972257c446a29e32fd1c36a810e47c63bc0 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -57,7 +57,9 @@ extern INT32 dc_texheight;
 extern INT32 ds_y, ds_x1, ds_x2;
 extern lighttable_t *ds_colormap;
 extern fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep;
-extern UINT8 *ds_source; // start of a 64*64 tile image
+extern UINT16 ds_flatwidth, ds_flatheight;
+extern boolean ds_powersoftwo;
+extern UINT8 *ds_source;
 extern UINT8 *ds_transmap;
 
 #ifdef ESLOPE
@@ -128,6 +130,8 @@ void R_FillBackScreen(void);
 void R_DrawViewBorder(void);
 #endif
 
+#define TRANSPARENTPIXEL 255
+
 // -----------------
 // 8bpp DRAWING CODE
 // -----------------
@@ -169,6 +173,13 @@ void R_DrawFogSpan_8(void);
 void R_DrawFogColumn_8(void);
 void R_DrawColumnShadowed_8(void);
 
+#ifndef NOWATER
+void R_DrawTranslucentWaterSpan_8(void);
+
+extern INT32 ds_bgofs;
+extern INT32 ds_waterofs;
+#endif
+
 // ------------------
 // 16bpp DRAWING CODE
 // ------------------
diff --git a/src/r_draw8.c b/src/r_draw8.c
index 8a2d37fb3d796d81b7bf1a470d8bfa93971f3d0e..77406f83c6d6420835a6cf11de32e7a7edb27441 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -105,8 +105,6 @@ void R_DrawColumn_8(void)
 	}
 }
 
-#define TRANSPARENTPIXEL 255
-
 void R_Draw2sMultiPatchColumn_8(void)
 {
 	INT32 count;
@@ -543,16 +541,19 @@ void R_DrawTranslatedColumn_8(void)
 */
 void R_DrawSpan_8 (void)
 {
-	UINT32 xposition;
-	UINT32 yposition;
-	UINT32 xstep, ystep;
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
 
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
 	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
 
-	size_t count;
+	size_t count = (ds_x2 - ds_x1 + 1);
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
 
 	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
 	// can be used for the fraction part. This allows calculation of the memory address in the
@@ -561,62 +562,88 @@ void R_DrawSpan_8 (void)
 	// bit per power of two (obviously)
 	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
 	// than the original span renderer. Whodathunkit?
-	xposition = ds_xfrac << nflatshiftup; yposition = ds_yfrac << nflatshiftup;
-	xstep = ds_xstep << nflatshiftup; ystep = ds_ystep << nflatshiftup;
+	if (ds_powersoftwo)
+	{
+		xposition <<= nflatshiftup; yposition <<= nflatshiftup;
+		xstep <<= nflatshiftup; ystep <<= nflatshiftup;
+	}
 
 	source = ds_source;
 	colormap = ds_colormap;
 	dest = ylookup[ds_y] + columnofs[ds_x1];
-	count = ds_x2 - ds_x1 + 1;
 
 	if (dest+8 > deststop)
 		return;
 
-	while (count >= 8)
+	if (!ds_powersoftwo)
 	{
-		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
-		// have the uber complicated math to calculate it now, so that was a memory write we didn't
-		// need!
-		dest[0] = colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[1] = colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[2] = colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[3] = colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[4] = colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[5] = colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[6] = colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[7] = colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest += 8;
-		count -= 8;
+		while (count-- && dest <= deststop)
+		{
+			fixed_t x = (xposition >> FRACBITS);
+			fixed_t y = (yposition >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			*dest++ = colormap[source[((y * ds_flatwidth) + x)]];
+			xposition += xstep;
+			yposition += ystep;
+		}
 	}
-	while (count-- && dest <= deststop)
+	else
 	{
-		*dest++ = colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]];
-		xposition += xstep;
-		yposition += ystep;
+		while (count >= 8)
+		{
+			// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
+			// have the uber complicated math to calculate it now, so that was a memory write we didn't
+			// need!
+			dest[0] = colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]];
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[1] = colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]];
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[2] = colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]];
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[3] = colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]];
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[4] = colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]];
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[5] = colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]];
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[6] = colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]];
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[7] = colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]];
+			xposition += xstep;
+			yposition += ystep;
+
+			dest += 8;
+			count -= 8;
+		}
+		while (count-- && dest <= deststop)
+		{
+			*dest++ = colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]];
+			xposition += xstep;
+			yposition += ystep;
+		}
 	}
 }
 
@@ -697,7 +724,24 @@ void R_DrawTiltedSpan_8(void)
 
 		colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
 
-		*dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]];
+		if (!ds_powersoftwo)
+		{
+			fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+			fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			*dest = colormap[source[((y * ds_flatwidth) + x)]];
+		}
+		else
+			*dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]];
 		dest++;
 		iz += ds_sz.x;
 		uz += ds_su.x;
@@ -734,7 +778,24 @@ void R_DrawTiltedSpan_8(void)
 		for (i = SPANSIZE-1; i >= 0; i--)
 		{
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			*dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]];
+			if (!ds_powersoftwo)
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				*dest = colormap[source[((y * ds_flatwidth) + x)]];
+			}
+			else
+				*dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]];
 			dest++;
 			u += stepu;
 			v += stepv;
@@ -750,7 +811,24 @@ void R_DrawTiltedSpan_8(void)
 			u = (INT64)(startu);
 			v = (INT64)(startv);
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			*dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]];
+			if (!ds_powersoftwo)
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				*dest = colormap[source[((y * ds_flatwidth) + x)]];
+			}
+			else
+				*dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]];
 		}
 		else
 		{
@@ -771,7 +849,24 @@ void R_DrawTiltedSpan_8(void)
 			for (; width != 0; width--)
 			{
 				colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-				*dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]];
+				if (!ds_powersoftwo)
+				{
+					fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+					fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+					// Carefully align all of my Friends.
+					if (x < 0)
+						x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+					if (y < 0)
+						y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+					x %= ds_flatwidth;
+					y %= ds_flatheight;
+
+					*dest = colormap[source[((y * ds_flatwidth) + x)]];
+				}
+				else
+					*dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]];
 				dest++;
 				u += stepu;
 				v += stepv;
@@ -832,7 +927,24 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		v = (INT64)(vz*z) + viewy;
 
 		colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-		*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
+		if (!ds_powersoftwo)
+		{
+			fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+			fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			*dest = *(ds_transmap + (colormap[source[((y * ds_flatwidth) + x)]] << 8) + *dest);
+		}
+		else
+			*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 		dest++;
 		iz += ds_sz.x;
 		uz += ds_su.x;
@@ -869,7 +981,24 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		for (i = SPANSIZE-1; i >= 0; i--)
 		{
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
+			if (!ds_powersoftwo)
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				*dest = *(ds_transmap + (colormap[source[((y * ds_flatwidth) + x)]] << 8) + *dest);
+			}
+			else
+				*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 			dest++;
 			u += stepu;
 			v += stepv;
@@ -885,7 +1014,24 @@ void R_DrawTiltedTranslucentSpan_8(void)
 			u = (INT64)(startu);
 			v = (INT64)(startv);
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
+			if (!ds_powersoftwo)
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				*dest = *(ds_transmap + (colormap[source[((y * ds_flatwidth) + x)]] << 8) + *dest);
+			}
+			else
+				*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 		}
 		else
 		{
@@ -906,7 +1052,24 @@ void R_DrawTiltedTranslucentSpan_8(void)
 			for (; width != 0; width--)
 			{
 				colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-				*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
+				if (!ds_powersoftwo)
+				{
+					fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+					fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+					// Carefully align all of my Friends.
+					if (x < 0)
+						x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+					if (y < 0)
+						y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+					x %= ds_flatwidth;
+					y %= ds_flatheight;
+
+					*dest = *(ds_transmap + (colormap[source[((y * ds_flatwidth) + x)]] << 8) + *dest);
+				}
+				else
+					*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 				dest++;
 				u += stepu;
 				v += stepv;
@@ -967,9 +1130,28 @@ void R_DrawTiltedSplat_8(void)
 
 		colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
 
-		val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+		if (!ds_powersoftwo)
+		{
+			fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+			fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			val = source[((y * ds_flatwidth) + x)];
+		}
+		else
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+
 		if (val != TRANSPARENTPIXEL)
 			*dest = colormap[val];
+
 		dest++;
 		iz += ds_sz.x;
 		uz += ds_su.x;
@@ -1006,7 +1188,24 @@ void R_DrawTiltedSplat_8(void)
 		for (i = SPANSIZE-1; i >= 0; i--)
 		{
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (!ds_powersoftwo)
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+			}
+			else
+				val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
 			if (val != TRANSPARENTPIXEL)
 				*dest = colormap[val];
 			dest++;
@@ -1024,7 +1223,24 @@ void R_DrawTiltedSplat_8(void)
 			u = (INT64)(startu);
 			v = (INT64)(startv);
 			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
-			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (!ds_powersoftwo)
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+			}
+			else
+				val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
 			if (val != TRANSPARENTPIXEL)
 				*dest = colormap[val];
 		}
@@ -1048,6 +1264,24 @@ void R_DrawTiltedSplat_8(void)
 			{
 				colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
 				val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+				if (!ds_powersoftwo)
+				{
+					fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+					fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+					// Carefully align all of my Friends.
+					if (x < 0)
+						x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+					if (y < 0)
+						y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+					x %= ds_flatwidth;
+					y %= ds_flatheight;
+
+					val = source[((y * ds_flatwidth) + x)];
+				}
+				else
+					val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
 				if (val != TRANSPARENTPIXEL)
 					*dest = colormap[val];
 				dest++;
@@ -1065,17 +1299,21 @@ void R_DrawTiltedSplat_8(void)
 */
 void R_DrawSplat_8 (void)
 {
-	UINT32 xposition;
-	UINT32 yposition;
-	UINT32 xstep, ystep;
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
 
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
 
-	size_t count;
+	size_t count = (ds_x2 - ds_x1 + 1);
 	UINT32 val;
 
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
+
 	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
 	// can be used for the fraction part. This allows calculation of the memory address in the
 	// texture with two shifts, an OR and one AND. (see below)
@@ -1083,99 +1321,125 @@ void R_DrawSplat_8 (void)
 	// bit per power of two (obviously)
 	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
 	// than the original span renderer. Whodathunkit?
-	xposition = ds_xfrac << nflatshiftup; yposition = ds_yfrac << nflatshiftup;
-	xstep = ds_xstep << nflatshiftup; ystep = ds_ystep << nflatshiftup;
+	if (ds_powersoftwo)
+	{
+		xposition <<= nflatshiftup; yposition <<= nflatshiftup;
+		xstep <<= nflatshiftup; ystep <<= nflatshiftup;
+	}
 
 	source = ds_source;
 	colormap = ds_colormap;
 	dest = ylookup[ds_y] + columnofs[ds_x1];
-	count = ds_x2 - ds_x1 + 1;
 
-	while (count >= 8)
+	if (!ds_powersoftwo)
 	{
-		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
-		// have the uber complicated math to calculate it now, so that was a memory write we didn't
-		// need!
-		//
-		// <Callum> 4194303 = (2048x2048)-1 (2048x2048 is maximum flat size)
-		val = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift);
-		val &= 4194303;
-		val = source[val];
-		if (val != TRANSPARENTPIXEL)
-			dest[0] = colormap[val];
-		xposition += xstep;
-		yposition += ystep;
+		while (count-- && dest <= deststop)
+		{
+			fixed_t x = (xposition >> FRACBITS);
+			fixed_t y = (yposition >> FRACBITS);
 
-		val = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift);
-		val &= 4194303;
-		val = source[val];
-		if (val != TRANSPARENTPIXEL)
-			dest[1] = colormap[val];
-		xposition += xstep;
-		yposition += ystep;
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
 
-		val = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift);
-		val &= 4194303;
-		val = source[val];
-		if (val != TRANSPARENTPIXEL)
-			dest[2] = colormap[val];
-		xposition += xstep;
-		yposition += ystep;
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
 
-		val = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift);
-		val &= 4194303;
-		val = source[val];
-		if (val != TRANSPARENTPIXEL)
-			dest[3] = colormap[val];
-		xposition += xstep;
-		yposition += ystep;
+			val = source[((y * ds_flatwidth) + x)];
+			if (val != TRANSPARENTPIXEL)
+				*dest = colormap[val];
+			dest++;
+			xposition += xstep;
+			yposition += ystep;
+		}
+	}
+	else
+	{
+		while (count >= 8)
+		{
+			// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
+			// have the uber complicated math to calculate it now, so that was a memory write we didn't
+			// need!
+			//
+			// <Callum> 4194303 = (2048x2048)-1 (2048x2048 is maximum flat size)
+			val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+			val &= 4194303;
+			val = source[val];
+			if (val != TRANSPARENTPIXEL)
+				dest[0] = colormap[val];
+			xposition += xstep;
+			yposition += ystep;
 
-		val = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift);
-		val &= 4194303;
-		val = source[val];
-		if (val != TRANSPARENTPIXEL)
-			dest[4] = colormap[val];
-		xposition += xstep;
-		yposition += ystep;
+			val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+			val &= 4194303;
+			val = source[val];
+			if (val != TRANSPARENTPIXEL)
+				dest[1] = colormap[val];
+			xposition += xstep;
+			yposition += ystep;
 
-		val = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift);
-		val &= 4194303;
-		val = source[val];
-		if (val != TRANSPARENTPIXEL)
-			dest[5] = colormap[val];
-		xposition += xstep;
-		yposition += ystep;
+			val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+			val &= 4194303;
+			val = source[val];
+			if (val != TRANSPARENTPIXEL)
+				dest[2] = colormap[val];
+			xposition += xstep;
+			yposition += ystep;
 
-		val = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift);
-		val &= 4194303;
-		val = source[val];
-		if (val != TRANSPARENTPIXEL)
-			dest[6] = colormap[val];
-		xposition += xstep;
-		yposition += ystep;
+			val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+			val &= 4194303;
+			val = source[val];
+			if (val != TRANSPARENTPIXEL)
+				dest[3] = colormap[val];
+			xposition += xstep;
+			yposition += ystep;
 
-		val = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift);
-		val &= 4194303;
-		val = source[val];
-		if (val != TRANSPARENTPIXEL)
-			dest[7] = colormap[val];
-		xposition += xstep;
-		yposition += ystep;
+			val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+			val &= 4194303;
+			val = source[val];
+			if (val != TRANSPARENTPIXEL)
+				dest[4] = colormap[val];
+			xposition += xstep;
+			yposition += ystep;
 
-		dest += 8;
-		count -= 8;
-	}
-	while (count--)
-	{
-		val = ((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift);
-		val &= 4194303;
-		val = source[val];
-		if (val != TRANSPARENTPIXEL)
-			*dest = colormap[val];
+			val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+			val &= 4194303;
+			val = source[val];
+			if (val != TRANSPARENTPIXEL)
+				dest[5] = colormap[val];
+			xposition += xstep;
+			yposition += ystep;
 
-		dest++;
-		xposition += xstep;
-		yposition += ystep;
+			val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+			val &= 4194303;
+			val = source[val];
+			if (val != TRANSPARENTPIXEL)
+				dest[6] = colormap[val];
+			xposition += xstep;
+			yposition += ystep;
+
+			val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+			val &= 4194303;
+			val = source[val];
+			if (val != TRANSPARENTPIXEL)
+				dest[7] = colormap[val];
+			xposition += xstep;
+			yposition += ystep;
+
+			dest += 8;
+			count -= 8;
+		}
+		while (count-- && dest <= deststop)
+		{
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				*dest = colormap[val];
+			dest++;
+			xposition += xstep;
+			yposition += ystep;
+		}
 	}
 }
 
@@ -1184,16 +1448,20 @@ void R_DrawSplat_8 (void)
 */
 void R_DrawTranslucentSplat_8 (void)
 {
-	UINT32 xposition;
-	UINT32 yposition;
-	UINT32 xstep, ystep;
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
 
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
 
-	size_t count;
-	UINT8 val;
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
 
 	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
 	// can be used for the fraction part. This allows calculation of the memory address in the
@@ -1202,79 +1470,107 @@ void R_DrawTranslucentSplat_8 (void)
 	// bit per power of two (obviously)
 	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
 	// than the original span renderer. Whodathunkit?
-	xposition = ds_xfrac << nflatshiftup; yposition = ds_yfrac << nflatshiftup;
-	xstep = ds_xstep << nflatshiftup; ystep = ds_ystep << nflatshiftup;
+	if (ds_powersoftwo)
+	{
+		xposition <<= nflatshiftup; yposition <<= nflatshiftup;
+		xstep <<= nflatshiftup; ystep <<= nflatshiftup;
+	}
 
 	source = ds_source;
 	colormap = ds_colormap;
 	dest = ylookup[ds_y] + columnofs[ds_x1];
-	count = ds_x2 - ds_x1 + 1;
 
-	while (count >= 8)
+	if (!ds_powersoftwo)
 	{
-		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
-		// have the uber complicated math to calculate it now, so that was a memory write we didn't
-		// need!
-		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
-		if (val != TRANSPARENTPIXEL)
-			dest[0] = *(ds_transmap + (colormap[val] << 8) + dest[0]);
-		xposition += xstep;
-		yposition += ystep;
+		while (count-- && dest <= deststop)
+		{
+			fixed_t x = (xposition >> FRACBITS);
+			fixed_t y = (yposition >> FRACBITS);
 
-		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
-		if (val != TRANSPARENTPIXEL)
-			dest[1] = *(ds_transmap + (colormap[val] << 8) + dest[1]);
-		xposition += xstep;
-		yposition += ystep;
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
 
-		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
-		if (val != TRANSPARENTPIXEL)
-			dest[2] = *(ds_transmap + (colormap[val] << 8) + dest[2]);
-		xposition += xstep;
-		yposition += ystep;
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
 
-		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
-		if (val != TRANSPARENTPIXEL)
-			dest[3] = *(ds_transmap + (colormap[val] << 8) + dest[3]);
-		xposition += xstep;
-		yposition += ystep;
+			val = source[((y * ds_flatwidth) + x)];
+			if (val != TRANSPARENTPIXEL)
+				*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+			dest++;
+			xposition += xstep;
+			yposition += ystep;
+		}
+	}
+	else
+	{
+		while (count >= 8)
+		{
+			// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
+			// have the uber complicated math to calculate it now, so that was a memory write we didn't
+			// need!
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				dest[0] = *(ds_transmap + (colormap[val] << 8) + dest[0]);
+			xposition += xstep;
+			yposition += ystep;
 
-		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
-		if (val != TRANSPARENTPIXEL)
-			dest[4] = *(ds_transmap + (colormap[val] << 8) + dest[4]);
-		xposition += xstep;
-		yposition += ystep;
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				dest[1] = *(ds_transmap + (colormap[val] << 8) + dest[1]);
+			xposition += xstep;
+			yposition += ystep;
 
-		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
-		if (val != TRANSPARENTPIXEL)
-			dest[5] = *(ds_transmap + (colormap[val] << 8) + dest[5]);
-		xposition += xstep;
-		yposition += ystep;
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				dest[2] = *(ds_transmap + (colormap[val] << 8) + dest[2]);
+			xposition += xstep;
+			yposition += ystep;
 
-		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
-		if (val != TRANSPARENTPIXEL)
-			dest[6] = *(ds_transmap + (colormap[val] << 8) + dest[6]);
-		xposition += xstep;
-		yposition += ystep;
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				dest[3] = *(ds_transmap + (colormap[val] << 8) + dest[3]);
+			xposition += xstep;
+			yposition += ystep;
 
-		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
-		if (val != TRANSPARENTPIXEL)
-			dest[7] = *(ds_transmap + (colormap[val] << 8) + dest[7]);
-		xposition += xstep;
-		yposition += ystep;
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				dest[4] = *(ds_transmap + (colormap[val] << 8) + dest[4]);
+			xposition += xstep;
+			yposition += ystep;
 
-		dest += 8;
-		count -= 8;
-	}
-	while (count--)
-	{
-		val = source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)];
-		if (val != TRANSPARENTPIXEL)
-			*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				dest[5] = *(ds_transmap + (colormap[val] << 8) + dest[5]);
+			xposition += xstep;
+			yposition += ystep;
 
-		dest++;
-		xposition += xstep;
-		yposition += ystep;
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				dest[6] = *(ds_transmap + (colormap[val] << 8) + dest[6]);
+			xposition += xstep;
+			yposition += ystep;
+
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				dest[7] = *(ds_transmap + (colormap[val] << 8) + dest[7]);
+			xposition += xstep;
+			yposition += ystep;
+
+			dest += 8;
+			count -= 8;
+		}
+		while (count-- && dest <= deststop)
+		{
+			val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+			dest++;
+			xposition += xstep;
+			yposition += ystep;
+		}
 	}
 }
 
@@ -1283,15 +1579,20 @@ void R_DrawTranslucentSplat_8 (void)
 */
 void R_DrawTranslucentSpan_8 (void)
 {
-	UINT32 xposition;
-	UINT32 yposition;
-	UINT32 xstep, ystep;
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
 
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
 
-	size_t count;
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
 
 	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
 	// can be used for the fraction part. This allows calculation of the memory address in the
@@ -1300,62 +1601,160 @@ void R_DrawTranslucentSpan_8 (void)
 	// bit per power of two (obviously)
 	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
 	// than the original span renderer. Whodathunkit?
-	xposition = ds_xfrac << nflatshiftup; yposition = ds_yfrac << nflatshiftup;
-	xstep = ds_xstep << nflatshiftup; ystep = ds_ystep << nflatshiftup;
+	if (ds_powersoftwo)
+	{
+		xposition <<= nflatshiftup; yposition <<= nflatshiftup;
+		xstep <<= nflatshiftup; ystep <<= nflatshiftup;
+	}
 
 	source = ds_source;
 	colormap = ds_colormap;
 	dest = ylookup[ds_y] + columnofs[ds_x1];
-	count = ds_x2 - ds_x1 + 1;
 
-	while (count >= 8)
+	if (!ds_powersoftwo)
 	{
-		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
-		// have the uber complicated math to calculate it now, so that was a memory write we didn't
-		// need!
-		dest[0] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[0]);
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[1] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[1]);
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[2] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[2]);
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[3] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[3]);
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[4] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[4]);
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[5] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[5]);
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[6] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[6]);
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[7] = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + dest[7]);
-		xposition += xstep;
-		yposition += ystep;
-
-		dest += 8;
-		count -= 8;
+		while (count-- && dest <= deststop)
+		{
+			fixed_t x = (xposition >> FRACBITS);
+			fixed_t y = (yposition >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			val = ((y * ds_flatwidth) + x);
+			*dest = *(ds_transmap + (colormap[source[val]] << 8) + *dest);
+			dest++;
+			xposition += xstep;
+			yposition += ystep;
+		}
 	}
-	while (count--)
+	else
 	{
-		*dest = *(ds_transmap + (colormap[source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)]] << 8) + *dest);
-		dest++;
-		xposition += xstep;
-		yposition += ystep;
+		while (count >= 8)
+		{
+			// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
+			// have the uber complicated math to calculate it now, so that was a memory write we didn't
+			// need!
+			dest[0] = *(ds_transmap + (colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]] << 8) + dest[0]);
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[1] = *(ds_transmap + (colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]] << 8) + dest[1]);
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[2] = *(ds_transmap + (colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]] << 8) + dest[2]);
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[3] = *(ds_transmap + (colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]] << 8) + dest[3]);
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[4] = *(ds_transmap + (colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]] << 8) + dest[4]);
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[5] = *(ds_transmap + (colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]] << 8) + dest[5]);
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[6] = *(ds_transmap + (colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]] << 8) + dest[6]);
+			xposition += xstep;
+			yposition += ystep;
+
+			dest[7] = *(ds_transmap + (colormap[source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)]] << 8) + dest[7]);
+			xposition += xstep;
+			yposition += ystep;
+
+			dest += 8;
+			count -= 8;
+		}
+		while (count-- && dest <= deststop)
+		{
+			val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+			*dest = *(ds_transmap + (colormap[source[val]] << 8) + *dest);
+			dest++;
+			xposition += xstep;
+			yposition += ystep;
+		}
+	}
+}
+
+#ifndef NOWATER
+void R_DrawTranslucentWaterSpan_8(void)
+{
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
+
+	UINT8 *source;
+	UINT8 *colormap;
+	UINT8 *dest;
+	UINT8 *dsrc;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+
+	size_t count = (ds_x2 - ds_x1 + 1);
+
+	xposition = ds_xfrac; yposition = (ds_yfrac + ds_waterofs);
+	xstep = ds_xstep; ystep = ds_ystep;
+
+	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
+	// can be used for the fraction part. This allows calculation of the memory address in the
+	// texture with two shifts, an OR and one AND. (see below)
+	// for texture sizes > 64 the amount of precision we can allow will decrease, but only by one
+	// bit per power of two (obviously)
+	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
+	// than the original span renderer. Whodathunkit?
+	if (ds_powersoftwo)
+	{
+		xposition <<= nflatshiftup; yposition <<= nflatshiftup;
+		xstep <<= nflatshiftup; ystep <<= nflatshiftup;
+	}
+
+	source = ds_source;
+	colormap = ds_colormap;
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
+
+	if (!ds_powersoftwo)
+	{
+		while (count-- && dest <= deststop)
+		{
+			fixed_t x = (xposition >> FRACBITS);
+			fixed_t y = (yposition >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			*dest++ = colormap[*(ds_transmap + (source[((y * ds_flatwidth) + x)] << 8) + *dsrc++)];
+			xposition += xstep;
+			yposition += ystep;
+		}
+	}
+	else
+	{
+		while (count-- && dest <= deststop)
+		{
+			*dest++ = colormap[*(ds_transmap + (source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)] << 8) + *dsrc++)];
+			xposition += xstep;
+			yposition += ystep;
+		}
 	}
 }
+#endif
 
 /**	\brief The R_DrawFogSpan_8 function
 	Draws the actual span with fogging.
diff --git a/src/r_main.c b/src/r_main.c
index db351e9911dc45675f0168b818890dede6fbab93..9cce412888ccba4c04b8c30925eb6fcd66501bed 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1182,6 +1182,8 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_cam_speed);
 	CV_RegisterVar(&cv_cam_rotate);
 	CV_RegisterVar(&cv_cam_rotspeed);
+	CV_RegisterVar(&cv_cam_orbit);
+	CV_RegisterVar(&cv_cam_adjust);
 
 	CV_RegisterVar(&cv_cam2_dist);
 	CV_RegisterVar(&cv_cam2_still);
@@ -1189,6 +1191,8 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_cam2_speed);
 	CV_RegisterVar(&cv_cam2_rotate);
 	CV_RegisterVar(&cv_cam2_rotspeed);
+	CV_RegisterVar(&cv_cam2_orbit);
+	CV_RegisterVar(&cv_cam2_adjust);
 
 	CV_RegisterVar(&cv_showhud);
 	CV_RegisterVar(&cv_translucenthud);
diff --git a/src/r_plane.c b/src/r_plane.c
index 2f6f97240a0418f3608d1901a8406b80716cf10f..db5fb0f249d690f70dacabaaca4e87767f7576d4 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -127,91 +127,13 @@ void R_InitPlanes(void)
 //  viewheight
 
 #ifndef NOWATER
-static INT32 bgofs;
+INT32 ds_bgofs;
+INT32 ds_waterofs;
+
 static INT32 wtofs=0;
-static INT32 waterofs;
 static boolean itswater;
 #endif
 
-#ifndef NOWATER
-static void R_DrawTranslucentWaterSpan_8(void)
-{
-	UINT32 xposition;
-	UINT32 yposition;
-	UINT32 xstep, ystep;
-
-	UINT8 *source;
-	UINT8 *colormap;
-	UINT8 *dest;
-	UINT8 *dsrc;
-
-	size_t count;
-
-	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
-	// can be used for the fraction part. This allows calculation of the memory address in the
-	// texture with two shifts, an OR and one AND. (see below)
-	// for texture sizes > 64 the amount of precision we can allow will decrease, but only by one
-	// bit per power of two (obviously)
-	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
-	// than the original span renderer. Whodathunkit?
-	xposition = ds_xfrac << nflatshiftup; yposition = (ds_yfrac + waterofs) << nflatshiftup;
-	xstep = ds_xstep << nflatshiftup; ystep = ds_ystep << nflatshiftup;
-
-	source = ds_source;
-	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
-	dsrc = screens[1] + (ds_y+bgofs)*vid.width + ds_x1;
-	count = ds_x2 - ds_x1 + 1;
-
-	while (count >= 8)
-	{
-		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
-		// have the uber complicated math to calculate it now, so that was a memory write we didn't
-		// need!
-		dest[0] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dsrc++)];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[1] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dsrc++)];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[2] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dsrc++)];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[3] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dsrc++)];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[4] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dsrc++)];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[5] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dsrc++)];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[6] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dsrc++)];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest[7] = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dsrc++)];
-		xposition += xstep;
-		yposition += ystep;
-
-		dest += 8;
-		count -= 8;
-	}
-	while (count--)
-	{
-		*dest++ = colormap[*(ds_transmap + (source[((yposition >> nflatyshift) & nflatmask) | (xposition >> nflatxshift)] << 8) + *dsrc++)];
-		xposition += xstep;
-		yposition += ystep;
-	}
-}
-#endif
-
 void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 {
 	angle_t angle, planecos, planesin;
@@ -258,17 +180,17 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 	{
 		const INT32 yay = (wtofs + (distance>>9) ) & 8191;
 		// ripples da water texture
-		bgofs = FixedDiv(FINESINE(yay), (1<<12) + (distance>>11))>>FRACBITS;
+		ds_bgofs = FixedDiv(FINESINE(yay), (1<<12) + (distance>>11))>>FRACBITS;
 		angle = (currentplane->viewangle + currentplane->plangle + xtoviewangle[x1])>>ANGLETOFINESHIFT;
 
 		angle = (angle + 2048) & 8191;  // 90 degrees
-		ds_xfrac += FixedMul(FINECOSINE(angle), (bgofs<<FRACBITS));
-		ds_yfrac += FixedMul(FINESINE(angle), (bgofs<<FRACBITS));
+		ds_xfrac += FixedMul(FINECOSINE(angle), (ds_bgofs<<FRACBITS));
+		ds_yfrac += FixedMul(FINESINE(angle), (ds_bgofs<<FRACBITS));
 
-		if (y+bgofs>=viewheight)
-			bgofs = viewheight-y-1;
-		if (y+bgofs<0)
-			bgofs = -y;
+		if (y+ds_bgofs>=viewheight)
+			ds_bgofs = viewheight-y-1;
+		if (y+ds_bgofs<0)
+			ds_bgofs = -y;
 	}
 #endif
 
@@ -680,7 +602,7 @@ void R_DrawPlanes(void)
 		}
 	}
 #ifndef NOWATER
-	waterofs = (leveltime & 1)*16384;
+	ds_waterofs = (leveltime & 1)*16384;
 	wtofs = leveltime * 140;
 #endif
 }
@@ -728,13 +650,169 @@ static void R_DrawSkyPlane(visplane_t *pl)
 	}
 }
 
+boolean R_CheckPowersOfTwo(void)
+{
+	boolean wpow2 = (!(ds_flatwidth & (ds_flatwidth - 1)));
+	boolean hpow2 = (!(ds_flatheight & (ds_flatheight - 1)));
+
+	// Initially, the flat isn't powers-of-two-sized.
+	ds_powersoftwo = false;
+
+	// But if the width and height are powers of two,
+	// and are EQUAL, then it's okay :]
+	if ((ds_flatwidth == ds_flatheight) && (wpow2 && hpow2))
+		ds_powersoftwo = true;
+
+	// Just return ds_powersoftwo.
+	return ds_powersoftwo;
+}
+
+void R_CheckFlatLength(size_t size)
+{
+	switch (size)
+	{
+		case 4194304: // 2048x2048 lump
+			nflatmask = 0x3FF800;
+			nflatxshift = 21;
+			nflatyshift = 10;
+			nflatshiftup = 5;
+			ds_flatwidth = ds_flatheight = 2048;
+			break;
+		case 1048576: // 1024x1024 lump
+			nflatmask = 0xFFC00;
+			nflatxshift = 22;
+			nflatyshift = 12;
+			nflatshiftup = 6;
+			ds_flatwidth = ds_flatheight = 1024;
+			break;
+		case 262144:// 512x512 lump
+			nflatmask = 0x3FE00;
+			nflatxshift = 23;
+			nflatyshift = 14;
+			nflatshiftup = 7;
+			ds_flatwidth = ds_flatheight = 512;
+			break;
+		case 65536: // 256x256 lump
+			nflatmask = 0xFF00;
+			nflatxshift = 24;
+			nflatyshift = 16;
+			nflatshiftup = 8;
+			ds_flatwidth = ds_flatheight = 256;
+			break;
+		case 16384: // 128x128 lump
+			nflatmask = 0x3F80;
+			nflatxshift = 25;
+			nflatyshift = 18;
+			nflatshiftup = 9;
+			ds_flatwidth = ds_flatheight = 128;
+			break;
+		case 1024: // 32x32 lump
+			nflatmask = 0x3E0;
+			nflatxshift = 27;
+			nflatyshift = 22;
+			nflatshiftup = 11;
+			ds_flatwidth = ds_flatheight = 32;
+			break;
+		default: // 64x64 lump
+			nflatmask = 0xFC0;
+			nflatxshift = 26;
+			nflatyshift = 20;
+			nflatshiftup = 10;
+			ds_flatwidth = ds_flatheight = 64;
+			break;
+	}
+}
+
+static UINT8 *R_GetPatchFlat(levelflat_t *levelflat, boolean leveltexture, boolean ispng)
+{
+	UINT8 *flat;
+	textureflat_t *texflat = &texflats[levelflat->texturenum];
+	patch_t *patch = NULL;
+	boolean texturechanged = (leveltexture ? (levelflat->texturenum != levelflat->lasttexturenum) : false);
+
+	// Check if the texture changed.
+	if (leveltexture && (!texturechanged))
+	{
+		if (texflat != NULL && texflat->flat)
+		{
+			flat = texflat->flat;
+			ds_flatwidth = texflat->width;
+			ds_flatheight = texflat->height;
+			texturechanged = false;
+		}
+		else
+			texturechanged = true;
+	}
+
+	// If the texture changed, or the patch doesn't exist, convert either of them to a flat.
+	if (levelflat->flatpatch == NULL || texturechanged)
+	{
+		if (leveltexture)
+		{
+			texture_t *texture = textures[levelflat->texturenum];
+			texflat->width = ds_flatwidth = texture->width;
+			texflat->height = ds_flatheight = texture->height;
+
+			texflat->flat = Z_Malloc(ds_flatwidth * ds_flatheight, PU_LEVEL, NULL);
+			memset(texflat->flat, TRANSPARENTPIXEL, ds_flatwidth * ds_flatheight);
+			R_TextureToFlat(levelflat->texturenum, texflat->flat);
+			flat = texflat->flat;
+
+			levelflat->flatpatch = flat;
+			levelflat->width = ds_flatwidth;
+			levelflat->height = ds_flatheight;
+		}
+		else
+		{
+			patch = (patch_t *)ds_source;
+#ifndef NO_PNG_LUMPS
+			if (ispng)
+			{
+				levelflat->flatpatch = R_PNGToFlat(levelflat, ds_source, W_LumpLength(levelflat->lumpnum));
+				levelflat->topoffset = levelflat->leftoffset = 0;
+				ds_flatwidth = levelflat->width;
+				ds_flatheight = levelflat->height;
+			}
+			else
+#endif
+			{
+				levelflat->width = ds_flatwidth = SHORT(patch->width);
+				levelflat->height = ds_flatheight = SHORT(patch->height);
+
+				levelflat->topoffset = patch->topoffset * FRACUNIT;
+				levelflat->leftoffset = patch->leftoffset * FRACUNIT;
+
+				levelflat->flatpatch = Z_Malloc(ds_flatwidth * ds_flatheight, PU_LEVEL, NULL);
+				memset(levelflat->flatpatch, TRANSPARENTPIXEL, ds_flatwidth * ds_flatheight);
+				R_PatchToFlat(patch, levelflat->flatpatch);
+			}
+			flat = levelflat->flatpatch;
+		}
+	}
+	else
+	{
+		flat = levelflat->flatpatch;
+		ds_flatwidth = levelflat->width;
+		ds_flatheight = levelflat->height;
+
+		xoffs += levelflat->leftoffset;
+		yoffs += levelflat->topoffset;
+	}
+
+	levelflat->lasttexturenum = levelflat->texturenum;
+	return flat;
+}
+
 void R_DrawSinglePlane(visplane_t *pl)
 {
+	UINT8 *flat;
 	INT32 light = 0;
 	INT32 x;
 	INT32 stop, angle;
 	size_t size;
 	ffloor_t *rover;
+	levelflat_t *levelflat;
+	boolean rawflat = false;
 
 	if (!(pl->minx <= pl->maxx))
 		return;
@@ -874,64 +952,51 @@ void R_DrawSinglePlane(visplane_t *pl)
 		viewangle = pl->viewangle+pl->plangle;
 	}
 
+	xoffs = pl->xoffs;
+	yoffs = pl->yoffs;
+	planeheight = abs(pl->height - pl->viewz);
+
 	currentplane = pl;
+	levelflat = &levelflats[pl->picnum];
+	size = W_LumpLength(levelflat->lumpnum);
+	ds_source = (UINT8 *)W_CacheLumpNum(levelflat->lumpnum, PU_STATIC); // Stay here until Z_ChangeTag
+
+	// Check if the flat is actually a wall texture.
+	if (levelflat->texturenum != 0 && levelflat->texturenum != -1)
+		flat = R_GetPatchFlat(levelflat, true, false);
+#ifndef NO_PNG_LUMPS
+	// Maybe it's a PNG?!
+	else if (R_IsLumpPNG(ds_source, size))
+		flat = R_GetPatchFlat(levelflat, false, true);
+#endif
+	// Maybe it's just a patch, then?
+	else if (R_CheckIfPatch(levelflat->lumpnum))
+		flat = R_GetPatchFlat(levelflat, false, false);
+	// It's a raw flat.
+	else
+	{
+		rawflat = true;
+		R_CheckFlatLength(size);
+		flat = ds_source;
+	}
 
-	ds_source = (UINT8 *)
-		W_CacheLumpNum(levelflats[pl->picnum].lumpnum,
-			PU_STATIC); // Stay here until Z_ChangeTag
+	Z_ChangeTag(ds_source, PU_CACHE);
+	ds_source = flat;
 
-	size = W_LumpLength(levelflats[pl->picnum].lumpnum);
+	if (ds_source == NULL)
+		return;
 
-	switch (size)
+	// Raw flats always have dimensions that are powers-of-two numbers.
+	if (rawflat)
+		ds_powersoftwo = true;
+	// Otherwise, check if this texture or patch has such dimensions.
+	else if (R_CheckPowersOfTwo())
 	{
-		case 4194304: // 2048x2048 lump
-			nflatmask = 0x3FF800;
-			nflatxshift = 21;
-			nflatyshift = 10;
-			nflatshiftup = 5;
-			break;
-		case 1048576: // 1024x1024 lump
-			nflatmask = 0xFFC00;
-			nflatxshift = 22;
-			nflatyshift = 12;
-			nflatshiftup = 6;
-			break;
-		case 262144:// 512x512 lump'
-			nflatmask = 0x3FE00;
-			nflatxshift = 23;
-			nflatyshift = 14;
-			nflatshiftup = 7;
-			break;
-		case 65536: // 256x256 lump
-			nflatmask = 0xFF00;
-			nflatxshift = 24;
-			nflatyshift = 16;
-			nflatshiftup = 8;
-			break;
-		case 16384: // 128x128 lump
-			nflatmask = 0x3F80;
-			nflatxshift = 25;
-			nflatyshift = 18;
-			nflatshiftup = 9;
-			break;
-		case 1024: // 32x32 lump
-			nflatmask = 0x3E0;
-			nflatxshift = 27;
-			nflatyshift = 22;
-			nflatshiftup = 11;
-			break;
-		default: // 64x64 lump
-			nflatmask = 0xFC0;
-			nflatxshift = 26;
-			nflatyshift = 20;
-			nflatshiftup = 10;
-			break;
+		R_CheckFlatLength(ds_flatwidth * ds_flatheight);
+		if (spanfunc == basespanfunc)
+			spanfunc = mmxspanfunc;
 	}
 
-	xoffs = pl->xoffs;
-	yoffs = pl->yoffs;
-	planeheight = abs(pl->height - pl->viewz);
-
 	if (light >= LIGHTLEVELS)
 		light = LIGHTLEVELS-1;
 
@@ -945,61 +1010,64 @@ void R_DrawSinglePlane(visplane_t *pl)
 		floatv3_t p, m, n;
 		float ang;
 		float vx, vy, vz;
+		float fudge = 0;
 		// compiler complains when P_GetZAt is used in FLOAT_TO_FIXED directly
 		// use this as a temp var to store P_GetZAt's return value each time
 		fixed_t temp;
-		// Okay, look, don't ask me why this works, but without this setup there's a disgusting-looking misalignment with the textures. -Red
-		const float fudge = ((1<<nflatshiftup)+1.0f)/(1<<nflatshiftup);
 
 		angle_t hack = (pl->plangle & (ANGLE_90-1));
 
 		yoffs *= 1;
 
-		if (hack)
-		{
-			/*
-			Essentially: We can't & the components along the regular axes when the plane is rotated.
-			This is because the distance on each regular axis in order to loop is different.
-			We rotate them, & the components, add them together, & them again, and then rotate them back.
-			These three seperate & operations are done per axis in order to prevent overflows.
-			toast 10/04/17
-			*/
-			const fixed_t cosinecomponent = FINECOSINE(hack>>ANGLETOFINESHIFT);
-			const fixed_t sinecomponent = FINESINE(hack>>ANGLETOFINESHIFT);
-
-			const fixed_t modmask = ((1 << (32-nflatshiftup)) - 1);
-
-			fixed_t ox = (FixedMul(pl->slope->o.x,cosinecomponent) & modmask) - (FixedMul(pl->slope->o.y,sinecomponent) & modmask);
-			fixed_t oy = (-FixedMul(pl->slope->o.x,sinecomponent) & modmask) - (FixedMul(pl->slope->o.y,cosinecomponent) & modmask);
-
-			temp = ox & modmask;
-			oy &= modmask;
-			ox = FixedMul(temp,cosinecomponent)+FixedMul(oy,-sinecomponent); // negative sine for opposite direction
-			oy = -FixedMul(temp,-sinecomponent)+FixedMul(oy,cosinecomponent);
-
-			temp = xoffs;
-			xoffs = (FixedMul(temp,cosinecomponent) & modmask) + (FixedMul(yoffs,sinecomponent) & modmask);
-			yoffs = (-FixedMul(temp,sinecomponent) & modmask) + (FixedMul(yoffs,cosinecomponent) & modmask);
-
-			temp = xoffs & modmask;
-			yoffs &= modmask;
-			xoffs = FixedMul(temp,cosinecomponent)+FixedMul(yoffs,-sinecomponent); // ditto
-			yoffs = -FixedMul(temp,-sinecomponent)+FixedMul(yoffs,cosinecomponent);
-
-			xoffs -= (pl->slope->o.x - ox);
-			yoffs += (pl->slope->o.y + oy);
-		}
-		else
+		if (ds_powersoftwo)
 		{
-			xoffs &= ((1 << (32-nflatshiftup))-1);
-			yoffs &= ((1 << (32-nflatshiftup))-1);
-			xoffs -= (pl->slope->o.x + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
-			yoffs += (pl->slope->o.y + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
+			// Okay, look, don't ask me why this works, but without this setup there's a disgusting-looking misalignment with the textures. -Red
+			fudge = ((1<<nflatshiftup)+1.0f)/(1<<nflatshiftup);
+			if (hack)
+			{
+				/*
+				Essentially: We can't & the components along the regular axes when the plane is rotated.
+				This is because the distance on each regular axis in order to loop is different.
+				We rotate them, & the components, add them together, & them again, and then rotate them back.
+				These three seperate & operations are done per axis in order to prevent overflows.
+				toast 10/04/17
+				*/
+				const fixed_t cosinecomponent = FINECOSINE(hack>>ANGLETOFINESHIFT);
+				const fixed_t sinecomponent = FINESINE(hack>>ANGLETOFINESHIFT);
+
+				const fixed_t modmask = ((1 << (32-nflatshiftup)) - 1);
+
+				fixed_t ox = (FixedMul(pl->slope->o.x,cosinecomponent) & modmask) - (FixedMul(pl->slope->o.y,sinecomponent) & modmask);
+				fixed_t oy = (-FixedMul(pl->slope->o.x,sinecomponent) & modmask) - (FixedMul(pl->slope->o.y,cosinecomponent) & modmask);
+
+				temp = ox & modmask;
+				oy &= modmask;
+				ox = FixedMul(temp,cosinecomponent)+FixedMul(oy,-sinecomponent); // negative sine for opposite direction
+				oy = -FixedMul(temp,-sinecomponent)+FixedMul(oy,cosinecomponent);
+
+				temp = xoffs;
+				xoffs = (FixedMul(temp,cosinecomponent) & modmask) + (FixedMul(yoffs,sinecomponent) & modmask);
+				yoffs = (-FixedMul(temp,sinecomponent) & modmask) + (FixedMul(yoffs,cosinecomponent) & modmask);
+
+				temp = xoffs & modmask;
+				yoffs &= modmask;
+				xoffs = FixedMul(temp,cosinecomponent)+FixedMul(yoffs,-sinecomponent); // ditto
+				yoffs = -FixedMul(temp,-sinecomponent)+FixedMul(yoffs,cosinecomponent);
+
+				xoffs -= (pl->slope->o.x - ox);
+				yoffs += (pl->slope->o.y + oy);
+			}
+			else
+			{
+				xoffs &= ((1 << (32-nflatshiftup))-1);
+				yoffs &= ((1 << (32-nflatshiftup))-1);
+				xoffs -= (pl->slope->o.x + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
+				yoffs += (pl->slope->o.y + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
+			}
+			xoffs = (fixed_t)(xoffs*fudge);
+			yoffs = (fixed_t)(yoffs/fudge);
 		}
 
-		xoffs = (fixed_t)(xoffs*fudge);
-		yoffs = (fixed_t)(yoffs/fudge);
-
 		vx = FIXED_TO_FLOAT(pl->viewx+xoffs);
 		vy = FIXED_TO_FLOAT(pl->viewy-yoffs);
 		vz = FIXED_TO_FLOAT(pl->viewz);
@@ -1033,13 +1101,16 @@ void R_DrawSinglePlane(visplane_t *pl)
 		temp = P_GetZAt(pl->slope, pl->viewx + FLOAT_TO_FIXED(cos(ang)), pl->viewy - FLOAT_TO_FIXED(sin(ang)));
 		n.y = FIXED_TO_FLOAT(temp) - zeroheight;
 
-		m.x /= fudge;
-		m.y /= fudge;
-		m.z /= fudge;
+		if (ds_powersoftwo)
+		{
+			m.x /= fudge;
+			m.y /= fudge;
+			m.z /= fudge;
 
-		n.x *= fudge;
-		n.y *= fudge;
-		n.z *= fudge;
+			n.x *= fudge;
+			n.y *= fudge;
+			n.z *= fudge;
+		}
 
 		// Eh. I tried making this stuff fixed-point and it exploded on me. Here's a macro for the only floating-point vector function I recall using.
 #define CROSS(d, v1, v2) \
@@ -1056,13 +1127,26 @@ void R_DrawSinglePlane(visplane_t *pl)
 		ds_sz.z *= focallengthf;
 
 		// Premultiply the texture vectors with the scale factors
-#define SFMULT 65536.f*(1<<nflatshiftup)
-		ds_su.x *= SFMULT;
-		ds_su.y *= SFMULT;
-		ds_su.z *= SFMULT;
-		ds_sv.x *= SFMULT;
-		ds_sv.y *= SFMULT;
-		ds_sv.z *= SFMULT;
+#define SFMULT 65536.f
+		if (ds_powersoftwo)
+		{
+			ds_su.x *= (SFMULT * (1<<nflatshiftup));
+			ds_su.y *= (SFMULT * (1<<nflatshiftup));
+			ds_su.z *= (SFMULT * (1<<nflatshiftup));
+			ds_sv.x *= (SFMULT * (1<<nflatshiftup));
+			ds_sv.y *= (SFMULT * (1<<nflatshiftup));
+			ds_sv.z *= (SFMULT * (1<<nflatshiftup));
+		}
+		else
+		{
+			// I'm essentially multiplying the vectors by FRACUNIT...
+			ds_su.x *= SFMULT;
+			ds_su.y *= SFMULT;
+			ds_su.z *= SFMULT;
+			ds_sv.x *= SFMULT;
+			ds_sv.y *= SFMULT;
+			ds_sv.z *= SFMULT;
+		}
 #undef SFMULT
 
 		if (spanfunc == R_DrawTranslucentSpan_8)
@@ -1170,8 +1254,6 @@ using the palette colors.
 		}
 	}
 #endif
-
-	Z_ChangeTag(ds_source, PU_CACHE);
 }
 
 void R_PlaneBounds(visplane_t *plane)
diff --git a/src/r_plane.h b/src/r_plane.h
index 238fde1827846f0be8278f6ee6a78e21aaf2c52b..11cbb56f1bcbffb94019630eaff2811f90c30e3a 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -96,6 +96,8 @@ void R_PlaneBounds(visplane_t *plane);
 
 // Draws a single visplane.
 void R_DrawSinglePlane(visplane_t *pl);
+void R_CheckFlatLength(size_t size);
+boolean R_CheckPowersOfTwo(void);
 
 typedef struct planemgr_s
 {
diff --git a/src/r_things.c b/src/r_things.c
index 92f2b946020bde6317faf9f52102cdab58f10d2d..43e006a30ac6bcaaee6529cbf03690b7404da799 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -254,6 +254,19 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 			// store sprite info in lookup tables
 			//FIXME : numspritelumps do not duplicate sprite replacements
 			W_ReadLumpHeaderPwad(wadnum, l, &patch, sizeof (patch_t), 0);
+#ifndef NO_PNG_LUMPS
+			{
+				patch_t *png = W_CacheLumpNumPwad(wadnum, l, PU_STATIC);
+				size_t len = W_LumpLengthPwad(wadnum, l);
+				// lump is a png so convert it
+				if (R_IsLumpPNG((UINT8 *)png, len))
+				{
+					png = R_PNGToPatch((UINT8 *)png, len, NULL, true);
+					M_Memcpy(&patch, png, sizeof(INT16)*4);
+				}
+				Z_Free(png);
+			}
+#endif
 			spritecachedinfo[numspritelumps].width = SHORT(patch.width)<<FRACBITS;
 			spritecachedinfo[numspritelumps].offset = SHORT(patch.leftoffset)<<FRACBITS;
 			spritecachedinfo[numspritelumps].topoffset = SHORT(patch.topoffset)<<FRACBITS;
@@ -701,7 +714,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	INT32 texturecolumn;
 #endif
 	fixed_t frac;
-	patch_t *patch = W_CacheLumpNum(vis->patch, PU_CACHE);
+	patch_t *patch = W_CachePatchNum(vis->patch, PU_CACHE);
 	fixed_t this_scale = vis->mobj->scale;
 	INT32 x1, x2;
 	INT64 overflow_test;
@@ -870,7 +883,7 @@ static void R_DrawPrecipitationVisSprite(vissprite_t *vis)
 	INT64 overflow_test;
 
 	//Fab : R_InitSprites now sets a wad lump number
-	patch = W_CacheLumpNum(vis->patch, PU_CACHE);
+	patch = W_CachePatchNum(vis->patch, PU_CACHE);
 	if (!patch)
 		return;
 
@@ -2511,7 +2524,7 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 	if (!skin)
 		return 0;
 
-	if ((unsigned)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
+	if ((playersprite_t)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
 		return 0;
 
 	while (!(skin->sprites[spr2].numframes)
@@ -2603,6 +2616,8 @@ static void Sk_SetDefaultValue(skin_t *skin)
 	skin->followitem = 0;
 
 	skin->highresscale = FRACUNIT;
+	skin->contspeed = 17;
+	skin->contangle = 0;
 
 	skin->availability = 0;
 
@@ -2869,6 +2884,8 @@ static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, ski
 	for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
 		R_AddSingleSpriteDef((spritename = spr2names[sprite2]), &skin->sprites[sprite2], wadnum, *lump, *lastlump);
 
+	if (skin->sprites[0].numframes == 0)
+		I_Error("R_LoadSkinSprites: no frames found for sprite SPR2_%s\n", spr2names[0]);
 }
 
 // returns whether found appropriate property
@@ -2907,6 +2924,8 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
 	GETINT(thrustfactor)
 	GETINT(accelstart)
 	GETINT(acceleration)
+	GETINT(contspeed)
+	GETINT(contangle)
 #undef GETINT
 
 #define GETSKINCOLOR(field) else if (!stricmp(stoken, #field)) skin->field = R_GetColorByName(value);
diff --git a/src/r_things.h b/src/r_things.h
index 9c3d16ab018b27abdb610e4ee5eefe7a6b5e66b3..0fa9c4b94936d895b5bd01bf5b4482dea7f9dccd 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -122,6 +122,8 @@ typedef struct
 	UINT8 prefoppositecolor; // if 0 use tables instead
 
 	fixed_t highresscale; // scale of highres, default is 0.5
+	UINT8 contspeed; // continue screen animation speed
+	UINT8 contangle; // initial angle on continue screen
 
 	// specific sounds per skin
 	sfxenum_t soundsid[NUMSKINSOUNDS]; // sound # in S_sfx table
diff --git a/src/s_sound.c b/src/s_sound.c
index 3068db1dfca2b4252b4e722480c3d918179cede5..299e4b88976935c271852f024d412bc70a6e635a 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -108,7 +108,9 @@ consvar_t cv_closedcaptioning = {"closedcaptioning", "Off", CV_SAVE|CV_CALL, CV_
 consvar_t cv_numChannels = {"snd_channels", "32", CV_SAVE|CV_CALL, CV_Unsigned, SetChannelsNum, 0, NULL, NULL, 0, 0, NULL};
 
 static consvar_t surround = {"surround", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_resetmusic = {"resetmusic", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+consvar_t cv_resetmusic = {"resetmusic", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_resetmusicbyheader = {"resetmusicbyheader", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 // Sound system toggles, saved into the config
 consvar_t cv_gamedigimusic = {"digimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameDigiMusic_OnChange, 0, NULL, NULL, 0, 0, NULL};
@@ -275,6 +277,7 @@ void S_RegisterSoundStuff(void)
 	CV_RegisterVar(&surround);
 	CV_RegisterVar(&cv_samplerate);
 	CV_RegisterVar(&cv_resetmusic);
+	CV_RegisterVar(&cv_resetmusicbyheader);
 	CV_RegisterVar(&cv_gamesounds);
 	CV_RegisterVar(&cv_gamedigimusic);
 	CV_RegisterVar(&cv_gamemidimusic);
@@ -2096,7 +2099,7 @@ void S_StartEx(boolean reset)
 		mapmusposition = mapheaderinfo[gamemap-1]->muspos;
 	}
 
-	if (cv_resetmusic.value || reset)
+	if (RESETMUSIC || reset)
 		S_StopMusic();
 	S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
 
diff --git a/src/s_sound.h b/src/s_sound.h
index e0737eff743643f90683d293aec483d2e4360a17..48128527ce62dd816a66608a886595a5df0b65de 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -31,7 +31,16 @@ openmpt_module *openmpt_mhandle;
 extern consvar_t stereoreverse;
 extern consvar_t cv_soundvolume, cv_closedcaptioning, cv_digmusicvolume, cv_midimusicvolume;
 extern consvar_t cv_numChannels;
+
 extern consvar_t cv_resetmusic;
+extern consvar_t cv_resetmusicbyheader;
+
+#define RESETMUSIC (!modeattacking && \
+	(cv_resetmusicbyheader.value ? \
+		(mapheaderinfo[gamemap-1]->musforcereset != -1 ? mapheaderinfo[gamemap-1]->musforcereset : cv_resetmusic.value) \
+		: cv_resetmusic.value) \
+	)
+
 extern consvar_t cv_gamedigimusic;
 extern consvar_t cv_gamemidimusic;
 extern consvar_t cv_gamesounds;
diff --git a/src/screen.c b/src/screen.c
index fc3f5b8e87f863112cdd88a5e7d0cd1f9faea95f..547036a60bf7b7624d0f4f58622df2f2d44feadf 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -49,6 +49,7 @@ void (*fuzzcolfunc)(void); // standard fuzzy effect column drawer
 void (*transcolfunc)(void); // translation column drawer
 void (*shadecolfunc)(void); // smokie test..
 void (*spanfunc)(void); // span drawer, use a 64x64 tile
+void (*mmxspanfunc)(void); // span drawer in MMX assembly
 void (*splatfunc)(void); // span drawer w/ transparency
 void (*basespanfunc)(void); // default span func for color mode
 void (*transtransfunc)(void); // translucent translated column drawer
@@ -112,7 +113,7 @@ void SCR_SetMode(void)
 	//
 	if (true)//vid.bpp == 1) //Always run in 8bpp. todo: remove all 16bpp code?
 	{
-		spanfunc = basespanfunc = R_DrawSpan_8;
+		spanfunc = basespanfunc = mmxspanfunc = R_DrawSpan_8;
 		splatfunc = R_DrawSplat_8;
 		transcolfunc = R_DrawTranslatedColumn_8;
 		transtransfunc = R_DrawTranslatedTranslucentColumn_8;
@@ -133,7 +134,7 @@ void SCR_SetMode(void)
 				//fuzzcolfunc = R_DrawTranslucentColumn_8_ASM;
 				walldrawerfunc = R_DrawWallColumn_8_MMX;
 				twosmultipatchfunc = R_Draw2sMultiPatchColumn_8_MMX;
-				spanfunc = basespanfunc = R_DrawSpan_8_MMX;
+				mmxspanfunc = R_DrawSpan_8_MMX;
 			}
 			else
 			{
diff --git a/src/screen.h b/src/screen.h
index 7aa6fdb638a1f68fa70373bb258777f71b701282..3554b5520a541783e3a8b66f780ed1906dd3b3d7 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -123,6 +123,7 @@ extern void (*transcolfunc)(void);
 extern void (*shadecolfunc)(void);
 extern void (*spanfunc)(void);
 extern void (*basespanfunc)(void);
+extern void (*mmxspanfunc)(void);
 extern void (*splatfunc)(void);
 extern void (*transtransfunc)(void);
 extern void (*twosmultipatchfunc)(void);
diff --git a/src/sdl/IMG_xpm.c b/src/sdl/IMG_xpm.c
index af76ec1ddd1d2dfe8ee0b02f22293c7db7696cdd..43fb4ded235ee33ac961870dcc18a7aa104a9db0 100644
--- a/src/sdl/IMG_xpm.c
+++ b/src/sdl/IMG_xpm.c
@@ -1,6 +1,6 @@
 /*
   SDL_image:  An example image loading library for use with SDL
-  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
+  Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
 
   This software is provided 'as-is', without any express or implied
   warranty.  In no event will the authors be held liable for any damages
@@ -34,7 +34,7 @@
  *
  * Besides the standard API, also provides
  *
- *     SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
+ *     SDL_Surface *IMG_ReadXPMFromArray(const char **xpm)
  *
  * that reads the image data from an XPM file included in the C source.
  *
@@ -88,8 +88,8 @@ struct color_hash {
     struct hash_entry **table;
     struct hash_entry *entries; /* array of all entries */
     struct hash_entry *next_free;
-    int size;
-    int maxnum;
+    size_t size;
+    size_t maxnum;
 };
 
 static int hash_key(const char *key, int cpp, int size)
@@ -103,14 +103,14 @@ static int hash_key(const char *key, int cpp, int size)
     return hash & (size - 1);
 }
 
-static struct color_hash *create_colorhash(int maxnum)
+static struct color_hash *create_colorhash(size_t maxnum)
 {
-    int bytes, s;
+    size_t bytes, s;
     struct color_hash *hash;
 
     /* we know how many entries we need, so we can allocate
        everything here */
-    hash = (struct color_hash *)SDL_malloc(sizeof *hash);
+    hash = (struct color_hash *)SDL_calloc(1, sizeof(*hash));
     if (!hash)
         return NULL;
 
@@ -119,15 +119,29 @@ static struct color_hash *create_colorhash(int maxnum)
         ;
     hash->size = s;
     hash->maxnum = maxnum;
+
     bytes = hash->size * sizeof(struct hash_entry **);
-    hash->entries = NULL;   /* in case malloc fails */
-    hash->table = (struct hash_entry **)SDL_malloc(bytes);
+    /* Check for overflow */
+    if ((bytes / sizeof(struct hash_entry **)) != hash->size) {
+        IMG_SetError("memory allocation overflow");
+        SDL_free(hash);
+        return NULL;
+    }
+    hash->table = (struct hash_entry **)SDL_calloc(1, bytes);
     if (!hash->table) {
         SDL_free(hash);
         return NULL;
     }
-    SDL_memset(hash->table, 0, bytes);
-    hash->entries = (struct hash_entry *)SDL_malloc(maxnum * sizeof(struct hash_entry));
+
+    bytes = maxnum * sizeof(struct hash_entry);
+    /* Check for overflow */
+    if ((bytes / sizeof(struct hash_entry)) != maxnum) {
+        IMG_SetError("memory allocation overflow");
+        SDL_free(hash->table);
+        SDL_free(hash);
+        return NULL;
+    }
+    hash->entries = (struct hash_entry *)SDL_calloc(1, bytes);
     if (!hash->entries) {
         SDL_free(hash->table);
         SDL_free(hash);
@@ -138,7 +152,7 @@ static struct color_hash *create_colorhash(int maxnum)
 }
 
 static int add_colorhash(struct color_hash *hash,
-                         char *key, int cpp, Uint32 color)
+                         const char *key, int cpp, Uint32 color)
 {
     int index = hash_key(key, cpp, hash->size);
     struct hash_entry *e = hash->next_free++;
@@ -995,10 +1009,11 @@ static SDL_Surface *load_xpm(const char **xpm, SDL_RWops *src)
 {
     Sint64 start = 0;
     SDL_Surface *image = NULL;
-    int index;
+    size_t index;
     int x, y;
-    int w, h, ncolors, cpp;
-    int indexed;
+    int w, h, cpp;
+    size_t ncolors;
+    size_t indexed;
     Uint8 *dst;
     struct color_hash *colors = NULL;
     SDL_Color *im_colors = NULL;
@@ -1029,12 +1044,17 @@ static SDL_Surface *load_xpm(const char **xpm, SDL_RWops *src)
      * Right now we don't use the hotspots but it should be handled
      * one day.
      */
-    if (SDL_sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4
+    if (SDL_sscanf(line, "%d %d %lu %d", &w, &h, &ncolors, &cpp) != 4
        || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) {
         error = "Invalid format description";
         goto done;
     }
 
+    /* Check for allocation overflow */
+    if ((size_t)(ncolors * cpp)/cpp != ncolors) {
+        error = "Invalid color specification";
+        goto done;
+    }
     keystrings = (char *)SDL_malloc(ncolors * cpp);
     if (!keystrings) {
         error = "Out of memory";
@@ -1102,8 +1122,9 @@ static SDL_Surface *load_xpm(const char **xpm, SDL_RWops *src)
                 c->g = (Uint8)(rgb >> 8);
                 c->b = (Uint8)(rgb);
                 pixel = index;
-            } else
+            } else {
                 pixel = rgb;
+            }
             add_colorhash(colors, nextkey, cpp, pixel);
             nextkey += cpp;
             if (rgb == 0xffffffff)
@@ -1192,7 +1213,7 @@ SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
     return(NULL);
 }
 
-SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
+SDL_Surface *IMG_ReadXPMFromArray(const char **xpm)
 {
     return NULL;
 }
diff --git a/src/sdl/SDL_icon.xpm b/src/sdl/SDL_icon.xpm
index 1d0f9d314a98ff081671715292b5e4b26d2c54a9..2180d782c4fa1447844a68209db6da2ad005796b 100644
--- a/src/sdl/SDL_icon.xpm
+++ b/src/sdl/SDL_icon.xpm
@@ -1,163 +1,99 @@
 /* XPM */
-const char * SDL_icon_xpm[] = {
-"96 96 64 1",
+static const char *SDL_icon_xpm[] = {
+"64 64 32 1",
 " 	c None",
-".	c #040656",
-"+	c #0100B2",
-"@	c #04056E",
-"#	c #0000BD",
-"$	c #0B0C09",
-"%	c #0B0D26",
-"&	c #090C42",
-"*	c #060AA7",
-"=	c #1604DA",
-"-	c #020CD5",
-";	c #100F8D",
-">	c #040DE4",
-",	c #11129B",
-"'	c #1D1A83",
-")	c #2A10FD",
-"!	c #1318FA",
-"~	c #25225B",
-"{	c #252271",
-"]	c #312E2B",
-"^	c #33334D",
-"/	c #363775",
-"(	c #3D3B69",
-"_	c #3A3B8B",
-":	c #373AFF",
-"<	c #4142AA",
-"[	c #4B4864",
-"}	c #4D4B4A",
-"|	c #60492F",
-"1	c #4F4C57",
-"2	c #4A4A9E",
-"3	c #4F4E85",
-"4	c #474ADE",
-"5	c #4E4FFE",
-"6	c #5D5CB3",
-"7	c #686663",
-"8	c #666682",
-"9	c #676875",
-"0	c #66659E",
-"a	c #8B6538",
-"b	c #6465D5",
-"c	c #7F694F",
-"d	c #6767FF",
-"e	c #7272FF",
-"f	c #91795C",
-"g	c #7677FD",
-"h	c #828396",
-"i	c #A78153",
-"j	c #888989",
-"k	c #8D897E",
-"l	c #9190FD",
-"m	c #CA9048",
-"n	c #C09968",
-"o	c #A9A8A1",
-"p	c #A6A8B0",
-"q	c #B0B1FB",
-"r	c #EEAC61",
-"s	c #E3B478",
-"t	c #C3C4BE",
-"u	c #FFC68C",
-"v	c #FCCD90",
-"w	c #D4D7D3",
-"x	c #E3E5E0",
-"y	c #FCFFFB",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                            ttj7777777joot                                      ",
-"                                           9hh8830000088hh9                                     ",
-"                                       9888(//__<bbbb2////3[888hpp                              ",
-"                                 oj}^/_6bbbbgggggggb2///_bgbbbbb631kt                           ",
-"                                (80066bgeeegggggggb22262/bbggeggb66081                          ",
-"         p9^jj              pp8(_2bgggggeeeeeeeegb2~_bgb//6geegged5*'(hp                        ",
-"         ^2<3[7           j^/2bbggggeeeeeeeeeeggb2_({'4eb/2ggge5:!!!>-*{^kt                     ",
-"         &,5b60^         (02<beggggeeeeeeeeeegb62__7}~:5g/_bgd5!))))))=+;20k                    ",
-"         @#:egb3^     pp({4dgggeeeeeeeeeeeeegg6/__3im}+:e//bd:!)))))))))!#;87                   ",
-"        p'-!:dgb3]   7['4egeeeeeeeeeeeeeeeegg2/__[armc,-523<:!)))))))))))!>*{}                  ",
-"       tp,-)!5egb3} ~_<4dgggeeeeeeeeeeeeeegb6/_2[amusf'#!<_'>))))))))))))!)>+{~                 ",
-"        p;-))!5gb2^^'#5eggeeeeeeeeeeeeeeegg6/_23amrusi{#!+;;>))))))))))))))!!-'8p               ",
-"       tp'#!)):d6(@*>5egeeeeeeeeeeeeeeeegg6_/<(amrrvvn{+)-,;>))))))!!!!!!)))!!>,~j              ",
-"        p;#!))-'{'+-5eggeeeeeeeeeeeeeeeegb222(cmrruvvn{+)>,@>!)!!)!!>>>>======>-,/8             ",
-"         ;#)!-*.;-!5eggeeeeeeeeeeeeeeeegb2_<6|mrrsvvvn{+)!,.-!!!!>>=--######+++-#@(k            ",
-"        h@-)+@.*>!5egeeeeeeeeeeeeeeeeeegb_</]mrrruvvvn{+))*@->>--###++++++###+;@{(9j            ",
-"       kh,#+@@,>!:dggeeeeeeeeeeeeeeeeeeebbb_]mrruuvvsf'#)!*.+-###+++++++##+*;'3(&^9             ",
-"        8*,@@*)):dggeeeeeeeeeeeeeeeeeeeeggg<(|iruvvvsc,=!!*.;*++++++++###+,@&1o                 ",
-"        8@@@-!)!5eeeeeeeeeeeeeeeeeeeeeeeeeggb2[csvvvn^#)!!+@;*#+++++###*@~[                     ",
-"        9&@*!)):5geeeeeeeeeeeeeeeeeeeeeeeeegge637nsvf{>))!+;;*-######*;{.^                      ",
-"        9%;!!)):dgeeeeeeeeeeeeeeeeeeeeeeeeeeeggb_1ir7;>))!+;;,++++++*'(}                        ",
-"        9{+!))!5egeeeeeeeeeeeeeeeeeddddeeeeeeeege2}|~#!))!#;@...@@@.^hp                         ",
-"        8,=!))):dggeeeeeeeeeeeeeeeeggggeeeeeeeeggb_~,>!))!+@@@;;;;@&^o                          ",
-"       }(-)))))!:eegeeeeeeeeeeeeeegllllgeeeeeeeegd5+=))))!+;,#>--#,'/hj                         ",
-"      o8.>))))))!:dgggeeeeeeeeeeellqqqqlgeeeeggg5:!!!)))))-*+>)!:55db631                        ",
-"     p8<*!)))))))!:5deggggggeeeegqqqqqqqqlggged5:!))))))))>->!!:5ddeegb3/                       ",
-"    oh'#!))))))))))!:ddeeeeeeeeglqqqqqqqqlgedd:!)))))))))))))!:dggggeggg239                     ",
-"     ^*>!))!)))))))))!::55dddeegglll600333_4:!!)))))))))))))):dggeeeeeeggb6(9o                  ",
-"     ~+=-+#>))))))))))!!!:::::5554<3889988[/,=)))))))))))))):5gggeeeeeeeggb6087                 ",
-"     ~**@~'+>!))))))))))))))))!!>*{1kkooook7(,-!)))))))))))!:5deeeeeeeeeeeggb289                ",
-"     ~,'1o7(*>!))))))))))))))))=,[jtttwxxxwto^;>!))))))))))!!!::5deggeeeeeeegbb3]               ",
-"     ~@/oxt7'#))))))))))))))))=,3ktwxxyyyyyyxk/+!))))))))))))))!:::5degggeeegggb3^              ",
-"     ^&8xyyt^,)))))))))))))))>,3otwxyyyyyyyyyxh'>)))))))))))))))))):5ddeeeeeeeggb3^             ",
-"    771pyyyx7'=!)))))))))))!!#(jtxxyyyyyyyyyyyt3-)))))))))))))))))))!!::degggeeegb2[o           ",
-"     77tyyyxk/+!!)))))))))))-;9owxyyyyyyyyyyyywh*>)))))))))))))))))))))!::5ddgggggb68j          ",
-"      owyyyyt8;>))))))))))))*(otwyyyyyyyyyyyyyxp'-)))))))))))))))))))))))!!:5deeeggg_8j         ",
-"     jtxyyyyxh'>)))))))))!!#_ktxyyyyyyyyyyyyyyyt_+))))))))))))))))))))))))))!!:5deggg63j        ",
-"    7jwyyyyyyp/=))))))))))>,3owxyyyyyyyyyyyyyyyw/+))))))))))))))))))))))))))))!::5degb689       ",
-"     7xyyyyyyo[#))))))))))-/jtwyyyyyyyyyyyyyyyyw/*)))))))))))))))))))))))))))))))!:5dgg_/       ",
-"     }xyyyyyyt9*=))))))))=*9owyyyyyyyyyyyyyyyyyw/*)))))))))))))))))))))))))))))))))!!:5d3}      ",
-"     }xyyyyyywj'#!))))))!#@7oxyyyyyyyyyyyyyyyyyw/*)))))))))))))))))))))))))))))))))))!!:4/7     ",
-"     7xyyyyyyxj&,!!))))!!,%}oyyyyyyyyyyyyyyyyyyw/*))))))))))))))))))))))))))))))))))))))>487    ",
-"     7xyyyyyywk$@!!)))!!-.$]oyyyyyyyyyyyyyyyyyyw/+))))))))))))))))))))))))))))))!!!!))))!>'     ",
-"     }xyyyyyywj$&+!!)!)>;%$]jyyyyyyyyyyyyyyyyyyt{#)))))))))))))))))))))!!!!!!))!)!!!!!!))!#'    ",
-"     7xyyyyyyt7$%@-!)!>*[]$$jyyyyyyyyyyyyyyyyyxp;-))))))))))))))))))!!!!!!!!!!!!>>>>>>>>>>!,^   ",
-"     7xyyyyyyt}$][;-)=,(o7$$7yyyyyyyyyyyyyyyyyxp,-)))))))))))!!!!)!!!!>>>>=-----########--=+'9  ",
-"     jwyyyyyyo}$}o(';@~7wj$$7yyyyyyyyyyyyyyyyywh*>)))))))))))!>>>=>=---#####+########+++***;@17 ",
-"     otxyyyyyt}$7t7}1}7kw7$$7yyyyyyyyyyyyyyyyyt0-)))))))))!!!>--####+++++++++++++##+***,;''.&]  ",
-"    ooowyyyyyt}$}j7owwojo}$$jyyyyyyyyyyyyyyyyyp2>)))))))!!!=##++++++++++++++###+*;@.~[8[9hph    ",
-"     ojtyyyyywj$$}jwyyxo}$$]jyyyyyyyyyyyyyyyyyp'>))))))!>>-#++++++++++++####+,;'_3/&^}77kot     ",
-"      7tyyyyyxo]$$oxyyyt]$$}tyyyyyyyyyyyyyyyyx0*!)))!!!>-#++++++++++++#+##+*;.&1ko              ",
-"      7tyyyyyyx7]}xyyyyxj}]oxyyyyyyyyyyyyyyyyp<=)!!!!>-#++++++++++++####*;.(8h                  ",
-"       owxyxxyytooywptwwtppxyyyyyyyyyyyyyyyxp3,-=!)!>-#++++++++++###+*,'_{&1k                   ",
-"        jtwwttwtwwtj7kjowxyyyyyyyyyyyyyyyyxt7~'',+>=#+++++++++++###*;@&^j                       ",
-"        ]joojj7}]}]|innfc7jtwyyyyyyyyyyyxtjcfnnnf[@*#+++++++++###+@.&%%                         ",
-"       ]$}77}}$$$$]fsssnnifkkotwwwwwwwtpjkfinvvvsi}@*#++++++###*;@.@@&[                         ",
-"      o7$]]]]]$$]|isvvvvvusifckopppopok7cisvvvvvvvn(,#++++++#+*@.&@*#;3o                        ",
-"       }}$]|||fnnsvvvuvvvuuvvsniffffffnnsvvvvuuuvvvc{*+#++##*@&.@*+#--<7                        ",
-"        }]cninsuvvvvuuuuuuvvvvusnnnnnssuvvvvvuuuuvvc~*+#+++*@.@;*##=>>,^                        ",
-"         7fvvvvvvuuuuuuuuuuuuvvvvvvvvvvvuuuuuuuuuvvc~*+#+#+,.@*###->!!*~                        ",
-"         pkivvvvuuuuuuuuuuuuuuuvvvvvvvvuuuvsnsuuuvvf~*+#++++*+++->!!)!#.                        ",
-"          kfsuvvuuuuuuuuuuuuuuuuuuuuuuuuuvvnfsuvuvvc{++#++++###->!!))!-;h                       ",
-"           kisvvvuuuuuuuuuuuuuuuuuuuuuuuvvvicsvvvvs1@##+++++++#>!!))))=,ho                      ",
-"            7imuvvvuuuuuuuuuuuuuuuuuuuuvusfcivvuvvn~;##+++++++#>!!))))!#8k                      ",
-"             cimruuuuuvuuuuuuuuuuuuuuuuvsnfisuvvvsc@*#+++++++++#>!!))))-3}                      ",
-"              7amrruuuuuuuuuuuuuuuuuuuuvsnnsvvuvvi^,##++++++++++#>!!)))>/^                      ",
-"               kfamrruuuuvvvuuuuuuuuuuuuuvvvvvvvn1@+#++++++++++++#>!)))>{~                      ",
-"                7|iimrrruuuuuuuuuuuuuuuuvvvvuusn1'+#########++++++->!))>;                       ",
-"                  7cammrrrrruuuuuuvvvvvuuuuurrm|.*-#+#######+###+++->!!!*'                      ",
-"                   ookcaimmrrrrrruuuuurrrrrmi|]%.@@@@@;,*,*+########->!!*6o                     ",
-"                    p7}|ainiimmmmmmmmmmminnia|$%.....{3322_{''',,**+#=!!#6k                     ",
-"                          j7||aaiiiiiaa||7j           ookok711^&.';,*+=!><k                     ",
-"                               koooook                          hph[~@+>><k                     ",
-"                                ppppp                            tk7^3_,+<j                     ",
-"                                                                     o7^@3j                     ",
-"                                                                        9jj                     ",
-"                                                                         o                      ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                ",
-"                                                                                                "};
+".	c #000271",
+"+	c #03035D",
+"@	c #00009F",
+"#	c #0A0A1B",
+"$	c #08058E",
+"%	c #060E4C",
+"&	c #11110E",
+"*	c #101339",
+"=	c #0D11CC",
+"-	c #1B1CFD",
+";	c #342B24",
+">	c #2325EC",
+",	c #3C3883",
+"'	c #3D3A9E",
+")	c #5B5170",
+"!	c #4B4CFF",
+"~	c #795339",
+"{	c #5E5B5C",
+"]	c #5F5ED3",
+"^	c #5E5EFB",
+"/	c #7271FF",
+"(	c #B37F5D",
+"_	c #8F8883",
+":	c #8887FF",
+"<	c #D59E76",
+"[	c #ABABA9",
+"}	c #A9AAFF",
+"|	c #C1C3C1",
+"1	c #FAC296",
+"2	c #D4D6D3",
+"3	c #F9FCF8",
+"                                                                ",
+"                                                                ",
+"                              #***,,,****                       ",
+"                          *,,]]]]]]]]]]]]]',,**                 ",
+"                       *,']//////////////////]]',*              ",
+"                    *,']/////////////////////////]'%            ",
+"                  *,]////////////////////^^!!>>>>>>>$+          ",
+"                *,]////////////////////!>---------->=@%         ",
+"    *          ,]///////////////////]^>------->==@@@$.#         ",
+"   +',       *']//////////////////],,,=---->=@@@@@$.%           ",
+"  #.=^'*    ,]//////////////////],']^]$--=@@@@@@@$+#            ",
+"  %$=-^'*  ,]/////////////////],]/!>-^'=@@@@@@@$.%              ",
+"  +$=->/,*,//////////////////]'/^!,$-!,$@@@@@@$+*               ",
+"  +@=--!''/////////////////]']^!,(()->%$@@@@@$+#                ",
+"  +@=--='/////////////////]']^${(<<)->,$@@@@$%                  ",
+"  +@=->']/////////:::////]]/^'(<111)->,$@@@$%                   ",
+" #+@@>$]////////::}}}://///!,(<1111)--%$@@.%                    ",
+" #+@@$$^////////:}}}}}://^>$(<<1111)--+$@.%                     ",
+"  +@$.>^///////:}}}}}}:/^>->,(<111<'--+$$*#                     ",
+"  +$.=-!///////:}}}}}:^!-----@(111<@--+$,'],,*                  ",
+"  %+%=->^///////:}}}:!--------@(11(=--$=^////],*                ",
+"  ,]]'>->^//////^^!!-----------'<1_>--@-!//////]'*              ",
+"  '!->@--->>>>>>--->===>--------)<,-->@->^///////]',            ",
+" *$--->----------='){__{'>------>'=--=@-->!^///////],*          ",
+" %$.=---------->$)[22332[)=----------=>----->^^//////],         ",
+" %$_,--------->'_|3333333['----------=--------->!^////],#       ",
+" *'[{=--------'_2333333333_=---------------------->!^///,*      ",
+" #)[_@-------@_|33333333332,------------------------->!^/'*     ",
+" #)2[$------=)|333332|23333{>--------------------------->^'*    ",
+"  {2|,------$[233333___3333_=----------------------------->$    ",
+"  ;22)=---->)|333332{2_2333[@-------------------------------$   ",
+"  &22{@----$_233333|{2||333|'--------------------------------$  ",
+"  &|3_.----,|333333[;2|[333|'--------------------------------=+ ",
+"   [3_%=--={2333333[&___333|'-------------------->>>====>>----@ ",
+"   _3[#$=@.[2333333[&&&_333[$------------->>==@@@@@@@@@@@@@@@==+",
+"   {3|;+$$)|3333333[&&&[333_=-------->==@@@@@@@@@@@@@@@$$$$.+++%",
+"   {23{*$${23333333|;&&|332)>----->=@@@@@@@@@@@@@@$$$.++%**     ",
+";{{;[3{&*)[333333333{&&|332,=---==@@@@@@@@@@@@$$.++%*           ",
+"{22_{|[;_|2333333333_&;233_$@@@@@@@@@@@@@@@@$$+%*               ",
+"&_|2{;{{[233333333332_[33[,$@@@@@@@@@@@@@$$+%#                  ",
+" &;{&&&;~(_|3333333333332)$@@@@@@@@@@@@$.+%#                    ",
+"  &&&&&;(11([33333333332{$@@@@@@@@@@@$...$@$*                   ",
+"    &~((1111<[333333332{%.$@@@@@@@@@$$$$@=--$                   ",
+"     ~<<11111<[33333|[_(<~,$@@@@@@@@@@@@@>-->.                  ",
+"     ;(<111111<(____(11111(+@@@@@@@@@@@@=----=%                 ",
+"      ~(<11111111<11111<(<<;$@@$$@@@@@@@=-----.                 ",
+"       ~(<1111111111111(~<1{$$$.$@@@@@@@=-----=                 ",
+"        ~(<1111111<<(((<11<*$+.$@@@@@@@@@>---->+                ",
+"         ;(<1111111<<1111<~%+$@@@@@@@@@@@=-----$                ",
+"           ~(<<111111111(~&*+$$$@@@@@@@@@@=----=%               ",
+"             ;~((<<<<(~~;    *%+$$@@@@@@@@@>----+               ",
+"                 ;;;            #%+$$@@@@@@@----.               ",
+"                                   *+$$@@@@@=---@               ",
+"                                     *+$@@@@@>--=               ",
+"                                       *.$@@@@-->%              ",
+"                                        #%.$@@=->+              ",
+"                                          *+$@@>-$              ",
+"                                            %$@=-$              ",
+"                                             %.@>@              ",
+"                                               +=@              ",
+"                                                ..              ",
+"                                                 *              ",
+"                                                                ",
+"                                                                "};
diff --git a/src/sdl/Srb2SDL.ico b/src/sdl/Srb2SDL.ico
index 700276fd4b9ac2810a6981eb054921f3708c702b..3b37433dbd0aeb1315eaae48e5a2831de7e305e7 100644
Binary files a/src/sdl/Srb2SDL.ico and b/src/sdl/Srb2SDL.ico differ
diff --git a/src/sdl/i_main.c b/src/sdl/i_main.c
index f54f0d7c5496226d84c84a9636bdb899dbdd4414..029febc059a10e8458de222eccce1180927e1a52 100644
--- a/src/sdl/i_main.c
+++ b/src/sdl/i_main.c
@@ -26,6 +26,8 @@
 #include <unistd.h>
 #endif
 
+#include "time.h" // For log timestamps
+
 #ifdef HAVE_SDL
 
 #ifdef HAVE_TTF
@@ -114,6 +116,7 @@ int main(int argc, char **argv)
 #endif
 {
 	const char *logdir = NULL;
+	char logfile[MAX_WADPATH];
 	myargc = argc;
 	myargv = argv; /// \todo pull out path to exe from this string
 
@@ -125,15 +128,36 @@ int main(int argc, char **argv)
 #endif
 #endif
 
-	logdir = D_Home();
-
 #ifdef LOGMESSAGES
+	if (!M_CheckParm("-nolog"))
+	{
+		time_t my_time;
+		struct tm * timeinfo;
+		char buf[26];
+
+		logdir = D_Home();
+
+		my_time = time(NULL);
+		timeinfo = localtime(&my_time);
+
+		strftime(buf, 26, "%Y-%m-%d %H-%M-%S", timeinfo);
+		strcpy(logfile, va("log-%s.txt", buf));
+
 #ifdef DEFAULTDIR
-	if (logdir)
-		logstream = fopen(va("%s/"DEFAULTDIR"/log.txt",logdir), "wt");
-	else
+		if (logdir)
+		{
+			// Create dirs here because D_SRB2Main() is too late.
+			I_mkdir(va("%s%s"DEFAULTDIR, logdir, PATHSEP), 0755);
+			I_mkdir(va("%s%s"DEFAULTDIR"%slogs",logdir, PATHSEP, PATHSEP), 0755);
+			logstream = fopen(va("%s%s"DEFAULTDIR"%slogs%s%s",logdir, PATHSEP, PATHSEP, PATHSEP, logfile), "wt");
+		}
+		else
 #endif
-		logstream = fopen("./log.txt", "wt");
+		{
+			I_mkdir("."PATHSEP"logs"PATHSEP, 0755);
+			logstream = fopen(va("."PATHSEP"logs"PATHSEP"%s", logfile), "wt");
+		}
+	}
 #endif
 
 	//I_OutputMsg("I_StartupSystem() ...\n");
@@ -160,6 +184,10 @@ int main(int argc, char **argv)
 	// startup SRB2
 	CONS_Printf("Setting up SRB2...\n");
 	D_SRB2Main();
+#ifdef LOGMESSAGES
+	if (!M_CheckParm("-nolog"))
+		CONS_Printf("Logfile: %s\n", logfile);
+#endif
 	CONS_Printf("Entering main game loop...\n");
 	// never return
 	D_SRB2Loop();
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 78363c85e9a6e3e0369b665b09d9e1364b891aa5..97690cf7e6762642194f886c32eca75852b61709 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -530,7 +530,7 @@ static void Impl_HandleKeyboardConsoleEvent(KEY_EVENT_RECORD evt, HANDLE co)
 				break;
 			case VK_RETURN:
 				entering_con_command = false;
-				// Fall through.
+				/* FALLTHRU */
 			default:
 				event.data1 = MapVirtualKey(evt.wVirtualKeyCode,2); // convert in to char
 		}
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 233cb9b26ebedecc9c2a51f180b9fe47d8714331..46c05aea9190000fdc6e0b478894a956d8ce5fa3 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -41,7 +41,7 @@
 
 #ifdef HAVE_IMAGE
 #include "SDL_image.h"
-#elif 1
+#elif defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON) // Windows doesn't need this, as SDL will do it for us.
 #define LOAD_XPM //I want XPM!
 #include "IMG_xpm.c" //Alam: I don't want to add SDL_Image.dll/so
 #define HAVE_IMAGE //I have SDL_Image, sortof
diff --git a/src/sdl/macosx/Srb2mac.icns b/src/sdl/macosx/Srb2mac.icns
index 96cb8a36d991818eb03b7d80343518ab168bda7d..a3e37aab3ee846900a873610f7d8d66fd34bfda3 100644
Binary files a/src/sdl/macosx/Srb2mac.icns and b/src/sdl/macosx/Srb2mac.icns differ
diff --git a/src/sdl/srb2icon.png b/src/sdl/srb2icon.png
deleted file mode 100644
index cdee18a8412313410fb4f0f0a132b710c1670c54..0000000000000000000000000000000000000000
Binary files a/src/sdl/srb2icon.png and /dev/null differ
diff --git a/src/sounds.c b/src/sounds.c
index 11ba1e0bcb4cb78c7d2547d0634fcb42a170f6ba..43225a6157a6b6a9d4f5890505c5fbe8a27435dc 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -35,6 +35,23 @@ sfxinfo_t S_sfx[NUMSFX] =
 //  name, singularity, priority, pitch, volume, data, length, skinsound, usefulness, lumpnum, caption
   {"none" ,  false,   0,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "///////////////////////////////"}, // maximum length
 
+  // A HUMBLE REQUEST FROM YOUR FRIENDLY NEIGHBORHOOD toaster!
+  //
+  // If you see a caption that's just "" (shows the lumpname in-game),
+  // and you intend to use the sound associated with it in a mod,
+  // PLEASE give it a caption through SOC or Lua.
+  //
+  // If the first character of the caption is '/', no caption will be
+  // produced; only do  this for "unimportant" sounds that aren't used
+  // to indicate gameplay.
+  //
+  // (to whomstever updates the sounds list wiki page for 2.2, please
+  // either copy this comment across, or make sure its desire is
+  // codified in the initial paragraph of the page.)
+  //
+  // Closed Captioning may be a niche feature, but it's an important one.
+  // Thank you! ^u^
+
   // Skin Sounds
   {"altdi1", false, 192, 16, -1, NULL, 0, SKSPLDET1,  -1, LUMPERROR, "Dying"},
   {"altdi2", false, 192, 16, -1, NULL, 0, SKSPLDET2,  -1, LUMPERROR, "Dying"},
@@ -290,6 +307,139 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"brakrl", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rocket launch"},
   {"brakrx", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rocket explosion"},
 
+  // Sonic 1 sounds
+  {"s1a0",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1a1",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1a2",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1a3",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1a4",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1a5",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1a6",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1a7",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1a8",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1a9",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1aa",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1ab",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1ac",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1ad",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1ae",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1af",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b0",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b1",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b2",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b3",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b4",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b5",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b6",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b7",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b8",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1b9",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1ba",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1bb",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1bc",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1bd",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1be",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1bf",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c0",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c1",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c2",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c3",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c4",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c5",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c6",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c7",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c8",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1c9",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1ca",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1cb",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1cc",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1cd",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1ce",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s1cf",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+
+  // Sonic 2 sounds
+  {"s220",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s221",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s222",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s223",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s224",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s225",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s226",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s227",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s228",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s229",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s22a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s22b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s22c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s22d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s22e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s22f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s230",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s231",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s232",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s233",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s234",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s235",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s236",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s237",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s238",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s239",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s23a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s23b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s23c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s23d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s23e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s23f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s240",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s241",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s242",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s243",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s244",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s245",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s246",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s247",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s248",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s249",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s24a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s24b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s24c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s24d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s24e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s24f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s250",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s251",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s252",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s253",   false, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s254",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s255",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s256",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s257",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s258",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s259",   false,  96,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s25a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s25b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s25c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s25d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s25e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s25f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s260",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s261",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s262",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s263",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s264",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s265",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s266",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s267",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s268",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s269",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s26a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s26b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s26c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s26d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s26e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s26f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s270",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+
   // S3&K sounds
   {"s3k33",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sparkle"}, // stereo in original game, identical to latter
   {"s3k34",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sparkle"}, // mono in original game, identical to previous
@@ -492,6 +642,174 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3kdbs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running on water"},
   {"s3kdbl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running on water"}, // ditto
 
+  // 3D Blast sounds (the "missing" ones are direct copies of S3K's, no minor differences what-so-ever)
+  {"3db06",  false,  96,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Collection"},
+  {"3db09",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Peep"},
+  {"3db14",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Chirp"},
+  {"3db16",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+
+  // Sonic CD sounds
+  {"cdfm00", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Skid"},
+  {"cdfm01", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm02", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jump"},
+  {"cdfm03", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Dying"},
+  {"cdfm04", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ring loss"},
+  {"cdfm05", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sparkle"},
+  {"cdfm06", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pop"},
+  {"cdfm07", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Shield"},
+  {"cdfm08", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spring"},
+  {"cdfm09", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm10", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm11", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm12", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm13", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm14", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm15", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm16", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm17", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm18", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm19", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm20", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm21", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm22", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm23", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm24", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm25", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm26", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm27", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm28", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm29", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bubble gasp"},
+  {"cdfm30", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starpost"},
+  {"cdfm31", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Warp"},
+  {"cdfm32", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm33", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm34", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm35", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm36", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm37", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm38", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drowning"},
+  {"cdfm39", false, 128,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm40", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm41", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm42", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm43", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm44", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Extra time"},
+  {"cdfm45", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm46", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm47", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm48", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm49", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aquaphobia"},
+  {"cdfm50", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm51", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm52", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm53", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm54", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm55", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm56", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Warp"},
+  {"cdfm57", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm58", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm59", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm60", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm61", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm62", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm63", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm64", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm65", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm66", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm67", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm68", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm69", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm70", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm71", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm72", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm73", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm74", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm75", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm76", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm77", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm78", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm79", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdpcm0", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Future."},
+  {"cdpcm1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Past."},
+  {"cdpcm2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "All right!"},
+  {"cdpcm3", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "I'm outta here..."},
+  {"cdpcm4", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Yes!"},
+  {"cdpcm5", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Yeah!"},
+  {"cdpcm6", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Giggles"},
+  {"cdpcm7", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eep!"},
+  {"cdpcm8", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdpcm9", false,  96,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bumper"},
+
+  // Knuckles Chaotix sounds
+  {"kc2a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc2b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc2c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc2d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc2e",   false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc2f",   false,  96,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc30",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc31",   false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc32",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc33",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc34",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc35",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc36",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc37",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc38",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc39",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc3a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc3b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc3c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc3d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc3e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc3f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc40",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc41",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc42",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Confirm"},
+  {"kc43",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc44",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc45",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc46",   false,  96,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc47",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc48",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Select"},
+  {"kc49",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc4a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc4b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc4c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc4d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc4e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc4f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc50",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc51",   false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc52",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc53",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc54",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc55",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc56",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc57",   false, 128,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sheer terror"},
+  {"kc58",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc59",   false, 128,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Shrink"},
+  {"kc5a",   false, 128,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Grow"},
+  {"kc5b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc5c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc5d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc5e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc5f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc60",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc61",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc62",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc63",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc64",   false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Terrifying rumble"},
+  {"kc65",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc66",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc67",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc68",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc69",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc6b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ascending"},
+  {"kc6c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc6d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc6e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+
   // skin sounds free slots to add sounds at run time (Boris HACK!!!)
   // initialized to NULL
 };
diff --git a/src/sounds.h b/src/sounds.h
index 20f89d9fbb241be125c057584634d618a3eda58e..674f51d798ff0576ecebda9e78c818497fa76fb4 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -356,6 +356,139 @@ typedef enum
 	sfx_brakrl, // Rocket launcher
 	sfx_brakrx, // Rocket explodes
 
+	// S1 sounds
+	sfx_s1a0,
+	sfx_s1a1,
+	sfx_s1a2,
+	sfx_s1a3,
+	sfx_s1a4,
+	sfx_s1a5,
+	sfx_s1a6,
+	sfx_s1a7,
+	sfx_s1a8,
+	sfx_s1a9,
+	sfx_s1aa,
+	sfx_s1ab,
+	sfx_s1ac,
+	sfx_s1ad,
+	sfx_s1ae,
+	sfx_s1af,
+	sfx_s1b0,
+	sfx_s1b1,
+	sfx_s1b2,
+	sfx_s1b3,
+	sfx_s1b4,
+	sfx_s1b5,
+	sfx_s1b6,
+	sfx_s1b7,
+	sfx_s1b8,
+	sfx_s1b9,
+	sfx_s1ba,
+	sfx_s1bb,
+	sfx_s1bc,
+	sfx_s1bd,
+	sfx_s1be,
+	sfx_s1bf,
+	sfx_s1c0,
+	sfx_s1c1,
+	sfx_s1c2,
+	sfx_s1c3,
+	sfx_s1c4,
+	sfx_s1c5,
+	sfx_s1c6,
+	sfx_s1c7,
+	sfx_s1c8,
+	sfx_s1c9,
+	sfx_s1ca,
+	sfx_s1cb,
+	sfx_s1cc,
+	sfx_s1cd,
+	sfx_s1ce,
+	sfx_s1cf,
+
+	// S2 sounds
+	sfx_s220,
+	sfx_s221,
+	sfx_s222,
+	sfx_s223,
+	sfx_s224,
+	sfx_s225,
+	sfx_s226,
+	sfx_s227,
+	sfx_s228,
+	sfx_s229,
+	sfx_s22a,
+	sfx_s22b,
+	sfx_s22c,
+	sfx_s22d,
+	sfx_s22e,
+	sfx_s22f,
+	sfx_s230,
+	sfx_s231,
+	sfx_s232,
+	sfx_s233,
+	sfx_s234,
+	sfx_s235,
+	sfx_s236,
+	sfx_s237,
+	sfx_s238,
+	sfx_s239,
+	sfx_s23a,
+	sfx_s23b,
+	sfx_s23c,
+	sfx_s23d,
+	sfx_s23e,
+	sfx_s23f,
+	sfx_s240,
+	sfx_s241,
+	sfx_s242,
+	sfx_s243,
+	sfx_s244,
+	sfx_s245,
+	sfx_s246,
+	sfx_s247,
+	sfx_s248,
+	sfx_s249,
+	sfx_s24a,
+	sfx_s24b,
+	sfx_s24c,
+	sfx_s24d,
+	sfx_s24e,
+	sfx_s24f,
+	sfx_s250,
+	sfx_s251,
+	sfx_s252,
+	sfx_s253,
+	sfx_s254,
+	sfx_s255,
+	sfx_s256,
+	sfx_s257,
+	sfx_s258,
+	sfx_s259,
+	sfx_s25a,
+	sfx_s25b,
+	sfx_s25c,
+	sfx_s25d,
+	sfx_s25e,
+	sfx_s25f,
+	sfx_s260,
+	sfx_s261,
+	sfx_s262,
+	sfx_s263,
+	sfx_s264,
+	sfx_s265,
+	sfx_s266,
+	sfx_s267,
+	sfx_s268,
+	sfx_s269,
+	sfx_s26a,
+	sfx_s26b,
+	sfx_s26c,
+	sfx_s26d,
+	sfx_s26e,
+	sfx_s26f,
+	sfx_s270,
+
 	// S3&K sounds
 	sfx_s3k33,
 	sfx_s3k34,
@@ -558,6 +691,174 @@ typedef enum
 	sfx_s3kdbs,
 	sfx_s3kdbl,
 
+	// 3DB sounds
+	sfx_3db06,
+	sfx_3db09,
+	sfx_3db14,
+	sfx_3db16,
+
+	// SCD sounds
+	sfx_cdfm00,
+	sfx_cdfm01,
+	sfx_cdfm02,
+	sfx_cdfm03,
+	sfx_cdfm04,
+	sfx_cdfm05,
+	sfx_cdfm06,
+	sfx_cdfm07,
+	sfx_cdfm08,
+	sfx_cdfm09,
+	sfx_cdfm10,
+	sfx_cdfm11,
+	sfx_cdfm12,
+	sfx_cdfm13,
+	sfx_cdfm14,
+	sfx_cdfm15,
+	sfx_cdfm16,
+	sfx_cdfm17,
+	sfx_cdfm18,
+	sfx_cdfm19,
+	sfx_cdfm20,
+	sfx_cdfm21,
+	sfx_cdfm22,
+	sfx_cdfm23,
+	sfx_cdfm24,
+	sfx_cdfm25,
+	sfx_cdfm26,
+	sfx_cdfm27,
+	sfx_cdfm28,
+	sfx_cdfm29,
+	sfx_cdfm30,
+	sfx_cdfm31,
+	sfx_cdfm32,
+	sfx_cdfm33,
+	sfx_cdfm34,
+	sfx_cdfm35,
+	sfx_cdfm36,
+	sfx_cdfm37,
+	sfx_cdfm38,
+	sfx_cdfm39,
+	sfx_cdfm40,
+	sfx_cdfm41,
+	sfx_cdfm42,
+	sfx_cdfm43,
+	sfx_cdfm44,
+	sfx_cdfm45,
+	sfx_cdfm46,
+	sfx_cdfm47,
+	sfx_cdfm48,
+	sfx_cdfm49,
+	sfx_cdfm50,
+	sfx_cdfm51,
+	sfx_cdfm52,
+	sfx_cdfm53,
+	sfx_cdfm54,
+	sfx_cdfm55,
+	sfx_cdfm56,
+	sfx_cdfm57,
+	sfx_cdfm58,
+	sfx_cdfm59,
+	sfx_cdfm60,
+	sfx_cdfm61,
+	sfx_cdfm62,
+	sfx_cdfm63,
+	sfx_cdfm64,
+	sfx_cdfm65,
+	sfx_cdfm66,
+	sfx_cdfm67,
+	sfx_cdfm68,
+	sfx_cdfm69,
+	sfx_cdfm70,
+	sfx_cdfm71,
+	sfx_cdfm72,
+	sfx_cdfm73,
+	sfx_cdfm74,
+	sfx_cdfm75,
+	sfx_cdfm76,
+	sfx_cdfm77,
+	sfx_cdfm78,
+	sfx_cdfm79,
+	sfx_cdpcm0,
+	sfx_cdpcm1,
+	sfx_cdpcm2,
+	sfx_cdpcm3,
+	sfx_cdpcm4,
+	sfx_cdpcm5,
+	sfx_cdpcm6,
+	sfx_cdpcm7,
+	sfx_cdpcm8,
+	sfx_cdpcm9,
+
+	// KC sounds
+	sfx_kc2a,
+	sfx_kc2b,
+	sfx_kc2c,
+	sfx_kc2d,
+	sfx_kc2e,
+	sfx_kc2f,
+	sfx_kc30,
+	sfx_kc31,
+	sfx_kc32,
+	sfx_kc33,
+	sfx_kc34,
+	sfx_kc35,
+	sfx_kc36,
+	sfx_kc37,
+	sfx_kc38,
+	sfx_kc39,
+	sfx_kc3a,
+	sfx_kc3b,
+	sfx_kc3c,
+	sfx_kc3d,
+	sfx_kc3e,
+	sfx_kc3f,
+	sfx_kc40,
+	sfx_kc41,
+	sfx_kc42,
+	sfx_kc43,
+	sfx_kc44,
+	sfx_kc45,
+	sfx_kc46,
+	sfx_kc47,
+	sfx_kc48,
+	sfx_kc49,
+	sfx_kc4a,
+	sfx_kc4b,
+	sfx_kc4c,
+	sfx_kc4d,
+	sfx_kc4e,
+	sfx_kc4f,
+	sfx_kc50,
+	sfx_kc51,
+	sfx_kc52,
+	sfx_kc53,
+	sfx_kc54,
+	sfx_kc55,
+	sfx_kc56,
+	sfx_kc57,
+	sfx_kc58,
+	sfx_kc59,
+	sfx_kc5a,
+	sfx_kc5b,
+	sfx_kc5c,
+	sfx_kc5d,
+	sfx_kc5e,
+	sfx_kc5f,
+	sfx_kc60,
+	sfx_kc61,
+	sfx_kc62,
+	sfx_kc63,
+	sfx_kc64,
+	sfx_kc65,
+	sfx_kc66,
+	sfx_kc67,
+	sfx_kc68,
+	sfx_kc69,
+	sfx_kc6b,
+	sfx_kc6c,
+	sfx_kc6d,
+	sfx_kc6e,
+
 	// free slots for S_AddSoundFx() at run-time --------------------
 	sfx_freeslot0,
 	//
diff --git a/src/st_stuff.c b/src/st_stuff.c
index a90661ef35fff94338b21b19b93d4404fa17cf42..20a132b3a19aecb158ba428e7a66460480e1de71 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -64,11 +64,12 @@ patch_t *sbotime; // Time logo
 patch_t *sbocolon; // Colon for time
 patch_t *sboperiod; // Period for time centiseconds
 patch_t *livesback; // Lives icon background
+patch_t *stlivex;
 static patch_t *nrec_timer; // Timer for NiGHTS records
 static patch_t *sborings;
-static patch_t *sboover;
-static patch_t *timeover;
-static patch_t *stlivex;
+static patch_t *slidgame;
+static patch_t *slidtime;
+static patch_t *slidover;
 static patch_t *sboredrings;
 static patch_t *sboredtime;
 static patch_t *getall; // Special Stage HUD
@@ -253,8 +254,10 @@ void ST_LoadGraphics(void)
 	sbocolon = W_CachePatchName("STTCOLON", PU_HUDGFX); // Colon for time
 	sboperiod = W_CachePatchName("STTPERIO", PU_HUDGFX); // Period for time centiseconds
 
-	sboover = W_CachePatchName("SBOOVER", PU_HUDGFX);
-	timeover = W_CachePatchName("TIMEOVER", PU_HUDGFX);
+	slidgame = W_CachePatchName("SLIDGAME", PU_HUDGFX);
+	slidtime = W_CachePatchName("SLIDTIME", PU_HUDGFX);
+	slidover = W_CachePatchName("SLIDOVER", PU_HUDGFX);
+
 	stlivex = W_CachePatchName("STLIVEX", PU_HUDGFX);
 	livesback = W_CachePatchName("STLIVEBK", PU_HUDGFX);
 	nrec_timer = W_CachePatchName("NGRTIMER", PU_HUDGFX); // Timer for NiGHTS
@@ -768,7 +771,12 @@ static inline void ST_drawRings(void)
 
 	ST_DrawPatchFromHud(HUD_RINGS, ((!stplyr->spectator && stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings), ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
 
-	ringnum = ((objectplacing) ? op_currentdoomednum : max(stplyr->rings, 0));
+	if (objectplacing)
+		ringnum = op_currentdoomednum;
+	else if (stplyr->rings < 0 || stplyr->spectator || stplyr->playerstate == PST_REBORN)
+		ringnum = 0;
+	else
+		ringnum = stplyr->rings;
 
 	if (cv_timetic.value == 2) // Yes, even in modeattacking
 		ST_DrawNumFromHud(HUD_RINGSNUMTICS, ringnum, V_PERPLAYER|((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
@@ -877,6 +885,8 @@ static void ST_drawLivesArea(void)
 				'\x16' | 0x80 | hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, false);
 		else
 		{
+			if (stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)))
+				livescount++;
 			if (livescount > 99)
 				livescount = 99;
 			V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8,
@@ -2029,42 +2039,7 @@ static void ST_drawTextHUD(void)
 			textHUDdraw(va("Lap:""\x82 %u/%d", stplyr->laps+1, cv_numlaps.value))
 	}
 
-	if (!stplyr->spectator && stplyr->exiting && cv_playersforexit.value && gametype == GT_COOP)
-	{
-		INT32 i, total = 0, exiting = 0;
-
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (!playeringame[i] || players[i].spectator)
-				continue;
-			if (players[i].lives <= 0)
-				continue;
-
-			total++;
-			if (players[i].exiting)
-				exiting++;
-		}
-
-		if (cv_playersforexit.value != 4)
-		{
-			total *= cv_playersforexit.value;
-			if (total & 3)
-				total += 4; // round up
-			total /= 4;
-		}
-
-		if (exiting < total)
-		{
-			if (!splitscreen && !donef12)
-			{
-				textHUDdraw(M_GetText("\x82""VIEWPOINT:""\x80 Switch view"))
-				donef12 = true;
-			}
-			total -= exiting;
-			textHUDdraw(va(M_GetText("%d player%s remaining"), total, ((total == 1) ? "" : "s")))
-		}
-	}
-	else if (gametype != GT_COOP && (stplyr->exiting || (G_GametypeUsesLives() && stplyr->lives <= 0 && countdown != 1)))
+	if (gametype != GT_COOP && (stplyr->exiting || (G_GametypeUsesLives() && stplyr->lives <= 0 && countdown != 1)))
 	{
 		if (!splitscreen && !donef12)
 		{
@@ -2072,7 +2047,7 @@ static void ST_drawTextHUD(void)
 			donef12 = true;
 		}
 	}
-	else if (!G_PlatformGametype() && stplyr->playerstate == PST_DEAD && stplyr->lives) //Death overrides spectator text.
+	else if (!G_PlatformGametype() && stplyr->playerstate == PST_DEAD && stplyr->lives) // Death overrides spectator text.
 	{
 		INT32 respawntime = cv_respawntime.value - stplyr->deadtimer/TICRATE;
 
@@ -2092,7 +2067,7 @@ static void ST_drawTextHUD(void)
 		textHUDdraw(M_GetText("\x82""JUMP:""\x80 Rise"))
 		textHUDdraw(M_GetText("\x82""SPIN:""\x80 Lower"))
 
-		if (G_IsSpecialStage(gamemap) && (maptol & TOL_NIGHTS))
+		if (G_IsSpecialStage(gamemap))
 			textHUDdraw(M_GetText("\x82""Wait for the stage to end..."))
 		else if (gametype == GT_COOP)
 		{
@@ -2125,7 +2100,46 @@ static void ST_drawTextHUD(void)
 			textHUDdraw(M_GetText("\x82""FIRE:""\x80 Enter game"))
 	}
 
-	if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (!stplyr->spectator))
+	if (gametype == GT_COOP && (!stplyr->spectator || (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))) && stplyr->exiting)
+	{
+		UINT8 numneeded = (G_IsSpecialStage(gamemap) ? 4 : cv_playersforexit.value);
+		if (numneeded)
+		{
+			INT32 i, total = 0, exiting = 0;
+
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i] || players[i].spectator)
+					continue;
+				if (players[i].lives <= 0)
+					continue;
+
+				total++;
+				if (players[i].exiting)
+					exiting++;
+			}
+
+			if (numneeded != 4)
+			{
+				total *= cv_playersforexit.value;
+				if (total & 3)
+					total += 4; // round up
+				total /= 4;
+			}
+
+			if (exiting < total)
+			{
+				if (!splitscreen && !donef12)
+				{
+					textHUDdraw(M_GetText("\x82""VIEWPOINT:""\x80 Switch view"))
+					donef12 = true;
+				}
+				total -= exiting;
+				textHUDdraw(va(M_GetText("%d player%s remaining"), total, ((total == 1) ? "" : "s")))
+			}
+		}
+	}
+	else if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (!stplyr->spectator))
 	{
 		if (leveltime < hidetime * TICRATE)
 		{
@@ -2409,25 +2423,20 @@ static void ST_overlayDrawer(void)
 		}
 	}
 
-	// GAME OVER pic
+	// GAME OVER hud
 	if ((gametype == GT_COOP)
 		&& (netgame || multiplayer)
 		&& (cv_cooplives.value == 0))
 	;
-	else if (G_GametypeUsesLives() && stplyr->lives <= 0 && !(hu_showscores && (netgame || multiplayer)))
+	else if ((G_GametypeUsesLives() || gametype == GT_RACE) && stplyr->lives <= 0 && !(hu_showscores && (netgame || multiplayer)))
 	{
-		patch_t *p;
-
-		if (countdown == 1)
-			p = timeover;
-		else
-			p = sboover;
+		INT32 i = MAXPLAYERS;
+		INT32 deadtimer = stplyr->spectator ? TICRATE : (stplyr->deadtimer-(TICRATE<<1));
 
 		if ((gametype == GT_COOP)
 		&& (netgame || multiplayer)
 		&& (cv_cooplives.value != 1))
 		{
-			INT32 i;
 			for (i = 0; i < MAXPLAYERS; i++)
 			{
 				if (!playeringame[i])
@@ -2437,15 +2446,18 @@ static void ST_overlayDrawer(void)
 					continue;
 
 				if (players[i].lives > 0)
-				{
-					p = NULL;
 					break;
-				}
 			}
 		}
 
-		if (p)
-			V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, BASEVIDHEIGHT/2 - (SHORT(p->height)/2), V_PERPLAYER|(stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS), p);
+		if (i == MAXPLAYERS && deadtimer >= 0)
+		{
+			INT32 lvlttlx = min(6*deadtimer, BASEVIDWIDTH/2);
+			UINT32 flags = V_PERPLAYER|(stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS);
+
+			V_DrawScaledPatch(lvlttlx - 8, BASEVIDHEIGHT/2, flags, (countdown == 1 ? slidtime : slidgame));
+			V_DrawScaledPatch(BASEVIDWIDTH + 8 - lvlttlx, BASEVIDHEIGHT/2, flags, slidover);
+		}
 	}
 
 	if (G_GametypeHasTeams())
diff --git a/src/st_stuff.h b/src/st_stuff.h
index 40574f46c1d07249318ee713fec74e1291778a4a..aaf01ca15a2c67ad7bfb84859d4df8efde9505fd 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -70,6 +70,7 @@ extern patch_t *sboperiod;
 extern patch_t *faceprefix[MAXSKINS]; // face status patches
 extern patch_t *superprefix[MAXSKINS]; // super face status patches
 extern patch_t *livesback;
+extern patch_t *stlivex;
 extern patch_t *ngradeletters[7];
 
 /** HUD location information (don't move this comment)
diff --git a/src/v_video.c b/src/v_video.c
index 082d849157614be68ffd1cfe409650cc9046c1b0..93fefdd97f8241752d3d2035248d133e9183da54 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -1045,9 +1045,15 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 			prevdelta = topdelta;
 			source = (const UINT8 *)(column) + 3;
 			dest = desttop;
-			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
+			if (topdelta-sy > 0)
+			{
+				dest += FixedInt(FixedMul((topdelta-sy)<<FRACBITS,fdup))*vid.width;
+				ofs = 0;
+			}
+			else
+				ofs = (sy-topdelta)<<FRACBITS;
 
-			for (ofs = sy<<FRACBITS; dest < deststop && (ofs>>FRACBITS) < column->length && (((ofs>>FRACBITS) - sy) + topdelta) < h; ofs += rowfrac)
+			for (; dest < deststop && (ofs>>FRACBITS) < column->length && (((ofs>>FRACBITS) - sy) + topdelta) < h; ofs += rowfrac)
 			{
 				if (dest >= screens[scrn&V_PARAMMASK]) // don't draw off the top of the screen (CRASH PREVENTION)
 					*dest = patchdrawfunc(dest, source, ofs);
@@ -1064,17 +1070,17 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 //
 void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT8 skincolor)
 {
-	if (skinnum < 0 || skinnum >= numskins || (skins[skinnum].flags & SF_HIRES))
-		V_DrawScaledPatch(x - 10, y - 14, flags, W_CachePatchName("CONTINS", PU_CACHE));
-	else
+	if (skinnum >= 0 && skinnum < numskins && skins[skinnum].sprites[SPR2_XTRA].numframes >= 4)
 	{
-		spriteframe_t *sprframe = &skins[skinnum].sprites[SPR2_WAIT].spriteframes[0];
-		patch_t *patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+		spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
+		spriteframe_t *sprframe = &sprdef->spriteframes[3];
+		patch_t *patch = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
 		const UINT8 *colormap = R_GetTranslationColormap(skinnum, skincolor, GTC_CACHE);
 
-		// No variant for translucency
-		V_DrawTinyMappedPatch(x, y, flags, patch, colormap);
+		V_DrawMappedPatch(x, y, flags, patch, colormap);
 	}
+	else
+		V_DrawScaledPatch(x - 10, y - 14, flags, W_CachePatchName("CONTINS", PU_CACHE));
 }
 
 //
diff --git a/src/w_wad.c b/src/w_wad.c
index d02ce9ce6061e43f3fa229e549c4821680d8090c..4d08b26dc03b03b0be41e4a70a23e736db5ba1ea 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -789,6 +789,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile)
 	// set up caching
 	//
 	Z_Calloc(numlumps * sizeof (*wadfile->lumpcache), PU_STATIC, &wadfile->lumpcache);
+	Z_Calloc(numlumps * sizeof (*wadfile->patchcache), PU_STATIC, &wadfile->patchcache);
 
 #ifdef HWRENDER
 	// allocates GLPatch info structures and store them in a tree
@@ -1182,8 +1183,6 @@ void zerr(int ret)
 }
 #endif
 
-#define NO_PNG_LUMPS
-
 #ifdef NO_PNG_LUMPS
 static void ErrorIfPNG(UINT8 *d, size_t s, char *f, char *l)
 {
@@ -1459,6 +1458,38 @@ boolean W_IsLumpCached(lumpnum_t lumpnum, void *ptr)
 	return W_IsLumpCachedPWAD(WADFILENUM(lumpnum),LUMPNUM(lumpnum), ptr);
 }
 
+//
+// W_IsPatchCached
+//
+// If a patch is already cached return true, otherwise
+// return false.
+//
+// no outside code uses the PWAD form, for now
+static inline boolean W_IsPatchCachedPWAD(UINT16 wad, UINT16 lump, void *ptr)
+{
+	void *lcache;
+
+	if (!TestValidLump(wad, lump))
+		return false;
+
+	lcache = wadfiles[wad]->patchcache[lump];
+
+	if (ptr)
+	{
+		if (ptr == lcache)
+			return true;
+	}
+	else if (lcache)
+		return true;
+
+	return false;
+}
+
+boolean W_IsPatchCached(lumpnum_t lumpnum, void *ptr)
+{
+	return W_IsPatchCachedPWAD(WADFILENUM(lumpnum),LUMPNUM(lumpnum), ptr);
+}
+
 // ==========================================================================
 // W_CacheLumpName
 // ==========================================================================
@@ -1482,18 +1513,53 @@ void *W_CacheLumpName(const char *name, INT32 tag)
 // Cache a patch into heap memory, convert the patch format as necessary
 //
 
-// Software-only compile cache the data without conversion
-#ifdef HWRENDER
-static inline void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
+void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 {
+#ifdef HWRENDER
 	GLPatch_t *grPatch;
-
-	if (rendermode == render_soft || rendermode == render_none)
-		return W_CacheLumpNumPwad(wad, lump, tag);
+#endif
 
 	if (!TestValidLump(wad, lump))
 		return NULL;
 
+#ifdef HWRENDER
+	// Software-only compile cache the data without conversion
+	if (rendermode == render_soft || rendermode == render_none)
+	{
+#endif
+		lumpcache_t *lumpcache = wadfiles[wad]->patchcache;
+		if (!lumpcache[lump])
+		{
+			size_t len = W_LumpLengthPwad(wad, lump);
+			void *ptr, *lumpdata, *srcdata = NULL;
+
+			ptr = Z_Malloc(len, tag, &lumpcache[lump]);
+			lumpdata = Z_Malloc(len, tag, NULL);
+
+			// read the lump in full
+			W_ReadLumpHeaderPwad(wad, lump, lumpdata, 0, 0);
+
+#ifndef NO_PNG_LUMPS
+			// lump is a png so convert it
+			if (R_IsLumpPNG((UINT8 *)lumpdata, len))
+			{
+				size_t newlen;
+				srcdata = R_PNGToPatch((UINT8 *)lumpdata, len, &newlen, true);
+				ptr = Z_Realloc(ptr, newlen, tag, &lumpcache[lump]);
+				M_Memcpy(ptr, srcdata, newlen);
+				Z_Free(srcdata);
+			}
+			else // just copy it into the patch cache
+#endif
+				M_Memcpy(ptr, lumpdata, len);
+		}
+		else
+			Z_ChangeTag(lumpcache[lump], tag);
+
+		return lumpcache[lump];
+#ifdef HWRENDER
+	}
+
 	grPatch = HWR_GetCachedGLPatchPwad(wad, lump);
 
 	if (grPatch->mipmap.grInfo.data)
@@ -1517,6 +1583,7 @@ static inline void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 
 	// return GLPatch_t, which can be casted to (patch_t) with valid patch header info
 	return (void *)grPatch;
+#endif
 }
 
 void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag)
@@ -1524,8 +1591,6 @@ void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag)
 	return W_CachePatchNumPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum),tag);
 }
 
-#endif // HWRENDER
-
 void W_UnlockCachedPatch(void *patch)
 {
 	// The hardware code does its own memory management, as its patches
@@ -1621,81 +1686,165 @@ void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5)
 #endif
 }
 
-// Note: This never opens lumps themselves and therefore doesn't have to
-// deal with compressed lumps.
-static int W_VerifyFile(const char *filename, lumpchecklist_t *checklist,
-	boolean status)
+// Verify versions for different archive
+// formats. checklist assumed to be valid.
+
+static int
+W_VerifyName (const char *name, lumpchecklist_t *checklist, boolean status)
 {
-	FILE *handle;
-	size_t i, j;
-	int goodfile = false;
+	size_t j;
+	for (j = 0; checklist[j].len && checklist[j].name; ++j)
+	{
+		if (( strncmp(name, checklist[j].name,
+						checklist[j].len) != false ) == status)
+		{
+			return true;
+		}
+	}
+	return false;
+}
 
-	if (!checklist)
-		I_Error("No checklist for %s\n", filename);
-	// open wad file
-	if ((handle = W_OpenWadFile(&filename, false)) == NULL)
-		return -1;
+static int
+W_VerifyWAD (FILE *fp, lumpchecklist_t *checklist, boolean status)
+{
+	size_t i;
 
-	// detect wad file by the absence of the other supported extensions
-	if (stricmp(&filename[strlen(filename) - 4], ".soc")
-#ifdef HAVE_BLUA
-	&& stricmp(&filename[strlen(filename) - 4], ".lua")
-#endif
-	&& stricmp(&filename[strlen(filename) - 4], ".pk3"))
-	{
-		// assume wad file
-		wadinfo_t header;
-		filelump_t lumpinfo;
+	// assume wad file
+	wadinfo_t header;
+	filelump_t lumpinfo;
 
-		// read the header
-		if (fread(&header, 1, sizeof header, handle) == sizeof header
+	// read the header
+	if (fread(&header, 1, sizeof header, fp) == sizeof header
 			&& header.numlumps < INT16_MAX
 			&& strncmp(header.identification, "ZWAD", 4)
 			&& strncmp(header.identification, "IWAD", 4)
 			&& strncmp(header.identification, "PWAD", 4)
 			&& strncmp(header.identification, "SDLL", 4))
-		{
-			fclose(handle);
+	{
+		return true;
+	}
+
+	header.numlumps = LONG(header.numlumps);
+	header.infotableofs = LONG(header.infotableofs);
+
+	// let seek to the lumpinfo list
+	if (fseek(fp, header.infotableofs, SEEK_SET) == -1)
+		return true;
+
+	for (i = 0; i < header.numlumps; i++)
+	{
+		// fill in lumpinfo for this wad file directory
+		if (fread(&lumpinfo, sizeof (lumpinfo), 1 , fp) != 1)
 			return true;
-		}
 
-		header.numlumps = LONG(header.numlumps);
-		header.infotableofs = LONG(header.infotableofs);
+		lumpinfo.filepos = LONG(lumpinfo.filepos);
+		lumpinfo.size = LONG(lumpinfo.size);
 
-		// let seek to the lumpinfo list
-		if (fseek(handle, header.infotableofs, SEEK_SET) == -1)
-		{
-			fclose(handle);
+		if (lumpinfo.size == 0)
+			continue;
+
+		if (! W_VerifyName(lumpinfo.name, checklist, status))
 			return false;
-		}
+	}
+
+	return true;
+}
+
+static int
+W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
+{
+    zend_t zend;
+    zentry_t zentry;
+
+	UINT16 numlumps;
+	size_t i;
+
+	char pat_central[] = {0x50, 0x4b, 0x01, 0x02, 0x00};
+	char pat_end[] = {0x50, 0x4b, 0x05, 0x06, 0x00};
+
+	char lumpname[9];
+
+	// Haha the ResGetLumpsZip function doesn't
+	// check for file errors, so neither will I.
+
+	// Central directory bullshit
+
+	fseek(fp, 0, SEEK_END);
+	if (!ResFindSignature(fp, pat_end, max(0, ftell(fp) - (22 + 65536))))
+		return true;
+
+	fseek(fp, -4, SEEK_CUR);
+	if (fread(&zend, 1, sizeof zend, fp) < sizeof zend)
+		return true;
+
+	numlumps = zend.entries;
+
+	fseek(fp, zend.cdiroffset, SEEK_SET);
+	for (i = 0; i < numlumps; i++)
+	{
+		char* fullname;
+		char* trimname;
+		char* dotpos;
 
-		goodfile = true;
-		for (i = 0; i < header.numlumps; i++)
+		if (fread(&zentry, 1, sizeof(zentry_t), fp) < sizeof(zentry_t))
+			return true;
+		if (memcmp(zentry.signature, pat_central, 4))
+			return true;
+
+		fullname = malloc(zentry.namelen + 1);
+		if (fgets(fullname, zentry.namelen + 1, fp) != fullname)
+			return true;
+
+		// Strip away file address and extension for the 8char name.
+		if ((trimname = strrchr(fullname, '/')) != 0)
+			trimname++;
+		else
+			trimname = fullname; // Care taken for root files.
+
+		if (*trimname) // Ignore directories
 		{
-			// fill in lumpinfo for this wad file directory
-			if (fread(&lumpinfo, sizeof (lumpinfo), 1 , handle) != 1)
-			{
-				fclose(handle);
-				return -1;
-			}
+			if ((dotpos = strrchr(trimname, '.')) == 0)
+				dotpos = fullname + strlen(fullname); // Watch for files without extension.
 
-			lumpinfo.filepos = LONG(lumpinfo.filepos);
-			lumpinfo.size = LONG(lumpinfo.size);
+			memset(lumpname, '\0', 9); // Making sure they're initialized to 0. Is it necessary?
+			strncpy(lumpname, trimname, min(8, dotpos - trimname));
 
-			if (lumpinfo.size == 0)
-				continue;
+			if (! W_VerifyName(lumpname, checklist, status))
+				return false;
+		}
 
-			for (j = 0; j < NUMSPRITES; j++)
-				if (sprnames[j] && !strncmp(lumpinfo.name, sprnames[j], 4)) // Sprites
-					continue;
+		free(fullname);
+	}
 
-			goodfile = false;
-			for (j = 0; checklist[j].len && checklist[j].name && !goodfile; j++)
-				if ((strncmp(lumpinfo.name, checklist[j].name, checklist[j].len) != false) == status)
-					goodfile = true;
+	return true;
+}
 
-			if (!goodfile)
-				break;
+// Note: This never opens lumps themselves and therefore doesn't have to
+// deal with compressed lumps.
+static int W_VerifyFile(const char *filename, lumpchecklist_t *checklist,
+	boolean status)
+{
+	FILE *handle;
+	int goodfile = false;
+
+	if (!checklist)
+		I_Error("No checklist for %s\n", filename);
+	// open wad file
+	if ((handle = W_OpenWadFile(&filename, false)) == NULL)
+		return -1;
+
+	if (stricmp(&filename[strlen(filename) - 4], ".pk3") == 0)
+		goodfile = W_VerifyPK3(handle, checklist, status);
+	else
+	{
+		// detect wad file by the absence of the other supported extensions
+		if (stricmp(&filename[strlen(filename) - 4], ".soc")
+#ifdef HAVE_BLUA
+		&& stricmp(&filename[strlen(filename) - 4], ".lua")
+#endif
+		)
+		{
+			goodfile = W_VerifyWAD(handle, checklist, status);
 		}
 	}
 	fclose(handle);
diff --git a/src/w_wad.h b/src/w_wad.h
index 6517388504d39b66945f284b9bd294aa1bcaae18..91d4e733e94ecdf1bfbeab2dd73edc71798bc0d1 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -102,6 +102,7 @@ typedef struct wadfile_s
 	restype_t type;
 	lumpinfo_t *lumpinfo;
 	lumpcache_t *lumpcache;
+	lumpcache_t *patchcache;
 #ifdef HWRENDER
 	aatree_t *hwrcache; // patches are cached in renderer's native format
 #endif
@@ -167,17 +168,13 @@ void *W_CacheLumpNum(lumpnum_t lump, INT32 tag);
 void *W_CacheLumpNumForce(lumpnum_t lumpnum, INT32 tag);
 
 boolean W_IsLumpCached(lumpnum_t lump, void *ptr);
+boolean W_IsPatchCached(lumpnum_t lump, void *ptr);
 
 void *W_CacheLumpName(const char *name, INT32 tag);
 void *W_CachePatchName(const char *name, INT32 tag);
 
-#ifdef HWRENDER
-//void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag); // return a patch_t
+void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag); // return a patch_t
 void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag); // return a patch_t
-#else
-//#define W_CachePatchNumPwad(wad, lump, tag) W_CacheLumpNumPwad(wad, lump, tag)
-#define W_CachePatchNum(lumpnum, tag) W_CacheLumpNum(lumpnum, tag)
-#endif
 
 void W_UnlockCachedPatch(void *patch);
 
diff --git a/src/win32/Srb2win.ico b/src/win32/Srb2win.ico
index 700276fd4b9ac2810a6981eb054921f3708c702b..3b37433dbd0aeb1315eaae48e5a2831de7e305e7 100644
Binary files a/src/win32/Srb2win.ico and b/src/win32/Srb2win.ico differ
diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc
index b60ba750d06cbc8e3413a02b9aebc5f335852fd7..8e7fdccc9b08cfc9438a625a2fe6abfdaf2c50dc 100644
--- a/src/win32/Srb2win.rc
+++ b/src/win32/Srb2win.rc
@@ -1,6 +1,7 @@
 //Microsoft Developer Studio generated resource script.
 //
 #include "resource.h"
+#include "winver.h"
 
 #define APSTUDIO_READONLY_SYMBOLS
 /////////////////////////////////////////////////////////////////////////////
@@ -62,9 +63,11 @@ END
 // Version
 //
 
+#include "../doomdef.h" // Needed for version string
+
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,0,9,0
- PRODUCTVERSION 1,0,9,0
+ FILEVERSION 2,2,0,0
+ PRODUCTVERSION 2,2,0,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -82,14 +85,14 @@ BEGIN
             VALUE "Comments", "Visit our web site at www.srb2.org for news and updates!\0"
             VALUE "CompanyName", "Sonic Team Junior\0"
             VALUE "FileDescription", "Sonic Robo Blast 2\0"
-            VALUE "FileVersion", "1, 09\0"
+            VALUE "FileVersion", VERSIONSTRING
             VALUE "InternalName", "srb2\0"
-            VALUE "LegalCopyright", "Copyright � 1998-2018 by Sonic Team Junior\0"
+            VALUE "LegalCopyright", "Copyright 1998-2019 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"
             VALUE "ProductName", "Sonic Robo Blast 2\0"
-            VALUE "ProductVersion", "1, 09\0"
+            VALUE "ProductVersion", VERSIONSTRING
             VALUE "SpecialBuild", "\0"
         END
     END
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index d10f73b587e521ca1dfbd2ced7b46e381d78600a..93b3ff52381998528099495a738236c918351498 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -327,7 +327,7 @@ static inline VOID I_GetConsoleEvents(VOID)
 							break;
 						case VK_RETURN:
 							entering_con_command = false;
-							// Fall through.
+							/* FALLTHRU */
 						default:
 							ev.data1 = MapVirtualKey(input.Event.KeyEvent.wVirtualKeyCode,2); // convert in to char
 					}
diff --git a/src/win32/win_vid.c b/src/win32/win_vid.c
index e2f32fa618d2ff646f1b81a2f156e798cc88f154..11c7a6744b4132baff1a48a4a2b72ec8d43736d8 100644
--- a/src/win32/win_vid.c
+++ b/src/win32/win_vid.c
@@ -322,9 +322,9 @@ static inline boolean I_SkipFrame(void)
 		case GS_LEVEL:
 			if (!paused)
 				return false;
-			/* FALLTHRU */
 		//case GS_TIMEATTACK: -- sorry optimisation but now we have a cool level platter and that being laggardly looks terrible
 #ifndef CLIENT_LOADINGSCREEN
+		/* FALLTHRU */
 		case GS_WAITINGPLAYERS:
 #endif
 			return skip; // Skip odd frames
diff --git a/src/y_inter.c b/src/y_inter.c
index 975902ab089b2593f738bb14540cb2e812df1df8..0d6a3d03cff6b9e7676838d7a7a9d93e18cbdbcc 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -261,7 +261,7 @@ void Y_IntermissionDrawer(void)
 
 			// draw time
 			ST_DrawPatchFromHud(HUD_TIME, sbotime);
-			if (cv_timetic.value == 1)
+			if (cv_timetic.value == 3)
 				ST_DrawNumFromHud(HUD_SECONDS, data.coop.tics);
 			else
 			{
@@ -275,8 +275,7 @@ void Y_IntermissionDrawer(void)
 				ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
 				ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds
 
-				// we should show centiseconds on the intermission screen too, if the conditions are right.
-				if (modeattacking || cv_timetic.value == 2)
+				if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking) // there's not enough room for tics in splitscreen, don't even bother trying!
 				{
 					ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
 					ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
@@ -429,7 +428,7 @@ void Y_IntermissionDrawer(void)
 				{
 					if ((data.spec.continues & 0x80) && i == continues-1 && (endtic < 0 || intertic%20 < 10))
 						break;
-					V_DrawContinueIcon(246 + xoffset5 - (i*12), 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
+					V_DrawContinueIcon(246 + xoffset5 - (i*20), 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
 				}
 			}
 		}