diff --git a/src/console.c b/src/console.c
index c1c5557b9d680dbf765f8559dca6a3321c1b55d2..09a6cab453b288f83d9d5778ab88cc878837642a 100644
--- a/src/console.c
+++ b/src/console.c
@@ -1608,7 +1608,7 @@ void CON_Drawer(void)
 	if (con_curlines > 0)
 		CON_DrawConsole();
 	else if (gamestate == GS_LEVEL
-	|| gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE
+	|| gamestate == GS_INTERMISSION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
 	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
 		CON_DrawHudlines();
 }
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index dbf70d33aefd5c39923da04173f8046763db237e..74b3339fc2252a362f615ad8b7f66e697d660942 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -4270,14 +4270,9 @@ static INT16 Consistancy(void)
 		ret += P_GetRandSeed();
 
 #ifdef MOBJCONSISTANCY
-	if (!thinkercap.next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		DEBFILE(va("Consistancy = %u\n", ret));
-		return ret;
-	}
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
-	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo = (mobj_t *)th;
diff --git a/src/d_main.c b/src/d_main.c
index c7d709aec456a943fe209d1fd0b10fcc057a550a..32e874f072a69c59aeb5e931de45944514cba2ad 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -310,6 +310,12 @@ static void D_Display(void)
 				wipe = true;
 			break;
 
+		case GS_ENDING:
+			F_EndingDrawer();
+			HU_Erase();
+			HU_Drawer();
+			break;
+
 		case GS_CUTSCENE:
 			F_CutsceneDrawer();
 			HU_Erase();
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index fbdf60baea0ff1edfb5dbc27f7a04076be30afa8..e6f5244f019a72cc0f8e0c859f82146f9ec73b95 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -4260,8 +4260,8 @@ static void Command_Archivetest_f(void)
 
 	// assign mobjnum
 	i = 1;
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed)
 			((mobj_t *)th)->mobjnum = i++;
 
 	// allocate buffer
diff --git a/src/d_player.h b/src/d_player.h
index cdc899f5eea0dd4c29d6598bc3cc97f51489f51d..c133af7039cac5b02ef609be63b8f1fadbc19453 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -196,6 +196,7 @@ typedef enum
 	SH_PITY = 1, // the world's most basic shield ever, given to players who suck at Match
 	SH_WHIRLWIND,
 	SH_ARMAGEDDON,
+	SH_PINK, // PITY IN PINK!
 
 	// Normal shields that use flags
 	SH_ATTRACT = SH_PITY|SH_PROTECTELECTRIC,
diff --git a/src/dehacked.c b/src/dehacked.c
index 8057f26d8eccd24485bc1f21de404e99bf6ae7bb..0cb7330d0d40306cf425ef5724c7d71c12710cc8 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -28,6 +28,7 @@
 #include "p_local.h" // for var1 and var2, and some constants
 #include "p_setup.h"
 #include "r_data.h"
+#include "r_draw.h"
 #include "r_sky.h"
 #include "fastcmp.h"
 #include "lua_script.h"
@@ -516,7 +517,9 @@ static void readfreeslots(MYFILE *f)
 					continue;
 				// Copy in the spr2 name and increment free_spr2.
 				if (free_spr2 < NUMPLAYERSPRITES) {
+					CONS_Printf("Sprite SPR2_%s allocated.\n",word);
 					strncpy(spr2names[free_spr2],word,4);
+					spr2defaults[free_spr2] = 0;
 					spr2names[free_spr2++][4] = 0;
 				} else
 					CONS_Alert(CONS_WARNING, "Ran out of free SPR2 slots!\n");
@@ -1107,6 +1110,7 @@ static void readlevelheader(MYFILE *f, INT32 num)
 				if      (fastcmp(word2, "TITLE"))      i = 1100;
 				else if (fastcmp(word2, "EVALUATION")) i = 1101;
 				else if (fastcmp(word2, "CREDITS"))    i = 1102;
+				else if (fastcmp(word2, "ENDING"))     i = 1103;
 				else
 				// Support using the actual map name,
 				// i.e., Nextlevel = AB, Nextlevel = FZ, etc.
@@ -4666,6 +4670,11 @@ 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_ATK1",
 	"S_EGGMOBILE3_ATK2",
 	"S_EGGMOBILE3_ATK3A",
@@ -4674,11 +4683,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE3_ATK3D",
 	"S_EGGMOBILE3_ATK4",
 	"S_EGGMOBILE3_ATK5",
-	"S_EGGMOBILE3_LAUGH1",
-	"S_EGGMOBILE3_LAUGH2",
-	"S_EGGMOBILE3_LAUGH3",
-	"S_EGGMOBILE3_LAUGH4",
-	"S_EGGMOBILE3_LAUGH5",
 	"S_EGGMOBILE3_LAUGH6",
 	"S_EGGMOBILE3_LAUGH7",
 	"S_EGGMOBILE3_LAUGH8",
@@ -4731,8 +4735,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FAKEMOBILE_ATK3B",
 	"S_FAKEMOBILE_ATK3C",
 	"S_FAKEMOBILE_ATK3D",
-	"S_FAKEMOBILE_ATK4",
-	"S_FAKEMOBILE_ATK5",
+	"S_FAKEMOBILE_DIE1",
+	"S_FAKEMOBILE_DIE2",
 
 	// Boss 4
 	"S_EGGMOBILE4_STND",
@@ -4750,15 +4754,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE4_RATK6",
 	"S_EGGMOBILE4_RAISE1",
 	"S_EGGMOBILE4_RAISE2",
-	"S_EGGMOBILE4_RAISE3",
-	"S_EGGMOBILE4_RAISE4",
-	"S_EGGMOBILE4_RAISE5",
-	"S_EGGMOBILE4_RAISE6",
-	"S_EGGMOBILE4_RAISE7",
-	"S_EGGMOBILE4_RAISE8",
-	"S_EGGMOBILE4_RAISE9",
-	"S_EGGMOBILE4_RAISE10",
-	"S_EGGMOBILE4_PAIN",
+	"S_EGGMOBILE4_PAIN1",
+	"S_EGGMOBILE4_PAIN2",
 	"S_EGGMOBILE4_DIE1",
 	"S_EGGMOBILE4_DIE2",
 	"S_EGGMOBILE4_DIE3",
@@ -4776,10 +4773,21 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_EGGMOBILE4_FLEE1",
 	"S_EGGMOBILE4_FLEE2",
 	"S_EGGMOBILE4_MACE",
+	"S_EGGMOBILE4_MACE_DIE1",
+	"S_EGGMOBILE4_MACE_DIE2",
+	"S_EGGMOBILE4_MACE_DIE3",
 
 	// Boss 4 jet flame
-	"S_JETFLAME1",
-	"S_JETFLAME2",
+	"S_JETFLAME",
+
+	// Boss 4 Spectator Eggrobo
+	"S_EGGROBO1_IDLE",
+	"S_EGGROBO1_BSLAP1",
+	"S_EGGROBO2_BSLAP2",
+	"S_EGGROBO1_PISSED",
+
+	// Boss 4 Spectator Eggrobo jet flame
+	"S_EGGROBOJET",
 
 	// Boss 5
 	"S_FANG_IDLE1",
@@ -5132,7 +5140,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_METALSONIC_BADBOUNCE",
 	"S_METALSONIC_SHOOT",
 	"S_METALSONIC_PAIN",
-	"S_METALSONIC_DEATH",
+	"S_METALSONIC_DEATH1",
+	"S_METALSONIC_DEATH2",
+	"S_METALSONIC_DEATH3",
+	"S_METALSONIC_DEATH4",
 	"S_METALSONIC_FLEE1",
 	"S_METALSONIC_FLEE2",
 	"S_METALSONIC_FLEE3",
@@ -5691,7 +5702,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	"S_CEZFLOWER",
 	"S_CEZPOLE",
-	"S_CEZBANNER",
+	"S_CEZBANNER1",
+	"S_CEZBANNER2",
 	"S_PINETREE",
 	"S_CEZBUSH1",
 	"S_CEZBUSH2",
@@ -5700,7 +5712,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLAMEHOLDER",
 	"S_FIRETORCH",
 	"S_WAVINGFLAG",
-	"S_WAVINGFLAGSEG",
+	"S_WAVINGFLAGSEG1",
+	"S_WAVINGFLAGSEG2",
 	"S_CRAWLASTATUE",
 	"S_FACESTABBERSTATUE",
 	"S_SUSPICIOUSFACESTABBERSTATUE_WAIT",
@@ -6170,6 +6183,12 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PITY4",
 	"S_PITY5",
 	"S_PITY6",
+	"S_PITY7",
+	"S_PITY8",
+	"S_PITY9",
+	"S_PITY10",
+	"S_PITY11",
+	"S_PITY12",
 
 	"S_FIRS1",
 	"S_FIRS2",
@@ -6654,6 +6673,12 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	"S_LOCKON1",
 	"S_LOCKON2",
+	"S_LOCKON3",
+	"S_LOCKON4",
+	"S_LOCKONINF1",
+	"S_LOCKONINF2",
+	"S_LOCKONINF3",
+	"S_LOCKONINF4",
 
 	// Tag Sign
 	"S_TTAG",
@@ -6662,6 +6687,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_GOTFLAG",
 
 	"S_CORK",
+	"S_LHRT",
 
 	// Red Ring
 	"S_RRNG1",
@@ -7167,6 +7193,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ROCKCRUMBLEN",
 	"S_ROCKCRUMBLEO",
 	"S_ROCKCRUMBLEP",
+	"S_BRICKDEBRIS",
 
 #ifdef SEENAMES
 	"S_NAMECHECK",
@@ -7251,11 +7278,14 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_EGGMOBILE3",
 	"MT_PROPELLER",
 	"MT_FAKEMOBILE",
+	"MT_SHOCK",
 
 	// Boss 4
 	"MT_EGGMOBILE4",
 	"MT_EGGMOBILE4_MACE",
 	"MT_JETFLAME",
+	"MT_EGGROBO1",
+	"MT_EGGROBO1JET",
 
 	// Boss 5
 	"MT_FANG",
@@ -7489,8 +7519,10 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_SMALLFIREBAR", // Small Firebar
 	"MT_BIGFIREBAR", // Big Firebar
 	"MT_CEZFLOWER", // Flower
-	"MT_CEZPOLE", // Pole
-	"MT_CEZBANNER", // Banner
+	"MT_CEZPOLE1", // Pole (with red banner)
+	"MT_CEZPOLE2", // Pole (with blue banner)
+	"MT_CEZBANNER1", // Banner (red)
+	"MT_CEZBANNER2", // Banner (blue)
 	"MT_PINETREE", // Pine Tree
 	"MT_CEZBUSH1", // Bush 1
 	"MT_CEZBUSH2", // Bush 2
@@ -7498,8 +7530,10 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_CANDLEPRICKET", // Candle pricket
 	"MT_FLAMEHOLDER", // Flame holder
 	"MT_FIRETORCH", // Fire torch
-	"MT_WAVINGFLAG", // Waving flag
-	"MT_WAVINGFLAGSEG", // Waving flag segment
+	"MT_WAVINGFLAG1", // Waving flag (red)
+	"MT_WAVINGFLAG2", // Waving flag (blue)
+	"MT_WAVINGFLAGSEG1", // Waving flag segment (red)
+	"MT_WAVINGFLAGSEG2", // Waving flag segment (blue)
 	"MT_CRAWLASTATUE", // Crawla statue
 	"MT_FACESTABBERSTATUE", // Facestabber statue
 	"MT_SUSPICIOUSFACESTABBERSTATUE", // :eggthinking:
@@ -7732,6 +7766,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_DROWNNUMBERS", // Drowning Timer
 	"MT_GOTEMERALD", // Chaos Emerald (intangible)
 	"MT_LOCKON", // Target
+	"MT_LOCKONINF", // In-level Target
 	"MT_TAG", // Tag Sign
 	"MT_GOTFLAG", // Got Flag sign
 
@@ -7749,6 +7784,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_MACHINEAMBIENCE",
 
 	"MT_CORK",
+	"MT_LHRT",
 
 	// Ring Weapons
 	"MT_REDRING",
@@ -7881,6 +7917,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_ROCKCRUMBLE14",
 	"MT_ROCKCRUMBLE15",
 	"MT_ROCKCRUMBLE16",
+	"MT_BRICKDEBRIS",
 
 #ifdef SEENAMES
 	"MT_NAMECHECK",
@@ -8521,6 +8558,7 @@ struct {
 	{"SH_PITY",SH_PITY},
 	{"SH_WHIRLWIND",SH_WHIRLWIND},
 	{"SH_ARMAGEDDON",SH_ARMAGEDDON},
+	{"SH_PINK",SH_PINK},
 	// normal shields that use flags
 	{"SH_ATTRACT",SH_ATTRACT},
 	{"SH_ELEMENTAL",SH_ELEMENTAL},
@@ -8764,10 +8802,8 @@ struct {
 #endif
 #ifdef ESLOPE
 	// Slope flags
-	{"SL_NOPHYSICS",SL_NOPHYSICS},      // Don't do momentum adjustment with this slope
-	{"SL_NODYNAMIC",SL_NODYNAMIC},      // Slope will never need to move during the level, so don't fuss with recalculating it
-	{"SL_ANCHORVERTEX",SL_ANCHORVERTEX},// Slope is using a Slope Vertex Thing to anchor its position
-	{"SL_VERTEXSLOPE",SL_VERTEXSLOPE},  // Slope is built from three Slope Vertex Things
+	{"SL_NOPHYSICS",SL_NOPHYSICS},
+	{"SL_DYNAMIC",SL_DYNAMIC},
 #endif
 
 	// Angles
@@ -8911,6 +8947,14 @@ struct {
 	{"KR_TIMEOUT",KR_TIMEOUT},
 	{"KR_BAN",KR_BAN},
 	{"KR_LEAVE",KR_LEAVE},
+
+	// translation colormaps
+	{"TC_DEFAULT",TC_DEFAULT},
+	{"TC_BOSS",TC_BOSS},
+	{"TC_METALSONIC",TC_METALSONIC},
+	{"TC_ALLWHITE",TC_ALLWHITE},
+	{"TC_RAINBOW",TC_RAINBOW},
+	{"TC_BLINK",TC_BLINK},
 #endif
 
 	{NULL,0}
@@ -9458,6 +9502,7 @@ static inline int lib_freeslot(lua_State *L)
 				{
 					CONS_Printf("Sprite SPR2_%s allocated.\n",word);
 					strncpy(spr2names[free_spr2],word,4);
+					spr2defaults[free_spr2] = 0;
 					spr2names[free_spr2++][4] = 0;
 				} else
 					CONS_Alert(CONS_WARNING, "Ran out of free SPR2 slots!\n");
@@ -9575,11 +9620,6 @@ static inline int lib_getenum(lua_State *L)
 				lua_pushinteger(L, ((lua_Integer)1<<i));
 				return 1;
 			}
-		if (fastcmp(p, "NETONLY"))
-		{
-			lua_pushinteger(L, (lua_Integer)ML_NETONLY);
-			return 1;
-		}
 		if (mathlib) return luaL_error(L, "linedef flag '%s' could not be found.\n", word);
 		return 0;
 	}
diff --git a/src/doomdata.h b/src/doomdata.h
index 2b9472569c598cbb345de0176351eacab42dc8d1..38727b85c7ad196994658a75f1fb9d0a0938b223 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -130,11 +130,9 @@ typedef struct
 #define ML_EFFECT4             512
 #define ML_EFFECT5            1024
 
-// New ones to disable lines for characters
-#define ML_NOSONIC           2048
-#define ML_NOTAILS           4096
-#define ML_NOKNUX            8192
-#define ML_NETONLY          14336 // all of the above
+#define ML_NETONLY           2048 // Apply effect only in netgames
+#define ML_NONET             4096 // Apply  effect only in single player games
+#define ML_EFFECT6           8192
 
 // Bounce off walls!
 #define ML_BOUNCY           16384
diff --git a/src/doomdef.h b/src/doomdef.h
index 475328918ab57a54c013e9f7c6ea965583450e61..7bcc533c2ecda2f63fb1eda960d4d9bd80987c1a 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -384,7 +384,7 @@ enum {
 	LE_PINCHPHASE      =    -2, // A boss entered pinch phase (and, in most cases, is preparing their pinch phase attack!)
 	LE_ALLBOSSESDEAD   =    -3, // All bosses in the map are dead (Egg capsule raise)
 	LE_BOSSDEAD        =    -4, // A boss in the map died (Chaos mode boss tally)
-	LE_BOSS4DROP       =    -5, // CEZ boss dropped its cage
+	LE_BOSS4DROP       =    -5, // CEZ boss dropped its cage (also subtract the number of hitpoints it's lost)
 	LE_BRAKVILEATACK   =    -6, // Brak's doing his LOS attack, oh noes
 	LE_TURRET          = 32000, // THZ turret
 	LE_BRAKPLATFORM    =  4200, // v2.0 Black Eggman destroys platform
diff --git a/src/doomstat.h b/src/doomstat.h
index 06dfcca0b9a9729b60248b3b87633e35e01d0a07..18300967c1231de7de860afcc54690e2a2d72271 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -146,6 +146,7 @@ extern UINT8 skincolor_redteam, skincolor_blueteam, skincolor_redring, skincolor
 
 extern tic_t countdowntimer;
 extern boolean countdowntimeup;
+extern boolean exitfadestarted;
 
 typedef struct
 {
@@ -233,7 +234,7 @@ extern textprompt_t *textprompts[MAX_PROMPTS];
 
 // For the Custom Exit linedef.
 extern INT16 nextmapoverride;
-extern boolean skipstats;
+extern UINT8 skipstats;
 
 extern UINT32 ssspheres; //  Total # of spheres in a level
 
diff --git a/src/f_finale.c b/src/f_finale.c
index a0fa3658a3c9d9d926cd7b6cd22ba7903c883c74..3d1b2ab83a4cc6bc19f396e9b11ed23c6bc80483 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -14,6 +14,7 @@
 #include "doomdef.h"
 #include "doomstat.h"
 #include "d_main.h"
+#include "d_netcmd.h"
 #include "f_finale.h"
 #include "g_game.h"
 #include "hu_stuff.h"
@@ -36,6 +37,7 @@
 #include "p_setup.h"
 #include "st_stuff.h" // hud hiding
 #include "fastcmp.h"
+#include "console.h"
 
 #ifdef HAVE_BLUA
 #include "lua_hud.h"
@@ -53,7 +55,6 @@ static INT32 continuetime; // Short delay when continuing
 
 static tic_t animtimer; // Used for some animation timings
 static INT16 skullAnimCounter; // Prompts: Chevron animation
-static INT32 roidtics; // Asteroid spinning
 
 static INT32 deplete;
 static tic_t stoptimer;
@@ -95,10 +96,22 @@ static patch_t *ttspop5;
 static patch_t *ttspop6;
 static patch_t *ttspop7;
 
+static boolean goodending;
+static patch_t *endbrdr[2]; // border - blue, white, pink - where have i seen those colours before?
+static patch_t *endbgsp[3]; // nebula, sun, planet
+static patch_t *endegrk[2]; // eggrock - replaced midway through good ending
+static patch_t *endfwrk[3]; // firework - replaced with skin when good ending
+static patch_t *endspkl[3]; // sparkle
+static patch_t *endglow[2]; // glow aura - replaced with black rock's midway through good ending
+static patch_t *endxpld[4]; // mini explosion
+static patch_t *endescp[5]; // escape pod + flame
+static INT32 sparkloffs[3][2]; // eggrock explosions/blackrock sparkles
+static INT32 sparklloop;
+
 //
 // PROMPT STATE
 //
-static boolean promptactive = false;
+boolean promptactive = false;
 static mobj_t *promptmo;
 static INT16 promptpostexectag;
 static boolean promptblockcontrols;
@@ -230,6 +243,7 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
 void F_StartIntro(void)
 {
 	S_StopMusic();
+	S_StopSounds();
 
 	if (introtoplay)
 	{
@@ -393,7 +407,6 @@ void F_StartIntro(void)
 
 	intro_scenenum = 0;
 	finalecount = animtimer = skullAnimCounter = stoptimer = 0;
-	roidtics = BASEVIDWIDTH - 64;
 	timetonext = introscenetime[intro_scenenum];
 }
 
@@ -676,11 +689,42 @@ static void F_IntroDrawScene(void)
 
 	if (intro_scenenum == 4) // The asteroid SPINS!
 	{
-		if (roidtics >= 0)
+		if (intro_curtime > 1)
 		{
-			V_DrawScaledPatch(roidtics, 24, 0,
-				(patch = W_CachePatchName(va("ROID00%.2d", intro_curtime%35), PU_CACHE)));
-			W_UnlockCachedPatch(patch);
+			INT32 worktics = intro_curtime - 1;
+			INT32 scale = FRACUNIT;
+			patch_t *rockpat;
+			UINT8 *colormap = NULL;
+			patch_t *glow;
+			INT32 trans = 0;
+
+			INT32 x = ((BASEVIDWIDTH - 64)<<FRACBITS) - ((intro_curtime*FRACUNIT)/3);
+			INT32 y = 24<<FRACBITS;
+
+			if (worktics < 5)
+			{
+				scale = (worktics<<(FRACBITS-2));
+				x += (30*(FRACUNIT-scale));
+				y += (30*(FRACUNIT-scale));
+			}
+
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (worktics % 35)), PU_LEVEL);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(worktics & 1)), PU_LEVEL);
+
+			if (worktics >= 5)
+				trans = (worktics-5)>>1;
+			if (trans < 10)
+				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, glow, NULL);
+
+			trans = (15-worktics);
+			if (trans < 0)
+				trans = -trans;
+
+			if (finalecount < 15)
+				colormap = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+			V_DrawFixedPatch(x, y, scale, 0, rockpat, colormap);
+			if (trans < 10)
+				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, rockpat, R_GetTranslationColormap(TC_BLINK, SKINCOLOR_AQUA, GTC_CACHE));
 		}
 	}
 
@@ -702,7 +746,7 @@ static void F_IntroDrawScene(void)
 		W_UnlockCachedPatch(sgrass);
 	}
 
-	V_DrawString(cx, cy, 0, cutscene_disptext);
+	V_DrawString(cx, cy, V_ALLOWLOWERCASE, cutscene_disptext);
 }
 
 //
@@ -724,8 +768,6 @@ void F_IntroDrawer(void)
 
 			S_ChangeMusicInternal("_intro", false);
 		}
-		else if (intro_scenenum == 3)
-			roidtics = BASEVIDWIDTH - 64;
 		else if (intro_scenenum == 10)
 		{
 			// The only fade to white in the entire damn game.
@@ -849,9 +891,6 @@ void F_IntroTicker(void)
 	// advance animation
 	finalecount++;
 
-	if (finalecount % 3 == 0)
-		roidtics--;
-
 	timetonext--;
 
 	F_WriteText();
@@ -947,6 +986,7 @@ static const char *credits[] = {
 	"\1Assistance",
 	"\"chi.miru\"", // helped port slope drawing code from ZDoom
 	"Andrew \"orospakr\" Clunis",
+	"Sally \"TehRealSalt\" Cochenour",
 	"Gregor \"Oogaland\" Dick",
 	"Louis-Antoine \"LJSonic\" de Moulins", // for fixing 2.1's netcode (de Rochefort doesn't quite fit on the screen sorry lol)
 	"Victor \"Steel Titanium\" Fuentes",
@@ -966,8 +1006,9 @@ static const char *credits[] = {
 	// Everyone else is acknowledged under "Special Thanks > SRB2 Community Contributors".
 	"",
 	"\1Sprite Artists",
-	"Odi \"Iceman404\" Atunzu",
+	"\"Iceman404\"",
 	"Victor \"VAdaPEGA\" Ara\x1Fjo", // Araújo -- sorry for our limited font! D:
+	"Sally \"TehRealSalt\" Cochenour",
 	"Jim \"MotorRoach\" DeMello",
 	"Desmond \"Blade\" DesJardins",
 	"Sherman \"CoatRack\" DesJardins",
@@ -1022,6 +1063,7 @@ static const char *credits[] = {
 	"",
 	"\1Boss Design",
 	"Ben \"Mystic\" Geyer",
+	"Vivian \"toaster\" Grannell",
 	"Thomas \"Shadow Hog\" Igoe",
 	"John \"JTE\" Muniz",
 	"Samuel \"Prime 2.0\" Peters",
@@ -1048,7 +1090,10 @@ static const char *credits[] = {
 	"Simon \"sirjuddington\" Judd", // SLADE developer
 	// Acknowledged here are the following:
 	// Minor merge request authors, see guideline above
-	// Golden - Expanded thin font
+	// - Golden - Expanded thin font
+	// Creators of small quantities of sprite/texture assets
+	// - Arietty - New Green Hill-styled textures
+	// - Scizor300 - the only other contributor to the 2.0 SRB2 Asset Pack
 	"SRB2 Community Contributors",
 	"",
 	"\1Produced By",
@@ -1102,6 +1147,7 @@ void F_StartCredits(void)
 	paused = false;
 	CON_ToggleOff();
 	S_StopMusic();
+	S_StopSounds();
 
 	S_ChangeMusicInternal("_creds", false);
 
@@ -1220,7 +1266,7 @@ boolean F_CreditResponder(event_t *event)
 			break;
 	}
 
-	if (!(timesBeaten) && !(netgame || multiplayer))
+	if (!(timesBeaten) && !(netgame || multiplayer) && !cv_debug)
 		return false;
 
 	if (event->type != ev_keydown)
@@ -1239,10 +1285,8 @@ boolean F_CreditResponder(event_t *event)
 // ============
 //  EVALUATION
 // ============
-#define INTERVAL 50
-#define TRANSLEVEL V_80TRANS
-static INT32 eemeralds_start;
-static boolean drawemblem = false, drawchaosemblem = false;
+
+#define SPARKLLOOPTIME 7 // must be odd
 
 void F_StartGameEvaluation(void)
 {
@@ -1264,76 +1308,122 @@ void F_StartGameEvaluation(void)
 	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
 		G_SaveGame((UINT32)cursaveslot);
 
+	goodending = (ALL7EMERALDS(emeralds));
+
 	gameaction = ga_nothing;
 	paused = false;
 	CON_ToggleOff();
 
-	finalecount = 0;
+	finalecount = -1;
+	sparklloop = 0;
 }
 
 void F_GameEvaluationDrawer(void)
 {
 	INT32 x, y, i;
-	const fixed_t radius = 48*FRACUNIT;
 	angle_t fa;
 	INT32 eemeralds_cur;
 	char patchname[7] = "CEMGx0";
+	const char* endingtext = (goodending ? "CONGRATULATIONS!" : "TRY AGAIN...");
 
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
 	// Draw all the good crap here.
-	if (ALL7EMERALDS(emeralds))
-		V_DrawString(114, 16, 0, "GOT THEM ALL!");
-	else
-		V_DrawString(124, 16, 0, "TRY AGAIN!");
 
-	eemeralds_start++;
-	eemeralds_cur = eemeralds_start;
-
-	for (i = 0; i < 7; ++i)
+	if (finalecount > 0)
 	{
-		fa = (FixedAngle(eemeralds_cur*FRACUNIT)>>ANGLETOFINESHIFT) & FINEMASK;
-		x = 160 + FixedInt(FixedMul(FINECOSINE(fa),radius));
-		y = 100 + FixedInt(FixedMul(FINESINE(fa),radius));
+		INT32 scale = FRACUNIT;
+		patch_t *rockpat;
+		UINT8 *colormap[2] = {NULL, NULL};
+		patch_t *glow;
+		INT32 trans = 0;
 
-		patchname[4] = 'A'+(char)i;
-		if (emeralds & (1<<i))
-			V_DrawScaledPatch(x, y, 0, W_CachePatchName(patchname, PU_CACHE));
-		else
-			V_DrawTranslucentPatch(x, y, TRANSLEVEL, W_CachePatchName(patchname, PU_CACHE));
+		x = (((BASEVIDWIDTH-82)/2)+11)<<FRACBITS;
+		y = (((BASEVIDHEIGHT-82)/2)+12)<<FRACBITS;
 
-		eemeralds_cur += INTERVAL;
-	}
-	if (eemeralds_start >= 360)
-		eemeralds_start -= 360;
-
-	if (finalecount == 5*TICRATE)
-	{
-		if ((!modifiedgame || savemoddata) && !(netgame || multiplayer))
+		if (finalecount < 5)
 		{
-			++timesBeaten;
+			scale = (finalecount<<(FRACBITS-2));
+			x += (30*(FRACUNIT-scale));
+			y += (30*(FRACUNIT-scale));
+		}
 
-			if (ALL7EMERALDS(emeralds))
-				++timesBeatenWithEmeralds;
+		if (goodending)
+		{
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (finalecount % 35)), PU_LEVEL);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(finalecount & 1)), PU_LEVEL);
+			x -= FRACUNIT;
+		}
+		else
+		{
+			rockpat = W_CachePatchName("ROID0000", PU_LEVEL);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", (finalecount & 1)), PU_LEVEL);
+		}
 
-			if (ultimatemode)
-				++timesBeatenUltimate;
+		if (finalecount >= 5)
+			trans = (finalecount-5)>>1;
+		if (trans < 10)
+			V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, glow, NULL);
 
-			if (M_UpdateUnlockablesAndExtraEmblems())
-				S_StartSound(NULL, sfx_s3k68);
+		trans = (15-finalecount);
+		if (trans < 0)
+			trans = -trans;
 
-			G_SaveGameData();
+		if (finalecount < 15)
+			colormap[0] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+		V_DrawFixedPatch(x, y, scale, 0, rockpat, colormap[0]);
+		if (trans < 10)
+		{
+			colormap[1] = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_AQUA, GTC_CACHE);
+			V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, rockpat, colormap[1]);
+		}
+		if (goodending)
+		{
+			INT32 j = (sparklloop & 1) ? 2 : 3;
+			if (j > (finalecount/SPARKLLOOPTIME))
+				j = (finalecount/SPARKLLOOPTIME);
+			while (j)
+			{
+				if (j > 1 || sparklloop >= 2)
+				{
+					// if j == 0 - alternate between 0 and 1
+					//         1 -                   1 and 2
+					//         2 -                   2 and not rendered
+					V_DrawFixedPatch(x+sparkloffs[j-1][0], y+sparkloffs[j-1][1], FRACUNIT, 0, W_CachePatchName(va("ENDSPKL%.1d", (j - ((sparklloop & 1) ? 0 : 1))), PU_LEVEL), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_AQUA, GTC_CACHE));
+				}
+				j--;
+			}
+		}
+		else
+		{
+			patch_t *eggrock = W_CachePatchName("ENDEGRK5", PU_LEVEL);
+			V_DrawFixedPatch(x, y, scale, 0, eggrock, colormap[0]);
+			if (trans < 10)
+				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, eggrock, colormap[1]);
+			else if (sparklloop)
+				V_DrawFixedPatch(x, y, scale, (10-sparklloop)<<V_ALPHASHIFT,
+					W_CachePatchName("ENDEGRK0", PU_LEVEL), colormap[1]);
 		}
 	}
 
-	if (finalecount >= 5*TICRATE)
+	eemeralds_cur = (finalecount % 360)<<FRACBITS;
+
+	for (i = 0; i < 7; ++i)
 	{
-		if (drawemblem)
-			V_DrawScaledPatch(120, 192, 0, W_CachePatchName("NWNGA0", PU_CACHE));
+		fa = (FixedAngle(eemeralds_cur)>>ANGLETOFINESHIFT) & FINEMASK;
+		x = (BASEVIDWIDTH<<(FRACBITS-1)) + (60*FINECOSINE(fa));
+		y = ((BASEVIDHEIGHT+16)<<(FRACBITS-1)) + (60*FINESINE(fa));
+		eemeralds_cur += (360<<FRACBITS)/7;
+
+		patchname[4] = 'A'+(char)i;
+		V_DrawFixedPatch(x, y, FRACUNIT, ((emeralds & (1<<i)) ? 0 : V_80TRANS), W_CachePatchName(patchname, PU_LEVEL), NULL);
+	}
 
-		if (drawchaosemblem)
-			V_DrawScaledPatch(200, 192, 0, W_CachePatchName("NWNGA0", PU_CACHE));
+	V_DrawCreditString((BASEVIDWIDTH - V_CreditStringWidth(endingtext))<<(FRACBITS-1), (BASEVIDHEIGHT-100)<<(FRACBITS-1), 0, endingtext);
 
+#if 0 // the following looks like hot garbage the more unlockables we add, and we now have a lot of unlockables
+	if (finalecount >= 5*TICRATE)
+	{
 		V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:");
 
 		if (!(netgame) && (!modifiedgame || savemoddata))
@@ -1356,16 +1446,603 @@ void F_GameEvaluationDrawer(void)
 		else
 			V_DrawString(8, 96, V_YELLOWMAP, "Prizes not\nawarded in\nmodified games!");
 	}
+#endif
 }
 
 void F_GameEvaluationTicker(void)
 {
-	finalecount++;
-
-	if (finalecount > 10*TICRATE)
+	if (++finalecount > 10*TICRATE)
+	{
 		F_StartGameEnd();
+		return;
+	}
+
+	if (!goodending)
+	{
+		if (sparklloop)
+			sparklloop--;
+
+		if (finalecount == (5*TICRATE)/2
+			|| finalecount == (7*TICRATE)/2
+			|| finalecount == ((7*TICRATE)/2)+5)
+		{
+			S_StartSound(NULL, sfx_s3k5c);
+			sparklloop = 10;
+		}
+	}
+	else if (++sparklloop == SPARKLLOOPTIME) // time to roll the randomisation again
+	{
+		angle_t workingangle = FixedAngle((M_RandomKey(360))<<FRACBITS)>>ANGLETOFINESHIFT;
+		fixed_t workingradius = M_RandomKey(26);
+
+		sparkloffs[2][0] = sparkloffs[1][0];
+		sparkloffs[2][1] = sparkloffs[1][1];
+		sparkloffs[1][0] = sparkloffs[0][0];
+		sparkloffs[1][1] = sparkloffs[0][1];
+
+		sparkloffs[0][0] = (30<<FRACBITS) + workingradius*FINECOSINE(workingangle);
+		sparkloffs[0][1] = (30<<FRACBITS) + workingradius*FINESINE(workingangle);
+		sparklloop = 0;
+	}
+
+	if (finalecount == 5*TICRATE)
+	{
+		if (netgame || multiplayer) // modify this when we finally allow unlocking stuff in 2P
+		{
+			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
+			HU_SetCEchoDuration(6);
+			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Prizes only awarded in singleplayer!");
+			S_StartSound(NULL, sfx_s3k68);
+		}
+		else if (!modifiedgame || savemoddata)
+		{
+			++timesBeaten;
+
+			if (ALL7EMERALDS(emeralds))
+				++timesBeatenWithEmeralds;
+
+			if (ultimatemode)
+				++timesBeatenUltimate;
+
+			if (M_UpdateUnlockablesAndExtraEmblems())
+				S_StartSound(NULL, sfx_s3k68);
+
+			G_SaveGameData();
+		}
+		else
+		{
+			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
+			HU_SetCEchoDuration(6);
+			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Prizes not awarded in modified games!");
+			S_StartSound(NULL, sfx_s3k68);
+		}
+	}
+}
+
+#undef SPARKLLOOPTIME
+
+// ==========
+//   ENDING
+// ==========
+
+#define INFLECTIONPOINT (6*TICRATE)
+#define SPARKLLOOPTIME 15 // must be odd
+
+void F_StartEnding(void)
+{
+	G_SetGamestate(GS_ENDING);
+	wipetypepost = INT16_MAX;
+
+	// Just in case they're open ... somehow
+	M_ClearMenus(true);
+
+	// Save before the credits sequence.
+	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
+		G_SaveGame((UINT32)cursaveslot);
+
+	gameaction = ga_nothing;
+	paused = false;
+	CON_ToggleOff();
+	S_StopMusic(); // todo: placeholder
+	S_StopSounds();
+
+	finalecount = -10; // what? this totally isn't a hack. why are you asking?
+
+	memset(sparkloffs, 0, sizeof(INT32)*3*2);
+	sparklloop = 0;
+
+	endbrdr[1] = W_CachePatchName("ENDBRDR1", PU_LEVEL);
+
+	endegrk[0] = W_CachePatchName("ENDEGRK0", PU_LEVEL);
+	endegrk[1] = W_CachePatchName("ENDEGRK1", PU_LEVEL);
+
+	endglow[0] = W_CachePatchName("ENDGLOW0", PU_LEVEL);
+	endglow[1] = W_CachePatchName("ENDGLOW1", PU_LEVEL);
+
+	endbgsp[0] = W_CachePatchName("ENDBGSP0", PU_LEVEL);
+	endbgsp[1] = W_CachePatchName("ENDBGSP1", PU_LEVEL);
+	endbgsp[2] = W_CachePatchName("ENDBGSP2", PU_LEVEL);
+
+	endspkl[0] = W_CachePatchName("ENDSPKL0", PU_LEVEL);
+	endspkl[1] = W_CachePatchName("ENDSPKL1", PU_LEVEL);
+	endspkl[2] = W_CachePatchName("ENDSPKL2", PU_LEVEL);
+
+	endxpld[0] = W_CachePatchName("ENDXPLD0", PU_LEVEL);
+	endxpld[1] = W_CachePatchName("ENDXPLD1", PU_LEVEL);
+	endxpld[2] = W_CachePatchName("ENDXPLD2", PU_LEVEL);
+	endxpld[3] = W_CachePatchName("ENDXPLD3", PU_LEVEL);
+
+	endescp[0] = W_CachePatchName("ENDESCP0", PU_LEVEL);
+	endescp[1] = W_CachePatchName("ENDESCP1", PU_LEVEL);
+	endescp[2] = W_CachePatchName("ENDESCP2", PU_LEVEL);
+	endescp[3] = W_CachePatchName("ENDESCP3", PU_LEVEL);
+	endescp[4] = W_CachePatchName("ENDESCP4", PU_LEVEL);
+
+	// so we only need to check once
+	if ((goodending = ALL7EMERALDS(emeralds)))
+	{
+		UINT8 skinnum = players[consoleplayer].skin;
+		spritedef_t *sprdef;
+		spriteframe_t *sprframe;
+		if (skins[skinnum].sprites[SPR2_XTRA].numframes >= 5)
+		{
+			sprdef = &skins[skinnum].sprites[SPR2_XTRA];
+			// character head, skin specific
+			sprframe = &sprdef->spriteframes[2];
+			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
+			sprframe = &sprdef->spriteframes[3];
+			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
+			sprframe = &sprdef->spriteframes[4];
+			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.
+		{
+			endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_LEVEL);
+			endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_LEVEL);
+			endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_LEVEL);
+		}
+
+		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_LEVEL);
+	}
+	else
+	{
+		// eggman, skin nonspecific
+		endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_LEVEL);
+		endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_LEVEL);
+		endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_LEVEL);
+
+		endbrdr[0] = W_CachePatchName("ENDBRDR0", PU_LEVEL);
+	}
+}
+
+void F_EndingTicker(void)
+{
+	if (++finalecount > INFLECTIONPOINT*2)
+	{
+		F_StartCredits();
+		wipetypepre = INT16_MAX;
+		return;
+	}
+
+	if (goodending && finalecount == INFLECTIONPOINT) // time to swap some assets
+	{
+		endegrk[0] = W_CachePatchName("ENDEGRK2", PU_LEVEL);
+		endegrk[1] = W_CachePatchName("ENDEGRK3", PU_LEVEL);
+
+		endglow[0] = W_CachePatchName("ENDGLOW2", PU_LEVEL);
+		endglow[1] = W_CachePatchName("ENDGLOW3", PU_LEVEL);
+
+		endxpld[0] = W_CachePatchName("ENDEGRK4", PU_LEVEL);
+	}
+
+	if (++sparklloop == SPARKLLOOPTIME) // time to roll the randomisation again
+	{
+		angle_t workingangle = FixedAngle((M_RandomRange(-170, 80))<<FRACBITS)>>ANGLETOFINESHIFT;
+		fixed_t workingradius = M_RandomKey(26);
+
+		sparkloffs[0][0] = (30<<FRACBITS) + workingradius*FINECOSINE(workingangle);
+		sparkloffs[0][1] = (30<<FRACBITS) + workingradius*FINESINE(workingangle);
+
+		sparklloop = 0;
+	}
 }
 
+void F_EndingDrawer(void)
+{
+	INT32 x, y, i, j, parallaxticker;
+	patch_t *rockpat;
+
+	if (!goodending || finalecount < INFLECTIONPOINT)
+		rockpat = W_CachePatchName("ROID0000", PU_LEVEL);
+	else
+		rockpat = W_CachePatchName(va("ROID00%.2d", 34 - ((finalecount - INFLECTIONPOINT) % 35)), PU_LEVEL);
+
+	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
+
+	parallaxticker = finalecount - INFLECTIONPOINT;
+	x = -((parallaxticker*20)<<FRACBITS)/INFLECTIONPOINT;
+	y = ((parallaxticker*7)<<FRACBITS)/INFLECTIONPOINT;
+	i = (((BASEVIDWIDTH-82)/2)+11)<<FRACBITS;
+	j = (((BASEVIDHEIGHT-82)/2)+12)<<FRACBITS;
+
+	if (finalecount <= -10)
+		;
+	else if (finalecount < 0)
+		V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 0, 10+finalecount);
+	else if (finalecount <= 20)
+	{
+		V_DrawFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0);
+		if (finalecount && finalecount < 20)
+		{
+			INT32 trans = (10-finalecount);
+			if (trans < 0)
+			{
+				trans = -trans;
+				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[0]);
+			}
+			V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, trans<<V_ALPHASHIFT, endbrdr[1]);
+		}
+		else if (finalecount == 20)
+			V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[0]);
+	}
+	else if (goodending && (parallaxticker == -2 || !parallaxticker))
+	{
+		V_DrawFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0);
+		V_DrawFixedPatch(x+i, y+j, FRACUNIT, 0, endegrk[0],
+			R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_CACHE));
+		//V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[1]);
+	}
+	else if (goodending && parallaxticker == -1)
+	{
+		V_DrawFixedPatch(x+i, y+j, FRACUNIT, 0, rockpat,
+			R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE));
+		V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[1]);
+	}
+	else
+	{
+		boolean doexplosions = false;
+		boolean borderstuff = false;
+		INT32 tweakx = 0, tweaky = 0;
+
+		if (parallaxticker < 75) // f background's supposed to be visible
+		{
+			V_DrawFixedPatch(-(x/10), -(y/10), FRACUNIT, 0, endbgsp[0], NULL); // nebula
+			V_DrawFixedPatch(-(x/5),  -(y/5),  FRACUNIT, 0, endbgsp[1], NULL); // sun
+			V_DrawFixedPatch(     0,  -(y/2),  FRACUNIT, 0, endbgsp[2], NULL); // planet
+
+			// player's escape pod
+			V_DrawFixedPatch((200<<FRACBITS)+(finalecount<<(FRACBITS-2)),
+				(100<<FRACBITS)+(finalecount<<(FRACBITS-2)),
+				FRACUNIT, 0, endescp[4], NULL);
+			if (parallaxticker > -19)
+			{
+				INT32 trans = (-parallaxticker)>>1;
+				if (trans < 0)
+					trans = 0;
+				V_DrawFixedPatch((200<<FRACBITS)+(finalecount<<(FRACBITS-2)),
+					(100<<FRACBITS)+(finalecount<<(FRACBITS-2)),
+					FRACUNIT, trans<<V_ALPHASHIFT, endescp[(finalecount/2)&3], NULL);
+			}
+
+			if (goodending && parallaxticker > 0) // gunchedrock
+			{
+				INT32 scale = FRACUNIT + ((parallaxticker-10)<<7);
+				INT32 trans = parallaxticker>>2;
+				UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JET, GTC_CACHE);
+
+				if (parallaxticker < 10)
+				{
+					tweakx = parallaxticker<<FRACBITS;
+					tweaky = ((7*parallaxticker)<<(FRACBITS-2))/5;
+				}
+				else
+				{
+					tweakx = 10<<FRACBITS;
+					tweaky = 7<<(FRACBITS-1);
+				}
+				i += tweakx;
+				j -= tweaky;
+
+				x <<= 1;
+				y <<= 1;
+
+				// center detritrus
+				V_DrawFixedPatch(i-x, j-y, FRACUNIT, 0, endegrk[0], colormap);
+				if (trans < 10)
+					V_DrawFixedPatch(i-x, j-y, FRACUNIT, trans<<V_ALPHASHIFT, endegrk[0], NULL);
+
+				 // ring detritrus
+				V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(2*x), (30*(FRACUNIT-scale))+j-(2*y) - ((7<<FRACBITS)/2), scale, 0, endegrk[1], colormap);
+				if (trans < 10)
+					V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(2*x), (30*(FRACUNIT-scale))+j-(2*y), scale, trans<<V_ALPHASHIFT, endegrk[1], NULL);
+
+				scale += ((parallaxticker-10)<<7);
+
+				 // shard detritrus
+				V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(x/2), (30*(FRACUNIT-scale))+j-(y/2) - ((7<<FRACBITS)/2), scale, 0, endxpld[0], colormap);
+				if (trans < 10)
+					V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(x/2), (30*(FRACUNIT-scale))+j-(y/2), scale, trans<<V_ALPHASHIFT, endxpld[0], NULL);
+			}
+		}
+		else if (goodending)
+		{
+			tweakx = 10<<FRACBITS;
+			tweaky = 7<<(FRACBITS-1);
+			i += tweakx;
+			j += tweaky;
+			x <<= 1;
+			y <<= 1;
+		}
+
+		if (goodending && parallaxticker > 0)
+		{
+			i -= (3+(tweakx<<1));
+			j += tweaky<<2;
+		}
+
+		if (parallaxticker <= 70) // eggrock/blackrock
+		{
+			INT32 trans;
+			fixed_t scale = FRACUNIT;
+			UINT8 *colormap[2] = {NULL, NULL};
+
+			x += i;
+			y += j;
+
+			if (parallaxticker > 66)
+			{
+				scale = ((70 - parallaxticker)<<(FRACBITS-2));
+				x += (30*(FRACUNIT-scale));
+				y += (30*(FRACUNIT-scale));
+			}
+			else if ((parallaxticker > 60) || (goodending && parallaxticker > 0))
+				;
+			else
+			{
+				doexplosions = true;
+				if (!sparklloop)
+				{
+					x += ((sparkloffs[0][0] < 30<<FRACBITS) ? FRACUNIT : -FRACUNIT);
+					y += ((sparkloffs[0][1] < 30<<FRACBITS) ? FRACUNIT : -FRACUNIT);
+				}
+			}
+
+			if (goodending && finalecount > INFLECTIONPOINT)
+				parallaxticker -= 40;
+
+			if ((-parallaxticker/4) < 5)
+			{
+				trans = (-parallaxticker/4) + 5;
+				if (trans < 0)
+					trans = 0;
+				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, endglow[(finalecount & 1) ? 0 : 1], NULL);
+			}
+
+			if (goodending && finalecount > INFLECTIONPOINT)
+			{
+				if (finalecount < INFLECTIONPOINT+10)
+					V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 0, INFLECTIONPOINT+10-finalecount);
+				parallaxticker -= 30;
+			}
+
+			if ((parallaxticker/2) > -15)
+				colormap[0] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+			V_DrawFixedPatch(x, y, scale, 0, rockpat, colormap[0]);
+			if ((parallaxticker/2) > -25)
+			{
+				trans = (parallaxticker/2) + 15;
+				if (trans < 0)
+					trans = -trans;
+				if (trans < 10)
+					V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, rockpat,
+						R_GetTranslationColormap(TC_BLINK, SKINCOLOR_AQUA, GTC_CACHE));
+			}
+
+			if (goodending && finalecount > INFLECTIONPOINT)
+			{
+				if (finalecount < INFLECTIONPOINT+10)
+					V_DrawFixedPatch(x, y, scale, (finalecount-INFLECTIONPOINT)<<V_ALPHASHIFT, rockpat,
+						R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_CACHE));
+			}
+			else
+			{
+				if ((-parallaxticker/2) < -5)
+					colormap[1] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+
+				V_DrawFixedPatch(x, y, scale, 0, endegrk[0], colormap[1]);
+
+				if ((-parallaxticker/2) < 5)
+				{
+					trans = (-parallaxticker/2) + 5;
+					if (trans < 0)
+						trans = -trans;
+					if (trans < 10)
+						V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, endegrk[1], NULL);
+				}
+			}
+		}
+		else // firework
+		{
+			fixed_t scale = FRACUNIT;
+			INT32 frame;
+			UINT8 *colormap = NULL;
+			parallaxticker -= 70;
+			x += ((BASEVIDWIDTH-3)<<(FRACBITS-1)) - tweakx;
+			y += (BASEVIDHEIGHT<<(FRACBITS-1)) + tweaky;
+			borderstuff = true;
+
+			if (parallaxticker < 5)
+			{
+				scale = (parallaxticker<<FRACBITS)/4;
+				V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 31, parallaxticker*2);
+			}
+			else
+				scale += (parallaxticker-4)<<5;
+
+			if (goodending)
+				colormap = R_GetTranslationColormap(players[consoleplayer].skin, players[consoleplayer].skincolor, GTC_CACHE);
+
+			if ((frame = ((parallaxticker & 1) ? 1 : 0) + (parallaxticker/TICRATE)) < 3)
+				V_DrawFixedPatch(x, y, scale, 0, endfwrk[frame], colormap);
+		}
+
+		// explosions
+		if (sparklloop >= 3 && doexplosions)
+		{
+			INT32 boomtime = parallaxticker - sparklloop;
+
+			x = ((((BASEVIDWIDTH-82)/2)+11)<<FRACBITS) - ((boomtime*20)<<FRACBITS)/INFLECTIONPOINT;
+			y = ((((BASEVIDHEIGHT-82)/2)+12)<<FRACBITS) + ((boomtime*7)<<FRACBITS)/INFLECTIONPOINT;
+
+			V_DrawFixedPatch(x + sparkloffs[0][0], y + sparkloffs[0][1],
+				FRACUNIT, 0, endxpld[sparklloop/4], NULL);
+		}
+
+		// initial fade
+		if (finalecount < 30)
+			V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 0, 30-finalecount);
+
+		// border - only emeralds can exist outside it
+		{
+			INT32 trans = 0;
+			if (borderstuff)
+				trans = (10*parallaxticker)/(3*TICRATE);
+			if (trans < 10)
+				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, trans<<V_ALPHASHIFT, endbrdr[0]);
+			if (borderstuff && parallaxticker < 11)
+				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, (parallaxticker-1)<<V_ALPHASHIFT, endbrdr[1]);
+			else if (goodending && finalecount > INFLECTIONPOINT && finalecount < INFLECTIONPOINT+10)
+				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, (finalecount-INFLECTIONPOINT)<<V_ALPHASHIFT, endbrdr[1]);
+		}
+
+		// emeralds and emerald accessories
+		if (goodending && finalecount >= TICRATE && finalecount < INFLECTIONPOINT)
+		{
+			INT32 workingtime = finalecount - TICRATE;
+			fixed_t radius = ((vid.width/vid.dupx)*(INFLECTIONPOINT - TICRATE - workingtime))/(INFLECTIONPOINT - TICRATE);
+			angle_t fa;
+			INT32 eemeralds_cur[4];
+			char patchname[7] = "CEMGx0";
+
+			radius <<= FRACBITS;
+
+			for (i = 0; i < 4; ++i)
+			{
+				if (i == 1)
+					workingtime -= sparklloop;
+				else if (i)
+					workingtime -= SPARKLLOOPTIME;
+				eemeralds_cur[i] = (workingtime % 360)<<FRACBITS;
+			}
+
+			// sparkles
+			for (i = 0; i < 7; ++i)
+			{
+				UINT8* colormap;
+				skincolors_t col = SKINCOLOR_GREEN;
+				switch (i)
+				{
+					case 1:
+						col = SKINCOLOR_MAGENTA;
+						break;
+					case 2:
+						col = SKINCOLOR_BLUE;
+						break;
+					case 3:
+						col = SKINCOLOR_SKY;
+						break;
+					case 4:
+						col = SKINCOLOR_ORANGE;
+						break;
+					case 5:
+						col = SKINCOLOR_RED;
+						break;
+					case 6:
+						col = SKINCOLOR_GREY;
+					default:
+					case 0:
+						break;
+				}
+
+				colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_CACHE);
+
+				j = (sparklloop & 1) ? 2 : 3;
+				while (j)
+				{
+					fa = (FixedAngle(eemeralds_cur[j])>>ANGLETOFINESHIFT) & FINEMASK;
+					x =  (BASEVIDWIDTH<<(FRACBITS-1)) + FixedMul(FINECOSINE(fa),radius);
+					y = (BASEVIDHEIGHT<<(FRACBITS-1)) + FixedMul(FINESINE(fa),radius);
+					eemeralds_cur[j] += (360<<FRACBITS)/7;
+
+					// if j == 0 - alternate between 0 and 1
+					//         1 -                   1 and 2
+					//         2 -                   2 and not rendered
+					V_DrawFixedPatch(x, y, FRACUNIT, 0, endspkl[(j - ((sparklloop & 1) ? 0 : 1))], colormap);
+
+					j--;
+				}
+			}
+
+			// ...then emeralds themselves
+			for (i = 0; i < 7; ++i)
+			{
+				fa = (FixedAngle(eemeralds_cur[0])>>ANGLETOFINESHIFT) & FINEMASK;
+				x = (BASEVIDWIDTH<<(FRACBITS-1)) + FixedMul(FINECOSINE(fa),radius);
+				y = ((BASEVIDHEIGHT+16)<<(FRACBITS-1)) + FixedMul(FINESINE(fa),radius);
+				eemeralds_cur[0] += (360<<FRACBITS)/7;
+
+				patchname[4] = 'A'+(char)i;
+				V_DrawFixedPatch(x, y, FRACUNIT, 0, W_CachePatchName(patchname, PU_LEVEL), NULL);
+			}
+		} // if (goodending...
+	} // (finalecount > 20)
+
+	// look, i make an ending for you last-minute, the least you could do is let me have this
+	if (cv_soundtest.value == 413)
+	{
+		INT32 trans = 0;
+		boolean donttouch = false;
+		const char *str;
+		if (goodending)
+			str = va("[S] %s: Engage.", skins[players[consoleplayer].skin].realname);
+		else
+			str = "[S] Eggman: Abscond.";
+
+		if (finalecount < 10)
+			trans = (10-finalecount)/2;
+		else if (finalecount > (2*INFLECTIONPOINT) - 20)
+		{
+			trans = 10 + (finalecount/2) - INFLECTIONPOINT;
+			donttouch = true;
+		}
+
+		if (trans != 10)
+		{
+			//colset(linkmap,  164, 165, 169); -- the ideal purple colour to represent a clicked in-game link, but not worth it just for a soundtest-controlled secret
+			V_DrawCenteredString(BASEVIDWIDTH/2, 8, V_ALLOWLOWERCASE|(trans<<V_ALPHASHIFT), str);
+			V_DrawCharacter(32, BASEVIDHEIGHT-16, '>'|(trans<<V_ALPHASHIFT), false);
+			V_DrawString(40, ((finalecount == (2*INFLECTIONPOINT)-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((timesBeaten || finalecount >= (2*INFLECTIONPOINT)-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<<V_ALPHASHIFT), " [S] ===>");
+		}
+
+		if (finalecount > (2*INFLECTIONPOINT)-(20+(2*TICRATE)))
+		{
+			INT32 trans2 = abs((5*FINECOSINE((FixedAngle((finalecount*5)<<FRACBITS)>>ANGLETOFINESHIFT & FINEMASK)))>>FRACBITS)+2;
+			if (!donttouch)
+			{
+				trans = 10 + ((2*INFLECTIONPOINT)-(20+(2*TICRATE))) - finalecount;
+				if (trans > trans2)
+					trans2 = trans;
+			}
+			else
+				trans2 += 2*trans;
+			if (trans2 < 10)
+				V_DrawCharacter(26, BASEVIDHEIGHT-33, '\x1C'|(trans2<<V_ALPHASHIFT), false);
+		}
+	}
+}
+
+#undef SPARKLLOOPTIME
+
 // ==========
 //  GAME END
 // ==========
@@ -1377,6 +2054,7 @@ void F_StartGameEnd(void)
 	paused = false;
 	CON_ToggleOff();
 	S_StopMusic();
+	S_StopSounds();
 
 	// In case menus are still up?!!
 	M_ClearMenus(true);
@@ -1692,9 +2370,9 @@ void F_TitleScreenTicker(boolean run)
 		// If there's a Line 422 Switch Cut-Away view, don't force us.
 		if (!titlemapcameraref || titlemapcameraref->type != MT_ALTVIEWMAN)
 		{
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -1891,7 +2569,7 @@ boolean F_ContinueResponder(event_t *event)
 	keypressed = true;
 	imcontinuing = true;
 	continuetime = TICRATE;
-	S_StartSound(0, sfx_itemup);
+	S_StartSound(NULL, sfx_itemup);
 	return true;
 }
 
@@ -2003,6 +2681,7 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
 			cutscenes[cutnum]->scene[scenenum].musswitchposition, 0, 0);
 	else
 		S_StopMusic();
+	S_StopSounds();
 }
 
 //
@@ -2043,7 +2722,7 @@ void F_CutsceneDrawer(void)
 		F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeoutid, true);
 	}
 
-	V_DrawString(textxpos, textypos, 0, cutscene_disptext);
+	V_DrawString(textxpos, textypos, V_ALLOWLOWERCASE, cutscene_disptext);
 }
 
 void F_CutsceneTicker(void)
diff --git a/src/f_finale.h b/src/f_finale.h
index c0c6360c316186698c1c78b200d56b3399f121f8..1149e1d5b9f9c800e0f70c7e8c7e5093d967228e 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -46,6 +46,9 @@ void F_GameEvaluationDrawer(void);
 void F_StartGameEvaluation(void);
 void F_GameEvaluationTicker(void);
 
+void F_EndingTicker(void);
+void F_EndingDrawer(void);
+
 void F_CreditTicker(void);
 void F_CreditDrawer(void);
 
@@ -63,6 +66,7 @@ boolean F_GetPromptHideHud(fixed_t y);
 void F_StartGameEnd(void);
 void F_StartIntro(void);
 void F_StartTitleScreen(void);
+void F_StartEnding(void);
 void F_StartCredits(void);
 
 boolean F_ContinueResponder(event_t *event);
@@ -82,7 +86,6 @@ typedef enum
 
 // Current menu parameters
 
-extern UINT8 titlemapinaction;
 extern mobj_t *titlemapcameraref;
 extern char curbgname[8];
 extern SINT8 curfadevalue;
@@ -126,6 +129,7 @@ enum
 	wipe_evaluation_toblack,
 	wipe_gameend_toblack,
 	wipe_intro_toblack,
+	wipe_ending_toblack,
 	wipe_cutscene_toblack,
 
 	// custom intermissions
@@ -142,15 +146,16 @@ enum
 	wipe_evaluation_final,
 	wipe_gameend_final,
 	wipe_intro_final,
+	wipe_ending_final,
 	wipe_cutscene_final,
 
 	// custom intermissions
 	wipe_specinter_final,
 	wipe_multinter_final,
 
-	NUMWIPEDEFS
+	NUMWIPEDEFS,
+	WIPEFINALSHIFT = (wipe_level_final-wipe_level_toblack)
 };
-#define WIPEFINALSHIFT 13
 extern UINT8 wipedefs[NUMWIPEDEFS];
 
 #endif
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 26c65ad91c9fb1d7c87410a59fdeebcceee96e39..05229f844dfd6f11bc59311f351cff335d50b30f 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -54,6 +54,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
 	0,  // wipe_evaluation_toblack
 	0,  // wipe_gameend_toblack
 	99, // wipe_intro_toblack (hardcoded)
+	0,  // wipe_ending_toblack
 	99, // wipe_cutscene_toblack (hardcoded)
 
 	0,  // wipe_specinter_toblack
@@ -69,6 +70,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
 	0,  // wipe_evaluation_final
 	0,  // wipe_gameend_final
 	99, // wipe_intro_final (hardcoded)
+	0,  // wipe_ending_final
 	99, // wipe_cutscene_final (hardcoded)
 
 	0,  // wipe_specinter_final
diff --git a/src/g_game.c b/src/g_game.c
index 632e33009c7b290268ff543a1597ac327e3e7ef0..dad873fe751110b5808bde696916bfc1f780f8d0 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -146,12 +146,13 @@ UINT8 skincolor_bluering = SKINCOLOR_CORNFLOWER;
 
 tic_t countdowntimer = 0;
 boolean countdowntimeup = false;
+boolean exitfadestarted = false;
 
 cutscene_t *cutscenes[128];
 textprompt_t *textprompts[MAX_PROMPTS];
 
 INT16 nextmapoverride;
-boolean skipstats;
+UINT8 skipstats;
 
 // Pointers to each CTF flag
 mobj_t *redflag;
@@ -1841,7 +1842,7 @@ boolean G_Responder(event_t *ev)
 			return true;
 		}
 	}
-	else if (gamestate == GS_CREDITS)
+	else if (gamestate == GS_CREDITS || gamestate == GS_ENDING) // todo: keep ending here?
 	{
 		if (HU_Responder(ev))
 			return true; // chat ate the event
@@ -2031,6 +2032,12 @@ void G_Ticker(boolean run)
 				F_IntroTicker();
 			break;
 
+		case GS_ENDING:
+			if (run)
+				F_EndingTicker();
+			HU_Ticker();
+			break;
+
 		case GS_CUTSCENE:
 			if (run)
 				F_CutsceneTicker();
@@ -2564,9 +2571,9 @@ void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo)
 	I_Assert((oldmo != NULL) && (newmo != NULL));
 
 	// scan all thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -2644,7 +2651,7 @@ void G_DoReborn(INT32 playernum)
 					//nextmapoverride = spstage_start;
 					nextmapoverride = gamemap;
 					countdown2 = TICRATE;
-					skipstats = true;
+					skipstats = 2;
 
 					for (i = 0; i < MAXPLAYERS; i++)
 					{
@@ -2848,6 +2855,10 @@ void G_ExitLevel(void)
 		// Remove CEcho text on round end.
 		HU_ClearCEcho();
 	}
+	else if (gamestate == GS_ENDING)
+	{
+		F_StartCredits();
+	}
 	else if (gamestate == GS_CREDITS)
 	{
 		F_StartGameEvaluation();
@@ -3115,7 +3126,7 @@ static void G_DoCompleted(void)
 		nextmap = cm;
 	}
 
-	if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1102-1)
+	if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1103-1)
 		I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1);
 
 	// wrap around in race
@@ -3169,7 +3180,7 @@ void G_AfterIntermission(void)
 {
 	HU_ClearCEcho();
 
-	if (mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking) // Start a custom cutscene.
+	if (mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1) // Start a custom cutscene.
 		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false);
 	else
 	{
@@ -3281,6 +3292,11 @@ void G_EndGame(void)
 	// Only do evaluation and credits in coop games.
 	if (gametype == GT_COOP)
 	{
+		if (nextmap == 1103-1) // end game with ending
+		{
+			F_StartEnding();
+			return;
+		}
 		if (nextmap == 1102-1) // end game with credits
 		{
 			F_StartCredits();
@@ -3699,7 +3715,7 @@ void G_SaveGame(UINT32 slot)
 	backup = va("%s",savename);
 
 	// save during evaluation or credits? game's over, folks!
-	if (gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
+	if (gamestate == GS_ENDING || gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
 		gamecomplete = true;
 
 	gameaction = ga_nothing;
@@ -3877,7 +3893,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	{
 		// Clear a bunch of variables
 		numgameovers = tokenlist = token = sstimer = redscore = bluescore = lastmap = 0;
-		countdown = countdown2 = 0;
+		countdown = countdown2 = exitfadestarted = 0;
 
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
@@ -4365,7 +4381,7 @@ void G_WriteGhostTic(mobj_t *ghost)
 		ghostext.flags = 0;
 	}
 
-	if (ghost->player && ghost->player->followmobj)
+	if (ghost->player && ghost->player->followmobj) // bloats tails runs but what can ya do
 	{
 		INT16 temp;
 
@@ -4466,16 +4482,15 @@ void G_ConsGhostTic(void)
 				demo_p += sizeof(angle_t); // angle, unnecessary for cons.
 
 				mobj = NULL;
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 					mobj = (mobj_t *)th;
 					if (mobj->type == (mobjtype_t)type && mobj->x == x && mobj->y == y && mobj->z == z)
 						break;
-					mobj = NULL; // wasn't this one, keep searching.
 				}
-				if (mobj && mobj->health != health) // Wasn't damaged?! This is desync! Fix it!
+				if (th != &thlist[THINK_MOBJ] && mobj->health != health) // Wasn't damaged?! This is desync! Fix it!
 				{
 					if (demosynced)
 						CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n"));
@@ -4592,6 +4607,9 @@ void G_GhostTicker(void)
 				switch(g->color)
 				{
 				default:
+				case GHC_RETURNSKIN:
+					g->mo->skin = g->oldmo.skin;
+					// fallthru
 				case GHC_NORMAL: // Go back to skin color
 					g->mo->color = g->oldmo.color;
 					break;
@@ -4602,6 +4620,9 @@ void G_GhostTicker(void)
 				case GHC_FIREFLOWER: // Fireflower
 					g->mo->color = SKINCOLOR_WHITE;
 					break;
+				case GHC_NIGHTSSKIN: // not actually a colour
+					g->mo->skin = &skins[DEFAULTNIGHTSSKIN];
+					break;
 				}
 			}
 			if (xziptic & EZT_FLIP)
@@ -5862,9 +5883,9 @@ void G_DoPlayMetal(void)
 		metalbuffer = metal_p = W_CacheLumpNum(l, PU_STATIC);
 
 	// find metal sonic
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo = (mobj_t *)th;
@@ -5873,7 +5894,7 @@ void G_DoPlayMetal(void)
 
 		break;
 	}
-	if (!mo)
+	if (th == &thlist[THINK_MOBJ])
 	{
 		CONS_Alert(CONS_ERROR, M_GetText("Failed to find bot entity.\n"));
 		Z_Free(metalbuffer);
diff --git a/src/g_game.h b/src/g_game.h
index 3cbde9a3c689259a30959c8772c9299f536fad9e..4b63bc180afcb0908e5ffeefddb907cb07d098a7 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -56,6 +56,8 @@ extern INT16 rw_maximums[NUM_WEAPONS];
 extern INT32 pausedelay;
 extern boolean pausebreakkey;
 
+extern boolean promptactive;
+
 // used in game menu
 extern consvar_t cv_tutorialprompt;
 extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection, cv_compactscoreboard;
@@ -140,7 +142,9 @@ typedef enum
 	GHC_NORMAL = 0,
 	GHC_SUPER,
 	GHC_FIREFLOWER,
-	GHC_INVINCIBLE
+	GHC_INVINCIBLE,
+	GHC_NIGHTSSKIN, // not actually a colour
+	GHC_RETURNSKIN // ditto
 } ghostcolor_t;
 
 // Record/playback tics
diff --git a/src/g_state.h b/src/g_state.h
index 76c9bd16f1e63e76cdd5b60c9d14fed560f765f1..9d34da6a97706ba99fb0d6aac9a25407622b581e 100644
--- a/src/g_state.h
+++ b/src/g_state.h
@@ -27,12 +27,14 @@ typedef enum
 
 	GS_TITLESCREEN,     // title screen
 	GS_TIMEATTACK,      // time attack menu
+
 	GS_CREDITS,         // credit sequence
 	GS_EVALUATION,      // Evaluation at the end of a game.
-	GS_GAMEEND,         // game end sequence
+	GS_GAMEEND,         // game end sequence - "did you get all those chaos emeralds?"
 
 	// Hardcoded fades or other fading methods
 	GS_INTRO,           // introduction
+	GS_ENDING,          // currently shared between bad and good endings
 	GS_CUTSCENE,        // custom cutscene
 
 	// Not fadable
@@ -50,6 +52,7 @@ typedef enum
 } gameaction_t;
 
 extern gamestate_t gamestate;
+extern UINT8 titlemapinaction;
 extern UINT8 ultimatemode; // was sk_insane
 extern gameaction_t gameaction;
 
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index dd9fa8423b21897fc8894d37bfd4e59486be171f..1ba357105b1e59e2d24dbc1ecebcc4ac52dfd449 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -283,7 +283,7 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 
 		if (!(option & V_SCALEPATCHMASK))
 		{
-			// if it's meant to cover the whole screen, black out the rest
+			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
 			// cx and cy are possibly *slightly* off from float maths
 			// This is done before here compared to software because we directly alter cx and cy to centre
 			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
@@ -291,8 +291,11 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 				// Need to temporarily cache the real patch to get the colour of the top left pixel
 				patch_t *realpatch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
 				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
-				const UINT8 *source = (const UINT8 *)(column) + 3;
-				HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				if (!column->topdelta)
+				{
+					const UINT8 *source = (const UINT8 *)(column) + 3;
+					HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				}
 				Z_Free(realpatch);
 			}
 			// centre screen
@@ -439,7 +442,7 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 
 		if (!(option & V_SCALEPATCHMASK))
 		{
-			// if it's meant to cover the whole screen, black out the rest
+			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
 			// cx and cy are possibly *slightly* off from float maths
 			// This is done before here compared to software because we directly alter cx and cy to centre
 			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
@@ -447,8 +450,11 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 				// Need to temporarily cache the real patch to get the colour of the top left pixel
 				patch_t *realpatch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
 				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
-				const UINT8 *source = (const UINT8 *)(column) + 3;
-				HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				if (!column->topdelta)
+				{
+					const UINT8 *source = (const UINT8 *)(column) + 3;
+					HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				}
 				Z_Free(realpatch);
 			}
 			// centre screen
@@ -683,8 +689,183 @@ void HWR_FadeScreenMenuBack(UINT16 color, UINT8 strength)
 	}
 	else // Do TRANSMAP** fade.
 	{
-		Surf.FlatColor.rgba = pLocalPalette[color].rgba;
-		Surf.FlatColor.s.alpha = (UINT8)(strength*25.5f);
+		Surf.FlatColor.rgba = V_GetColor(color).rgba;
+		Surf.FlatColor.s.alpha = softwaretranstogl[strength];
+	}
+	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
+}
+
+// -----------------+
+// HWR_DrawFadeFill : draw flat coloured rectangle, with transparency
+// -----------------+
+void HWR_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT16 actualcolor, UINT8 strength)
+{
+	FOutVector v[4];
+	FSurfaceInfo Surf;
+	float fx, fy, fw, fh;
+
+	UINT8 perplayershuffle = 0;
+
+//  3--2
+//  | /|
+//  |/ |
+//  0--1
+
+	if (splitscreen && (color & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				color &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	fx = (float)x;
+	fy = (float)y;
+	fw = (float)w;
+	fh = (float)h;
+
+	if (!(color & V_NOSCALESTART))
+	{
+		float dupx = (float)vid.dupx, dupy = (float)vid.dupy;
+
+		fx *= dupx;
+		fy *= dupy;
+		fw *= dupx;
+		fh *= dupy;
+
+		if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
+		{
+			if (color & V_SNAPTORIGHT)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
+			else if (!(color & V_SNAPTOLEFT))
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				fx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+		}
+		if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f)
+		{
+			// same thing here
+			if (color & V_SNAPTOBOTTOM)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
+			else if (!(color & V_SNAPTOTOP))
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				fy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+		}
+	}
+
+	if (fx >= vid.width || fy >= vid.height)
+		return;
+	if (fx < 0)
+	{
+		fw += fx;
+		fx = 0;
+	}
+	if (fy < 0)
+	{
+		fh += fy;
+		fy = 0;
+	}
+
+	if (fw <= 0 || fh <= 0)
+		return;
+	if (fx + fw > vid.width)
+		fw = (float)vid.width - fx;
+	if (fy + fh > vid.height)
+		fh = (float)vid.height - fy;
+
+	fx = -1 + fx / (vid.width / 2);
+	fy = 1 - fy / (vid.height / 2);
+	fw = fw / (vid.width / 2);
+	fh = fh / (vid.height / 2);
+
+	v[0].x = v[3].x = fx;
+	v[2].x = v[1].x = fx + fw;
+	v[0].y = v[1].y = fy;
+	v[2].y = v[3].y = fy - fh;
+
+	//Hurdler: do we still use this argb color? if not, we should remove it
+	v[0].argb = v[1].argb = v[2].argb = v[3].argb = 0xff00ff00; //;
+	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
+
+	v[0].sow = v[3].sow = 0.0f;
+	v[2].sow = v[1].sow = 1.0f;
+	v[0].tow = v[1].tow = 0.0f;
+	v[2].tow = v[3].tow = 1.0f;
+
+	if (actualcolor & 0xFF00) // Do COLORMAP fade.
+	{
+		Surf.FlatColor.rgba = UINT2RGBA(0x01010160);
+		Surf.FlatColor.s.alpha = (strength*8);
+	}
+	else // Do TRANSMAP** fade.
+	{
+		Surf.FlatColor.rgba = V_GetColor(actualcolor).rgba;
+		Surf.FlatColor.s.alpha = softwaretranstogl[strength];
 	}
 	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
 }
@@ -905,54 +1086,117 @@ void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, UINT32 color, INT32
 	FSurfaceInfo Surf;
 	float fx, fy, fw, fh;
 
-	if (w < 0 || h < 0)
-		return; // consistency w/ software
+	UINT8 perplayershuffle = 0;
 
 //  3--2
 //  | /|
 //  |/ |
 //  0--1
 
+	if (splitscreen && (color & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((color & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)/2.0f;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				color &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(color & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				color &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(color & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				color &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
 	fx = (float)x;
 	fy = (float)y;
 	fw = (float)w;
 	fh = (float)h;
 
-	if (!(options & V_NOSCALESTART))
+	if (!(color & V_NOSCALESTART))
 	{
 		float dupx = (float)vid.dupx, dupy = (float)vid.dupy;
 
-		if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
-		{
-			RGBA_t rgbaColour = V_GetColor(color);
-			FRGBAFloat clearColour;
-			clearColour.red = (float)rgbaColour.s.red / 255;
-			clearColour.green = (float)rgbaColour.s.green / 255;
-			clearColour.blue = (float)rgbaColour.s.blue / 255;
-			clearColour.alpha = 1;
-			HWD.pfnClearBuffer(true, false, &clearColour);
-			return;
-		}
-
 		fx *= dupx;
 		fy *= dupy;
 		fw *= dupx;
 		fh *= dupy;
 
-		if (fabsf((float)vid.width - ((float)BASEVIDWIDTH * dupx)) > 1.0E-36f)
+		if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
 		{
-			if (options & V_SNAPTORIGHT)
+			if (color & V_SNAPTORIGHT)
 				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx));
-			else if (!(options & V_SNAPTOLEFT))
+			else if (!(color & V_SNAPTOLEFT))
 				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				fx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4;
 		}
-		if (fabsf((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) > 1.0E-36f)
+		if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f)
 		{
 			// same thing here
-			if (options & V_SNAPTOBOTTOM)
+			if (color & V_SNAPTOBOTTOM)
 				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy));
-			else if (!(options & V_SNAPTOTOP))
+			else if (!(color & V_SNAPTOTOP))
 				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				fy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4;
 		}
 	}
 
@@ -1012,9 +1256,6 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 
 	UINT8 perplayershuffle = 0;
 
-	if (w < 0 || h < 0)
-		return; // consistency w/ software
-
 //  3--2
 //  | /|
 //  |/ |
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index 8baa547af31bf94bfa0f252e89d0909609b71c19..a52d72869ee87e9c40fa6f6348d2d489da67f73c 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -202,6 +202,7 @@ light_t *t_lspr[NUMSPRITES] =
 	// Boss 4 (Castle Eggman)
 	&lspr[NOLIGHT],     // SPR_EGGP
 	&lspr[REDBALL_L],   // SPR_EFIR
+	&lspr[NOLIGHT],     // SPR_EGR1
 
 	// Boss 5 (Arid Canyon)
 	&lspr[NOLIGHT],     //SPR_FANG // replaces EGGQ
@@ -487,6 +488,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_GFLG
 
 	&lspr[NOLIGHT],     // SPR_CORK
+	&lspr[NOLIGHT],     // SPR_LHRT
 
 	// Ring Weapons
 	&lspr[RINGLIGHT_L],     // SPR_RRNG
@@ -580,6 +582,9 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_ROIO
 	&lspr[NOLIGHT],     // SPR_ROIP
 
+	// Bricks
+	&lspr[NOLIGHT], // SPR_BRIC
+
 	// Gravity Well Objects
 	&lspr[NOLIGHT],     // SPR_GWLG
 	&lspr[NOLIGHT],     // SPR_GWLR
@@ -799,6 +804,14 @@ void HWR_WallLighting(FOutVector *wlVerts)
 		FSurfaceInfo    Surf;
 		float           dist_p2d, d[4], s;
 
+		if (!dynlights->mo[j])
+			continue;
+		if (P_MobjWasRemoved(dynlights->mo[j]))
+		{
+			P_SetTarget(&dynlights->mo[j], NULL);
+			continue;
+		}
+
 		// check bounding box first
 		if (SphereTouchBBox3D(&wlVerts[2], &wlVerts[0], &LIGHT_POS(j), DL_RADIUS(j))==false)
 			continue;
@@ -849,8 +862,6 @@ void HWR_WallLighting(FOutVector *wlVerts)
 #ifdef DL_HIGH_QUALITY
 		Surf.FlatColor.s.alpha = (UINT8)((1-dist_p2d/DL_SQRRADIUS(j))*Surf.FlatColor.s.alpha);
 #endif
-		if (!dynlights->mo[j]->state)
-			return;
 		// next state is null so fade out with alpha
 		if (dynlights->mo[j]->state->nextstate == S_NULL)
 			Surf.FlatColor.s.alpha = (UINT8)(((float)dynlights->mo[j]->tics/(float)dynlights->mo[j]->state->tics)*Surf.FlatColor.s.alpha);
@@ -881,6 +892,14 @@ void HWR_PlaneLighting(FOutVector *clVerts, int nrClipVerts)
 		FSurfaceInfo    Surf;
 		float           dist_p2d, s;
 
+		if (!dynlights->mo[j])
+			continue;
+		if (P_MobjWasRemoved(dynlights->mo[j]))
+		{
+			P_SetTarget(&dynlights->mo[j], NULL);
+			continue;
+		}
+
 		// BP: The kickass Optimization: check if light touch bounding box
 		if (SphereTouchBBox3D(&p1, &p2, &dynlights->position[j], DL_RADIUS(j))==false)
 			continue;
@@ -912,8 +931,6 @@ void HWR_PlaneLighting(FOutVector *clVerts, int nrClipVerts)
 #ifdef DL_HIGH_QUALITY
 		Surf.FlatColor.s.alpha = (unsigned char)((1 - dist_p2d/DL_SQRRADIUS(j))*Surf.FlatColor.s.alpha);
 #endif
-		if (!dynlights->mo[j]->state)
-			return;
 		// next state is null so fade out with alpha
 		if ((dynlights->mo[j]->state->nextstate == S_NULL))
 			Surf.FlatColor.s.alpha = (unsigned char)(((float)dynlights->mo[j]->tics/(float)dynlights->mo[j]->state->tics)*Surf.FlatColor.s.alpha);
@@ -1044,6 +1061,14 @@ void HWR_DrawCoronas(void)
 		if (!(p_lspr->type & CORONA_SPR))
 			continue;
 
+		if (!dynlights->mo[j])
+			continue;
+		if (P_MobjWasRemoved(dynlights->mo[j]))
+		{
+			P_SetTarget(&dynlights->mo[j], NULL);
+			continue;
+		}
+
 		transform(&cx,&cy,&cz);
 
 		// more realistique corona !
@@ -1105,7 +1130,8 @@ void HWR_DrawCoronas(void)
 // --------------------------------------------------------------------------
 void HWR_ResetLights(void)
 {
-	dynlights->nb = 0;
+	while (dynlights->nb)
+		P_SetTarget(&dynlights->mo[--dynlights->nb], NULL);
 }
 
 // --------------------------------------------------------------------------
@@ -1136,24 +1162,25 @@ void HWR_DL_AddLight(gr_vissprite_t *spr, GLPatch_t *patch)
 		return;
 #endif
 
+	if (dynlights->nb >= DL_MAX_LIGHT)
+		return;
+
 	// check if sprite contain dynamic light
 	p_lspr = t_lspr[spr->mobj->sprite];
-	if ((p_lspr->type&DYNLIGHT_SPR)
-	  && ((p_lspr->type != LIGHT_SPR) || cv_grstaticlighting.value)
-	  && (dynlights->nb < DL_MAX_LIGHT)
+	if (!(p_lspr->type & DYNLIGHT_SPR))
+		return;
+	if ((p_lspr->type != LIGHT_SPR) || cv_grstaticlighting.value)
+		return;
 
-	  && spr->mobj->state)
-	{
-		LIGHT_POS(dynlights->nb).x = FIXED_TO_FLOAT(spr->mobj->x);
-		LIGHT_POS(dynlights->nb).y = FIXED_TO_FLOAT(spr->mobj->z)+FIXED_TO_FLOAT(spr->mobj->height>>1)+p_lspr->light_yoffset;
-		LIGHT_POS(dynlights->nb).z = FIXED_TO_FLOAT(spr->mobj->y);
+	LIGHT_POS(dynlights->nb).x = FIXED_TO_FLOAT(spr->mobj->x);
+	LIGHT_POS(dynlights->nb).y = FIXED_TO_FLOAT(spr->mobj->z)+FIXED_TO_FLOAT(spr->mobj->height>>1)+p_lspr->light_yoffset;
+	LIGHT_POS(dynlights->nb).z = FIXED_TO_FLOAT(spr->mobj->y);
 
-		P_SetTarget(&dynlights->mo[dynlights->nb], spr->mobj);
+	P_SetTarget(&dynlights->mo[dynlights->nb], spr->mobj);
 
-		dynlights->p_lspr[dynlights->nb] = p_lspr;
+	dynlights->p_lspr[dynlights->nb] = p_lspr;
 
-		dynlights->nb++;
-	}
+	dynlights->nb++;
 }
 
 static GLPatch_t lightmappatch;
@@ -1307,6 +1334,14 @@ static void HWR_CheckSubsector(size_t num, fixed_t *bbox)
 //			if (CircleTouchBBox(&p1, &p2, &LIGHT_POS(lightnum), DL_RADIUS(lightnum))==false)
 //				continue;
 
+			if (!dynlights->mo[lightnum])
+				continue;
+			if (P_MobjWasRemoved(dynlights->mo[lightnum]))
+			{
+				P_SetTarget(&dynlights->mo[lightnum], NULL);
+				continue;
+			}
+
 			count = sub->numlines;          // how many linedefs
 			line = &segs[sub->firstline];   // first line seg
 			while (count--)
@@ -1324,18 +1359,20 @@ static void HWR_CheckSubsector(size_t num, fixed_t *bbox)
 // --------------------------------------------------------------------------
 static void HWR_AddMobjLights(mobj_t *thing)
 {
-	if (t_lspr[thing->sprite]->type & CORONA_SPR)
-	{
-		LIGHT_POS(dynlights->nb).x = FIXED_TO_FLOAT(thing->x);
-		LIGHT_POS(dynlights->nb).y = FIXED_TO_FLOAT(thing->z) + t_lspr[thing->sprite]->light_yoffset;
-		LIGHT_POS(dynlights->nb).z = FIXED_TO_FLOAT(thing->y);
+	if (dynlights->nb >= DL_MAX_LIGHT)
+		return;
+	if (!(t_lspr[thing->sprite]->type & CORONA_SPR))
+		return;
 
-		dynlights->p_lspr[dynlights->nb] = t_lspr[thing->sprite];
+	LIGHT_POS(dynlights->nb).x = FIXED_TO_FLOAT(thing->x);
+	LIGHT_POS(dynlights->nb).y = FIXED_TO_FLOAT(thing->z) + t_lspr[thing->sprite]->light_yoffset;
+	LIGHT_POS(dynlights->nb).z = FIXED_TO_FLOAT(thing->y);
 
-		dynlights->nb++;
-		if (dynlights->nb > DL_MAX_LIGHT)
-			dynlights->nb = DL_MAX_LIGHT;
-	}
+	P_SetTarget(&dynlights->mo[dynlights->nb], thing);
+
+	dynlights->p_lspr[dynlights->nb] = t_lspr[thing->sprite];
+
+	dynlights->nb++;
 }
 
 //Hurdler: The goal of this function is to walk through all the bsp starting
@@ -1361,12 +1398,9 @@ static void HWR_SearchLightsInMobjs(void)
 	//mobj_t *            mobj;
 
 	// search in the list of thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
-	{
-		// a mobj ?
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed)
 			HWR_AddMobjLights((mobj_t *)th);
-	}
 }
 #endif
 
@@ -1378,7 +1412,7 @@ void HWR_CreateStaticLightmaps(int bspnum)
 #ifdef STATICLIGHT
 	CONS_Debug(DBG_RENDER, "HWR_CreateStaticLightmaps\n");
 
-	dynlights->nb = 0;
+	HWR_ResetLights();
 
 	// First: Searching for lights
 	// BP: if i was you, I will make it in create mobj since mobj can be create
@@ -1389,8 +1423,6 @@ void HWR_CreateStaticLightmaps(int bspnum)
 	// Second: Build all lightmap for walls covered by lights
 	validcount++; // to be sure
 	HWR_ComputeLightMapsInBSPNode(bspnum, NULL);
-
-	dynlights->nb = 0;
 #else
 	(void)bspnum;
 #endif
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index cb0009da75a88e3185fc85dad4c286545735d0e5..c600800fd9ac9e0710a0eecdf44d7e7bf8a68753 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -580,7 +580,7 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	if (nrPlaneVerts < 3)   //not even a triangle ?
 		return;
 
-	if (nrPlaneVerts > UINT16_MAX) // FIXME: exceeds plVerts size
+	if (nrPlaneVerts > (INT32)UINT16_MAX) // FIXME: exceeds plVerts size
 	{
 		CONS_Debug(DBG_RENDER, "polygon size of %d exceeds max value of %d vertices\n", nrPlaneVerts, UINT16_MAX);
 		return;
@@ -3190,7 +3190,7 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	if (nrPlaneVerts < 3)   //not even a triangle ?
 		return;
 
-	if (nrPlaneVerts > UINT16_MAX) // FIXME: exceeds plVerts size
+	if (nrPlaneVerts > (size_t)UINT16_MAX) // FIXME: exceeds plVerts size
 	{
 		CONS_Debug(DBG_RENDER, "polygon size of %s exceeds max value of %d vertices\n", sizeu1(nrPlaneVerts), UINT16_MAX);
 		return;
@@ -5660,9 +5660,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	vis->z2 = z2;
 
 	//Hurdler: 25/04/2000: now support colormap in hardware mode
-	if ((vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && (leveltime & 1)) // Bosses "flash"
+	if ((vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
 	{
-		if (vis->mobj->type == MT_CYBRAKDEMON)
+		if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized)
 			vis->colormap = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
 		else if (vis->mobj->type == MT_METALSONIC_BATTLE)
 			vis->colormap = R_GetTranslationColormap(TC_METALSONIC, 0, GTC_CACHE);
@@ -5672,7 +5672,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	else if (thing->color)
 	{
 		// New colormap stuff for skins Tails 06-07-2002
-		if (thing->skin && thing->sprite == SPR_PLAY) // This thing is a player!
+		if (thing->colorized)
+			vis->colormap = R_GetTranslationColormap(TC_RAINBOW, thing->color, GTC_CACHE);
+		else if (thing->skin && thing->sprite == SPR_PLAY) // This thing is a player!
 		{
 			size_t skinnum = (skin_t*)thing->skin-skins;
 			vis->colormap = R_GetTranslationColormap((INT32)skinnum, thing->color, GTC_CACHE);
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index fdfc1d25722712d7f11740492d54483639d21bf1..fab18e08ac541345552e956e90f323657ce302da 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -49,6 +49,7 @@ void HWR_CreatePlanePolygons(INT32 bspnum);
 void HWR_CreateStaticLightmaps(INT32 bspnum);
 void HWR_PrepLevelCache(size_t pnumtextures);
 void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color);
+void HWR_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT16 actualcolor, UINT8 strength);
 void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, UINT32 color, INT32 options);	// Lat: separate flags from color since color needs to be an uint to work right.
 void HWR_DrawPic(INT32 x,INT32 y,lumpnum_t lumpnum);
 
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index d69233a9b8e8aab91c1fcf252d71ee6a195600f6..c2faa8b862745097660f12a98bb70ad706f45708 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -40,6 +40,8 @@
 #include "../w_wad.h"
 #include "../z_zone.h"
 #include "../r_things.h"
+#include "../r_draw.h"
+#include "../p_tick.h"
 
 #include "hw_main.h"
 #include "../v_video.h"
@@ -978,8 +980,18 @@ spritemd2found:
 	fclose(f);
 }
 
-static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, GLMipmap_t *grmip, skincolors_t color)
+// Define for getting accurate color brightness readings according to how the human eye sees them.
+// https://en.wikipedia.org/wiki/Relative_luminance
+// 0.2126 to red
+// 0.7152 to green
+// 0.0722 to blue
+// (See this same define in k_kart.c!)
+#define SETBRIGHTNESS(brightness,r,g,b) \
+	brightness = (UINT8)(((1063*((UINT16)r)/5000) + (3576*((UINT16)g)/5000) + (361*((UINT16)b)/5000)) / 3)
+
+static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, GLMipmap_t *grmip, INT32 skinnum, skincolors_t color)
 {
+	UINT8 i;
 	UINT16 w = gpatch->width, h = gpatch->height;
 	UINT32 size = w*h;
 	RGBA_t *image, *blendimage, *cur, blendcolor;
@@ -1005,50 +1017,112 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 	image = gpatch->mipmap.grInfo.data;
 	blendimage = blendgpatch->mipmap.grInfo.data;
 
+	// Average all of the translation's colors
 	if (color == SKINCOLOR_NONE || color >= MAXTRANSLATIONS)
 		blendcolor = V_GetColor(0xff);
 	else
-		blendcolor = V_GetColor(Color_Index[color-1][4]);
-
-	while (size--)
 	{
-		if (blendimage->s.alpha == 0)
+		const UINT8 div = 6;
+		const UINT8 start = 4;
+		UINT32 r, g, b;
+
+		blendcolor = V_GetColor(Color_Index[color-1][start]);
+		r = (UINT32)(blendcolor.s.red*blendcolor.s.red);
+		g = (UINT32)(blendcolor.s.green*blendcolor.s.green);
+		b = (UINT32)(blendcolor.s.blue*blendcolor.s.blue);
+
+		for (i = 1; i < div; i++)
 		{
-			// Don't bother with blending the pixel if the alpha of the blend pixel is 0
-			cur->rgba = image->rgba;
+			RGBA_t nextcolor = V_GetColor(Color_Index[color-1][start+i]);
+			r += (UINT32)(nextcolor.s.red*nextcolor.s.red);
+			g += (UINT32)(nextcolor.s.green*nextcolor.s.green);
+			b += (UINT32)(nextcolor.s.blue*nextcolor.s.blue);
 		}
-		else
+
+		blendcolor.s.red = (UINT8)(FixedSqrt((r/div)<<FRACBITS)>>FRACBITS);
+		blendcolor.s.green = (UINT8)(FixedSqrt((g/div)<<FRACBITS)>>FRACBITS);
+		blendcolor.s.blue = (UINT8)(FixedSqrt((b/div)<<FRACBITS)>>FRACBITS);
+	}
+
+	// rainbow support, could theoretically support boss ones too
+	if (skinnum == TC_RAINBOW)
+	{
+		while (size--)
 		{
-			INT32 tempcolor;
-			INT16 tempmult, tempalpha;
-			tempalpha = -(abs(blendimage->s.red-127)-127)*2;
-			if (tempalpha > 255)
-				tempalpha = 255;
-			else if (tempalpha < 0)
-				tempalpha = 0;
-
-			tempmult = (blendimage->s.red-127)*2;
-			if (tempmult > 255)
-				tempmult = 255;
-			else if (tempmult < 0)
-				tempmult = 0;
-
-			tempcolor = (image->s.red*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.red)/255)) * blendimage->s.alpha)/255;
-			cur->s.red = (UINT8)tempcolor;
-			tempcolor = (image->s.green*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.green)/255)) * blendimage->s.alpha)/255;
-			cur->s.green = (UINT8)tempcolor;
-			tempcolor = (image->s.blue*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.blue)/255)) * blendimage->s.alpha)/255;
-			cur->s.blue = (UINT8)tempcolor;
-			cur->s.alpha = image->s.alpha;
+			if (image->s.alpha == 0 && blendimage->s.alpha == 0)
+			{
+				// Don't bother with blending the pixel if the alpha of the blend pixel is 0
+				cur->rgba = image->rgba;
+			}
+			else
+			{
+				UINT32 tempcolor;
+				UINT16 imagebright, blendbright, finalbright, colorbright;
+				SETBRIGHTNESS(imagebright,image->s.red,image->s.green,image->s.blue);
+				SETBRIGHTNESS(blendbright,blendimage->s.red,blendimage->s.green,blendimage->s.blue);
+				// slightly dumb average between the blend image color and base image colour, usually one or the other will be fully opaque anyway
+				finalbright = (imagebright*(255-blendimage->s.alpha))/255 + (blendbright*blendimage->s.alpha)/255;
+				SETBRIGHTNESS(colorbright,blendcolor.s.red,blendcolor.s.green,blendcolor.s.blue);
+
+				tempcolor = (finalbright*blendcolor.s.red)/colorbright;
+				tempcolor = min(255, tempcolor);
+				cur->s.red = (UINT8)tempcolor;
+				tempcolor = (finalbright*blendcolor.s.green)/colorbright;
+				tempcolor = min(255, tempcolor);
+				cur->s.green = (UINT8)tempcolor;
+				tempcolor = (finalbright*blendcolor.s.blue)/colorbright;
+				tempcolor = min(255, tempcolor);
+				cur->s.blue = (UINT8)tempcolor;
+				cur->s.alpha = image->s.alpha;
+			}
+
+			cur++; image++; blendimage++;
 		}
+	}
+	else
+	{
+		while (size--)
+		{
+			if (blendimage->s.alpha == 0)
+			{
+				// Don't bother with blending the pixel if the alpha of the blend pixel is 0
+				cur->rgba = image->rgba;
+			}
+			else
+			{
+				INT32 tempcolor;
+				INT16 tempmult, tempalpha;
+				tempalpha = -(abs(blendimage->s.red-127)-127)*2;
+				if (tempalpha > 255)
+					tempalpha = 255;
+				else if (tempalpha < 0)
+					tempalpha = 0;
+
+				tempmult = (blendimage->s.red-127)*2;
+				if (tempmult > 255)
+					tempmult = 255;
+				else if (tempmult < 0)
+					tempmult = 0;
+
+				tempcolor = (image->s.red*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.red)/255)) * blendimage->s.alpha)/255;
+				cur->s.red = (UINT8)tempcolor;
+				tempcolor = (image->s.green*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.green)/255)) * blendimage->s.alpha)/255;
+				cur->s.green = (UINT8)tempcolor;
+				tempcolor = (image->s.blue*(255-blendimage->s.alpha))/255 + ((tempmult + ((tempalpha*blendcolor.s.blue)/255)) * blendimage->s.alpha)/255;
+				cur->s.blue = (UINT8)tempcolor;
+				cur->s.alpha = image->s.alpha;
+			}
 
-		cur++; image++; blendimage++;
+			cur++; image++; blendimage++;
+		}
 	}
 
 	return;
 }
 
-static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, const UINT8 *colormap, skincolors_t color)
+#undef SETBRIGHTNESS
+
+static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT32 skinnum, const UINT8 *colormap, skincolors_t color)
 {
 	// mostly copied from HWR_GetMappedPatch, hence the similarities and comment
 	GLMipmap_t *grmip, *newmip;
@@ -1089,13 +1163,14 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, con
 	grmip->nextcolormap = newmip;
 	newmip->colormap = colormap;
 
-	HWR_CreateBlendedTexture(gpatch, blendgpatch, newmip, color);
+	HWR_CreateBlendedTexture(gpatch, blendgpatch, newmip, skinnum, color);
 
 	HWD.pfnSetTexture(newmip);
 	Z_ChangeTag(newmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
 }
 
 
+
 // -----------------+
 // HWR_DrawMD2      : Draw MD2
 //                  : (monsters, bonuses, weapons, lights, ...)
@@ -1123,6 +1198,9 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 	if (!md2 || !skin)
 		return 0;
 
+	if ((spr2 & ~FF_SPR2SUPER) >= free_spr2)
+		return 0;
+
 	while (!(md2->model->spr2frames[spr2*2 + 1])
 		&& spr2 != SPR2_STND
 		&& ++i != 32) // recursion limiter
@@ -1145,7 +1223,10 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
 			break;
 		case SPR2_TIRE:
-			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
+			spr2 = ((player
+					? player->charability
+					: skin->ability)
+					== CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
 			break;
 
 		// Use the handy list, that's what it's there for!
@@ -1157,6 +1238,9 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 		spr2 |= super;
 	}
 
+	if (i >= 32) // probably an infinite loop...
+		return 0;
+
 	return spr2;
 }
 
@@ -1285,7 +1369,30 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 				md2->blendgrpatch && ((GLPatch_t *)md2->blendgrpatch)->mipmap.grInfo.format
 				&& gpatch->width == ((GLPatch_t *)md2->blendgrpatch)->width && gpatch->height == ((GLPatch_t *)md2->blendgrpatch)->height)
 			{
-				HWR_GetBlendedTexture(gpatch, (GLPatch_t *)md2->blendgrpatch, spr->colormap, (skincolors_t)spr->mobj->color);
+				INT32 skinnum = TC_DEFAULT;
+				if ((spr->mobj->flags & (MF_ENEMY|MF_BOSS)) && (spr->mobj->flags2 & MF2_FRET) && !(spr->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
+				{
+					if (spr->mobj->type == MT_CYBRAKDEMON || spr->mobj->colorized)
+						skinnum = TC_ALLWHITE;
+					else if (spr->mobj->type == MT_METALSONIC_BATTLE)
+						skinnum = TC_METALSONIC;
+					else
+						skinnum = TC_BOSS;
+				}
+				else if (spr->mobj->color)
+				{
+					if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
+					{
+						if (spr->mobj->colorized)
+							skinnum = TC_RAINBOW;
+						else
+						{
+							skinnum = (INT32)((skin_t*)spr->mobj->skin-skins);
+						}
+					}
+					else skinnum = TC_DEFAULT;
+				}
+				HWR_GetBlendedTexture(gpatch, (GLPatch_t *)md2->blendgrpatch, skinnum, spr->colormap, (skincolors_t)spr->mobj->color);
 			}
 			else
 			{
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index f86100e27148054a0fc4fa167f0e429fc220f7a8..3bc643c3c2537f691e9545ceccfeec370c9a2539 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2138,7 +2138,7 @@ void HU_Drawer(void)
 	if (!Playing()
 	 || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE
 	 || gamestate == GS_CREDITS      || gamestate == GS_EVALUATION
-	 || gamestate == GS_GAMEEND)
+	 || gamestate == GS_ENDING       || gamestate == GS_GAMEEND)
 		return;
 
 	// draw multiplayer rankings
diff --git a/src/info.c b/src/info.c
index 074e31ba2eb044476607796a58ab05f0d6c90c1e..d35be7b5814d4e5ed7c09de6ed8c6ce5f346c894 100644
--- a/src/info.c
+++ b/src/info.c
@@ -90,6 +90,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	// Boss 4 (Castle Eggman)
 	"EGGP",
 	"EFIR", // Boss 4 jet flame
+	"EGR1", // Boss 4 Spectator Eggrobo
 
 	// Boss 5 (Arid Canyon)
 	"FANG", // replaces EGGQ
@@ -382,6 +383,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	"GFLG", // Got Flag sign
 
 	"CORK",
+	"LHRT",
 
 	// Ring Weapons
 	"RRNG", // Red Ring
@@ -475,6 +477,9 @@ char sprnames[NUMSPRITES + 1][5] =
 	"ROIO",
 	"ROIP",
 
+	// Bricks
+	"BRIC",
+
 	// Gravity Well Objects
 	"GWLG",
 	"GWLR",
@@ -573,7 +578,8 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"TALB",
 
 	"SIGN",
-	"LIFE"
+	"LIFE",
+	"XTRA",
 };
 playersprite_t free_spr2 = SPR2_FIRSTFREESLOT;
 
@@ -590,7 +596,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	SPR2_DEAD, // SPR2_DRWN,
 	0, // SPR2_ROLL,
 	SPR2_SPNG, // SPR2_GASP,
-	0, // SPR2_JUMP, (conditional)
+	0, // SPR2_JUMP, (conditional, will never be referenced)
 	SPR2_FALL, // SPR2_SPNG,
 	SPR2_WALK, // SPR2_FALL,
 	0, // SPR2_EDGE,
@@ -600,7 +606,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 
 	SPR2_SPNG, // SPR2_FLY ,
 	SPR2_FLY , // SPR2_SWIM,
-	0, // SPR2_TIRE, (conditional)
+	0, // SPR2_TIRE, (conditional, will never be referenced)
 
 	SPR2_FLY , // SPR2_GLID,
 	SPR2_CLMB, // SPR2_CLNG,
@@ -627,7 +633,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	SPR2_NSTN, // SPR2_NPUL,
 	FF_SPR2SUPER|SPR2_ROLL, // SPR2_NATK,
 
-	0, // SPR2_NGT0, (should never be referenced)
+	0, // SPR2_NGT0, (will never be referenced unless skin 0 lacks this)
 	SPR2_NGT0, // SPR2_NGT1,
 	SPR2_NGT1, // SPR2_NGT2,
 	SPR2_NGT2, // SPR2_NGT3,
@@ -655,7 +661,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	SPR2_NGTB, // SPR2_DRLB,
 	SPR2_NGTC, // SPR2_DRLC,
 
-	0, // SPR2_TAL0,
+	0, // SPR2_TAL0, (this will look mighty stupid but oh well)
 	SPR2_TAL0, // SPR2_TAL1,
 	SPR2_TAL1, // SPR2_TAL2,
 	SPR2_TAL2, // SPR2_TAL3,
@@ -670,6 +676,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 
 	0, // SPR2_SIGN,
 	0, // SPR2_LIFE,
+	0, // SPR2_XTRA (should never be referenced)
 };
 
 // Doesn't work with g++, needs actionf_p1 (don't modify this comment)
@@ -742,10 +749,10 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_FIRE,                 15, {NULL},        S_PLAY_STND, 0, S_PLAY_STND},   // S_PLAY_FIRE_FINISH
 
 	// CA_TWINSPIN
-	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
+	{SPR_PLAY, SPR2_TWIN|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_JUMP, 0, S_PLAY_TWINSPIN}, // S_PLAY_TWINSPIN
 
 	// CA2_MELEE
-	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 1, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
+	{SPR_PLAY, SPR2_MLEE|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_MELEE_FINISH, 0, S_PLAY_MELEE}, // S_PLAY_MELEE
 	{SPR_PLAY, SPR2_MLEE,                70, {NULL},                   0, 0, S_PLAY_FALL},  // S_PLAY_MELEE_FINISH
 	{SPR_PLAY, SPR2_MLEL,                35, {NULL},                   0, 0, S_PLAY_WALK},  // S_PLAY_MELEE_LANDING
 
@@ -1268,6 +1275,11 @@ 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,  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
@@ -1275,12 +1287,7 @@ state_t states[NUMSTATES] =
 	{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_LAUGH1},  // S_EGGMOBILE3_ATK5
-	{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_LAUGH6},  // S_EGGMOBILE3_LAUGH5
+	{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
@@ -1327,14 +1334,14 @@ state_t states[NUMSTATES] =
 	// Boss 3 Pinch
 	{SPR_FAKE, 0,  1, {A_BossJetFume},            1, 0, S_FAKEMOBILE},       // S_FAKEMOBILE_INIT
 	{SPR_FAKE, 0,  1, {A_Boss3Path},              0, 0, S_FAKEMOBILE},       // S_FAKEMOBILE
-	{SPR_FAKE, 0,  2, {NULL},                     0, 0, S_FAKEMOBILE_ATK2},  // S_FAKEMOBILE_ATK1
+	{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
 	{SPR_FAKE, 0,  2, {A_BossFireShot}, MT_TORPEDO2, 2, S_FAKEMOBILE_ATK3B}, // S_FAKEMOBILE_ATK3A
 	{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_ATK4},  // S_FAKEMOBILE_ATK3D
-	{SPR_FAKE, 0,  2, {NULL},                     0, 0, S_FAKEMOBILE_ATK5},  // S_FAKEMOBILE_ATK4
-	{SPR_FAKE, 0,  2, {NULL},                     0, 0, S_FAKEMOBILE},       // S_FAKEMOBILE_ATK5
+	{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_NULL, 0,  1, {NULL},                     0, 0, S_FAKEMOBILE_DIE1},  // S_FAKEMOBILE_DIE2
 
 	// Boss 4
 	{SPR_EGGP, 0, -1, {NULL},           0,          0, S_NULL},              // S_EGGMOBILE4_STND
@@ -1351,16 +1358,9 @@ state_t states[NUMSTATES] =
 	{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, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE3}, // S_EGGMOBILE4_RAISE2
-	{SPR_EGGP,14, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE4}, // S_EGGMOBILE4_RAISE3
-	{SPR_EGGP,13, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE5}, // S_EGGMOBILE4_RAISE4
-	{SPR_EGGP,14, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE6}, // S_EGGMOBILE4_RAISE5
-	{SPR_EGGP,13, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE7}, // S_EGGMOBILE4_RAISE6
-	{SPR_EGGP,14, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE8}, // S_EGGMOBILE4_RAISE7
-	{SPR_EGGP,13, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE9}, // S_EGGMOBILE4_RAISE8
-	{SPR_EGGP,14, 10, {NULL},           0,          0, S_EGGMOBILE4_RAISE10},// S_EGGMOBILE4_RAISE9
-	{SPR_EGGP,13, 10, {NULL},           0,          0, S_EGGMOBILE4_STND},   // S_EGGMOBILE4_RAISE10
-	{SPR_EGGP,11, 24, {A_Pain},         0,          0, S_EGGMOBILE4_STND},   // S_EGGMOBILE4_PAIN
+	{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,  8, {A_Fall},         0,          0, S_EGGMOBILE4_DIE2},   // S_EGGMOBILE4_DIE1
 	{SPR_EGGP,12,  8, {A_BossScream},   0,          0, S_EGGMOBILE4_DIE3},   // S_EGGMOBILE4_DIE2
 	{SPR_EGGP,12,  8, {A_BossScream},   0,          0, S_EGGMOBILE4_DIE4},   // S_EGGMOBILE4_DIE3
@@ -1378,10 +1378,21 @@ state_t states[NUMSTATES] =
 	{SPR_EGGP,13,  5, {NULL},           0,          0, S_EGGMOBILE4_FLEE2},  // S_EGGMOBILE4_FLEE1
 	{SPR_EGGP,14,  5, {NULL},           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, MT_SONIC3KBOSSEXPLODE, S_EGGMOBILE4_MACE_DIE2},  // S_EGGMOBILE4_MACE_DIE1
+	{SPR_NULL, 0,  2, {A_BossScream},   1, MT_SONIC3KBOSSEXPLODE, S_EGGMOBILE4_MACE_DIE3},  // S_EGGMOBILE4_MACE_DIE2
+	{SPR_NULL, 0,  0, {A_Repeat},       7, S_EGGMOBILE4_MACE_DIE1,    S_BOSSEXPLODE},       // S_EGGMOBILE4_MACE_DIE3
 
-	// Boss 4 Jet flame
-	{SPR_EFIR, FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_JETFLAME2}, // S_JETFLAME1
-	{SPR_EFIR, FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_JETFLAME1}, // S_JETFLAME2
+	// Boss 4 jet flame
+	{SPR_EFIR, FF_FULLBRIGHT|FF_ANIMATE, -1, {NULL}, 1, 1, S_NULL}, // S_JETFLAME
+
+	// Boss 4 Spectator Eggrobo
+	{SPR_EGR1,            0, -1, {NULL}, 0, 0, S_NULL},            // S_EGGROBO1_STND
+	{SPR_EGR1,            5,  2, {NULL}, 0, 0, S_EGGROBO1_BSLAP2}, // S_EGGROBO1_BSLAP1
+	{SPR_EGR1, FF_ANIMATE|6, 35, {NULL}, 1, 2, S_EGGROBO1_STND},   // S_EGGROBO1_BSLAP2
+	{SPR_EGR1, FF_ANIMATE|3, -1, {NULL}, 1, 2, S_NULL},            // S_EGGROBO1_PISSED
+
+	// Boss 4 Spectator Eggrobo jet flame
+	{SPR_EFIR, FF_FULLBRIGHT|2,          -1, {NULL}, 0, 0, S_NULL}, // S_EGGROBOJET
 
 	// Boss 5
 	{SPR_FANG, 2, 16, {A_Look}, 1, 0, S_FANG_IDLE2}, // S_FANG_IDLE1
@@ -1746,20 +1757,23 @@ state_t states[NUMSTATES] =
 	{SPR_METL,  9,  2, {NULL}, 0, 0, S_METALSONIC_RUN1},  // S_METALSONIC_RUN4
 
 	{SPR_METL,  4, -1, {NULL},         0, 0, S_NULL},             // S_METALSONIC_FLOAT
-	{SPR_METL, 12, -1, {NULL},         0, 0, S_METALSONIC_STUN},  // S_METALSONIC_VECTOR
-	{SPR_METL,  0, -1, {NULL},         0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_STUN
-	{SPR_METL, 13, 40, {NULL},         0, 0, S_METALSONIC_GATHER},// S_METALSONIC_RAISE
+	{SPR_METL, 12|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_METALSONIC_STUN}, // S_METALSONIC_VECTOR
+	{SPR_METL, 11, -1, {NULL},         0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_STUN
+	{SPR_METL, 13, 20, {NULL},         0, 0, S_METALSONIC_GATHER},// S_METALSONIC_RAISE
 	{SPR_METL, 14, -1, {NULL},         0, 0, S_NULL},             // S_METALSONIC_GATHER
 	{SPR_METL, 15, -1, {NULL},         0, 0, S_METALSONIC_BOUNCE},// S_METALSONIC_DASH
 	{SPR_METL, 14, -1, {NULL},         0, 0, S_NULL},             // S_METALSONIC_BOUNCE
 	{SPR_METL, 16, -1, {NULL},         0, 0, S_NULL},             // S_METALSONIC_BADBOUNCE
 	{SPR_METL, 13, -1, {NULL},         0, 0, S_METALSONIC_GATHER},// S_METALSONIC_SHOOT
 	{SPR_METL, 11, 40, {A_Pain},       0, 0, S_METALSONIC_FLOAT}, // S_METALSONIC_PAIN
-	{SPR_METL, 11, -1, {A_BossDeath},  0, 0, S_NULL},             // S_METALSONIC_DEATH
-	{SPR_METL,  3,  4, {NULL},         0, 0, S_METALSONIC_FLEE2}, // S_METALSONIC_FLEE1
-	{SPR_METL,  4,  4, {A_BossScream}, 0, 0, S_METALSONIC_FLEE3}, // S_METALSONIC_FLEE2
-	{SPR_METL,  5,  4, {NULL},         0, 0, S_METALSONIC_FLEE4}, // S_METALSONIC_FLEE3
-	{SPR_METL,  4,  4, {NULL},         0, 0, S_METALSONIC_FLEE1}, // S_METALSONIC_FLEE4
+	{SPR_METL, 13,  8, {A_Fall},       0, 0, S_METALSONIC_DEATH2},// S_METALSONIC_DEATH1
+	{SPR_METL, 13,  8, {A_BossScream}, 0, 0, S_METALSONIC_DEATH3},// S_METALSONIC_DEATH2
+	{SPR_METL, 13, 0, {A_Repeat}, 11, S_METALSONIC_DEATH2, S_METALSONIC_DEATH4}, // S_METALSONIC_DEATH3
+	{SPR_METL, 13, -1, {A_BossDeath},  0, 0, S_NULL},             // S_METALSONIC_DEATH4
+	{SPR_METL, 11,  4, {NULL},         0, 0, S_METALSONIC_FLEE2}, // S_METALSONIC_FLEE1
+	{SPR_METL, 11,  4, {A_BossScream}, 0, 0, S_METALSONIC_FLEE3}, // S_METALSONIC_FLEE2
+	{SPR_METL, 11,  4, {NULL},         0, 0, S_METALSONIC_FLEE4}, // S_METALSONIC_FLEE3
+	{SPR_METL, 11,  4, {NULL},         0, 0, S_METALSONIC_FLEE1}, // S_METALSONIC_FLEE4
 
 	{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
@@ -1779,7 +1793,11 @@ state_t states[NUMSTATES] =
 
 	// Blue Sphere for special stages
 	{SPR_SPHR, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BLUESPHERE
-	{SPR_SPHR, FF_FULLBRIGHT|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 1, 4, S_NULL}, // S_BLUESPHEREBONUS
+	{SPR_SPHR, FF_FULLBRIGHT
+#ifdef MANIASPHERES
+							|FF_ANIMATE|FF_RANDOMANIM
+#endif
+													, -1, {NULL}, 1, 4, S_NULL}, // S_BLUESPHEREBONUS
 	{SPR_SPHR, 0, 20, {NULL}, 0, 0, S_NULL}, // S_BLUESPHERESPARK
 
 	// Bomb Sphere
@@ -2310,9 +2328,10 @@ state_t states[NUMSTATES] =
 	{SPR_BFBR, FF_FULLBRIGHT|15,  1, {NULL},            0, 0, S_BIGFIREBAR1},  // S_BIGFIREBAR16
 
 	{SPR_FWR4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZFLOWER
-	{SPR_BANR, 1, -1, {NULL}, 0, 0, S_NULL}, // S_CEZPOLE
+	{SPR_BANR, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZPOLE
 
-	{SPR_BANR, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_CEZBANNER
+	{SPR_BANR, FF_PAPERSPRITE|1, -1, {NULL}, 0, 0, S_NULL}, // S_CEZBANNER1
+	{SPR_BANR, FF_PAPERSPRITE|2, -1, {NULL}, 0, 0, S_NULL}, // S_CEZBANNER2
 
 	{SPR_PINE, 0, -1, {NULL}, 0, 0, S_NULL}, // S_PINETREE
 	{SPR_CEZB, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZBUSH1
@@ -2326,7 +2345,8 @@ state_t states[NUMSTATES] =
 	{SPR_CTRC, FF_FULLBRIGHT|FF_ANIMATE, 8*3, {A_FlameParticle}, 3, 3, S_FIRETORCH}, // S_FIRETORCH
 
 	{SPR_CFLG,                0, -1, {NULL}, 0, 0, S_NULL}, // S_WAVINGFLAG
-	{SPR_CFLG, FF_PAPERSPRITE|1, -1, {NULL}, 0, 0, S_NULL}, // S_WAVINGFLAGSEG
+	{SPR_CFLG, FF_PAPERSPRITE|1, -1, {NULL}, 0, 0, S_NULL}, // S_WAVINGFLAGSEG1
+	{SPR_CFLG, FF_PAPERSPRITE|2, -1, {NULL}, 0, 0, S_NULL}, // S_WAVINGFLAGSEG2
 
 	{SPR_CSTA, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CRAWLASTATUE
 
@@ -2803,12 +2823,18 @@ state_t states[NUMSTATES] =
 	{SPR_ELEM, FF_FULLBRIGHT|20, 1, {NULL}, 0, 0, S_ELEMF10}, // S_ELEMF9
 	{SPR_NULL, 0,                1, {NULL}, 0, 0, S_ELEMF1 }, // S_ELEMF10
 
-	{SPR_PITY, FF_TRANS30  , 2, {NULL}, 0, 0, S_PITY2}, // S_PITY1
-	{SPR_PITY, FF_TRANS30|1, 2, {NULL}, 0, 0, S_PITY3}, // S_PITY2
-	{SPR_PITY, FF_TRANS30|2, 2, {NULL}, 0, 0, S_PITY4}, // S_PITY3
-	{SPR_PITY, FF_TRANS20|3, 2, {NULL}, 0, 0, S_PITY5}, // S_PITY4
-	{SPR_PITY, FF_TRANS30|4, 2, {NULL}, 0, 0, S_PITY6}, // S_PITY5
-	{SPR_PITY, FF_TRANS20|5, 2, {NULL}, 0, 0, S_PITY1}, // S_PITY6
+	{SPR_PITY, FF_TRANS30   , 2, {NULL}, 0, 0, S_PITY2},  // S_PITY1
+	{SPR_PITY, FF_TRANS30| 1, 2, {NULL}, 0, 0, S_PITY3},  // S_PITY2
+	{SPR_PITY, FF_TRANS30| 2, 2, {NULL}, 0, 0, S_PITY4},  // S_PITY3
+	{SPR_PITY, FF_TRANS30| 3, 2, {NULL}, 0, 0, S_PITY5},  // S_PITY4
+	{SPR_PITY, FF_TRANS30| 4, 2, {NULL}, 0, 0, S_PITY6},  // S_PITY5
+	{SPR_PITY, FF_TRANS30| 5, 2, {NULL}, 0, 0, S_PITY7},  // S_PITY6
+	{SPR_PITY, FF_TRANS30| 6, 2, {NULL}, 0, 0, S_PITY8},  // S_PITY7
+	{SPR_PITY, FF_TRANS30| 7, 2, {NULL}, 0, 0, S_PITY9},  // S_PITY8
+	{SPR_PITY, FF_TRANS30| 8, 2, {NULL}, 0, 0, S_PITY10}, // S_PITY9
+	{SPR_PITY, FF_TRANS30| 9, 2, {NULL}, 0, 0, S_PITY11}, // S_PITY10
+	{SPR_PITY, FF_TRANS30|10, 2, {NULL}, 0, 0, S_PITY12}, // S_PITY11
+	{SPR_PITY, FF_TRANS30|11, 2, {NULL}, 0, 0, S_PITY1},  // S_PITY12
 
 	{SPR_FIRS, FF_FULLBRIGHT|FF_TRANS40  , 2, {NULL}, 0, 0, S_FIRS2}, // S_FIRS1
 	{SPR_FIRS, FF_FULLBRIGHT|FF_TRANS40|1, 2, {NULL}, 0, 0, S_FIRS3}, // S_FIRS2
@@ -2886,7 +2912,7 @@ state_t states[NUMSTATES] =
 	{SPR_NULL,                           0, 15*2, {NULL}, 0, 0, S_ZAPSB2 }, // S_ZAPSB11
 
 	// Thunder spark
-	{SPR_SSPK, FF_ANIMATE, 18, {NULL}, 1, 2, S_NULL},   // S_THUNDERCOIN_SPARK
+	{SPR_SSPK, FF_ANIMATE, -1, {NULL}, 1, 2, S_NULL},   // S_THUNDERCOIN_SPARK
 
 	// Invincibility Sparkles
 	{SPR_IVSP, FF_ANIMATE, 32, {NULL}, 31, 1, S_NULL},   // S_IVSP
@@ -3187,8 +3213,8 @@ state_t states[NUMSTATES] =
 	{SPR_SSWB, 1, 1, {NULL}, 0, 0, S_BHORIZ1},   // S_BHORIZ8
 
 	// Rain
-	{SPR_RAIN, FF_TRANS50, -1, {NULL}, 0, 0, S_NULL}, // S_RAIN1
-	{SPR_RAIN, FF_TRANS50, 1, {NULL}, 0, 0, S_RAIN1}, // S_RAINRETURN
+	{SPR_RAIN, FF_FULLBRIGHT|FF_TRANS50, -1, {NULL}, 0, 0, S_NULL}, // S_RAIN1
+	{SPR_RAIN, FF_FULLBRIGHT|FF_TRANS50, 1, {NULL}, 0, 0, S_RAIN1}, // S_RAINRETURN
 
 	// Snowflake
 	{SPR_SNO1, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SNOW1
@@ -3298,13 +3324,21 @@ state_t states[NUMSTATES] =
 
 	{SPR_LCKN,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON1
 	{SPR_LCKN, 1|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON2
+	{SPR_LCKN, 2|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON3
+	{SPR_LCKN, 3|FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LOCKON4
+
+	{SPR_LCKN,   FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LOCKONINF1
+	{SPR_LCKN, 1|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LOCKONINF2
+	{SPR_LCKN, 2|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LOCKONINF3
+	{SPR_LCKN, 3|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LOCKONINF4
 
 	{SPR_TTAG, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_TTAG
 
 	// CTF Sign
 	{SPR_GFLG,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTFLAG
 
-	{SPR_CORK, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CORK
+	{SPR_CORK,             0, -1, {NULL}, 0, 0, S_NULL}, // S_CORK
+	{SPR_LHRT, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LHRT
 
 	// Red Rings (thrown)
 	{SPR_RRNG, FF_FULLBRIGHT,   1, {A_ThrownRing}, 0, 0, S_RRNG2}, // S_RRNG1
@@ -3861,6 +3895,8 @@ state_t states[NUMSTATES] =
 	{SPR_ROIO, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEO
 	{SPR_ROIP, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEP
 
+	{SPR_BRIC, FF_ANIMATE, -1, {A_DebrisRandom}, 7, 2, S_NULL}, // S_BRICKDEBRIS
+
 #ifdef SEENAMES
 	{SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_NAMECHECK
 #endif
@@ -5500,7 +5536,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MT_PROPELLER,       // painchance
 		sfx_dmpain,         // painsound
 		S_NULL,             // meleestate
-		S_EGGMOBILE3_ATK1,  // missilestate
+		S_EGGMOBILE3_LAUGH1,// missilestate
 		S_EGGMOBILE3_DIE1,  // deathstate
 		S_EGGMOBILE3_FLEE1, // xdeathstate
 		sfx_cybdth,         // deathsound
@@ -5555,9 +5591,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_s3k7b,          // painsound
 		S_NULL,             // meleestate
 		S_FAKEMOBILE_ATK1,  // missilestate
-		S_XPLD1,            // deathstate
+		S_FAKEMOBILE_DIE1,  // deathstate
 		S_NULL,             // xdeathstate
-		sfx_pop,            // deathsound
+		sfx_mswarp,         // deathsound
 		8*FRACUNIT,         // speed
 		32*FRACUNIT,        // radius
 		116*FRACUNIT,       // height
@@ -5569,6 +5605,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL              // raisestate
 	},
 
+	{           // MT_SHOCK
+		-1,             // doomednum
+		S_THUNDERCOIN_SPARK, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		10*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		35*FRACUNIT,    // height
+		0,              // display offset
+		DMG_ELECTRIC|(sfx_buzz2<<8), // mass
+		20,             // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_EGGMOBILE4
 		203,               // doomednum
 		S_EGGMOBILE4_STND, // spawnstate
@@ -5577,7 +5640,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,          // seesound
 		0,                 // reactiontime
 		sfx_None,          // attacksound
-		S_EGGMOBILE4_PAIN, // painstate
+		S_EGGMOBILE4_PAIN1,// painstate
 		0,                 // painchance
 		sfx_dmpain,        // painsound
 		S_EGGMOBILE4_LATK1,// meleestate
@@ -5609,9 +5672,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_BOSSEXPLODE,  // deathstate
+		S_EGGMOBILE4_MACE_DIE1, // deathstate
 		S_NULL,         // xdeathstate
-		sfx_cybdth,     // deathsound
+		sfx_None,       // deathsound
 		48*FRACUNIT,    // speed
 		34*FRACUNIT,    // radius
 		68*FRACUNIT,    // height
@@ -5625,7 +5688,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_JETFLAME
 		-1,             // doomednum
-		S_JETFLAME1,    // spawnstate
+		S_JETFLAME,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -5646,7 +5709,61 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		DMG_FIRE,       // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_PAIN|MF_FIRE, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_EGGROBO1
+		1127,           // doomednum
+		S_EGGROBO1_STND,// spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_s3ka0,      // seesound
+		8,              // reactiontime
+		sfx_bsnipe,     // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_EGGROBO1_BSLAP1, // meleestate
+		S_NULL,         // missilestate
+		S_EGGROBO1_PISSED, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_s3ka0,      // deathsound
+		12*FRACUNIT,    // speed
+		20*FRACUNIT,    // radius
+		72*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_EGGROBOJET
+		-1,             // doomednum
+		S_EGGROBOJET,   // 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
+		10*FRACUNIT,    // radius
+		28*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -5673,7 +5790,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,                 // mass
 		3,                 // damage
 		sfx_boingf,        // activesound
-		MF_SPECIAL|MF_BOSS|MF_SHOOTABLE, // flags
+		MF_SPECIAL|MF_BOSS|MF_SHOOTABLE|MF_GRENADEBOUNCE, // flags
 		S_NULL             // raisestate
 	},
 
@@ -6251,13 +6368,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_METALSONIC_DASH,  // seestate
 		sfx_s3k54,          // seesound
 		0,                  // reactiontime
-		sfx_trpowr,         // attacksound
+		sfx_bechrg,         // attacksound
 		S_METALSONIC_PAIN,  // painstate
 		S_METALSONIC_VECTOR,// painchance
 		sfx_dmpain,         // painsound
 		S_METALSONIC_BADBOUNCE, // meleestate
 		S_METALSONIC_SHOOT, // missilestate
-		S_METALSONIC_DEATH, // deathstate
+		S_METALSONIC_DEATH1,// deathstate
 		S_METALSONIC_FLEE1, // xdeathstate
 		sfx_s3k6e,          // deathsound
 		MT_ENERGYBALL,      // speed
@@ -6289,7 +6406,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		0,              // speed
 		32*FRACUNIT,    // radius
-		64*FRACUNIT,    // height
+		52*FRACUNIT,    // height
 		0,              // display offset
 		0,              // mass
 		0,              // damage
@@ -7383,7 +7500,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_s3k64,      // painsound
-		S_NULL,         // meleestate
+		S_WALLSPIKE4,   // meleestate
 		S_NULL,         // missilestate
 		S_WALLSPIKED1,  // deathstate
 		S_WALLSPIKED2,  // xdeathstate
@@ -9086,7 +9203,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_MINE_BOOM1,   // deathstate
+		S_XPLD1,        // deathstate
 		S_NULL,         // xdeathstate
 		sfx_cybdth,     // deathsound
 		20*FRACUNIT,    // speed
@@ -9113,7 +9230,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_MINE_BOOM1,   // deathstate
+		S_XPLD1,        // deathstate
 		S_NULL,         // xdeathstate
 		sfx_cybdth,     // deathsound
 		20*FRACUNIT,    // speed
@@ -9132,7 +9249,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_ENERGYBALL1,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_s3k54,      // seesound
+		sfx_bexpld,     // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -10909,7 +11026,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CEZPOLE
+	{           // MT_CEZPOLE1
 		1117,           // doomednum
 		S_CEZPOLE,      // spawnstate
 		1000,           // spawnhealth
@@ -10936,9 +11053,63 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CEZBANNER
+	{           // MT_CEZPOLE2
+		1118,           // doomednum
+		S_CEZPOLE,      // 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
+		40*FRACUNIT,    // radius
+		224*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CEZBANNER1
+		-1,             // doomednum
+		S_CEZBANNER1,    // 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
+		40*FRACUNIT,    // radius
+		224*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CEZBANNER2
 		-1,             // doomednum
-		S_CEZBANNER,    // spawnstate
+		S_CEZBANNER2,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11152,8 +11323,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_WAVINGFLAG
-		1118,           // doomednum
+	{           // MT_WAVINGFLAG1
+		1128,           // doomednum
 		S_WAVINGFLAG,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
@@ -11169,8 +11340,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		4*FRACUNIT,     // radius
-		104*FRACUNIT,   // height
+		8*FRACUNIT,     // radius
+		208*FRACUNIT,   // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
@@ -11179,9 +11350,36 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_WAVINGFLAGSEG
+	{           // MT_WAVINGFLAG2
+		1129,           // doomednum
+		S_WAVINGFLAG,   // 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
+		8*FRACUNIT,     // radius
+		208*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SOLID,       // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WAVINGFLAGSEG1
 		-1,             // doomednum
-		S_WAVINGFLAGSEG, // spawnstate
+		S_WAVINGFLAGSEG1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11196,7 +11394,34 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		4*FRACUNIT,     // radius
+		8*FRACUNIT,     // radius
+		1,              // height -- this is not a typo
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WAVINGFLAGSEG2
+		-1,             // doomednum
+		S_WAVINGFLAGSEG2, // 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
+		8*FRACUNIT,     // radius
 		1,              // height -- this is not a typo
 		0,              // display offset
 		100,            // mass
@@ -16113,7 +16338,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		-24*FRACUNIT,   // speed
+		-72*FRACUNIT,   // speed
 		1*FRACUNIT,     // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
@@ -16529,6 +16754,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_LOCKONINF
+		1126,           // doomednum
+		S_INVISIBLE,    // 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
+		8,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		111,            // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_TAG
 		-1,             // doomednum
 		S_TTAG,         // spawnstate
@@ -16915,6 +17167,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_LHRT
+		-1,             // doomednum
+		S_LHRT,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_SPRK1,        // deathstate
+		S_SPRK1,        // xdeathstate
+		sfx_None,       // deathsound
+		60*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_MISSILE, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_REDRING
 		-1,             // doomednum
 		S_RRNG1,        // spawnstate
@@ -19479,7 +19758,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // display offset
 		100,            // mass
 		0,              // damage
-		sfx_None,       // activesound
+		sfx_wbreak,     // activesound
 		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_RUNSPAWNFUNC|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
@@ -19970,6 +20249,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_BRICKDEBRIS
+		-1,             // doomednum
+		S_BRICKDEBRIS, // spawnstate
+		1,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,     // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,            // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,           // mass
+		0,              // damage
+		sfx_None,     // activesound
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_RUNSPAWNFUNC|MF_NOCLIPHEIGHT|MF_SCENERY,  // flags
+		S_NULL          // raisestate
+	},
+
 #ifdef SEENAMES
 	{           // MT_NAMECHECK
 		-1,             // doomednum
diff --git a/src/info.h b/src/info.h
index 13abfa5f60249d91afd05bc0fba7b35376f41a44..45c1d79b27077a930dd4b50885f3c2ab3ae9954d 100644
--- a/src/info.h
+++ b/src/info.h
@@ -335,6 +335,7 @@ typedef enum sprite
 	// Boss 4 (Castle Eggman)
 	SPR_EGGP,
 	SPR_EFIR, // Boss 4 jet flame
+	SPR_EGR1, // Boss 4 Spectator Eggrobo
 
 	// Boss 5 (Arid Canyon)
 	SPR_FANG, // replaces EGGQ
@@ -627,6 +628,7 @@ typedef enum sprite
 	SPR_GFLG, // Got Flag sign
 
 	SPR_CORK,
+	SPR_LHRT,
 
 	// Ring Weapons
 	SPR_RRNG, // Red Ring
@@ -720,6 +722,9 @@ typedef enum sprite
 	SPR_ROIO,
 	SPR_ROIP,
 
+	// Bricks
+	SPR_BRIC,
+
 	// Gravity Well Objects
 	SPR_GWLG,
 	SPR_GWLR,
@@ -829,6 +834,7 @@ typedef enum playersprite
 
 	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_FIRSTFREESLOT,
 	SPR2_LASTFREESLOT = 0x7f,
@@ -1421,6 +1427,11 @@ 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_ATK1,
 	S_EGGMOBILE3_ATK2,
 	S_EGGMOBILE3_ATK3A,
@@ -1429,11 +1440,6 @@ typedef enum state
 	S_EGGMOBILE3_ATK3D,
 	S_EGGMOBILE3_ATK4,
 	S_EGGMOBILE3_ATK5,
-	S_EGGMOBILE3_LAUGH1,
-	S_EGGMOBILE3_LAUGH2,
-	S_EGGMOBILE3_LAUGH3,
-	S_EGGMOBILE3_LAUGH4,
-	S_EGGMOBILE3_LAUGH5,
 	S_EGGMOBILE3_LAUGH6,
 	S_EGGMOBILE3_LAUGH7,
 	S_EGGMOBILE3_LAUGH8,
@@ -1486,8 +1492,8 @@ typedef enum state
 	S_FAKEMOBILE_ATK3B,
 	S_FAKEMOBILE_ATK3C,
 	S_FAKEMOBILE_ATK3D,
-	S_FAKEMOBILE_ATK4,
-	S_FAKEMOBILE_ATK5,
+	S_FAKEMOBILE_DIE1,
+	S_FAKEMOBILE_DIE2,
 
 	// Boss 4
 	S_EGGMOBILE4_STND,
@@ -1505,15 +1511,8 @@ typedef enum state
 	S_EGGMOBILE4_RATK6,
 	S_EGGMOBILE4_RAISE1,
 	S_EGGMOBILE4_RAISE2,
-	S_EGGMOBILE4_RAISE3,
-	S_EGGMOBILE4_RAISE4,
-	S_EGGMOBILE4_RAISE5,
-	S_EGGMOBILE4_RAISE6,
-	S_EGGMOBILE4_RAISE7,
-	S_EGGMOBILE4_RAISE8,
-	S_EGGMOBILE4_RAISE9,
-	S_EGGMOBILE4_RAISE10,
-	S_EGGMOBILE4_PAIN,
+	S_EGGMOBILE4_PAIN1,
+	S_EGGMOBILE4_PAIN2,
 	S_EGGMOBILE4_DIE1,
 	S_EGGMOBILE4_DIE2,
 	S_EGGMOBILE4_DIE3,
@@ -1531,10 +1530,21 @@ typedef enum state
 	S_EGGMOBILE4_FLEE1,
 	S_EGGMOBILE4_FLEE2,
 	S_EGGMOBILE4_MACE,
+	S_EGGMOBILE4_MACE_DIE1,
+	S_EGGMOBILE4_MACE_DIE2,
+	S_EGGMOBILE4_MACE_DIE3,
 
 	// Boss 4 jet flame
-	S_JETFLAME1,
-	S_JETFLAME2,
+	S_JETFLAME,
+
+	// Boss 4 Spectator Eggrobo
+	S_EGGROBO1_STND,
+	S_EGGROBO1_BSLAP1,
+	S_EGGROBO1_BSLAP2,
+	S_EGGROBO1_PISSED,
+
+	// Boss 4 Spectator Eggrobo jet flame
+	S_EGGROBOJET,
 
 	// Boss 5
 	S_FANG_IDLE1,
@@ -1887,7 +1897,10 @@ typedef enum state
 	S_METALSONIC_BADBOUNCE,
 	S_METALSONIC_SHOOT,
 	S_METALSONIC_PAIN,
-	S_METALSONIC_DEATH,
+	S_METALSONIC_DEATH1,
+	S_METALSONIC_DEATH2,
+	S_METALSONIC_DEATH3,
+	S_METALSONIC_DEATH4,
 	S_METALSONIC_FLEE1,
 	S_METALSONIC_FLEE2,
 	S_METALSONIC_FLEE3,
@@ -2446,7 +2459,8 @@ typedef enum state
 
 	S_CEZFLOWER,
 	S_CEZPOLE,
-	S_CEZBANNER,
+	S_CEZBANNER1,
+	S_CEZBANNER2,
 	S_PINETREE,
 	S_CEZBUSH1,
 	S_CEZBUSH2,
@@ -2455,7 +2469,8 @@ typedef enum state
 	S_FLAMEHOLDER,
 	S_FIRETORCH,
 	S_WAVINGFLAG,
-	S_WAVINGFLAGSEG,
+	S_WAVINGFLAGSEG1,
+	S_WAVINGFLAGSEG2,
 	S_CRAWLASTATUE,
 	S_FACESTABBERSTATUE,
 	S_SUSPICIOUSFACESTABBERSTATUE_WAIT,
@@ -2925,6 +2940,12 @@ typedef enum state
 	S_PITY4,
 	S_PITY5,
 	S_PITY6,
+	S_PITY7,
+	S_PITY8,
+	S_PITY9,
+	S_PITY10,
+	S_PITY11,
+	S_PITY12,
 
 	S_FIRS1,
 	S_FIRS2,
@@ -3409,6 +3430,12 @@ typedef enum state
 
 	S_LOCKON1,
 	S_LOCKON2,
+	S_LOCKON3,
+	S_LOCKON4,
+	S_LOCKONINF1,
+	S_LOCKONINF2,
+	S_LOCKONINF3,
+	S_LOCKONINF4,
 
 	// Tag Sign
 	S_TTAG,
@@ -3417,6 +3444,7 @@ typedef enum state
 	S_GOTFLAG,
 
 	S_CORK,
+	S_LHRT,
 
 	// Red Ring
 	S_RRNG1,
@@ -3923,6 +3951,9 @@ typedef enum state
 	S_ROCKCRUMBLEO,
 	S_ROCKCRUMBLEP,
 
+	// Bricks
+	S_BRICKDEBRIS,
+
 #ifdef SEENAMES
 	S_NAMECHECK,
 #endif
@@ -4026,11 +4057,14 @@ typedef enum mobj_type
 	MT_EGGMOBILE3,
 	MT_PROPELLER,
 	MT_FAKEMOBILE,
+	MT_SHOCK,
 
 	// Boss 4
 	MT_EGGMOBILE4,
 	MT_EGGMOBILE4_MACE,
 	MT_JETFLAME,
+	MT_EGGROBO1,
+	MT_EGGROBO1JET,
 
 	// Boss 5
 	MT_FANG,
@@ -4264,8 +4298,10 @@ typedef enum mobj_type
 	MT_SMALLFIREBAR, // Small Firebar
 	MT_BIGFIREBAR, // Big Firebar
 	MT_CEZFLOWER, // Flower
-	MT_CEZPOLE, // Pole
-	MT_CEZBANNER, // Banner
+	MT_CEZPOLE1, // Pole (with red banner)
+	MT_CEZPOLE2, // Pole (with blue banner)
+	MT_CEZBANNER1, // Banner (red)
+	MT_CEZBANNER2, // Banner (blue)
 	MT_PINETREE, // Pine Tree
 	MT_CEZBUSH1, // Bush 1
 	MT_CEZBUSH2, // Bush 2
@@ -4273,8 +4309,10 @@ typedef enum mobj_type
 	MT_CANDLEPRICKET, // Candle pricket
 	MT_FLAMEHOLDER, // Flame holder
 	MT_FIRETORCH, // Fire torch
-	MT_WAVINGFLAG, // Waving flag
-	MT_WAVINGFLAGSEG, // Waving flag segment
+	MT_WAVINGFLAG1, // Waving flag (red)
+	MT_WAVINGFLAG2, // Waving flag (blue)
+	MT_WAVINGFLAGSEG1, // Waving flag segment (red)
+	MT_WAVINGFLAGSEG2, // Waving flag segment (blue)
 	MT_CRAWLASTATUE, // Crawla statue
 	MT_FACESTABBERSTATUE, // Facestabber statue
 	MT_SUSPICIOUSFACESTABBERSTATUE, // :eggthinking:
@@ -4507,6 +4545,7 @@ typedef enum mobj_type
 	MT_DROWNNUMBERS, // Drowning Timer
 	MT_GOTEMERALD, // Chaos Emerald (intangible)
 	MT_LOCKON, // Target
+	MT_LOCKONINF, // In-level Target
 	MT_TAG, // Tag Sign
 	MT_GOTFLAG, // Got Flag sign
 
@@ -4524,6 +4563,7 @@ typedef enum mobj_type
 	MT_MACHINEAMBIENCE,
 
 	MT_CORK,
+	MT_LHRT,
 
 	// Ring Weapons
 	MT_REDRING,
@@ -4657,6 +4697,9 @@ typedef enum mobj_type
 	MT_ROCKCRUMBLE15,
 	MT_ROCKCRUMBLE16,
 
+	// Bricks
+	MT_BRICKDEBRIS,
+
 #ifdef SEENAMES
 	MT_NAMECHECK,
 #endif
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 755b76835390f6b6df7c3fb111f81a3445d7a08e..3c136a43695b66fdafb3ae2cb29f8ef3ff18dbe5 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -33,8 +33,6 @@
 
 #define NOHUD if (hud_running)\
 return luaL_error(L, "HUD rendering code should not call this function!");
-#define INLEVEL if (gamestate != GS_LEVEL)\
-return luaL_error(L, "This function can only be used in a level!");
 
 boolean luaL_checkboolean(lua_State *L, int narg) {
 	luaL_checktype(L, narg, LUA_TBOOLEAN);
@@ -539,7 +537,8 @@ static int lib_pSpawnLockOn(lua_State *L)
 	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
-		visual->target = lockon;
+		P_SetTarget(&visual->target, lockon);
+		visual->flags2 |= MF2_DONTDRAW;
 		P_SetMobjStateNF(visual, state);
 	}
 	return 0;
@@ -951,6 +950,21 @@ static int lib_pResetPlayer(lua_State *L)
 	return 0;
 }
 
+static int lib_pPlayerCanDamage(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	mobj_t *thing = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+	NOHUD // was hud safe but then i added a lua hook
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	if (!thing)
+		return LUA_ErrInvalid(L, "mobj_t");
+	lua_pushboolean(L, P_PlayerCanDamage(player, thing));
+	return 1;
+}
+
+
 static int lib_pIsObjectInGoop(lua_State *L)
 {
 	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@@ -1219,8 +1233,8 @@ static int lib_pHomingAttack(lua_State *L)
 	INLEVEL
 	if (!source || !enemy)
 		return LUA_ErrInvalid(L, "mobj_t");
-	P_HomingAttack(source, enemy);
-	return 0;
+	lua_pushboolean(L, P_HomingAttack(source, enemy));
+	return 1;
 }
 
 static int lib_pSuperReady(lua_State *L)
@@ -2031,12 +2045,22 @@ static int lib_pStartQuake(lua_State *L)
 
 static int lib_evCrumbleChain(lua_State *L)
 {
-	sector_t *sec = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
-	ffloor_t *rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
+	sector_t *sec = NULL;
+	ffloor_t *rover = NULL;
 	NOHUD
 	INLEVEL
-	if (!sec)
-		return LUA_ErrInvalid(L, "sector_t");
+	if (!lua_isnone(L, 2))
+	{
+		if (!lua_isnil(L, 1))
+		{
+			sec = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
+			if (!sec)
+				return LUA_ErrInvalid(L, "sector_t");
+		}
+		rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
+	}
+	else
+		rover = *((ffloor_t **)luaL_checkudata(L, 1, META_FFLOOR));
 	if (!rover)
 		return LUA_ErrInvalid(L, "ffloor_t");
 	EV_CrumbleChain(sec, rover);
@@ -2585,12 +2609,12 @@ static int lib_gSetCustomExitVars(lua_State *L)
 			nextmapoverride = (INT16)luaL_checknumber(L, 1);
 			lua_remove(L, 1); // remove nextmapoverride; skipstats now 1 if available
 		}
-		skipstats = lua_optboolean(L, 1);
+		skipstats = luaL_optinteger(L, 2, 0);
 	}
 	else
 	{
 		nextmapoverride = 0;
-		skipstats = false;
+		skipstats = 0;
 	}
 	// ---
 
@@ -2774,6 +2798,7 @@ static luaL_Reg lib[] = {
 	{"P_PlayerInPain",lib_pPlayerInPain},
 	{"P_DoPlayerPain",lib_pDoPlayerPain},
 	{"P_ResetPlayer",lib_pResetPlayer},
+	{"P_PlayerCanDamage",lib_pPlayerCanDamage},
 	{"P_IsObjectInGoop",lib_pIsObjectInGoop},
 	{"P_IsObjectOnGround",lib_pIsObjectOnGround},
 	{"P_InSpaceSector",lib_pInSpaceSector},
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
index dabbdd9f629bc9a77379e2d46749e9d6ff22455d..2383bb32e1f77812b38f8602f7bccb7765a8cf4f 100644
--- a/src/lua_blockmaplib.c
+++ b/src/lua_blockmaplib.c
@@ -54,10 +54,12 @@ static UINT8 lib_searchBlockmap_Objects(lua_State *L, INT32 x, INT32 y, mobj_t *
 				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
 			blockfuncerror = true;
+			P_SetTarget(&bnext, NULL);
 			return 0; // *shrugs*
 		}
 		if (!lua_isnil(gL, -1))
 		{ // if nil, continue
+			P_SetTarget(&bnext, NULL);
 			if (lua_toboolean(gL, -1))
 				return 2; // stop whole search
 			else
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 98d18d8db06713c4eed15cc9b5364d858956589d..c6856b426224b17f29f7fd26b122865ad0d51b51 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -28,9 +28,6 @@ return luaL_error(L, "HUD rendering code should not call this function!");
 // for functions not allowed in hooks or coroutines (supercedes above)
 #define NOHOOK if (!lua_lumploading)\
 		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
-// for functions only allowed within a level
-#define INLEVEL if (gamestate != GS_LEVEL)\
-return luaL_error(L, "This function can only be used in a level!");
 
 static const char *cvname = NULL;
 
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 9fcc365940a785ca021f524eb5587d2c0e021b1e..45e116c344243ec23a78f05eb1d0581160747d52 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -48,6 +48,7 @@ enum hook {
 	hook_MobjMoveBlocked,
 	hook_MapThingSpawn,
 	hook_FollowMobj,
+	hook_PlayerCanDamage,
 	hook_PlayerQuit,
 
 	hook_MAX // last hook
@@ -87,7 +88,8 @@ boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8
 #define LUAh_ShieldSpecial(player) LUAh_PlayerHook(player, hook_ShieldSpecial) // Hook for shield abilities
 #define LUAh_MobjMoveBlocked(mo) LUAh_MobjHook(mo, hook_MobjMoveBlocked) // Hook for P_XYMovement (when movement is blocked)
 boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnMapThing by mobj type
-boolean LUAh_FollowMobj(player_t *player, mobj_t *mo); // Hook for P_PlayerAfterThink Smiles mobj-following
+boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj); // Hook for P_PlayerAfterThink Smiles mobj-following
+UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj); // Hook for P_PlayerCanDamage
 void LUAh_PlayerQuit(player_t *plr, int reason); // Hook for player quitting
 
 #endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index d605499e291488eb7499b78621c944fb2ab86d5f..7f7e8adc672d19dcb6276bcff0075bc51f56bfb8 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -59,6 +59,7 @@ const char *const hookNames[hook_MAX+1] = {
 	"MobjMoveBlocked",
 	"MapThingSpawn",
 	"FollowMobj",
+	"PlayerCanDamage",
 	"PlayerQuit",
 	NULL
 };
@@ -200,6 +201,7 @@ static int lib_addHook(lua_State *L)
 	case hook_JumpSpinSpecial:
 	case hook_PlayerSpawn:
 	case hook_FollowMobj:
+	case hook_PlayerCanDamage:
 	case hook_ShieldSpawn:
 	case hook_ShieldSpecial:
 		lastp = &playerhooks;
@@ -250,44 +252,48 @@ boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
 
 	// Look for all generic mobj hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == which)
-		{
-			if (lua_gettop(gL) == 0)
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			if (lua_pcall(gL, 1, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		if (lua_pcall(gL, 1, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next)
-		if (hookp->type == which)
-		{
-			if (lua_gettop(gL) == 0)
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			if (lua_pcall(gL, 1, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		if (lua_pcall(gL, 1, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -303,24 +309,26 @@ boolean LUAh_PlayerHook(player_t *plr, enum hook which)
 	lua_settop(gL, 0);
 
 	for (hookp = playerhooks; hookp; hookp = hookp->next)
-		if (hookp->type == which)
-		{
-			if (lua_gettop(gL) == 0)
-				LUA_PushUserdata(gL, plr, META_PLAYER);
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			if (lua_pcall(gL, 1, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+			LUA_PushUserdata(gL, plr, META_PLAYER);
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		if (lua_pcall(gL, 1, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -337,13 +345,15 @@ void LUAh_MapChange(INT16 mapnumber)
 	lua_pushinteger(gL, mapnumber);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MapChange)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			LUA_Call(gL, 1);
-		}
+	{
+		if (hookp->type != hook_MapChange)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		LUA_Call(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 }
@@ -359,13 +369,15 @@ void LUAh_MapLoad(void)
 	lua_pushinteger(gL, gamemap);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MapLoad)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			LUA_Call(gL, 1);
-		}
+	{
+		if (hookp->type != hook_MapLoad)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		LUA_Call(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 }
@@ -381,13 +393,15 @@ void LUAh_PlayerJoin(int playernum)
 	lua_pushinteger(gL, playernum);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_PlayerJoin)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2);
-			LUA_Call(gL, 1);
-		}
+	{
+		if (hookp->type != hook_PlayerJoin)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2);
+		LUA_Call(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 }
@@ -400,17 +414,19 @@ void LUAh_ThinkFrame(void)
 		return;
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_ThinkFrame)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			if (lua_pcall(gL, 0, 0, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-			}
+	{
+		if (hookp->type != hook_ThinkFrame)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		if (lua_pcall(gL, 0, 0, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
 		}
+	}
 }
 
 // Hook for mobj collisions
@@ -427,62 +443,66 @@ UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which)
 
 	// Look for all generic mobj collision hooks
 	for (hookp = mobjcollidehooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == which)
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, thing1, META_MOBJ);
-				LUA_PushUserdata(gL, thing2, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (!lua_isnil(gL, -1))
-			{ // if nil, leave shouldCollide = 0.
-				if (lua_toboolean(gL, -1))
-					shouldCollide = 1; // Force yes
-				else
-					shouldCollide = 2; // Force no
-			}
+			LUA_PushUserdata(gL, thing1, META_MOBJ);
+			LUA_PushUserdata(gL, thing2, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, leave shouldCollide = 0.
+			if (lua_toboolean(gL, -1))
+				shouldCollide = 1; // Force yes
+			else
+				shouldCollide = 2; // Force no
 		}
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjcollidehooks[thing1->type]; hookp; hookp = hookp->next)
-		if (hookp->type == which)
+	{
+		if (hookp->type != which)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, thing1, META_MOBJ);
-				LUA_PushUserdata(gL, thing2, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (!lua_isnil(gL, -1))
-			{ // if nil, leave shouldCollide = 0.
-				if (lua_toboolean(gL, -1))
-					shouldCollide = 1; // Force yes
-				else
-					shouldCollide = 2; // Force no
-			}
+			LUA_PushUserdata(gL, thing1, META_MOBJ);
+			LUA_PushUserdata(gL, thing2, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, leave shouldCollide = 0.
+			if (lua_toboolean(gL, -1))
+				shouldCollide = 1; // Force yes
+			else
+				shouldCollide = 2; // Force no
 		}
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return shouldCollide;
@@ -557,52 +577,56 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 
 	// Look for all generic touch special hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_TouchSpecial)
+	{
+		if (hookp->type != hook_TouchSpecial)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, special, META_MOBJ);
-				LUA_PushUserdata(gL, toucher, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, special, META_MOBJ);
+			LUA_PushUserdata(gL, toucher, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[special->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_TouchSpecial)
+	{
+		if (hookp->type != hook_TouchSpecial)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, special, META_MOBJ);
-				LUA_PushUserdata(gL, toucher, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, special, META_MOBJ);
+			LUA_PushUserdata(gL, toucher, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -622,72 +646,75 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 
 	// Look for all generic should damage hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_ShouldDamage)
+	{
+		if (hookp->type != hook_ShouldDamage)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damage);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (!lua_isnil(gL, -1))
-			{
-				if (lua_toboolean(gL, -1))
-					shouldDamage = 1; // Force yes
-				else
-					shouldDamage = 2; // Force no
-			}
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damage);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{
+			if (lua_toboolean(gL, -1))
+				shouldDamage = 1; // Force yes
+			else
+				shouldDamage = 2; // Force no
 		}
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_ShouldDamage)
+	{
+		if (hookp->type != hook_ShouldDamage)
+			continue;
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damage);
-				lua_pushinteger(gL, damagetype);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			if (lua_pcall(gL, 5, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (!lua_isnil(gL, -1))
-			{
-				if (lua_toboolean(gL, -1))
-					shouldDamage = 1; // Force yes
-				else
-					shouldDamage = 2; // Force no
-			}
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damage);
+			lua_pushinteger(gL, damagetype);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		if (lua_pcall(gL, 5, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{
+			if (lua_toboolean(gL, -1))
+				shouldDamage = 1; // Force yes
+			else
+				shouldDamage = 2; // Force no
 		}
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return shouldDamage;
@@ -707,62 +734,66 @@ boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 
 	// Look for all generic mobj damage hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MobjDamage)
+	{
+		if (hookp->type != hook_MobjDamage)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damage);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damage);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MobjDamage)
+	{
+		if (hookp->type != hook_MobjDamage)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damage);
-				lua_pushinteger(gL, damagetype);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			lua_pushvalue(gL, -6);
-			if (lua_pcall(gL, 5, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damage);
+			lua_pushinteger(gL, damagetype);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		if (lua_pcall(gL, 5, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -782,58 +813,62 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8
 
 	// Look for all generic mobj death hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MobjDeath)
+	{
+		if (hookp->type != hook_MobjDeath)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -4);
-			lua_pushvalue(gL, -4);
-			lua_pushvalue(gL, -4);
-			if (lua_pcall(gL, 3, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		if (lua_pcall(gL, 3, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MobjDeath)
+	{
+		if (hookp->type != hook_MobjDeath)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, target, META_MOBJ);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damagetype);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, target, META_MOBJ);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damagetype);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -850,28 +885,30 @@ boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd)
 	lua_settop(gL, 0);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_BotTiccmd)
+	{
+		if (hookp->type != hook_BotTiccmd)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, bot, META_PLAYER);
-				LUA_PushUserdata(gL, cmd, META_TICCMD);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, bot, META_PLAYER);
+			LUA_PushUserdata(gL, cmd, META_TICCMD);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -888,51 +925,53 @@ boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 	lua_settop(gL, 0);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_BotAI
-		&& (hookp->s.skinname == NULL || !strcmp(hookp->s.skinname, ((skin_t*)tails->skin)->name)))
+	{
+		if (hookp->type != hook_BotAI
+		|| (hookp->s.skinname && strcmp(hookp->s.skinname, ((skin_t*)tails->skin)->name)))
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, sonic, META_MOBJ);
-				LUA_PushUserdata(gL, tails, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 8, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
+			LUA_PushUserdata(gL, sonic, META_MOBJ);
+			LUA_PushUserdata(gL, tails, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 8, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
 
-			// This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails.
-			if (lua_istable(gL, 2+1)) {
-				boolean forward=false, backward=false, left=false, right=false, strafeleft=false, straferight=false, jump=false, spin=false;
+		// This turns forward, backward, left, right, jump, and spin into a proper ticcmd for tails.
+		if (lua_istable(gL, 2+1)) {
+			boolean forward=false, backward=false, left=false, right=false, strafeleft=false, straferight=false, jump=false, spin=false;
 #define CHECKFIELD(field) \
-				lua_getfield(gL, 2+1, #field);\
-				if (lua_toboolean(gL, -1))\
-					field = true;\
-				lua_pop(gL, 1);
-
-				CHECKFIELD(forward)
-				CHECKFIELD(backward)
-				CHECKFIELD(left)
-				CHECKFIELD(right)
-				CHECKFIELD(strafeleft)
-				CHECKFIELD(straferight)
-				CHECKFIELD(jump)
-				CHECKFIELD(spin)
+			lua_getfield(gL, 2+1, #field);\
+			if (lua_toboolean(gL, -1))\
+				field = true;\
+			lua_pop(gL, 1);
+
+			CHECKFIELD(forward)
+			CHECKFIELD(backward)
+			CHECKFIELD(left)
+			CHECKFIELD(right)
+			CHECKFIELD(strafeleft)
+			CHECKFIELD(straferight)
+			CHECKFIELD(jump)
+			CHECKFIELD(spin)
 #undef CHECKFIELD
-				B_KeysToTiccmd(tails, cmd, forward, backward, left, right, strafeleft, straferight, jump, spin);
-			} else
-				B_KeysToTiccmd(tails, cmd, lua_toboolean(gL, 2+1), lua_toboolean(gL, 2+2), lua_toboolean(gL, 2+3), lua_toboolean(gL, 2+4), lua_toboolean(gL, 2+5), lua_toboolean(gL, 2+6), lua_toboolean(gL, 2+7), lua_toboolean(gL, 2+8));
+			B_KeysToTiccmd(tails, cmd, forward, backward, left, right, strafeleft, straferight, jump, spin);
+		} else
+			B_KeysToTiccmd(tails, cmd, lua_toboolean(gL, 2+1), lua_toboolean(gL, 2+2), lua_toboolean(gL, 2+3), lua_toboolean(gL, 2+4), lua_toboolean(gL, 2+5), lua_toboolean(gL, 2+6), lua_toboolean(gL, 2+7), lua_toboolean(gL, 2+8));
 
-			lua_pop(gL, 8);
-			hooked = true;
-		}
+		lua_pop(gL, 8);
+		hooked = true;
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -949,22 +988,24 @@ boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
 	lua_settop(gL, 0);
 
 	for (hookp = linedefexecutorhooks; hookp; hookp = hookp->next)
-		if (!strcmp(hookp->s.funcname, line->text))
+	{
+		if (strcmp(hookp->s.funcname, line->text))
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, line, META_LINE);
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-				LUA_PushUserdata(gL, sector, META_SECTOR);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -4);
-			lua_pushvalue(gL, -4);
-			lua_pushvalue(gL, -4);
-			LUA_Call(gL, 3);
-			hooked = true;
+			LUA_PushUserdata(gL, line, META_LINE);
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+			LUA_PushUserdata(gL, sector, META_SECTOR);
 		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		LUA_Call(gL, 3);
+		hooked = true;
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -981,43 +1022,45 @@ boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
 	lua_settop(gL, 0);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_PlayerMsg)
+	{
+		if (hookp->type != hook_PlayerMsg)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, &players[source], META_PLAYER); // Source player
-				if (flags & 2 /*HU_CSAY*/) { // csay TODO: make HU_CSAY accessible outside hu_stuff.c
-					lua_pushinteger(gL, 3); // type
-					lua_pushnil(gL); // target
-				} else if (target == -1) { // sayteam
-					lua_pushinteger(gL, 1); // type
-					lua_pushnil(gL); // target
-				} else if (target == 0) { // say
-					lua_pushinteger(gL, 0); // type
-					lua_pushnil(gL); // target
-				} else { // sayto
-					lua_pushinteger(gL, 2); // type
-					LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target
-				}
-				lua_pushstring(gL, msg); // msg
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
+			LUA_PushUserdata(gL, &players[source], META_PLAYER); // Source player
+			if (flags & 2 /*HU_CSAY*/) { // csay TODO: make HU_CSAY accessible outside hu_stuff.c
+				lua_pushinteger(gL, 3); // type
+				lua_pushnil(gL); // target
+			} else if (target == -1) { // sayteam
+				lua_pushinteger(gL, 1); // type
+				lua_pushnil(gL); // target
+			} else if (target == 0) { // say
+				lua_pushinteger(gL, 0); // type
+				lua_pushnil(gL); // target
+			} else { // sayto
+				lua_pushinteger(gL, 2); // type
+				LUA_PushUserdata(gL, &players[target-1], META_PLAYER); // target
 			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			lua_pushstring(gL, msg); // msg
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -1035,33 +1078,35 @@ boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8
 	lua_settop(gL, 0);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_HurtMsg
-		&& (hookp->s.mt == MT_NULL || (inflictor && hookp->s.mt == inflictor->type)))
+	{
+		if (hookp->type != hook_HurtMsg
+		|| (hookp->s.mt && !(inflictor && hookp->s.mt == inflictor->type)))
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, player, META_PLAYER);
-				LUA_PushUserdata(gL, inflictor, META_MOBJ);
-				LUA_PushUserdata(gL, source, META_MOBJ);
-				lua_pushinteger(gL, damagetype);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			lua_pushvalue(gL, -5);
-			if (lua_pcall(gL, 4, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			LUA_PushUserdata(gL, inflictor, META_MOBJ);
+			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damagetype);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -1084,13 +1129,15 @@ void LUAh_NetArchiveHook(lua_CFunction archFunc)
 	// stack: tables, archFunc
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_NetVars)
-		{
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -2); // archFunc
-			LUA_Call(gL, 1);
-		}
+	{
+		if (hookp->type != hook_NetVars)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -2); // archFunc
+		LUA_Call(gL, 1);
+	}
 
 	lua_pop(gL, 1); // pop archFunc
 	// stack: tables
@@ -1107,52 +1154,56 @@ boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing)
 
 	// Look for all generic mobj map thing spawn hooks
 	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MapThingSpawn)
+	{
+		if (hookp->type != hook_MapThingSpawn)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-				LUA_PushUserdata(gL, mthing, META_MAPTHING);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+			LUA_PushUserdata(gL, mthing, META_MAPTHING);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next)
-		if (hookp->type == hook_MapThingSpawn)
+	{
+		if (hookp->type != hook_MapThingSpawn)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, mo, META_MOBJ);
-				LUA_PushUserdata(gL, mthing, META_MAPTHING);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, mo, META_MOBJ);
+			LUA_PushUserdata(gL, mthing, META_MAPTHING);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
@@ -1169,33 +1220,80 @@ boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj)
 	lua_settop(gL, 0);
 
 	for (hookp = playerhooks; hookp; hookp = hookp->next)
-		if (hookp->type == hook_FollowMobj)
+	{
+		if (hookp->type != hook_FollowMobj)
+			continue;
+
+		if (lua_gettop(gL) == 0)
 		{
-			if (lua_gettop(gL) == 0)
-			{
-				LUA_PushUserdata(gL, player, META_PLAYER);
-				LUA_PushUserdata(gL, mobj, META_MOBJ);
-			}
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			if (lua_pcall(gL, 2, 1, 0)) {
-				if (!hookp->error || cv_debug & DBG_LUA)
-					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
-				lua_pop(gL, 1);
-				hookp->error = true;
-				continue;
-			}
-			if (lua_toboolean(gL, -1))
-				hooked = true;
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			LUA_PushUserdata(gL, mobj, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
 		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
 
 	lua_settop(gL, 0);
 	return hooked;
 }
 
+// Hook for P_PlayerCanDamage
+UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj)
+{
+	hook_p hookp;
+	UINT8 shouldCollide = 0; // 0 = default, 1 = force yes, 2 = force no.
+	if (!gL || !(hooksAvailable[hook_PlayerCanDamage/8] & (1<<(hook_PlayerCanDamage%8))))
+		return 0;
+
+	lua_settop(gL, 0);
+
+	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_PlayerCanDamage)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+		{
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			LUA_PushUserdata(gL, mobj, META_MOBJ);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, leave shouldCollide = 0.
+			if (lua_toboolean(gL, -1))
+				shouldCollide = 1; // Force yes
+			else
+				shouldCollide = 2; // Force no
+		}
+		lua_pop(gL, 1);
+	}
+
+	lua_settop(gL, 0);
+	return shouldCollide;
+}
+
 void LUAh_PlayerQuit(player_t *plr, int reason)
 {
 	hook_p hookp;
@@ -1205,19 +1303,21 @@ void LUAh_PlayerQuit(player_t *plr, int reason)
 	lua_settop(gL, 0);
 
 	for (hookp = roothook; hookp; hookp = hookp->next)
-		if (hookp->type == hook_PlayerQuit)
-		{
-		    if (lua_gettop(gL) == 0)
-		    {
-		        LUA_PushUserdata(gL, plr, META_PLAYER); // Player that quit
-		        lua_pushinteger(gL, reason); // Reason for quitting
-		    }
-			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
-			lua_gettable(gL, LUA_REGISTRYINDEX);
-			lua_pushvalue(gL, -3);
-			lua_pushvalue(gL, -3);
-			LUA_Call(gL, 2);
-		}
+	{
+		if (hookp->type != hook_PlayerQuit)
+			continue;
+
+	    if (lua_gettop(gL) == 0)
+	    {
+	        LUA_PushUserdata(gL, plr, META_PLAYER); // Player that quit
+	        lua_pushinteger(gL, reason); // Reason for quitting
+	    }
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		LUA_Call(gL, 2);
+	}
 
 	lua_settop(gL, 0);
 }
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index f3a1ba210f226fc21dec536500b4507b8a2983d3..8c1134bca7c942ed4fe18398570e0713ec9fe686 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -670,8 +670,8 @@ static int libd_getColormap(lua_State *L)
 	else if (lua_type(L, 1) == LUA_TNUMBER) // skin number
 	{
 		skinnum = (INT32)luaL_checkinteger(L, 1);
-		if (skinnum < TC_ALLWHITE || skinnum >= MAXSKINS)
-			return luaL_error(L, "skin number %d is out of range (%d - %d)", skinnum, TC_ALLWHITE, MAXSKINS-1);
+		if (skinnum < TC_BLINK || skinnum >= MAXSKINS)
+			return luaL_error(L, "skin number %d is out of range (%d - %d)", skinnum, TC_BLINK, MAXSKINS-1);
 	}
 	else // skin name
 	{
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 55afa387462ecb04a801b90edcb3f00f0aa5120b..8bd4ce9ffda9635f6f141b08d13e1085cf359633 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -157,6 +157,18 @@ static int lib_setSpr2default(lua_State *L)
 	playersprite_t i;
 	UINT8 j = 0;
 
+	if (hud_running)
+		return luaL_error(L, "Do not alter spr2defaults[] in HUD rendering code!");
+
+// todo: maybe allow setting below first freeslot..? step 1 is toggling this, step 2 is testing to see whether it's net-safe
+#ifdef SETALLSPR2DEFAULTS
+#define FIRSTMODIFY 0
+#else
+#define FIRSTMODIFY SPR2_FIRSTFREESLOT
+	if (free_spr2 == SPR2_FIRSTFREESLOT)
+		return luaL_error(L, "You can only modify the spr2defaults[] entries of sprite2 freeslots, and none are currently added.");
+#endif
+
 	lua_remove(L, 1); // don't care about spr2defaults[] dummy userdata.
 
 	if (lua_isnumber(L, 1))
@@ -175,8 +187,9 @@ static int lib_setSpr2default(lua_State *L)
 	else
 		return luaL_error(L, "spr2defaults[] invalid index");
 
-	if (i < SPR2_FIRSTFREESLOT || i >= free_spr2)
-		return luaL_error(L, "spr2defaults[] index %d out of range (%d - %d)", i, SPR2_FIRSTFREESLOT, free_spr2-1);
+	if (i < FIRSTMODIFY || i >= free_spr2)
+		return luaL_error(L, "spr2defaults[] index %d out of range (%d - %d)", i, FIRSTMODIFY, free_spr2-1);
+#undef FIRSTMODIFY
 
 	if (lua_isnumber(L, 2))
 		j = lua_tonumber(L, 2);
@@ -189,11 +202,13 @@ static int lib_setSpr2default(lua_State *L)
 				break;
 		}
 		if (j == free_spr2)
-			return luaL_error(L, "spr2defaults[] invalid index");
+			return luaL_error(L, "spr2defaults[] invalid set");
 	}
+	else
+		return luaL_error(L, "spr2defaults[] invalid set");
 
-	if (j >= free_spr2)
-		j = 0; // return luaL_error(L, "spr2defaults[] set %d out of range (%d - %d)", j, 0, free_spr2-1);
+	if (j < 0 || j >= free_spr2)
+		return luaL_error(L, "spr2defaults[] set %d out of range (%d - %d)", j, 0, free_spr2-1);
 
 	spr2defaults[i] = j;
 	return 0;
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index c5555009f849c8addd92c641a2db1d6414f6bba5..82843db4ee768a129f23e82e728d00964ffdcdde 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -292,8 +292,6 @@ enum slope_e {
 	slope_normal,
 	slope_zangle,
 	slope_xydirection,
-	slope_sourceline,
-	slope_refpos,
 	slope_flags
 };
 
@@ -305,8 +303,6 @@ static const char *const slope_opt[] = {
 	"normal",
 	"zangle",
 	"xydirection",
-	"sourceline",
-	"refpos",
 	"flags",
 	NULL};
 
@@ -337,8 +333,7 @@ static int lib_iterateSectorThinglist(lua_State *L)
 	mobj_t *state = NULL;
 	mobj_t *thing = NULL;
 
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call sector.thinglist() directly, use it as 'for rover in sector.thinglist do <block> end'.");
@@ -373,8 +368,7 @@ static int lib_iterateSectorFFloors(lua_State *L)
 	ffloor_t *state = NULL;
 	ffloor_t *ffloor = NULL;
 
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call sector.ffloors() directly, use it as 'for rover in sector.ffloors do <block> end'.");
@@ -1255,8 +1249,7 @@ static int bbox_get(lua_State *L)
 static int lib_iterateSectors(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call sectors.iterate() directly, use it as 'for sector in sectors.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1274,8 +1267,7 @@ static int lib_iterateSectors(lua_State *L)
 static int lib_getSector(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1309,8 +1301,7 @@ static int lib_numsectors(lua_State *L)
 static int lib_iterateSubsectors(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call subsectors.iterate() directly, use it as 'for subsector in subsectors.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1328,8 +1319,7 @@ static int lib_iterateSubsectors(lua_State *L)
 static int lib_getSubsector(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1363,8 +1353,7 @@ static int lib_numsubsectors(lua_State *L)
 static int lib_iterateLines(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call lines.iterate() directly, use it as 'for line in lines.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1382,8 +1371,7 @@ static int lib_iterateLines(lua_State *L)
 static int lib_getLine(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1417,8 +1405,7 @@ static int lib_numlines(lua_State *L)
 static int lib_iterateSides(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call sides.iterate() directly, use it as 'for side in sides.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1436,8 +1423,7 @@ static int lib_iterateSides(lua_State *L)
 static int lib_getSide(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1471,8 +1457,7 @@ static int lib_numsides(lua_State *L)
 static int lib_iterateVertexes(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call vertexes.iterate() directly, use it as 'for vertex in vertexes.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1490,8 +1475,7 @@ static int lib_iterateVertexes(lua_State *L)
 static int lib_getVertex(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1527,8 +1511,7 @@ static int lib_numvertexes(lua_State *L)
 static int lib_iterateSegs(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call segs.iterate() directly, use it as 'for seg in segs.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1546,8 +1529,7 @@ static int lib_iterateSegs(lua_State *L)
 static int lib_getSeg(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1581,8 +1563,7 @@ static int lib_numsegs(lua_State *L)
 static int lib_iterateNodes(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call nodes.iterate() directly, use it as 'for node in nodes.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -1600,8 +1581,7 @@ static int lib_iterateNodes(lua_State *L)
 static int lib_getNode(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
@@ -1831,12 +1811,6 @@ static int slope_get(lua_State *L)
 	case slope_xydirection: // xydirection
 		lua_pushangle(L, slope->xydirection);
 		return 1;
-	case slope_sourceline: // source linedef
-		LUA_PushUserdata(L, slope->sourceline, META_LINE);
-		return 1;
-	case slope_refpos: // refpos
-		lua_pushinteger(L, slope->refpos);
-		return 1;
 	case slope_flags: // flags
 		lua_pushinteger(L, slope->flags);
 		return 1;
@@ -1858,11 +1832,9 @@ static int slope_set(lua_State *L)
 	switch(field) // todo: reorganize this shit
 	{
 	case slope_valid: // valid
-	case slope_sourceline: // sourceline
 	case slope_d: // d
 	case slope_flags: // flags
 	case slope_normal: // normal
-	case slope_refpos: // refpos
 	default:
 		return luaL_error(L, "pslope_t field " LUA_QS " cannot be set.", slope_opt[field]);
 	case slope_o: { // o
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 0835e5cc0114230c89f3dfdf11e7236be147021a..063158b263b58b4f4a6b7764a6c3075a4e72867c 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -83,12 +83,11 @@ enum mobj_e {
 	mobj_extravalue1,
 	mobj_extravalue2,
 	mobj_cusval,
-#ifdef ESLOPE
 	mobj_cvmem,
-	mobj_standingslope
-#else
-	mobj_cvmem
+#ifdef ESLOPE
+	mobj_standingslope,
 #endif
+	mobj_colorized
 };
 
 static const char *const mobj_opt[] = {
@@ -154,6 +153,7 @@ static const char *const mobj_opt[] = {
 #ifdef ESLOPE
 	"standingslope",
 #endif
+	"colorized",
 	NULL};
 
 #define UNIMPLEMENTED luaL_error(L, LUA_QL("mobj_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", mobj_opt[field])
@@ -274,10 +274,19 @@ static int mobj_get(lua_State *L)
 		// bprev -- same deal as sprev above, but for the blockmap.
 		return UNIMPLEMENTED;
 	case mobj_hnext:
+		if (mo->hnext && P_MobjWasRemoved(mo->hnext))
+		{ // don't put invalid mobj back into Lua.
+			P_SetTarget(&mo->hnext, NULL);
+			return 0;
+		}
 		LUA_PushUserdata(L, mo->hnext, META_MOBJ);
 		break;
 	case mobj_hprev:
-		// implimented differently from sprev and bprev because SSNTails.
+		if (mo->hprev && P_MobjWasRemoved(mo->hprev))
+		{ // don't put invalid mobj back into Lua.
+			P_SetTarget(&mo->hprev, NULL);
+			return 0;
+		}
 		LUA_PushUserdata(L, mo->hprev, META_MOBJ);
 		break;
 	case mobj_type:
@@ -371,6 +380,9 @@ static int mobj_get(lua_State *L)
 		LUA_PushUserdata(L, mo->standingslope, META_SLOPE);
 		break;
 #endif
+	case mobj_colorized:
+		lua_pushboolean(L, mo->colorized);
+		break;
 	default: // extra custom variables in Lua memory
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
@@ -692,6 +704,9 @@ static int mobj_set(lua_State *L)
 	case mobj_standingslope:
 		return NOSET;
 #endif
+	case mobj_colorized:
+		mo->colorized = luaL_checkboolean(L, 3);
+		break;
 	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
@@ -789,7 +804,12 @@ static int mapthing_set(lua_State *L)
 	else if(fastcmp(field,"z"))
 		mt->z = (INT16)luaL_checkinteger(L, 3);
 	else if(fastcmp(field,"extrainfo"))
-		mt->extrainfo = (UINT8)luaL_checkinteger(L, 3);
+	{
+		INT32 extrainfo = luaL_checkinteger(L, 3);
+		if (extrainfo & ~15)
+			return luaL_error(L, "mapthing_t extrainfo set %d out of range (%d - %d)", extrainfo, 0, 15);
+		mt->extrainfo = (UINT8)extrainfo;
+	}
 	else if(fastcmp(field,"mobj"))
 		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 	else
@@ -801,8 +821,7 @@ static int mapthing_set(lua_State *L)
 static int lib_iterateMapthings(lua_State *L)
 {
 	size_t i = 0;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 		return luaL_error(L, "Don't call mapthings.iterate() directly, use it as 'for mapthing in mapthings.iterate do <block> end'.");
 	lua_settop(L, 2);
@@ -820,8 +839,7 @@ static int lib_iterateMapthings(lua_State *L)
 static int lib_getMapthing(lua_State *L)
 {
 	int field;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	lua_settop(L, 2);
 	lua_remove(L, 1); // dummy userdata table is unused.
 	if (lua_isnumber(L, 1))
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index f3eb978f6163a1d7ef1ccd89c34a74e3f7199e4c..b7bdaa1be87a078974490fe569e171c15906b319 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -25,8 +25,7 @@
 static int lib_iteratePlayers(lua_State *L)
 {
 	INT32 i = -1;
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 	if (lua_gettop(L) < 2)
 	{
 		//return luaL_error(L, "Don't call players.iterate() directly, use it as 'for player in players.iterate do <block> end'.");
@@ -53,8 +52,7 @@ static int lib_getPlayer(lua_State *L)
 {
 	const char *field;
 	// i -> players[i]
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "You cannot access this outside of a level!");
+	INLEVEL
 	if (lua_type(L, 2) == LUA_TNUMBER)
 	{
 		lua_Integer i = luaL_checkinteger(L, 2);
@@ -476,7 +474,12 @@ static int player_set(lua_State *L)
 	else if (fastcmp(field,"followitem"))
 		plr->followitem = luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"followmobj"))
-		P_SetTarget(&plr->followmobj, *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)));
+	{
+		mobj_t *mo = NULL;
+		if (!lua_isnil(L, 3))
+			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+		P_SetTarget(&plr->followmobj, mo);
+	}
 	else if (fastcmp(field,"actionspd"))
 		plr->actionspd = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"mindash"))
@@ -560,9 +563,19 @@ static int player_set(lua_State *L)
 	else if (fastcmp(field,"old_angle_pos"))
 		plr->old_angle_pos = luaL_checkangle(L, 3);
 	else if (fastcmp(field,"axis1"))
-		P_SetTarget(&plr->axis1, *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)));
+	{
+		mobj_t *mo = NULL;
+		if (!lua_isnil(L, 3))
+			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+		P_SetTarget(&plr->axis1, mo);
+	}
 	else if (fastcmp(field,"axis2"))
-		P_SetTarget(&plr->axis2, *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)));
+	{
+		mobj_t *mo = NULL;
+		if (!lua_isnil(L, 3))
+			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+		P_SetTarget(&plr->axis2, mo);
+	}
 	else if (fastcmp(field,"bumpertime"))
 		plr->bumpertime = (tic_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"flyangle"))
diff --git a/src/lua_script.c b/src/lua_script.c
index 04aa55cf07a9423cde72f9ea770273bc090e1cf6..deb644dc0351c169c60cb859952627278538241c 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -420,9 +420,9 @@ void LUA_InvalidateLevel(void)
 	ffloor_t *rover = NULL;
 	if (!gL)
 		return;
-
-	for (th = thinkercap.next; th && th != &thinkercap; th = th->next)
-		LUA_InvalidateUserdata(th);
+	for (i = 0; i < NUM_THINKERLISTS; i++)
+		for (th = thlist[i].next; th && th != &thlist[i]; th = th->next)
+			LUA_InvalidateUserdata(th);
 
 	LUA_InvalidateMapthings();
 
@@ -1127,13 +1127,16 @@ void LUA_Archive(void)
 		ArchiveExtVars(&players[i], "player");
 	}
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			// archive function will determine when to skip mobjs,
-			// and write mobjnum in otherwise.
-			ArchiveExtVars(th, "mobj");
-		}
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	{
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		// archive function will determine when to skip mobjs,
+		// and write mobjnum in otherwise.
+		ArchiveExtVars(th, "mobj");
+	}
+
 	WRITEUINT32(save_p, UINT32_MAX); // end of mobjs marker, replaces mobjnum.
 
 	LUAh_NetArchiveHook(NetArchive); // call the NetArchive hook in archive mode
@@ -1161,10 +1164,14 @@ void LUA_UnArchive(void)
 
 	do {
 		mobjnum = READUINT32(save_p); // read a mobjnum
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
-			if (th->function.acp1 == (actionf_p1)P_MobjThinker
-			&& ((mobj_t *)th)->mobjnum == mobjnum) // find matching mobj
-				UnArchiveExtVars(th); // apply variables
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		{
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				continue;
+			if (((mobj_t *)th)->mobjnum != mobjnum) // find matching mobj
+				continue;
+			UnArchiveExtVars(th); // apply variables
+		}
 	} while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker.
 
 	LUAh_NetArchiveHook(NetUnArchive); // call the NetArchive hook in unarchive mode
diff --git a/src/lua_script.h b/src/lua_script.h
index b690e4fa7577e6f0076a7f6d7b878bde9d01581d..fcbca29379e0acd50aeeab20250696ee16a3233d 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -15,6 +15,7 @@
 #include "m_fixed.h"
 #include "doomtype.h"
 #include "d_player.h"
+#include "g_state.h"
 
 #include "blua/lua.h"
 #include "blua/lualib.h"
@@ -97,4 +98,7 @@ void COM_Lua_f(void);
 // uncomment if you want seg_t/node_t in Lua
 // #define HAVE_LUA_SEGS
 
+#define INLEVEL if (gamestate != GS_LEVEL && !titlemapinaction)\
+return luaL_error(L, "This can only be used in a level!");
+
 #endif
diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c
index a8f785c5aec9024f2b757e15c1b9e37194c87ee8..a28f6a359556da233d71c6b598264101e56bb4e8 100644
--- a/src/lua_skinlib.c
+++ b/src/lua_skinlib.c
@@ -27,9 +27,6 @@ enum skin {
 	skin_flags,
 	skin_realname,
 	skin_hudname,
-	skin_charsel,
-	skin_face,
-	skin_superface,
 	skin_ability,
 	skin_ability2,
 	skin_thokitem,
@@ -66,9 +63,6 @@ static const char *const skin_opt[] = {
 	"flags",
 	"realname",
 	"hudname",
-	"charsel",
-	"face",
-	"superface",
 	"ability",
 	"ability2",
 	"thokitem",
@@ -104,7 +98,6 @@ static int skin_get(lua_State *L)
 {
 	skin_t *skin = *((skin_t **)luaL_checkudata(L, 1, META_SKIN));
 	enum skin field = luaL_checkoption(L, 2, NULL, skin_opt);
-	INT32 i;
 
 	// skins are always valid, only added, never removed
 	I_Assert(skin != NULL);
@@ -131,24 +124,6 @@ static int skin_get(lua_State *L)
 	case skin_hudname:
 		lua_pushstring(L, skin->hudname);
 		break;
-	case skin_charsel:
-		for (i = 0; i < 8; i++)
-			if (!skin->charsel[i])
-				break;
-		lua_pushlstring(L, skin->charsel, i);
-		break;
-	case skin_face:
-		for (i = 0; i < 8; i++)
-			if (!skin->face[i])
-				break;
-		lua_pushlstring(L, skin->face, i);
-		break;
-	case skin_superface:
-		for (i = 0; i < 8; i++)
-			if (!skin->superface[i])
-				break;
-		lua_pushlstring(L, skin->superface, i);
-		break;
 	case skin_ability:
 		lua_pushinteger(L, skin->ability);
 		break;
diff --git a/src/lua_thinkerlib.c b/src/lua_thinkerlib.c
index ae648613a9ad77ec7b752a13e2ca0b092cfed61d..877294898e1483541b12d093adf70b9accb9cd35 100644
--- a/src/lua_thinkerlib.c
+++ b/src/lua_thinkerlib.c
@@ -18,7 +18,7 @@
 
 #define META_ITERATIONSTATE "iteration state"
 
-static const char *const iter_opt[] = {
+/*static const char *const iter_opt[] = {
 	"all",
 	"mobj",
 	NULL};
@@ -26,7 +26,7 @@ static const char *const iter_opt[] = {
 static const actionf_p1 iter_funcs[] = {
 	NULL,
 	(actionf_p1)P_MobjThinker
-};
+};*/
 
 struct iterationState {
 	actionf_p1 filter;
@@ -56,15 +56,14 @@ static int lib_iterateThinkers(lua_State *L)
 	thinker_t *th = NULL, *next = NULL;
 	struct iterationState *it;
 
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 
 	it = luaL_checkudata(L, 1, META_ITERATIONSTATE);
 
 	lua_settop(L, 2);
 
 	if (lua_isnil(L, 2))
-		th = &thinkercap;
+		th = &thlist[THINK_MOBJ];
 	else if (lua_isuserdata(L, 2))
 	{
 		if (lua_islightuserdata(L, 2))
@@ -94,11 +93,11 @@ static int lib_iterateThinkers(lua_State *L)
 	if (!next)
 		return luaL_error(L, "next thinker invalidated during iteration");
 
-	for (; next != &thinkercap; next = next->next)
+	for (; next != &thlist[THINK_MOBJ]; next = next->next)
 		if (!it->filter || next->function.acp1 == it->filter)
 		{
 			push_thinker(next);
-			if (next->next != &thinkercap)
+			if (next->next != &thlist[THINK_MOBJ])
 			{
 				push_thinker(next->next);
 				it->next = luaL_ref(L, LUA_REGISTRYINDEX);
@@ -112,15 +111,14 @@ static int lib_startIterate(lua_State *L)
 {
 	struct iterationState *it;
 
-	if (gamestate != GS_LEVEL)
-		return luaL_error(L, "This function can only be used in a level!");
+	INLEVEL
 
 	lua_pushvalue(L, lua_upvalueindex(1));
 	it = lua_newuserdata(L, sizeof(struct iterationState));
 	luaL_getmetatable(L, META_ITERATIONSTATE);
 	lua_setmetatable(L, -2);
 
-	it->filter = iter_funcs[luaL_checkoption(L, 1, "mobj", iter_opt)];
+	it->filter = (actionf_p1)P_MobjThinker; //iter_funcs[luaL_checkoption(L, 1, "mobj", iter_opt)];
 	it->next = LUA_REFNIL;
 	return 2;
 }
@@ -138,7 +136,7 @@ int LUA_ThinkerLib(lua_State *L)
 		lua_pushcfunction(L, lib_iterateThinkers);
 		lua_pushcclosure(L, lib_startIterate, 1);
 		lua_setfield(L, -2, "iterate");
-	lua_setglobal(L, "thinkers");
+	lua_setglobal(L, "mobjs");
 	return 0;
 }
 
diff --git a/src/m_cheat.c b/src/m_cheat.c
index bee60087fec00c0d44beb4665779d907d4360d74..29e8c8a0231425ea2440227d28ee7377b898e514 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -577,9 +577,9 @@ void Command_Teleport_f(void)
 			INT32 starpostmax = 0;
 			intz = starpostpath; // variable reuse - counting down for selection purposes
 
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -600,7 +600,7 @@ void Command_Teleport_f(void)
 				break;
 			}
 
-			if (th == &thinkercap)
+			if (th == &thlist[THINK_MOBJ])
 			{
 				if (intz == starpostpath)
 					CONS_Alert(CONS_NOTICE, M_GetText("No starpost of position %d found (%d max).\n"), starpostnum, starpostmax);
@@ -1069,15 +1069,16 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c
 		thinker_t *th;
 		mobj_t *mo;
 
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo = (mobj_t *)th;
 			// get offset from mt, which points to old mapthings, then add new location
-			if (mo->spawnpoint)
-				mo->spawnpoint = (mo->spawnpoint - mt) + mapthings;
+			if (!mo->spawnpoint)
+				continue;
+			mo->spawnpoint = (mo->spawnpoint - mt) + mapthings;
 		}
 	}
 
diff --git a/src/m_cond.c b/src/m_cond.c
index e03542bf36c00beec606d0d340c10b690effb809..539c6d1f6d1fc38efacad7d1e56329a323010afb 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -240,7 +240,7 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
 	if (cechoLines)
 	{
 		char slashed[1024] = "";
-		for (i = 0; (i < 21) && (i < 24 - cechoLines); ++i)
+		for (i = 0; (i < 19) && (i < 24 - cechoLines); ++i)
 			slashed[i] = '\\';
 		slashed[i] = 0;
 
diff --git a/src/m_menu.c b/src/m_menu.c
index 58dbcafcbef8f604a8fe2d8667c3cd95a692ed08..0accef2a8b5832ee7eb06a499272da6ca0b4a610 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -2289,21 +2289,24 @@ static boolean MIT_SetCurBackground(UINT32 menutype, INT32 level, INT32 *retval,
 	(void)retval;
 	(void)fromoldest;
 
+	if (!menutype) // if there's nothing in this level, do nothing
+		return false;
+
 	if (menupres[menutype].bgcolor >= 0)
 	{
 		curbgcolor = menupres[menutype].bgcolor;
 		return true;
 	}
-	else if (menupres[menutype].bgname[0] && (!menupres[menutype].bghide || !titlemapinaction))
+	else if (menupres[menutype].bghide && titlemapinaction) // hide the background
 	{
-		strncpy(curbgname, menupres[menutype].bgname, 8);
-		curbgxspeed = menupres[menutype].titlescrollxspeed != INT32_MAX ? menupres[menutype].titlescrollxspeed : titlescrollxspeed;
-		curbgyspeed = menupres[menutype].titlescrollyspeed != INT32_MAX ? menupres[menutype].titlescrollyspeed : titlescrollyspeed;
+		curbghide = true;
 		return true;
 	}
-	else if (menupres[menutype].bghide && titlemapinaction) // hide the background
+	else if (menupres[menutype].bgname[0])
 	{
-		curbghide = true;
+		strncpy(curbgname, menupres[menutype].bgname, 8);
+		curbgxspeed = menupres[menutype].titlescrollxspeed != INT32_MAX ? menupres[menutype].titlescrollxspeed : titlescrollxspeed;
+		curbgyspeed = menupres[menutype].titlescrollyspeed != INT32_MAX ? menupres[menutype].titlescrollyspeed : titlescrollyspeed;
 		return true;
 	}
 	else if (!level)
@@ -2329,6 +2332,9 @@ static boolean MIT_ChangeMusic(UINT32 menutype, INT32 level, INT32 *retval, void
 	(void)retval;
 	(void)fromoldest;
 
+	if (!menutype) // if there's nothing in this level, do nothing
+		return false;
+
 	if (menupres[menutype].musname[0])
 	{
 		S_ChangeMusic(menupres[menutype].musname, menupres[menutype].mustrack, menupres[menutype].muslooping);
@@ -2353,6 +2359,9 @@ static boolean MIT_SetCurFadeValue(UINT32 menutype, INT32 level, INT32 *retval,
 	(void)retval;
 	(void)fromoldest;
 
+	if (!menutype) // if there's nothing in this level, do nothing
+		return false;
+
 	if (menupres[menutype].fadestrength >= 0)
 	{
 		curfadevalue = (menupres[menutype].fadestrength % 32);
@@ -2369,6 +2378,9 @@ static boolean MIT_SetCurHideTitlePics(UINT32 menutype, INT32 level, INT32 *retv
 	(void)retval;
 	(void)fromoldest;
 
+	if (!menutype) // if there's nothing in this level, do nothing
+		return false;
+
 	if (menupres[menutype].hidetitlepics >= 0)
 	{
 		curhidepics = menupres[menutype].hidetitlepics;
@@ -2470,7 +2482,7 @@ static void M_HandleMenuPresState(menu_t *newMenu)
 	curbgcolor = -1;
 	curbgxspeed = titlescrollxspeed;
 	curbgyspeed = titlescrollyspeed;
-	curbghide = true;
+	curbghide = (gamestate != GS_TIMEATTACK); // show in time attack, hide in other menus
 
 	// don't do the below during the in-game menus
 	if (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK)
@@ -2799,8 +2811,8 @@ boolean M_Responder(event_t *ev)
 	void (*routine)(INT32 choice); // for some casting problem
 
 	if (dedicated || (demoplayback && titledemo)
-	|| gamestate == GS_INTRO || gamestate == GS_CUTSCENE || gamestate == GS_GAMEEND
-	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
+	|| gamestate == GS_INTRO || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
+	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_GAMEEND)
 		return false;
 
 	if (noFurtherInput)
@@ -2954,8 +2966,9 @@ boolean M_Responder(event_t *ev)
 					return true;
 				M_StartControlPanel();
 				M_Options(0);
-				currentMenu = &OP_SoundOptionsDef;
-				itemOn = 0;
+				// Uncomment the below if you want the menu to reset to the top each time like before. M_SetupNextMenu will fix it automatically.
+				//OP_SoundOptionsDef.lastOn = 0;
+				M_SetupNextMenu(&OP_SoundOptionsDef);
 				return true;
 
 			case KEY_F5: // Video Mode
@@ -3499,6 +3512,7 @@ void M_InitCharacterTables(void)
 		strcpy(description[i].picname, "");
 		strcpy(description[i].skinname, "");
 		description[i].prev = description[i].next = 0;
+		description[i].pic = NULL;
 	}
 }
 
@@ -7545,8 +7559,19 @@ static void M_SetupChoosePlayer(INT32 choice)
 				if (i == char_on)
 					allowed = true;
 
-				if (description[i].picname[0] == '\0')
-					strncpy(description[i].picname, skins[skinnum].charsel, 8);
+				if (!(description[i].picname[0]))
+				{
+					if (skins[skinnum].sprites[SPR2_XTRA].numframes >= 2)
+					{
+						spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
+						spriteframe_t *sprframe = &sprdef->spriteframes[1];
+						description[i].pic = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+					}
+					else
+						description[i].pic = W_CachePatchName("MISSING", PU_CACHE);
+				}
+				else
+					description[i].pic = W_CachePatchName(description[i].picname, PU_CACHE);
 			}
 			// else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
 			Z_Free(name);
@@ -7700,7 +7725,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		// Draw prev character if it's visible and its number isn't greater than the current one or there's more than two
 		if (o < 32)
 		{
-			patch = W_CachePatchName(description[prev].picname, PU_CACHE);
+			patch = description[prev].pic;
 			if (SHORT(patch->width) >= 256)
 				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT/2, 0, patch, 0, SHORT(patch->height) + 2*(o-32), SHORT(patch->width), 64 - 2*o);
 			else
@@ -7711,7 +7736,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		// Draw next character if it's visible and its number isn't less than the current one or there's more than two
 		if (o < 128) // (next != i) was previously a part of this, but it's implicitly true if (prev != i) is true.
 		{
-			patch = W_CachePatchName(description[next].picname, PU_CACHE);
+			patch = description[next].pic;
 			if (SHORT(patch->width) >= 256)
 				V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, FRACUNIT/2, 0, patch, 0, 0, SHORT(patch->width), 2*o);
 			else
@@ -7720,7 +7745,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		}
 	}
 
-	patch = W_CachePatchName(description[i].picname, PU_CACHE);
+	patch = description[i].pic;
 	if (o >= 0 && o <= 32)
 	{
 		if (SHORT(patch->width) >= 256)
@@ -8112,9 +8137,16 @@ void M_DrawTimeAttackMenu(void)
 	V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
 
 	// Character face!
-	if (W_CheckNumForName(skins[cv_chooseskin.value-1].charsel) != LUMPERROR)
 	{
-		PictureOfUrFace = W_CachePatchName(skins[cv_chooseskin.value-1].charsel, PU_CACHE);
+		if (skins[cv_chooseskin.value-1].sprites[SPR2_XTRA].numframes >= 2)
+		{
+			spritedef_t *sprdef = &skins[cv_chooseskin.value-1].sprites[SPR2_XTRA];
+			spriteframe_t *sprframe = &sprdef->spriteframes[1];
+			PictureOfUrFace = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+		}
+		else
+			PictureOfUrFace = W_CachePatchName("MISSING", PU_CACHE);
+
 		if (PictureOfUrFace->width >= 256)
 			V_DrawTinyScaledPatch(224, 120, 0, PictureOfUrFace);
 		else
@@ -8234,6 +8266,7 @@ static void M_TimeAttack(INT32 choice)
 	M_PatchSkinNameTable();
 
 	G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
+	titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
 	M_SetupNextMenu(&SP_TimeAttackDef);
 	if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
 		CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
@@ -8415,6 +8448,7 @@ static void M_NightsAttack(INT32 choice)
 
 	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
 	if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
 		CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
 	else
diff --git a/src/m_menu.h b/src/m_menu.h
index 04146ebdc7ccc97cc9bf8c68f78abed928eaf290..347725e10ba3262ef64eb76073031fb15849fb69 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -316,6 +316,7 @@ typedef struct
 	char notes[441];
 	char picname[8];
 	char skinname[SKINNAMESIZE*2+2]; // skin&skin\0
+	patch_t *pic;
 	UINT8 prev;
 	UINT8 next;
 } description_t;
diff --git a/src/p_ceilng.c b/src/p_ceilng.c
index 757edebae120e882c7cfd498f0076b46a4aa36a1..5395f618c5e6dd8361b168bb9bd1c58101f650b3 100644
--- a/src/p_ceilng.c
+++ b/src/p_ceilng.c
@@ -409,7 +409,7 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 		// new door thinker
 		rtn = 1;
 		ceiling = Z_Calloc(sizeof (*ceiling), PU_LEVSPEC, NULL);
-		P_AddThinker(&ceiling->thinker);
+		P_AddThinker(THINK_MAIN, &ceiling->thinker);
 		sec->ceilingdata = ceiling;
 		ceiling->thinker.function.acp1 = (actionf_p1)T_MoveCeiling;
 		ceiling->sector = sec;
@@ -629,7 +629,7 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 		// new door thinker
 		rtn = 1;
 		ceiling = Z_Calloc(sizeof (*ceiling), PU_LEVSPEC, NULL);
-		P_AddThinker(&ceiling->thinker);
+		P_AddThinker(THINK_MAIN, &ceiling->thinker);
 		sec->ceilingdata = ceiling;
 		ceiling->thinker.function.acp1 = (actionf_p1)T_CrushCeiling;
 		ceiling->sector = sec;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 2861f448020da629178503808189aff4185c02e3..18d6f20c0e9fd75748c6ff4bf654036512141082 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -1391,7 +1391,7 @@ void A_StatueBurst(mobj_t *actor)
 		return;
 
 	new->angle = actor->angle;
-	new->target = actor->target;
+	P_SetTarget(&new->target, actor->target);
 	if (locvar2)
 		P_SetMobjState(new, (statenum_t)locvar2);
 	S_StartSound(new, new->info->attacksound);
@@ -2155,7 +2155,7 @@ void A_CrushclawLaunch(mobj_t *actor)
 		for (i = 0; (i < CSEGS); i++)
 		{
 			mobj_t *newchain = P_SpawnMobjFromMobj(actor, 0, 0, 0, actor->info->raisestate);
-			prevchain->target = newchain;
+			P_SetTarget(&prevchain->target, newchain);
 			prevchain = newchain;
 		}
 		actor->target->angle = R_PointToAngle2(actor->target->x, actor->target->y, crab->target->x, crab->target->y);
@@ -2348,7 +2348,7 @@ void A_VultureHover(mobj_t *actor)
 	fixed_t targetz;
 	fixed_t distdif;
 	fixed_t memz = actor->z;
-	INT8 i;
+	SINT8 i;
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_VultureHover", actor))
@@ -2411,6 +2411,8 @@ void A_VultureBlast(mobj_t *actor)
 {
 	mobj_t *dust;
 	UINT8 i;
+	angle_t faa;
+	fixed_t faacos, faasin;
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_VultureBlast", actor))
@@ -2419,18 +2421,21 @@ void A_VultureBlast(mobj_t *actor)
 
 	S_StartSound(actor, actor->info->attacksound);
 
+	faa = (actor->angle >> ANGLETOFINESHIFT) & FINEMASK;
+	faacos = FINECOSINE(faa);
+	faasin = FINESINE(faa);
+
 	for (i = 0; i <= 7; i++)
 	{
 		angle_t fa = ((i*(angle_t)ANGLE_45) >> ANGLETOFINESHIFT) & FINEMASK;
-		angle_t faa = (actor->angle >> ANGLETOFINESHIFT) & FINEMASK;
-		dust = P_SpawnMobj(actor->x + 48*FixedMul(FINECOSINE(fa), -FINESINE(faa)), actor->y + 48*FixedMul(FINECOSINE(fa), FINECOSINE(faa)), actor->z + actor->height/2 + 48*FINESINE(fa), MT_PARTICLE);
+		dust = P_SpawnMobj(actor->x + 48*FixedMul(FINECOSINE(fa), -faasin), actor->y + 48*FixedMul(FINECOSINE(fa), faacos), actor->z + actor->height/2 + 48*FINESINE(fa), MT_PARTICLE);
 
 		P_SetScale(dust, 4*FRACUNIT);
 		dust->destscale = FRACUNIT;
 		dust->scalespeed = 4*FRACUNIT/TICRATE;
 		dust->fuse = TICRATE;
-		dust->momx = FixedMul(FINECOSINE(fa), -FINESINE(faa))*3;
-		dust->momy = FixedMul(FINECOSINE(fa), FINECOSINE(faa))*3;
+		dust->momx = FixedMul(FINECOSINE(fa), -faasin)*3;
+		dust->momy = FixedMul(FINECOSINE(fa), faacos)*3;
 		dust->momz = FINESINE(fa)*6;
 	}
 }
@@ -2858,6 +2863,7 @@ void A_BossFireShot(mobj_t *actor)
 	fixed_t x, y, z;
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
+	mobj_t *missile;
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_BossFireShot", actor))
@@ -2925,7 +2931,10 @@ void A_BossFireShot(mobj_t *actor)
 			break;
 	}
 
-	P_SpawnXYZMissile(actor, actor->target, locvar1, x, y, z);
+	missile = P_SpawnXYZMissile(actor, actor->target, locvar1, x, y, z);
+
+	if (missile && actor->tracer && (actor->tracer->flags & MF_BOSS)) // Don't harm your papa.
+		P_SetTarget(&missile->target, actor->tracer);
 }
 
 // Function: A_Boss7FireMissiles
@@ -3079,7 +3088,7 @@ void A_Boss1Laser(mobj_t *actor)
 	if (z - floorz < mobjinfo[MT_EGGMOBILE_FIRE].height>>1)
 	{
 		point = P_SpawnMobj(x, y, floorz+1, MT_EGGMOBILE_FIRE);
-		point->target = actor;
+		P_SetTarget(&point->target, actor);
 		point->destscale = 3*FRACUNIT;
 		point->scalespeed = FRACUNIT>>2;
 		point->fuse = TICRATE;
@@ -3154,21 +3163,35 @@ void A_FocusTarget(mobj_t *actor)
 // Description: Reverse arms direction.
 //
 // var1 = sfx to play
-// var2 = unused
+// var2 = sfx to play in pinch
 //
 void A_Boss4Reverse(mobj_t *actor)
 {
 	sfxenum_t locvar1 = (sfxenum_t)var1;
+	sfxenum_t locvar2 = (sfxenum_t)var2;
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_Boss4Reverse", actor))
 		return;
 #endif
-	S_StartSound(NULL, locvar1);
 	actor->reactiontime = 0;
-	if (actor->movedir == 1)
-		actor->movedir = 2;
+	if (actor->movedir < 3)
+	{
+		S_StartSound(NULL, locvar1);
+		if (actor->movedir == 1)
+			actor->movedir = 2;
+		else
+			actor->movedir = 1;
+	}
 	else
-		actor->movedir = 1;
+	{
+		S_StartSound(NULL, locvar2);
+		if (actor->movedir == 4)
+			actor->movedir = 5;
+		else
+			actor->movedir = 4;
+		actor->angle += ANGLE_180;
+		actor->movefactor = -actor->movefactor;
+	}
 }
 
 // Function: A_Boss4SpeedUp
@@ -3465,9 +3488,11 @@ void A_1upThinker(mobj_t *actor)
 
 	if (closestplayer == -1 || skins[players[closestplayer].skin].sprites[SPR2_LIFE].numframes == 0)
 	{ // Closest player not found (no players in game?? may be empty dedicated server!), or does not have correct sprite.
-		if (actor->tracer) {
-			P_RemoveMobj(actor->tracer);
-			actor->tracer = NULL;
+		if (actor->tracer)
+		{
+			mobj_t *tracer = actor->tracer;
+			P_SetTarget(&actor->tracer, NULL);
+			P_RemoveMobj(tracer);
 		}
 		return;
 	}
@@ -3761,9 +3786,9 @@ void A_BossDeath(mobj_t *mo)
 
 	// scan the remaining thinkers to see
 	// if all bosses are dead
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -3853,6 +3878,8 @@ bossjustdie:
 		}
 		default: //eggmobiles
 		{
+			UINT8 extrainfo = (mo->spawnpoint ? mo->spawnpoint->extrainfo : 0);
+
 			// Stop exploding and prepare to run.
 			P_SetMobjState(mo, mo->info->xdeathstate);
 			if (P_MobjWasRemoved(mo))
@@ -3862,9 +3889,9 @@ bossjustdie:
 
 			// Flee! Flee! Find a point to escape to! If none, just shoot upward!
 			// scan the thinkers to find the runaway point
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -3872,6 +3899,9 @@ bossjustdie:
 				if (mo2->type != MT_BOSSFLYPOINT)
 					continue;
 
+				if (mo2->spawnpoint && mo2->spawnpoint->extrainfo != extrainfo)
+					continue;
+
 				// If this one's further then the last one, don't go for it.
 				if (mo->target &&
 					P_AproxDistance(P_AproxDistance(mo->x - mo2->x, mo->y - mo2->y), mo->z - mo2->z) >
@@ -6118,9 +6148,9 @@ void A_RingExplode(mobj_t *actor)
 
 	S_StartSound(actor, sfx_prloop);
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -6631,6 +6661,9 @@ void A_RecyclePowers(mobj_t *actor)
 		players[recv_pl].ringweapons = weapons[send_pl];
 		players[recv_pl].currentweapon = weaponheld[send_pl];
 
+		if (((players[recv_pl].powers[pw_shield] & SH_NOSTACK) == SH_PINK) && (players[recv_pl].revitem == MT_LHRT || players[recv_pl].spinitem == MT_LHRT || players[recv_pl].thokitem == MT_LHRT)) // Healers can't keep their buff.
+			players[recv_pl].powers[pw_shield] &= SH_STACK;
+
 		P_SpawnShieldOrb(&players[recv_pl]);
 		if (P_IsLocalPlayer(&players[recv_pl]))
 			P_RestoreMusic(&players[recv_pl]);
@@ -7246,7 +7279,7 @@ void A_Boss2PogoTarget(mobj_t *actor)
 	if (actor->info->missilestate) // spawn the pogo stick collision box
 	{
 		mobj_t *pogo = P_SpawnMobj(actor->x, actor->y, actor->z - mobjinfo[actor->info->missilestate].height, (mobjtype_t)actor->info->missilestate);
-		pogo->target = actor;
+		P_SetTarget(&pogo->target, actor);
 	}
 
 	actor->reactiontime = 1;
@@ -7762,9 +7795,11 @@ void A_Boss3TakeDamage(mobj_t *actor)
 		return;
 #endif
 	actor->movecount = var1;
+	actor->movefactor = -512*FRACUNIT;
+
+	/*if (actor->target && actor->target->spawnpoint)
+		actor->threshold = actor->target->spawnpoint->extrainfo;*/
 
-	if (actor->target && actor->target->spawnpoint)
-		actor->threshold = actor->target->spawnpoint->extrainfo;
 }
 
 // Function: A_Boss3Path
@@ -7801,24 +7836,34 @@ void A_Boss3Path(mobj_t *actor)
 	}
 	else if (actor->threshold >= 0) // Traveling mode
 	{
-		thinker_t *th;
-		mobj_t *mo2;
-		fixed_t dist, dist2;
+		fixed_t dist = 0;
 		fixed_t speed;
 
-		P_SetTarget(&actor->target, NULL);
-
-		// scan the thinkers
-		// to find a point that matches
-		// the number
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		if (!(actor->flags2 & MF2_STRONGBOX))
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
-				continue;
+			thinker_t *th;
+			mobj_t *mo2;
 
-			mo2 = (mobj_t *)th;
-			if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint && mo2->spawnpoint->angle == actor->threshold)
+			P_SetTarget(&actor->target, NULL);
+
+			// scan the thinkers
+			// to find a point that matches
+			// the number
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					continue;
+
+				mo2 = (mobj_t *)th;
+				if (mo2->type != MT_BOSS3WAYPOINT)
+					continue;
+				if (!mo2->spawnpoint)
+					continue;
+				if (mo2->spawnpoint->angle != actor->threshold)
+					continue;
+				if (mo2->spawnpoint->extrainfo != actor->cusval)
+					continue;
+
 				P_SetTarget(&actor->target, mo2);
 				break;
 			}
@@ -7826,67 +7871,62 @@ void A_Boss3Path(mobj_t *actor)
 
 		if (!actor->target) // Should NEVER happen
 		{
-			CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 Dummy was unable to find specified waypoint: %d\n", actor->threshold);
+			CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 Dummy was unable to find specified waypoint: %d, %d\n", actor->threshold, actor->cusval);
 			return;
 		}
 
-		dist = P_AproxDistance(P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y), actor->target->z - actor->z);
-
-		if (dist < 1)
-			dist = 1;
-
 		if (actor->tracer && ((actor->tracer->movedir)
 		|| (actor->tracer->health <= actor->tracer->info->damage)))
 			speed = actor->info->speed * 2;
 		else
 			speed = actor->info->speed;
 
-		actor->momx = FixedMul(FixedDiv(actor->target->x - actor->x, dist), speed);
-		actor->momy = FixedMul(FixedDiv(actor->target->y - actor->y, dist), speed);
-		actor->momz = FixedMul(FixedDiv(actor->target->z - actor->z, dist), speed);
+		if (actor->target->x == actor->x && actor->target->y == actor->y)
+		{
+			dist = P_AproxDistance(P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y), actor->target->z + actor->movefactor - actor->z);
 
-		if (actor->momx != 0 || actor->momy != 0)
-			actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy);
+			if (dist < 1)
+				dist = 1;
 
-		dist2 = P_AproxDistance(P_AproxDistance(actor->target->x - (actor->x + actor->momx), actor->target->y - (actor->y + actor->momy)), actor->target->z - (actor->z + actor->momz));
+			actor->momx = FixedMul(FixedDiv(actor->target->x - actor->x, dist), speed);
+			actor->momy = FixedMul(FixedDiv(actor->target->y - actor->y, dist), speed);
+			actor->momz = FixedMul(FixedDiv(actor->target->z + actor->movefactor - actor->z, dist), speed);
 
-		if (dist2 < 1)
-			dist2 = 1;
+			if (actor->momx != 0 || actor->momy != 0)
+				actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy);
+		}
 
-		if ((dist >> FRACBITS) <= (dist2 >> FRACBITS))
+		if (dist <= speed)
 		{
 			// If further away, set XYZ of mobj to waypoint location
 			P_UnsetThingPosition(actor);
 			actor->x = actor->target->x;
 			actor->y = actor->target->y;
-			actor->z = actor->target->z;
+			actor->z = actor->target->z + actor->movefactor;
 			actor->momx = actor->momy = actor->momz = 0;
 			P_SetThingPosition(actor);
 
-			if (actor->threshold == 0)
+			if (!actor->movefactor) // firing mode
 			{
-				P_RemoveMobj(actor); // Cycle completed. Dummy removed.
-				return;
+				actor->movecount |= 2;
+				actor->movefactor = -512*FRACUNIT;
+				actor->flags2 &= ~MF2_STRONGBOX;
 			}
-
-			// Set to next waypoint in sequence
-			if (actor->target->spawnpoint)
+			else if (!(actor->flags2 & MF2_STRONGBOX)) // just spawned or going down
 			{
-				// From the center point, choose one of the five paths
-				if (actor->target->spawnpoint->angle == 0)
-				{
-					P_RemoveMobj(actor); // Cycle completed. Dummy removed.
-					return;
-				}
-				else
-					actor->threshold = actor->target->spawnpoint->extrainfo;
-
-				// If the deaf flag is set, go into firing mode
-				if (actor->target->spawnpoint->options & MTF_AMBUSH)
-					actor->movecount |= 2;
+				actor->flags2 |= MF2_STRONGBOX;
+				actor->movefactor = -512*FRACUNIT;
+			}
+			else if (!(actor->flags2 & MF2_AMBUSH)) // just shifted tube
+			{
+				actor->flags2 |= MF2_AMBUSH;
+				actor->movefactor = 0;
+			}
+			else // just hit the bottom of your tube
+			{
+				P_RemoveMobj(actor); // Cycle completed. Dummy removed.
+				return;
 			}
-			else // This should never happen, as well
-				CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 Dummy waypoint has no spawnpoint associated with it.\n");
 		}
 	}
 }
@@ -8204,9 +8244,9 @@ void A_FindTarget(mobj_t *actor)
 	CONS_Debug(DBG_GAMELOGIC, "A_FindTarget called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
 
 	// scan the thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -8269,9 +8309,9 @@ void A_FindTracer(mobj_t *actor)
 	CONS_Debug(DBG_GAMELOGIC, "A_FindTracer called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
 
 	// scan the thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -8684,8 +8724,8 @@ void A_BossJetFume(mobj_t *actor)
 	{
 		fixed_t jetx, jety, jetz;
 
-		jetx = actor->x + P_ReturnThrustX(actor, actor->angle, -FixedMul(60*FRACUNIT, actor->scale));
-		jety = actor->y + P_ReturnThrustY(actor, actor->angle, -FixedMul(60*FRACUNIT, actor->scale));
+		jetx = actor->x + P_ReturnThrustX(actor, actor->angle, -60*actor->scale);
+		jety = actor->y + P_ReturnThrustY(actor, actor->angle, -60*actor->scale);
 		if (actor->eflags & MFE_VERTICALFLIP)
 			jetz = actor->z + actor->height - FixedMul(17*FRACUNIT + mobjinfo[MT_PROPELLER].height, actor->scale);
 		else
@@ -8718,7 +8758,7 @@ void A_BossJetFume(mobj_t *actor)
 		if (actor->eflags & MFE_VERTICALFLIP)
 			jetz = actor->z + actor->height + FixedMul(50*FRACUNIT - mobjinfo[MT_JETFLAME].height, actor->scale);
 		else
-			jetz = actor->z - FixedMul(50*FRACUNIT, actor->scale);
+			jetz = actor->z - 50*actor->scale;
 		filler = P_SpawnMobj(actor->x, actor->y, jetz, MT_JETFLAME);
 		P_SetTarget(&filler->target, actor);
 		// Boss 4 already uses its tracer for other things
@@ -8727,6 +8767,30 @@ void A_BossJetFume(mobj_t *actor)
 		if (actor->eflags & MFE_VERTICALFLIP)
 			filler->flags2 |= MF2_OBJECTFLIP;
 	}
+	else if (locvar1 == 4) // Boss 4 Spectator Eggrobo jet flame
+	{
+		fixed_t jetx, jety, jetz, movefactor = 12;
+
+		jetz = actor->z;
+		if (actor->eflags & MFE_VERTICALFLIP)
+			jetz += (actor->height - FixedMul(mobjinfo[MT_EGGROBO1JET].height, actor->scale));
+
+		while (true)
+		{
+			jetx = actor->x + P_ReturnThrustX(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustX(actor, actor->angle, 19*actor->scale);
+			jety = actor->y + P_ReturnThrustY(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustY(actor, actor->angle, 19*actor->scale);
+			filler = P_SpawnMobj(jetx, jety, jetz, MT_EGGROBO1JET);
+			filler->movefactor = movefactor;
+			P_SetTarget(&filler->target, actor);
+			filler->destscale = actor->scale;
+			P_SetScale(filler, filler->destscale);
+			if (actor->eflags & MFE_VERTICALFLIP)
+				filler->flags2 |= MF2_OBJECTFLIP;
+			if (movefactor <= 0)
+				break;
+			movefactor = -movefactor;
+		}
+	}
 }
 
 // Function: A_RandomState
@@ -8832,9 +8896,9 @@ void A_RemoteAction(mobj_t *actor)
 		fixed_t dist1 = 0, dist2 = 0;
 
 		// scan the thinkers
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -9098,9 +9162,9 @@ void A_SetObjectTypeState(mobj_t *actor)
 		return;
 #endif
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -9736,9 +9800,9 @@ void A_CheckThingCount(mobj_t *actor)
 		return;
 #endif
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -12244,6 +12308,7 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 	//INT32 locvar2 = var2;
 	boolean avoidcenter;
 	UINT32 i;
+	UINT8 extrainfo = (actor->spawnpoint ? actor->spawnpoint->extrainfo : 0);
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_Boss5FindWaypoint", actor))
 		return;
@@ -12253,16 +12318,34 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 
 	if (locvar1 == 2) // look for the boss waypoint
 	{
-		for (i = 0; i < nummapthings; i++)
+		thinker_t *th;
+		mobj_t *mo2;
+		P_SetTarget(&actor->tracer, NULL);
+		// Flee! Flee! Find a point to escape to! If none, just shoot upward!
+		// scan the thinkers to find the runaway point
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (!mapthings[i].mobj)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
-			if (mapthings[i].mobj->type != MT_BOSSFLYPOINT)
+
+			mo2 = (mobj_t *)th;
+
+			if (mo2->type != MT_BOSSFLYPOINT)
 				continue;
-			P_SetTarget(&actor->tracer, mapthings[i].mobj);
-			break;
+
+			if (mo2->spawnpoint && mo2->spawnpoint->extrainfo != extrainfo)
+				continue;
+
+			// If this one's further then the last one, don't go for it.
+			if (actor->tracer &&
+				P_AproxDistance(P_AproxDistance(actor->x - mo2->x, actor->y - mo2->y), actor->z - mo2->z) >
+				P_AproxDistance(P_AproxDistance(actor->x - actor->tracer->x, actor->y - actor->tracer->y), actor->z - actor->tracer->z))
+					continue;
+
+			// Otherwise... Do!
+			P_SetTarget(&actor->tracer, mo2);
 		}
-		if (i == nummapthings)
+		if (!actor->tracer)
 			return; // no boss flypoints found
 	}
 	else if (locvar1 == 1) // always go to ambush-marked waypoint
@@ -12276,11 +12359,13 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 				continue;
 			if (mapthings[i].mobj->type != MT_FANGWAYPOINT)
 				continue;
-			if (mapthings[i].options & MTF_AMBUSH)
-			{
-				P_SetTarget(&actor->tracer, mapthings[i].mobj);
-				break;
-			}
+			if (mapthings[i].extrainfo != extrainfo)
+				continue;
+			if (!(mapthings[i].options & MTF_AMBUSH))
+				continue;
+
+			P_SetTarget(&actor->tracer, mapthings[i].mobj);
+			break;
 		}
 
 		if (i == nummapthings)
@@ -12304,6 +12389,8 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 				continue;
 			if (actor->tracer == mapthings[i].mobj) // this was your tracer last time
 				continue;
+			if (mapthings[i].extrainfo != extrainfo)
+				continue;
 			if (mapthings[i].options & MTF_AMBUSH)
 			{
 				if (avoidcenter)
@@ -12359,6 +12446,8 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 				continue;
 			if (actor->tracer == mapthings[i].mobj) // this was your tracer last time
 				continue;
+			if (mapthings[i].extrainfo != extrainfo)
+				continue;
 			if (mapthings[i].options & MTF_AMBUSH)
 			{
 				if (avoidcenter)
@@ -13015,7 +13104,14 @@ static boolean PIT_TNTExplode(mobj_t *nearby)
 			nearby->momx = FixedMul(FixedDiv(dx, dm), explodethrust);
 			nearby->momy = FixedMul(FixedDiv(dy, dm), explodethrust);
 			nearby->momz = FixedMul(FixedDiv(dz, dm), explodethrust);
+			P_UnsetThingPosition(nearby);
+			if (sector_list)
+			{
+				P_DelSeclist(sector_list);
+				sector_list = NULL;
+			}
 			nearby->flags = MF_NOBLOCKMAP|MF_MISSILE;
+			P_SetThingPosition(nearby);
 			P_SetMobjState(nearby, nearby->info->missilestate);
 		}
 	}
@@ -13024,9 +13120,10 @@ static boolean PIT_TNTExplode(mobj_t *nearby)
 		if (barrel->target == nearby)
 		{
 			mobj_t *tar = barrel->target; // temporarily store barrel's target
-			barrel->target = NULL;
+			P_SetTarget(&barrel->target, NULL);
 			P_DamageMobj(nearby, barrel, NULL, 1, 0);
-			barrel->target = tar;
+			if (!P_MobjWasRemoved(barrel))
+				P_SetTarget(&barrel->target, tar);
 		}
 		else
 		{
@@ -13059,8 +13156,14 @@ void A_TNTExplode(mobj_t *actor)
 	if (LUA_CallAction("A_TNTExplode", actor))
 		return;
 #endif
-
+	P_UnsetThingPosition(actor);
+	if (sector_list)
+	{
+		P_DelSeclist(sector_list);
+		sector_list = NULL;
+	}
 	actor->flags = MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP;
+	P_SetThingPosition(actor);
 	actor->flags2 = MF2_EXPLOSION;
 	if (actor->info->deathsound)
 		S_StartSound(actor, actor->info->deathsound);
@@ -13522,7 +13625,7 @@ void A_SaloonDoorSpawn(mobj_t *actor)
 	door->extravalue2 = 0;
 
 	// Origin door
-	door->tracer = actor;
+	P_SetTarget(&door->tracer, actor);
 
 	//Back
 	door = P_SpawnMobj(x - c*d, y - s*d, z, MT_SALOONDOOR);
@@ -13535,7 +13638,7 @@ void A_SaloonDoorSpawn(mobj_t *actor)
 	door->extravalue2 = 0;
 
 	// Origin door
-	door->tracer = actor;
+	P_SetTarget(&door->tracer, actor);
 }
 
 // Function: A_MinecartSparkThink
diff --git a/src/p_floor.c b/src/p_floor.c
index a1b1c45fc08dc51ab30eab179bf740290713bdbf..7887dc530a6291adb50adb1304baee52197fe440 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -719,6 +719,8 @@ void T_ContinuousFalling(levelspecthink_t *faller)
 		}
 	}
 
+	P_CheckSector(faller->sector, false); // you might think this is irrelevant. you would be wrong
+
 	faller->sector->floorspeed = faller->speed*faller->direction;
 	faller->sector->ceilspeed = 42;
 	faller->sector->moved = true;
@@ -1975,25 +1977,27 @@ void T_ThwompSector(levelspecthink_t *thwomp)
 	}
 	else // Not going anywhere, so look for players.
 	{
-		thinker_t *th;
-		mobj_t *mo;
-
 		if (!rover || (rover->flags & FF_EXISTS))
 		{
-			// scan the thinkers to find players!
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			UINT8 i;
+			// scan the players to find victims!
+			for (i = 0; i < MAXPLAYERS; i++)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (!playeringame[i])
+					continue;
+				if (players[i].spectator)
+					continue;
+				if (!players[i].mo)
+					continue;
+				if (!players[i].mo->health)
+					continue;
+				if (players[i].mo->z > thwomp->sector->ceilingheight)
+					continue;
+				if (P_AproxDistance(thwompx - players[i].mo->x, thwompy - players[i].mo->y) > 96 * FRACUNIT)
 					continue;
 
-				mo = (mobj_t *)th;
-				if (mo->type == MT_PLAYER && mo->health && mo->player && !mo->player->spectator
-				    && mo->z <= thwomp->sector->ceilingheight
-					&& P_AproxDistance(thwompx - mo->x, thwompy - mo->y) <= 96*FRACUNIT)
-				{
-					thwomp->direction = -1;
-					break;
-				}
+				thwomp->direction = -1;
+				break;
 			}
 		}
 
@@ -2701,7 +2705,7 @@ INT32 EV_DoFloor(line_t *line, floor_e floortype)
 		// new floor thinker
 		rtn = 1;
 		dofloor = Z_Calloc(sizeof (*dofloor), PU_LEVSPEC, NULL);
-		P_AddThinker(&dofloor->thinker);
+		P_AddThinker(THINK_MAIN, &dofloor->thinker);
 
 		// make sure another floor thinker won't get started over this one
 		sec->floordata = dofloor;
@@ -2922,7 +2926,7 @@ INT32 EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 		// create and initialize new elevator thinker
 		rtn = 1;
 		elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
-		P_AddThinker(&elevator->thinker);
+		P_AddThinker(THINK_MAIN, &elevator->thinker);
 		sec->floordata = elevator;
 		sec->ceilingdata = elevator;
 		elevator->thinker.function.acp1 = (actionf_p1)T_MoveElevator;
@@ -3027,20 +3031,40 @@ INT32 EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 
 void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 {
-	size_t i;
-	size_t leftmostvertex = 0, rightmostvertex = 0;
-	size_t topmostvertex = 0, bottommostvertex = 0;
-	fixed_t leftx, rightx;
-	fixed_t topy, bottomy;
-	fixed_t topz, bottomz;
-	fixed_t widthfactor = FRACUNIT, heightfactor = FRACUNIT;
-	fixed_t a, b, c;
-	mobjtype_t type = MT_ROCKCRUMBLE1;
-	fixed_t spacing = (32<<FRACBITS);
-	tic_t lifetime = 3*TICRATE;
-	INT16 flags = 0;
-
-#define controlsec rover->master->frontsector
+	size_t i, leftmostvertex, rightmostvertex, topmostvertex, bottommostvertex;
+	fixed_t leftx, rightx, topy, bottomy, topz, bottomz, widthfactor, heightfactor, a, b, c, spacing;
+	mobjtype_t type;
+	tic_t lifetime;
+	INT16 flags;
+
+	sector_t *controlsec = rover->master->frontsector;
+
+	if (sec == NULL)
+	{
+		if (controlsec->numattached)
+		{
+			for (i = 0; i < controlsec->numattached; i++)
+			{
+				sec = &sectors[controlsec->attached[i]];
+				if (!sec->ffloors)
+					continue;
+
+				for (rover = sec->ffloors; rover; rover = rover->next)
+				{
+					if (rover->master->frontsector == controlsec)
+						EV_CrumbleChain(sec, rover);
+				}
+			}
+		}
+		return;
+	}
+
+	leftmostvertex = rightmostvertex = topmostvertex = bottommostvertex = 0;
+	widthfactor = heightfactor = FRACUNIT;
+	spacing = (32<<FRACBITS);
+	type = MT_ROCKCRUMBLE1;
+	lifetime = 3*TICRATE;
+	flags = 0;
 
 	if (controlsec->tag != 0)
 	{
@@ -3133,7 +3157,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	// no longer exists (can't collide with again)
 	rover->flags &= ~FF_EXISTS;
 	rover->master->frontsector->moved = true;
-	sec->moved = true;
+	P_RecalcPrecipInSector(sec);
 }
 
 // Used for bobbing platforms on the water
@@ -3149,7 +3173,7 @@ INT32 EV_BounceSector(sector_t *sec, fixed_t momz, line_t *sourceline)
 		return 0;
 
 	bouncer = Z_Calloc(sizeof (*bouncer), PU_LEVSPEC, NULL);
-	P_AddThinker(&bouncer->thinker);
+	P_AddThinker(THINK_MAIN, &bouncer->thinker);
 	sec->ceilingdata = bouncer;
 	bouncer->thinker.function.acp1 = (actionf_p1)T_BounceCheese;
 
@@ -3183,7 +3207,7 @@ INT32 EV_DoContinuousFall(sector_t *sec, sector_t *backsector, fixed_t spd, bool
 
 	// create and initialize new thinker
 	faller = Z_Calloc(sizeof (*faller), PU_LEVSPEC, NULL);
-	P_AddThinker(&faller->thinker);
+	P_AddThinker(THINK_MAIN, &faller->thinker);
 	faller->thinker.function.acp1 = (actionf_p1)T_ContinuousFalling;
 
 	// set up the fields
@@ -3232,7 +3256,7 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 
 	// create and initialize new elevator thinker
 	elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
-	P_AddThinker(&elevator->thinker);
+	P_AddThinker(THINK_MAIN, &elevator->thinker);
 	elevator->thinker.function.acp1 = (actionf_p1)T_StartCrumble;
 
 	// Does this crumbler return?
@@ -3311,7 +3335,7 @@ INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 		// create and initialize new elevator thinker
 
 		block = Z_Calloc(sizeof (*block), PU_LEVSPEC, NULL);
-		P_AddThinker(&block->thinker);
+		P_AddThinker(THINK_MAIN, &block->thinker);
 		roversec->floordata = block;
 		roversec->ceilingdata = block;
 		block->thinker.function.acp1 = (actionf_p1)T_MarioBlock;
diff --git a/src/p_inter.c b/src/p_inter.c
index cd1e80de2dbbb1a231c3558c1b0e293812d84a33..0489bab90439e1aa75bd3eff84bed6f88bfd8f0d 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -97,9 +97,9 @@ void P_ClearStarPost(INT32 postnum)
 	mobj_t *mo2;
 
 	// scan the thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -126,15 +126,17 @@ void P_ResetStarposts(void)
 	thinker_t *th;
 	mobj_t *post;
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		post = (mobj_t *)th;
 
-		if (post->type == MT_STARPOST)
-			P_SetMobjState(post, post->info->spawnstate);
+		if (post->type != MT_STARPOST)
+			continue;
+
+		P_SetMobjState(post, post->info->spawnstate);
 	}
 }
 
@@ -321,6 +323,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 	// Can happen with a sliding player corpse.
 	if (toucher->health <= 0)
 		return;
+	if (special->health <= 0)
+		return;
 
 	if (heightcheck)
 	{
@@ -346,9 +350,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		}
 	}
 
-	if (special->health <= 0)
-		return;
-
 	player = toucher->player;
 	I_Assert(player != NULL); // Only players can touch stuff!
 
@@ -453,13 +454,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				break;
 		}
 
-		if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
-		|| ((player->pflags & PF_JUMPED) && (!(player->pflags & PF_NOJUMPDAMAGE) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
-		|| (player->pflags & (PF_SPINNING|PF_GLIDING))
-		|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
-		|| ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0))
-		|| player->powers[pw_invulnerability] || player->powers[pw_super]
-		|| elementalpierce) // Do you possess the ability to subdue the object?
+		if (P_PlayerCanDamage(player, special)) // Do you possess the ability to subdue the object?
 		{
 			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
 			{
@@ -474,18 +469,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			{
 				toucher->momx = -toucher->momx;
 				toucher->momy = -toucher->momy;
+				if (player->charability == CA_FLY && player->panim == PA_ABILITY)
+					toucher->momz = -toucher->momz/2;
 			}
 			P_DamageMobj(special, toucher, toucher, 1, 0);
-		}
-		else if (((toucher->z < special->z && !(toucher->eflags & MFE_VERTICALFLIP))
-		|| (toucher->z + toucher->height > special->z + special->height && (toucher->eflags & MFE_VERTICALFLIP)))
-		&& player->charability == CA_FLY
-		&& (player->powers[pw_tailsfly]
-		|| toucher->state-states == S_PLAY_FLY_TIRED)) // Tails can shred stuff with her propeller.
-		{
-			toucher->momz = -toucher->momz/2;
-
-			P_DamageMobj(special, toucher, toucher, 1, 0);
+			if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
+				P_TwinSpinRejuvenate(player, player->thokitem);
 		}
 		else
 			P_DamageMobj(toucher, special, special, 1, 0);
@@ -681,7 +670,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				P_DoMatchSuper(player);
 			}
 			else
+			{
 				emeralds |= special->info->speed;
+				stagefailed = false;
+			}
 
 			if (special->target && special->target->type == MT_EMERALDSPAWN)
 			{
@@ -845,24 +837,24 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						// The player might have two Ideyas: toucher->tracer and toucher->tracer->hnext
 						// so handle their anchorpoints accordingly.
 						// scan the thinkers to find the corresponding anchorpoint
-						for (th = thinkercap.next; th != &thinkercap; th = th->next)
+						for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 						{
-							if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+							if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 								continue;
 
 							mo2 = (mobj_t *)th;
 
-							if (mo2->type == MT_IDEYAANCHOR)
-							{
-								if (mo2->health == toucher->tracer->health) // do ideya numberes match?
-									anchorpoint = mo2;
-								else if (toucher->tracer->hnext && mo2->health == toucher->tracer->hnext->health)
-									anchorpoint2 = mo2;
+							if (mo2->type != MT_IDEYAANCHOR)
+								continue;
 
-								if ((!toucher->tracer->hnext && anchorpoint)
-									|| (toucher->tracer->hnext && anchorpoint && anchorpoint2))
-									break;
-							}
+							if (mo2->health == toucher->tracer->health) // do ideya numberes match?
+								anchorpoint = mo2;
+							else if (toucher->tracer->hnext && mo2->health == toucher->tracer->hnext->health)
+								anchorpoint2 = mo2;
+
+							if ((!toucher->tracer->hnext && anchorpoint)
+								|| (toucher->tracer->hnext && anchorpoint && anchorpoint2))
+								break;
 						}
 
 						if (anchorpoint)
@@ -939,9 +931,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				count = 1;
 
 				// scan the remaining thinkers
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -989,9 +981,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 				// Now we RE-scan all the thinkers to find close objects to pull
 				// in from the paraloop. Isn't this just so efficient?
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -1363,9 +1355,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				EV_DoElevator(&junk, bridgeFall, false);
 
 				// scan the remaining thinkers to find koopa
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -1463,10 +1455,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				thinker_t *th;
 				mobj_t *mo2;
 
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
-					continue;
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+						continue;
 
 					mo2 = (mobj_t *)th;
 
@@ -1578,6 +1570,45 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			}
 			return;
 
+		case MT_EGGROBO1:
+			if (special->state == &states[special->info->deathstate])
+				return;
+			if (P_PlayerInPain(player))
+				return;
+
+			P_SetMobjState(special, special->info->meleestate);
+			special->angle = special->movedir;
+			special->momx = special->momy = 0;
+
+			// Buenos Dias Mandy
+			P_SetPlayerMobjState(toucher, S_PLAY_STUN);
+			player->pflags &= ~PF_APPLYAUTOBRAKE;
+			player->drawangle = special->angle + ANGLE_180;
+			P_InstaThrust(toucher, special->angle, FixedMul(3*special->info->speed, special->scale/2));
+			toucher->z += P_MobjFlip(toucher);
+			if (toucher->eflags & MFE_UNDERWATER) // unlikely.
+				P_SetObjectMomZ(toucher, FixedDiv(10511*FRACUNIT,2600*FRACUNIT), false);
+			else
+				P_SetObjectMomZ(toucher, FixedDiv(69*FRACUNIT,10*FRACUNIT), false);
+			if (P_IsLocalPlayer(player))
+			{
+				quake.intensity = 9*FRACUNIT;
+				quake.time = TICRATE/2;
+				quake.epicenter = NULL;
+			}
+
+#if 0 // camera redirection - deemed unnecessary
+			toucher->angle = special->angle;
+			if (player == &players[consoleplayer])
+				localangle = toucher->angle;
+			else if (player == &players[secondarydisplayplayer])
+				localangle2 = toucher->angle;
+#endif
+
+			S_StartSound(toucher, special->info->attacksound); // home run
+
+			return;
+
 		case MT_BIGTUMBLEWEED:
 		case MT_LITTLETUMBLEWEED:
 			if (toucher->momx || toucher->momy)
@@ -1819,6 +1850,10 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 
 	deadtarget = (player->mo->health <= 0);
 
+	// Don't log every hazard hit if they don't want us to.
+	if (!deadtarget && !cv_hazardlog.value)
+		return;
+
 	// Target's name
 	snprintf(targetname, sizeof(targetname), "%s%s%s",
 	         CTFTEAMCODE(player),
@@ -1922,7 +1957,7 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 		switch (damagetype)
 		{
 			case DMG_WATER:
-				str = M_GetText("%s was %s by chemical water.\n");
+				str = M_GetText("%s was %s by dangerous water.\n");
 				break;
 			case DMG_FIRE:
 				str = M_GetText("%s was %s by molten lava.\n");
@@ -1970,10 +2005,6 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 	if (!str) // Should not happen! Unless we missed catching something above.
 		return;
 
-	// Don't log every hazard hit if they don't want us to.
-	if (!deadtarget && !cv_hazardlog.value)
-		return;
-
 	if (deathonly)
 	{
 		if (!deadtarget)
@@ -2567,20 +2598,26 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 				UINT32 i = 0; // to check how many clones we've removed
 
 				// scan the thinkers to make sure all the old pinch dummies are gone on death
-				// this can happen if the boss was hurt earlier than expected
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo = (mobj_t *)th;
-					if (mo->type == (mobjtype_t)target->info->mass && mo->tracer == target)
-					{
-						P_RemoveMobj(mo);
-						i++;
-					}
-					if (i == 2) // we've already removed 2 of these, let's stop now
+					if (mo->type != (mobjtype_t)target->info->mass)
+						continue;
+					if (mo->tracer != target)
+						continue;
+
+					P_KillMobj(mo, inflictor, source, damagetype);
+					mo->destscale = mo->scale/8;
+					mo->scalespeed = (mo->scale - mo->destscale)/(2*TICRATE);
+					mo->momz = mo->info->speed;
+					mo->angle = FixedAngle((P_RandomKey(36)*10)<<FRACBITS);
+					if (++i == 2) // we've already removed 2 of these, let's stop now
 						break;
+					else
+						S_StartSound(mo, mo->info->deathsound); // done once to prevent sound stacking
 				}
 			}
 			break;
@@ -2895,26 +2932,47 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 	if (player->powers[pw_flashing] || player->powers[pw_invulnerability])
 		return false;
 
+	// Don't allow any damage before the round starts.
+	if (leveltime <= hidetime * TICRATE)
+		return false;
+
 	// Ignore IT players shooting each other, unless friendlyfire is on.
 	if ((player->pflags & PF_TAGIT && !((cv_friendlyfire.value || (damagetype & DMG_CANHURTSELF)) &&
 		source && source->player && source->player->pflags & PF_TAGIT)))
+	{
+		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
+		{
+			if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
+			{
+				P_SwitchShield(player, SH_PINK);
+				S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
+			}
+		}
 		return false;
-
-	// Don't allow any damage before the round starts.
-	if (leveltime <= hidetime * TICRATE)
-		return false;
+	}
 
 	// Don't allow players on the same team to hurt one another,
 	// unless cv_friendlyfire is on.
 	if (!(cv_friendlyfire.value || (damagetype & DMG_CANHURTSELF)) && (player->pflags & PF_TAGIT) == (source->player->pflags & PF_TAGIT))
 	{
-		if (!(inflictor->flags & MF_FIRE))
+		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
+		{
+			if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
+			{
+				P_SwitchShield(player, SH_PINK);
+				S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
+			}
+		}
+		else if (!(inflictor->flags & MF_FIRE))
 			P_GivePlayerRings(player, 1);
 		if (inflictor->flags2 & MF2_BOUNCERING)
 			inflictor->fuse = 0; // bounce ring disappears at -1 not 0
 		return false;
 	}
 
+	if (inflictor->type == MT_LHRT)
+		return false;
+
 	// The tag occurs so long as you aren't shooting another tagger with friendlyfire on.
 	if (source->player->pflags & PF_TAGIT && !(player->pflags & PF_TAGIT))
 	{
@@ -2981,7 +3039,17 @@ static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj
 
 		// In COOP/RACE, you can't hurt other players unless cv_friendlyfire is on
 		if (!cv_friendlyfire.value && (G_PlatformGametype()))
+		{
+			if (gametype == GT_COOP && inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK)) // co-op only
+			{
+				if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
+				{
+					P_SwitchShield(player, SH_PINK);
+					S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
+				}
+			}
 			return false;
+		}
 	}
 
 	// Tag handling
@@ -2995,7 +3063,15 @@ static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj
 		// unless cv_friendlyfire is on.
 		if (!cv_friendlyfire.value && target->player->ctfteam == source->player->ctfteam)
 		{
-			if (!(inflictor->flags & MF_FIRE))
+			if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
+			{
+				if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
+				{
+					P_SwitchShield(player, SH_PINK);
+					S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
+				}
+			}
+			else if (!(inflictor->flags & MF_FIRE))
 				P_GivePlayerRings(target->player, 1);
 			if (inflictor->flags2 & MF2_BOUNCERING)
 				inflictor->fuse = 0; // bounce ring disappears at -1 not 0
@@ -3004,6 +3080,9 @@ static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj
 		}
 	}
 
+	if (inflictor->type == MT_LHRT)
+		return false;
+
 	// Add pity.
 	if (!player->powers[pw_flashing] && !player->powers[pw_invulnerability] && !player->powers[pw_super]
 	&& source->player->score > player->score)
@@ -3576,7 +3655,7 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 {
 	INT32 i;
 	mobj_t *mo;
-	angle_t fa;
+	angle_t fa, va;
 	fixed_t ns;
 	fixed_t z;
 	boolean nightsreplace = ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap));
@@ -3598,6 +3677,11 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 	// Spill weapons first
 	P_PlayerWeaponPanelOrAmmoBurst(player);
 
+	if (abs(player->mo->momx) > player->mo->scale || abs(player->mo->momy) > player->mo->scale)
+		va = R_PointToAngle2(player->mo->momx, player->mo->momy, 0, 0)>>ANGLETOFINESHIFT;
+	else
+		va = player->mo->angle>>ANGLETOFINESHIFT;
+
 	for (i = 0; i < num_rings; i++)
 	{
 		INT32 objType = mobjinfo[MT_RING].reactiontime;
@@ -3619,7 +3703,7 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 		P_SetScale(mo, player->mo->scale);
 
 		// Angle offset by player angle, then slightly offset by amount of rings
-		fa = ((i*FINEANGLES/16) + (player->mo->angle>>ANGLETOFINESHIFT) - ((num_rings-1)*FINEANGLES/32)) & FINEMASK;
+		fa = ((i*FINEANGLES/16) + va - ((num_rings-1)*FINEANGLES/32)) & FINEMASK;
 
 		// Make rings spill out around the player in 16 directions like SA, but spill like Sonic 2.
 		// Technically a non-SA way of spilling rings. They just so happen to be a little similar.
diff --git a/src/p_lights.c b/src/p_lights.c
index 67ec55e80b42125ded814d2ba48ffdb8235b737a..8459d9ea044d066e5ba02c37b2717d549849b9ce 100644
--- a/src/p_lights.c
+++ b/src/p_lights.c
@@ -76,7 +76,7 @@ fireflicker_t *P_SpawnAdjustableFireFlicker(sector_t *minsector, sector_t *maxse
 	P_RemoveLighting(maxsector); // out with the old, in with the new
 	flick = Z_Calloc(sizeof (*flick), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&flick->thinker);
+	P_AddThinker(THINK_MAIN, &flick->thinker);
 
 	flick->thinker.function.acp1 = (actionf_p1)T_FireFlicker;
 	flick->sector = maxsector;
@@ -155,7 +155,7 @@ void P_SpawnLightningFlash(sector_t *sector)
 
 	flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&flash->thinker);
+	P_AddThinker(THINK_MAIN, &flash->thinker);
 
 	flash->thinker.function.acp1 = (actionf_p1)T_LightningFlash;
 	flash->sector = sector;
@@ -214,7 +214,7 @@ strobe_t *P_SpawnAdjustableStrobeFlash(sector_t *minsector, sector_t *maxsector,
 	P_RemoveLighting(maxsector); // out with the old, in with the new
 	flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&flash->thinker);
+	P_AddThinker(THINK_MAIN, &flash->thinker);
 
 	flash->sector = maxsector;
 	flash->darktime = darktime;
@@ -289,7 +289,7 @@ glow_t *P_SpawnAdjustableGlowingLight(sector_t *minsector, sector_t *maxsector,
 	P_RemoveLighting(maxsector); // out with the old, in with the new
 	g = Z_Calloc(sizeof (*g), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&g->thinker);
+	P_AddThinker(THINK_MAIN, &g->thinker);
 
 	g->sector = maxsector;
 	g->minlight = minsector->lightlevel;
@@ -349,7 +349,7 @@ void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean
 	ll->thinker.function.acp1 = (actionf_p1)T_LightFade;
 	sector->lightingdata = ll; // set it to the lightlevel_t
 
-	P_AddThinker(&ll->thinker); // add thinker
+	P_AddThinker(THINK_MAIN, &ll->thinker); // add thinker
 
 	ll->sector = sector;
 	ll->sourcelevel = sector->lightlevel;
diff --git a/src/p_local.h b/src/p_local.h
index 2149a95bf06419c039a95d17927aedd16ecb81e9..3c62d6277df880c45499d1026b6d438d0f86b7d2 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -61,15 +61,21 @@
 #define P_GetPlayerHeight(player) FixedMul(player->height, player->mo->scale)
 #define P_GetPlayerSpinHeight(player) FixedMul(player->spinheight, player->mo->scale)
 
-//
-// P_TICK
-//
-
-// both the head and tail of the thinker list
-extern thinker_t thinkercap;
+typedef enum
+{
+	THINK_POLYOBJ,
+	THINK_MAIN,
+	THINK_MOBJ,
+#ifdef ESLOPE
+	THINK_DYNSLOPE,
+#endif
+	THINK_PRECIP,
+	NUM_THINKERLISTS
+} thinklistnum_t; /**< Thinker lists. */
+extern thinker_t thlist[];
 
 void P_InitThinkers(void);
-void P_AddThinker(thinker_t *thinker);
+void P_AddThinker(const thinklistnum_t n, thinker_t *thinker);
 void P_RemoveThinker(thinker_t *thinker);
 
 //
@@ -128,6 +134,7 @@ pflags_t P_GetJumpFlags(player_t *player);
 boolean P_PlayerInPain(player_t *player);
 void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor);
 void P_ResetPlayer(player_t *player);
+boolean P_PlayerCanDamage(player_t *player, mobj_t *thing);
 boolean P_IsLocalPlayer(player_t *player);
 
 boolean P_IsObjectInGoop(mobj_t *mo);
@@ -135,7 +142,7 @@ boolean P_IsObjectOnGround(mobj_t *mo);
 boolean P_IsObjectOnGroundIn(mobj_t *mo, sector_t *sec);
 boolean P_InSpaceSector(mobj_t *mo);
 boolean P_InQuicksand(mobj_t *mo);
-boolean P_PlayerHitFloor(player_t *player);
+boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff);
 
 void P_SetObjectMomZ(mobj_t *mo, fixed_t value, boolean relative);
 void P_RestoreMusic(player_t *player);
@@ -158,6 +165,7 @@ boolean P_AutoPause(void);
 void P_DoJumpShield(player_t *player);
 void P_DoBubbleBounce(player_t *player);
 void P_DoAbilityBounce(player_t *player, boolean changemomz);
+void P_TwinSpinRejuvenate(player_t *player, mobjtype_t type);
 void P_BlackOw(player_t *player);
 void P_ElementalFire(player_t *player, boolean cropcircle);
 
@@ -174,7 +182,7 @@ void P_InstaThrustEvenIn2D(mobj_t *mo, angle_t angle, fixed_t move);
 
 mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet);
 void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius);
-void P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
+boolean P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
 boolean P_SuperReady(player_t *player);
 void P_DoJump(player_t *player, boolean soundandstate);
 #if 0
diff --git a/src/p_map.c b/src/p_map.c
index abd4174d67c1748a65266082e64f55183a295f37..6beca92f14c2a5614971b21e4925b3c57d1f705e 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -135,6 +135,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 	fixed_t vertispeed = spring->info->mass;
 	fixed_t horizspeed = spring->info->damage;
 	boolean final = false;
+	UINT8 strong = 0;
 
 	// Object was already sprung this tic
 	if (object->eflags & MFE_SPRUNG)
@@ -148,6 +149,14 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 	if (!spring->health || !object->health)
 		return false;
 
+	if (object->player)
+	{
+		if (object->player->charability == CA_TWINSPIN && object->player->panim == PA_ABILITY)
+			strong = 1;
+		else if (object->player->charability2 == CA2_MELEE && object->player->panim == PA_ABILITY2)
+			strong = 2;
+	}
+
 	if (spring->info->painchance == -1) // Pinball bumper mode.
 	{
 		// The first of the entirely different spring modes!
@@ -188,6 +197,9 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		{
 			fixed_t playervelocity;
 
+			if (strong)
+				vertispeed <<= 1;
+
 			if (!(object->player->pflags & PF_THOKKED) && !(object->player->homing)
 			&& ((playervelocity = FixedDiv(9*FixedHypot(object->player->speed, object->momz), 10<<FRACBITS)) > vertispeed))
 				vertispeed = playervelocity;
@@ -260,11 +272,8 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		return false;
 	}
 
-	if (object->player
-	&& ((object->player->charability == CA_TWINSPIN && object->player->panim == PA_ABILITY)
-	|| (object->player->charability2 == CA2_MELEE && object->player->panim == PA_ABILITY2)))
+	if (strong)
 	{
-		S_StartSound(object, sfx_s3k8b);
 		if (horizspeed)
 			horizspeed = FixedMul(horizspeed, (4*FRACUNIT)/3);
 		if (vertispeed)
@@ -333,7 +342,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		if (horizspeed)
 		{
 			object->player->drawangle = spring->angle;
-			if (object->player->cmd.forwardmove == 0 && object->player->cmd.sidemove == 0)
+			if (vertispeed || (object->player->cmd.forwardmove == 0 && object->player->cmd.sidemove == 0))
 			{
 				object->angle = spring->angle;
 
@@ -399,6 +408,12 @@ springstate:
 			P_AddPlayerScore(object->player, 10);
 			spring->reactiontime--;
 		}
+
+		if (strong)
+		{
+			P_TwinSpinRejuvenate(object->player, (strong == 1 ? object->player->thokitem : object->player->revitem));
+			S_StartSound(object, sfx_sprong); // strong spring. sprong.
+		}
 	}
 
 	return final;
@@ -710,6 +725,27 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		return true;
 	}
 
+	// vectorise metal - done in a special case as at this point neither has the right flags for touching
+	if (thing->type == MT_METALSONIC_BATTLE
+	&& (tmthing->flags & MF_MISSILE)
+	&& tmthing->target != thing
+	&& thing->state == &states[thing->info->spawnstate])
+	{
+		blockdist = thing->radius + tmthing->radius;
+
+		if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist)
+			return true; // didn't hit it
+
+		if (tmthing->z > thing->z + thing->height)
+			return true; // overhead
+		if (tmthing->z + tmthing->height < thing->z)
+			return true; // underneath
+
+		thing->flags2 |= MF2_CLASSICPUSH;
+
+		return true;
+	}
+
 	if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE|MF_SPRING)))
 		return true;
 
@@ -1153,7 +1189,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			tmthing->y = thing->y;
 			P_SetThingPosition(tmthing);
 		}
-		else if (!(tmthing->type == MT_SHELL && thing->player)) // player collision handled in touchspecial
+		else if (!(tmthing->type == MT_SHELL && thing->player)) // player collision handled in touchspecial for shell
 		{
 			UINT8 damagetype = tmthing->info->mass;
 			if (!damagetype && tmthing->flags & MF_FIRE) // BURN!
@@ -1482,51 +1518,45 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		}
 		// Monitor?
 		else if (thing->flags & MF_MONITOR
-		&& !((thing->type == MT_RING_REDBOX && tmthing->player->ctfteam != 1) || (thing->type == MT_RING_BLUEBOX && tmthing->player->ctfteam != 2)))
+		&& !((thing->type == MT_RING_REDBOX && tmthing->player->ctfteam != 1) || (thing->type == MT_RING_BLUEBOX && tmthing->player->ctfteam != 2))
+		&& (!(thing->flags & MF_SOLID) || P_PlayerCanDamage(tmthing->player, thing)))
 		{
-			// 0 = none, 1 = elemental pierce, 2 = bubble bounce
-			UINT8 elementalpierce = (((tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (tmthing->player->pflags & PF_SHIELDABILITY)
-			? (((tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) ? 1 : 2)
-			: 0);
-			if (!(thing->flags & MF_SOLID)
-			|| tmthing->player->pflags & (PF_SPINNING|PF_GLIDING)
-			|| ((tmthing->player->pflags & PF_JUMPED)
-				&& (!(tmthing->player->pflags & PF_NOJUMPDAMAGE)
-				|| (tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
-			|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2)
-			|| ((tmthing->player->charflags & SF_STOMPDAMAGE || tmthing->player->pflags & PF_BOUNCING)
-				&& (P_MobjFlip(tmthing)*(tmthing->z - (thing->z + thing->height/2)) > 0) && (P_MobjFlip(tmthing)*tmthing->momz < 0))
-			|| elementalpierce)
+			if (thing->z - thing->scale <= tmthing->z + tmthing->height
+			&& thing->z + thing->height + thing->scale >= tmthing->z)
 			{
-				if (thing->z - thing->scale <= tmthing->z + tmthing->height
-				&& thing->z + thing->height + thing->scale >= tmthing->z)
+				player_t *player = tmthing->player;
+				// 0 = none, 1 = elemental pierce, 2 = bubble bounce
+				UINT8 elementalpierce = (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (player->pflags & PF_SHIELDABILITY)
+				? (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) ? 1 : 2)
+				: 0);
+				SINT8 flipval = P_MobjFlip(thing); // Save this value in case monitor gets removed.
+				fixed_t *momz = &tmthing->momz; // tmthing gets changed by P_DamageMobj, so we need a new pointer?! X_x;;
+				fixed_t *z = &tmthing->z; // aau.
+				// Going down? Then bounce back up.
+				if (P_DamageMobj(thing, tmthing, tmthing, 1, 0) // break the monitor
+				&& (flipval*(*momz) < 0) // monitor is on the floor and you're going down, or on the ceiling and you're going up
+				&& (elementalpierce != 1)) // you're not piercing through the monitor...
 				{
-					player_t *player = tmthing->player;
-					SINT8 flipval = P_MobjFlip(thing); // Save this value in case monitor gets removed.
-					fixed_t *momz = &tmthing->momz; // tmthing gets changed by P_DamageMobj, so we need a new pointer?! X_x;;
-					fixed_t *z = &tmthing->z; // aau.
-					// Going down? Then bounce back up.
-					if (P_DamageMobj(thing, tmthing, tmthing, 1, 0) // break the monitor
-					&& (flipval*(*momz) < 0) // monitor is on the floor and you're going down, or on the ceiling and you're going up
-					&& (elementalpierce != 1)) // you're not piercing through the monitor...
+					if (elementalpierce == 2)
+						P_DoBubbleBounce(player);
+					else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 					{
-						if (elementalpierce == 2)
-							P_DoBubbleBounce(player);
-						else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
-							*momz = -*momz; // Therefore, you should be thrust in the opposite direction, vertically.
-					}
-					if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
-					{
-						if (player->pflags & PF_BOUNCING)
-							P_DoAbilityBounce(player, false);
-						return false;
+						*momz = -*momz; // Therefore, you should be thrust in the opposite direction, vertically.
+						if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
+							P_TwinSpinRejuvenate(player, player->thokitem);
 					}
-					else
-						*z -= *momz; // to ensure proper collision.
 				}
-
-				return true;
+				if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
+				{
+					if (player->pflags & PF_BOUNCING)
+						P_DoAbilityBounce(player, false);
+					return false;
+				}
+				else
+					*z -= *momz; // to ensure proper collision.
 			}
+
+			return true;
 		}
 	}
 
@@ -1778,7 +1808,7 @@ static boolean PIT_CheckLine(line_t *ld)
 	{
 		tmceilingz = opentop;
 		ceilingline = ld;
-		tmceilingrover = NULL;
+		tmceilingrover = openceilingrover;
 #ifdef ESLOPE
 		tmceilingslope = opentopslope;
 #endif
@@ -1787,7 +1817,7 @@ static boolean PIT_CheckLine(line_t *ld)
 	if (openbottom > tmfloorz)
 	{
 		tmfloorz = openbottom;
-		tmfloorrover = NULL;
+		tmfloorrover = openfloorrover;
 #ifdef ESLOPE
 		tmfloorslope = openbottomslope;
 #endif
@@ -2059,6 +2089,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 #ifdef ESLOPE
 							tmfloorslope = NULL;
 #endif
+							tmfloorrover = NULL;
 						}
 
 						if (polybottom < tmceilingz && abs(delta1) >= abs(delta2)) {
@@ -2066,6 +2097,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 #ifdef ESLOPE
 							tmceilingslope = NULL;
 #endif
+							tmceilingrover = NULL;
 						}
 					}
 					plink = (polymaplink_t *)(plink->link.next);
@@ -2790,7 +2822,7 @@ boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y)
 static boolean P_ThingHeightClip(mobj_t *thing)
 {
 	boolean floormoved;
-	fixed_t oldfloorz = thing->floorz;
+	fixed_t oldfloorz = thing->floorz, oldz = thing->z;
 	ffloor_t *oldfloorrover = thing->floorrover;
 	ffloor_t *oldceilingrover = thing->ceilingrover;
 	boolean onfloor = P_IsObjectOnGround(thing);//(thing->z <= thing->floorz);
@@ -2849,6 +2881,12 @@ static boolean P_ThingHeightClip(mobj_t *thing)
 			thing->z = thing->ceilingz - thing->height;
 	}
 
+	if (thing->z != oldz)
+	{
+		if (thing->player)
+			P_PlayerHitFloor(thing->player, false);
+	}
+
 	// debug: be sure it falls to the floor
 	thing->eflags &= ~MFE_ONGROUND;
 
@@ -4003,7 +4041,7 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 						thinker_t *think;
 						elevator_t *crumbler;
 
-						for (think = thinkercap.next; think != &thinkercap; think = think->next)
+						for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
 						{
 							if (think->function.acp1 != (actionf_p1)T_StartCrumble)
 								continue;
@@ -4049,6 +4087,7 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 boolean P_CheckSector(sector_t *sector, boolean crunch)
 {
 	msecnode_t *n;
+	size_t i;
 
 	nofit = false;
 	crushchange = crunch;
@@ -4063,9 +4102,57 @@ boolean P_CheckSector(sector_t *sector, boolean crunch)
 
 
 	// First, let's see if anything will keep it from crushing.
+
+	// Sal: This stupid function chain is required to fix polyobjects not being able to crush.
+	// Monster Iestyn: don't use P_CheckSector actually just look for objects in the blockmap instead
+	validcount++;
+
+	for (i = 0; i < sector->linecount; i++)
+	{
+		if (sector->lines[i]->polyobj)
+		{
+			polyobj_t *po = sector->lines[i]->polyobj;
+			if (po->validcount == validcount)
+				continue; // skip if already checked
+			if (!(po->flags & POF_SOLID))
+				continue;
+			if (po->lines[0]->backsector == sector) // Make sure you're currently checking the control sector
+			{
+				INT32 x, y;
+				po->validcount = validcount;
+
+				for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
+				{
+					for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
+					{
+						mobj_t *mo;
+
+						if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+							continue;
+
+						mo = blocklinks[y * bmapwidth + x];
+
+						for (; mo; mo = mo->bnext)
+						{
+							// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
+
+							if (!P_MobjTouchingPolyobj(po, mo))
+								continue;
+
+							if (!PIT_ChangeSector(mo, false))
+							{
+								nofit = true;
+								return nofit;
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
 	if (sector->numattached)
 	{
-		size_t i;
 		sector_t *sec;
 		for (i = 0; i < sector->numattached; i++)
 		{
@@ -4125,9 +4212,53 @@ boolean P_CheckSector(sector_t *sector, boolean crunch)
 	} while (n); // repeat from scratch until all things left are marked valid
 
 	// Nothing blocked us, so lets crush for real!
+
+	// Sal: This stupid function chain is required to fix polyobjects not being able to crush.
+	// Monster Iestyn: don't use P_CheckSector actually just look for objects in the blockmap instead
+	validcount++;
+
+	for (i = 0; i < sector->linecount; i++)
+	{
+		if (sector->lines[i]->polyobj)
+		{
+			polyobj_t *po = sector->lines[i]->polyobj;
+			if (po->validcount == validcount)
+				continue; // skip if already checked
+			if (!(po->flags & POF_SOLID))
+				continue;
+			if (po->lines[0]->backsector == sector) // Make sure you're currently checking the control sector
+			{
+				INT32 x, y;
+				po->validcount = validcount;
+
+				for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
+				{
+					for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
+					{
+						mobj_t *mo;
+
+						if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+							continue;
+
+						mo = blocklinks[y * bmapwidth + x];
+
+						for (; mo; mo = mo->bnext)
+						{
+							// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
+
+							if (!P_MobjTouchingPolyobj(po, mo))
+								continue;
+
+							PIT_ChangeSector(mo, true);
+							return nofit;
+						}
+					}
+				}
+			}
+		}
+	}
 	if (sector->numattached)
 	{
-		size_t i;
 		sector_t *sec;
 		for (i = 0; i < sector->numattached; i++)
 		{
diff --git a/src/p_maputl.c b/src/p_maputl.c
index 1be57399cba4ab3e7e12595adc8988975e08651a..740797fb0c10dd407b30fd972169a0f727830c20 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -311,6 +311,7 @@ fixed_t opentop, openbottom, openrange, lowfloor, highceiling;
 #ifdef ESLOPE
 pslope_t *opentopslope, *openbottomslope;
 #endif
+ffloor_t *openfloorrover, *openceilingrover;
 
 // P_CameraLineOpening
 // P_LineOpening, but for camera
@@ -517,6 +518,8 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 	I_Assert(front != NULL);
 	I_Assert(back != NULL);
 
+	openfloorrover = openceilingrover = NULL;
+
 	{ // Set open and high/low values here
 		fixed_t frontheight, backheight;
 
@@ -641,6 +644,8 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 			pslope_t *ceilingslope = opentopslope;
 			pslope_t *floorslope = openbottomslope;
 #endif
+			ffloor_t *floorrover = NULL;
+			ffloor_t *ceilingrover = NULL;
 
 			// Check for frontsector's fake floors
 			for (rover = front->ffloors; rover; rover = rover->next)
@@ -668,6 +673,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 #ifdef ESLOPE
 						ceilingslope = *rover->b_slope;
 #endif
+						ceilingrover = rover;
 					}
 					else if (bottomheight < highestceiling)
 						highestceiling = bottomheight;
@@ -680,6 +686,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 #ifdef ESLOPE
 						floorslope = *rover->t_slope;
 #endif
+						floorrover = rover;
 					}
 					else if (topheight > lowestfloor)
 						lowestfloor = topheight;
@@ -712,6 +719,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 #ifdef ESLOPE
 						ceilingslope = *rover->b_slope;
 #endif
+						ceilingrover = rover;
 					}
 					else if (bottomheight < highestceiling)
 						highestceiling = bottomheight;
@@ -724,6 +732,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 #ifdef ESLOPE
 						floorslope = *rover->t_slope;
 #endif
+						floorrover = rover;
 					}
 					else if (topheight > lowestfloor)
 						lowestfloor = topheight;
@@ -743,6 +752,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 #ifdef ESLOPE
 					ceilingslope = NULL;
 #endif
+					ceilingrover = NULL;
 				}
 				else if (polysec->floorheight < highestceiling && delta1 >= delta2)
 					highestceiling = polysec->floorheight;
@@ -752,6 +762,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 #ifdef ESLOPE
 					floorslope = NULL;
 #endif
+					floorrover = NULL;
 				}
 				else if (polysec->ceilingheight > lowestfloor && delta1 < delta2)
 					lowestfloor = polysec->ceilingheight;
@@ -765,6 +776,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 #ifdef ESLOPE
 				openbottomslope = floorslope;
 #endif
+				openfloorrover = floorrover;
 			}
 
 			if (lowestceiling < opentop) {
@@ -772,6 +784,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 #ifdef ESLOPE
 				opentopslope = ceilingslope;
 #endif
+				openceilingrover = ceilingrover;
 			}
 
 			if (lowestfloor > lowfloor)
@@ -1093,7 +1106,10 @@ boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean (*func)(mobj_t *))
 	{
 		P_SetTarget(&bnext, mobj->bnext); // We want to note our reference to bnext here incase it is MF_NOTHINK and gets removed!
 		if (!func(mobj))
+		{
+			P_SetTarget(&bnext, NULL);
 			return false;
+		}
 		if (P_MobjWasRemoved(tmthing) // func just popped our tmthing, cannot continue.
 		|| (bnext && P_MobjWasRemoved(bnext))) // func just broke blockmap chain, cannot continue.
 		{
diff --git a/src/p_maputl.h b/src/p_maputl.h
index 1fcb68d4c7fa44cc62f2755410952121d5d4f475..5042817c54ec8a13112626a75ea330ab3c612549 100644
--- a/src/p_maputl.h
+++ b/src/p_maputl.h
@@ -58,6 +58,7 @@ extern fixed_t opentop, openbottom, openrange, lowfloor, highceiling;
 #ifdef ESLOPE
 extern pslope_t *opentopslope, *openbottomslope;
 #endif
+extern ffloor_t *openfloorrover, *openceilingrover;
 
 void P_LineOpening(line_t *plinedef, mobj_t *mobj);
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index c8ba239ebf812c1f5043e45408cbb63f3b0f23ec..8c74fd76ef40651e5f8880edeffb7eb1edeace9f 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -281,6 +281,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		player->panim = PA_FALL;
 		break;
 	case S_PLAY_FLY:
+	case S_PLAY_FLY_TIRED:
 	case S_PLAY_SWIM:
 	case S_PLAY_GLIDE:
 	case S_PLAY_BOUNCE:
@@ -713,10 +714,10 @@ void P_EmeraldManager(void)
 		spawnpoints[i] = NULL;
 	}
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
 
@@ -1509,8 +1510,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 	if (mo->player)
 	{
 		if ((mo->player->pflags & PF_GLIDING)
-		|| (mo->player->charability == CA_FLY && (mo->player->powers[pw_tailsfly]
-			|| mo->state-states == S_PLAY_FLY_TIRED)))
+		|| (mo->player->charability == CA_FLY && mo->player->panim == PA_ABILITY))
 			gravityadd = gravityadd/3; // less gravity while flying/gliding
 		if (mo->player->climbing || (mo->player->powers[pw_carry] == CR_NIGHTSMODE))
 			gravityadd = 0;
@@ -1784,7 +1784,7 @@ static void P_PushableCheckBustables(mobj_t *mo)
 							continue;
 					}
 
-					EV_CrumbleChain(node->m_sector, rover);
+					EV_CrumbleChain(NULL, rover); // node->m_sector
 
 					// Run a linedef executor??
 					if (rover->master->flags & ML_EFFECT5)
@@ -3047,7 +3047,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 				}
 			}
 
-			clipmomz = P_PlayerHitFloor(mo->player);
+			clipmomz = P_PlayerHitFloor(mo->player, true);
 
 			if (!(mo->player->pflags & PF_SPINNING) && mo->player->powers[pw_carry] != CR_NIGHTSMODE)
 				mo->player->pflags &= ~PF_STARTDASH;
@@ -3129,7 +3129,7 @@ nightsdone:
 						{
 							// DO THE MARIO!
 							if (rover->flags & FF_SHATTERBOTTOM) // Brick block!
-								EV_CrumbleChain(node->m_sector, rover);
+								EV_CrumbleChain(NULL, rover); // node->m_sector
 							else // Question block!
 								EV_MarioBlock(rover, node->m_sector, mo);
 						}
@@ -3396,11 +3396,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 		if (!((p->powers[pw_super]) || (p->powers[pw_invulnerability])))
 		{
 			boolean electric = !!(p->powers[pw_shield] & SH_PROTECTELECTRIC);
-#define SH_OP (SH_PROTECTFIRE|SH_PROTECTWATER|SH_PROTECTELECTRIC)
-			if ((p->powers[pw_shield] & SH_OP) == SH_OP) // No.
-				P_KillMobj(mobj, NULL, NULL, DMG_INSTAKILL);
-#undef SH_OP
-			else if (electric || ((p->powers[pw_shield] & SH_PROTECTFIRE) && !(p->powers[pw_shield] & SH_PROTECTWATER)))
+			if (electric || ((p->powers[pw_shield] & SH_PROTECTFIRE) && !(p->powers[pw_shield] & SH_PROTECTWATER)))
 			{ // Water removes electric and non-water fire shields...
 				P_FlashPal(p,
 				electric
@@ -3699,10 +3695,10 @@ void P_DestroyRobots(void)
 	mobj_t *mo;
 	thinker_t *think;
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
 		if (mo->health <= 0 || !(mo->flags & (MF_ENEMY|MF_BOSS)))
@@ -4384,6 +4380,8 @@ static void P_Boss3Thinker(mobj_t *mobj)
 	}
 
 	if (mobj->health <= 0)
+		return;
+	/*
 	{
 		mobj->movecount = 0;
 		mobj->reactiontime = 0;
@@ -4396,157 +4394,110 @@ static void P_Boss3Thinker(mobj_t *mobj)
 			mobj->momz = mobj->info->speed;
 			return;
 		}
-	}
+		else
+		{
+			mobj->flags |= MF_NOGRAVITY|MF_NOCLIP;
+			mobj->flags |= MF_NOCLIPHEIGHT;
+			mobj->threshold = -1;
+			return;
+		}
+	}*/
 
-	if (mobj->reactiontime) // Shock mode
+	if (mobj->reactiontime) // At the bottom of the water
 	{
 		UINT32 i;
+		SINT8 curpath = mobj->threshold;
+
+		// Choose one of the paths you're not already on
+		mobj->threshold = P_RandomKey(8-1);
+		if (mobj->threshold >= curpath)
+			mobj->threshold++;
 
 		if (mobj->state != &states[mobj->info->spawnstate])
 			P_SetMobjState(mobj, mobj->info->spawnstate);
 
 		mobj->reactiontime--;
-		if (!mobj->reactiontime)
-		{
-			ffloor_t *rover;
-
-			// Shock the water
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i] || players[i].spectator)
-					continue;
-
-				if (!players[i].mo)
-					continue;
-
-				if (players[i].mo->health <= 0)
-					continue;
-
-				if (players[i].mo->eflags & MFE_UNDERWATER)
-					P_DamageMobj(players[i].mo, mobj, mobj, 1, 0);
-			}
-
-			// Make the water flash
-			for (i = 0; i < numsectors; i++)
-			{
-				if (!sectors[i].ffloors)
-					continue;
-
-				for (rover = sectors[i].ffloors; rover; rover = rover->next)
-				{
-					if (!(rover->flags & FF_EXISTS))
-						continue;
-
-					if (!(rover->flags & FF_SWIMMABLE))
-						continue;
-
-					P_SpawnLightningFlash(rover->master->frontsector);
-					break;
-				}
-			}
-
-			if ((UINT32)mobj->extravalue1 + TICRATE*2 < leveltime)
-			{
-				mobj->extravalue1 = (INT32)leveltime;
-				S_StartSound(0, sfx_buzz1);
-			}
-
-			// If in the center, check to make sure
-			// none of the players are in the water
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i] || players[i].spectator)
-					continue;
-
-				if (!players[i].mo || players[i].bot)
-					continue;
-
-				if (players[i].mo->health <= 0)
-					continue;
-
-				if (players[i].mo->eflags & MFE_UNDERWATER)
-				{ // Stay put
-					mobj->reactiontime = 2*TICRATE;
-					return;
-				}
-			}
-		}
 
 		if (!mobj->reactiontime && mobj->health <= mobj->info->damage)
 		{ // Spawn pinch dummies from the center when we're leaving it.
 			thinker_t *th;
 			mobj_t *mo2;
 			mobj_t *dummy;
-			SINT8 way = mobj->threshold - 1; // 0 through 4.
-			SINT8 way2;
+			SINT8 way0 = mobj->threshold; // 0 through 4.
+			SINT8 way1, way2;
 
 			i = 0; // reset i to 0 so we can check how many clones we've removed
 
 			// scan the thinkers to make sure all the old pinch dummies are gone before making new ones
 			// this can happen if the boss was hurt earlier than expected
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
-				if (mo2->type == (mobjtype_t)mobj->info->mass && mo2->tracer == mobj)
-				{
-					P_RemoveMobj(mo2);
-					i++;
-				}
-				if (i == 2) // we've already removed 2 of these, let's stop now
+				if (mo2->type != (mobjtype_t)mobj->info->mass)
+					continue;
+				if (mo2->tracer != mobj)
+					continue;
+
+				P_RemoveMobj(mo2);
+				if (++i == 2) // we've already removed 2 of these, let's stop now
 					break;
 			}
 
-			way = (way + P_RandomRange(1,3)) % 5; // dummy 1 at one of the first three options after eggmobile
+			way1 = P_RandomKey(8-2);
+			if (way1 >= curpath)
+				way1++;
+			if (way1 >= way0)
+			{
+				way1++;
+				if (way1 == curpath)
+					way1++;
+			}
+
 			dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
 			dummy->angle = mobj->angle;
-			dummy->threshold = way + 1;
-			dummy->tracer = mobj;
+			dummy->threshold = way1;
+			P_SetTarget(&dummy->tracer, mobj);
+			dummy->movefactor = mobj->movefactor;
+			dummy->cusval = mobj->cusval;
+
+			way2 = P_RandomKey(8-3);
+			if (way2 >= curpath)
+				way2++;
+			if (way2 >= way0)
+			{
+				way2++;
+				if (way2 == curpath)
+					way2++;
+			}
+			if (way2 >= way1)
+			{
+				way2++;
+				if (way2 == curpath || way2 == way0)
+					way2++;
+			}
 
-			do
-				way2 = (way + P_RandomRange(1,3)) % 5; // dummy 2 has to be careful,
-			while (way2 == mobj->threshold - 1); // to make sure it doesn't try to go the Eggman Way if dummy 1 rolled high.
 			dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
 			dummy->angle = mobj->angle;
-			dummy->threshold = way2 + 1;
-			dummy->tracer = mobj;
+			dummy->threshold = way2;
+			P_SetTarget(&dummy->tracer, mobj);
+			dummy->movefactor = mobj->movefactor;
+			dummy->cusval = mobj->cusval;
 
-			CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", mobj->threshold, way + 1, dummy->threshold);
-			P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
+			CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", way0, way1, way2);
+			P_LinedefExecute(LE_PINCHPHASE+(mobj->cusval*LE_PARAMWIDTH), mobj, NULL);
 		}
 	}
 	else if (mobj->movecount) // Firing mode
 	{
-		UINT32 i;
-
 		// look for a new target
 		P_BossTargetPlayer(mobj, false);
 
 		if (!mobj->target || !mobj->target->player)
 			return;
 
-		// Are there any players underwater? If so, shock them!
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (!playeringame[i] || players[i].spectator)
-				continue;
-
-			if (!players[i].mo || players[i].bot)
-				continue;
-
-			if (players[i].mo->health <= 0)
-				continue;
-
-			if (players[i].mo->eflags & MFE_UNDERWATER)
-			{
-				mobj->movecount = 0;
-				P_SetMobjState(mobj, mobj->info->spawnstate);
-				return;
-			}
-		}
-
 		// Always face your target.
 		A_FaceTarget(mobj);
 
@@ -4561,9 +4512,7 @@ static void P_Boss3Thinker(mobj_t *mobj)
 	}
 	else if (mobj->threshold >= 0) // Traveling mode
 	{
-		thinker_t *th;
-		mobj_t *mo2;
-		fixed_t dist, dist2;
+		fixed_t dist = 0;
 		fixed_t speed;
 
 		P_SetTarget(&mobj->target, NULL);
@@ -4572,97 +4521,112 @@ static void P_Boss3Thinker(mobj_t *mobj)
 			&& !(mobj->flags2 & MF2_FRET))
 			P_SetMobjState(mobj, mobj->info->spawnstate);
 
-		// scan the thinkers
-		// to find a point that matches
-		// the number
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		if (!(mobj->flags2 & MF2_STRONGBOX))
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
-				continue;
+			thinker_t *th;
+			mobj_t *mo2;
 
-			mo2 = (mobj_t *)th;
-			if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint && mo2->spawnpoint->angle == mobj->threshold)
+			P_SetTarget(&mobj->tracer, NULL);
+
+			// scan the thinkers
+			// to find a point that matches
+			// the number
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				P_SetTarget(&mobj->target, mo2);
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					continue;
+
+				mo2 = (mobj_t *)th;
+				if (mo2->type != MT_BOSS3WAYPOINT)
+					continue;
+				if (!mo2->spawnpoint)
+					continue;
+				if (mo2->spawnpoint->angle != mobj->threshold)
+					continue;
+				if (mo2->spawnpoint->extrainfo != mobj->cusval)
+					continue;
+
+				P_SetTarget(&mobj->tracer, mo2);
 				break;
 			}
 		}
 
-		if (!mobj->target) // Should NEVER happen
+		if (!mobj->tracer) // Should NEVER happen
 		{
-			CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 was unable to find specified waypoint: %d\n", mobj->threshold);
+			CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 was unable to find specified waypoint: %d, %d\n", mobj->threshold, mobj->cusval);
 			return;
 		}
 
-		dist = P_AproxDistance(P_AproxDistance(mobj->target->x - mobj->x, mobj->target->y - mobj->y), mobj->target->z - mobj->z);
-
-		if (dist < 1)
-			dist = 1;
-
 		if ((mobj->movedir) || (mobj->health <= mobj->info->damage))
 			speed = mobj->info->speed * 2;
 		else
 			speed = mobj->info->speed;
 
-		mobj->momx = FixedMul(FixedDiv(mobj->target->x - mobj->x, dist), speed);
-		mobj->momy = FixedMul(FixedDiv(mobj->target->y - mobj->y, dist), speed);
-		mobj->momz = FixedMul(FixedDiv(mobj->target->z - mobj->z, dist), speed);
+		if (mobj->tracer->x == mobj->x && mobj->tracer->y == mobj->y)
+		{
+			// apply ambush for old routing, otherwise whack a mole only
+			dist = P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z + mobj->movefactor - mobj->z);
 
-		if (mobj->momx != 0 || mobj->momy != 0)
-			mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
+			if (dist < 1)
+				dist = 1;
 
-		dist2 = P_AproxDistance(P_AproxDistance(mobj->target->x - (mobj->x + mobj->momx), mobj->target->y - (mobj->y + mobj->momy)), mobj->target->z - (mobj->z + mobj->momz));
+			mobj->momx = FixedMul(FixedDiv(mobj->tracer->x - mobj->x, dist), speed);
+			mobj->momy = FixedMul(FixedDiv(mobj->tracer->y - mobj->y, dist), speed);
+			mobj->momz = FixedMul(FixedDiv(mobj->tracer->z + mobj->movefactor - mobj->z, dist), speed);
 
-		if (dist2 < 1)
-			dist2 = 1;
+			if (mobj->momx != 0 || mobj->momy != 0)
+				mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
+		}
 
-		if ((dist >> FRACBITS) <= (dist2 >> FRACBITS))
+		if (dist <= speed)
 		{
-			// If further away, set XYZ of mobj to waypoint location
+			// If distance to point is less than travel in that frame, set XYZ of mobj to waypoint location
 			P_UnsetThingPosition(mobj);
-			mobj->x = mobj->target->x;
-			mobj->y = mobj->target->y;
-			mobj->z = mobj->target->z;
+			mobj->x = mobj->tracer->x;
+			mobj->y = mobj->tracer->y;
+			mobj->z = mobj->tracer->z + mobj->movefactor;
 			mobj->momx = mobj->momy = mobj->momz = 0;
 			P_SetThingPosition(mobj);
 
-			if (mobj->threshold == 0)
+			if (!mobj->movefactor) // to firing mode
 			{
-				mobj->reactiontime = 1; // Bzzt! Shock the water!
-				mobj->movedir = 0;
+				UINT8 i;
+				angle_t ang = 0;
 
-				if (mobj->health <= 0)
+				mobj->movecount = mobj->health+1;
+				mobj->movefactor = -512*FRACUNIT;
+
+				// shock the water!
+				for (i = 0; i < 64; i++)
 				{
-					mobj->flags |= MF_NOGRAVITY|MF_NOCLIP;
-					mobj->flags |= MF_NOCLIPHEIGHT;
-					mobj->threshold = -1;
-					return;
+					mobj_t *shock = P_SpawnMobjFromMobj(mobj, 0, 0, 4*FRACUNIT, MT_SHOCK);
+					P_SetTarget(&shock->target, mobj);
+					P_InstaThrust(shock, ang, shock->info->speed);
+					P_CheckMissileSpawn(shock);
+					ang += (ANGLE_MAX/64);
 				}
+				S_StartSound(mobj, sfx_fizzle);
 			}
-
-			// Set to next waypoint in sequence
-			if (mobj->target->spawnpoint)
+			else if (mobj->flags2 & (MF2_STRONGBOX|MF2_CLASSICPUSH)) // just hit the bottom of your tube
 			{
-				// From the center point, choose one of the five paths
-				if (mobj->target->spawnpoint->angle == 0)
-					mobj->threshold = P_RandomRange(1,5);
-				else
-					mobj->threshold = mobj->target->spawnpoint->extrainfo;
-
-				// If the deaf flag is set, go into firing mode
-				if (mobj->target->spawnpoint->options & MTF_AMBUSH)
-					mobj->movecount = mobj->health+1;
+				mobj->flags2 &= ~(MF2_STRONGBOX|MF2_CLASSICPUSH);
+				mobj->reactiontime = 1; // spawn pinch dummies
+				mobj->movedir = 0;
+			}
+			else // just shifted to another tube
+			{
+				mobj->flags2 |= MF2_STRONGBOX;
+				if (mobj->health > 0)
+					mobj->movefactor = 0;
 			}
-			else // This should never happen, as well
-				CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 waypoint has no spawnpoint associated with it.\n");
 		}
 	}
 }
 
 // Move Boss4's sectors by delta.
-static boolean P_Boss4MoveCage(fixed_t delta)
+static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta)
 {
-	const UINT16 tag = 65534;
+	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
 	INT32 snum;
 	sector_t *sector;
 	for (snum = sectors[tag%numsectors].firsttag; snum != -1; snum = sector->nexttag)
@@ -4682,7 +4646,7 @@ static void P_Boss4MoveSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz)
 {
 	INT32 s;
 	mobj_t *base = mobj, *seg;
-	fixed_t dist, bz = mobj->watertop+(16<<FRACBITS);
+	fixed_t dist, bz = mobj->watertop+(8<<FRACBITS);
 	while ((base = base->tracer))
 	{
 		for (seg = base, dist = 172*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 124*FRACUNIT, --s)
@@ -4692,26 +4656,44 @@ static void P_Boss4MoveSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz)
 }
 
 // Pull them closer.
-static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz)
+static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
 {
 	INT32 s;
 	mobj_t *base = mobj, *seg;
-	fixed_t dist, bz = mobj->watertop+(16<<FRACBITS);
-	while ((base = base->tracer))
+	fixed_t originx, originy, workx, worky, dx, dy, bz = mobj->watertop+(8<<FRACBITS);
+
+	if (mobj->spawnpoint)
+	{
+		originx = mobj->spawnpoint->x << FRACBITS;
+		originy = mobj->spawnpoint->y << FRACBITS;
+	}
+	else
 	{
-		for (seg = base, dist = 112*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 132*FRACUNIT, --s)
+		originx = mobj->x;
+		originy = mobj->y;
+	}
+
+	dz /= 9;
+
+	while ((base = base->tracer)) // there are 10 per spoke, remember that
+	{
+		dx = (originx + P_ReturnThrustX(mobj, angle, (9*132)<<FRACBITS) - mobj->x)/9;
+		dy = (originy + P_ReturnThrustY(mobj, angle, (9*132)<<FRACBITS) - mobj->y)/9;
+		workx = mobj->x + P_ReturnThrustX(mobj, angle, (112)<<FRACBITS);
+		worky = mobj->y + P_ReturnThrustY(mobj, angle, (112)<<FRACBITS);
+		for (seg = base, s = 9; seg; seg = seg->hnext, --s)
 		{
-			seg->z = bz + FixedMul(fz, FixedDiv(s<<FRACBITS, 9<<FRACBITS));
-			P_TryMove(seg, mobj->x + P_ReturnThrustX(mobj, angle, dist), mobj->y + P_ReturnThrustY(mobj, angle, dist), true);
+			seg->z = bz + (dz*(9-s));
+			P_TryMove(seg, workx + (dx*s), worky + (dy*s), true);
 		}
 		angle += ANGLE_MAX/3;
 	}
 }
 
 // Destroy cage FOFs.
-static void P_Boss4DestroyCage(void)
+static void P_Boss4DestroyCage(mobj_t *mobj)
 {
-	const UINT16 tag = 65534;
+	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
 	INT32 snum, next;
 	size_t a;
 	sector_t *sector, *rsec;
@@ -4774,9 +4756,11 @@ static void P_Boss4PopSpikeballs(mobj_t *mobj)
 //
 static void P_Boss4Thinker(mobj_t *mobj)
 {
+	fixed_t movespeed = 0;
+
 	if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate)
 	{
-		if (mobj->health > mobj->info->damage || mobj->movedir == 4)
+		if (mobj->flags2 & MF2_FRET && (mobj->health > mobj->info->damage))
 			mobj->flags2 &= ~MF2_FRET;
 		mobj->reactiontime = 0; // Drop the cage immediately.
 	}
@@ -4786,12 +4770,50 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	{
 		if (mobj->tracer) // need to clean up!
 		{
-			P_Boss4DestroyCage(); // Just in case pinch phase was skipped.
+			P_Boss4DestroyCage(mobj); // Just in case pinch phase was skipped.
 			P_Boss4PopSpikeballs(mobj);
 		}
 		return;
 	}
 
+	if (mobj->movedir) // only not during init
+	{
+		INT32 oldmovecount = mobj->movecount;
+		if (mobj->movedir == 3) // pinch start
+			movespeed = -(210<<(FRACBITS>>1));
+		else if (mobj->movedir > 3) // pinch
+		{
+			movespeed = 420<<(FRACBITS>>1);
+			movespeed += (420*(mobj->info->damage-mobj->health)<<(FRACBITS>>1));
+			if (mobj->movedir == 4)
+				movespeed = -movespeed;
+		}
+		else // normal
+		{
+			movespeed = 170<<(FRACBITS>>1);
+			movespeed += ((50*(mobj->info->spawnhealth-mobj->health))<<(FRACBITS>>1));
+			if (mobj->movedir == 2)
+				movespeed = -movespeed;
+			if (mobj->movefactor)
+				movespeed /= 2;
+			else if (mobj->threshold)
+			{
+				// 1 -> 1.5 second timer
+				INT32 maxtimer = TICRATE+(TICRATE*(mobj->info->spawnhealth-mobj->health)/10);
+				if (maxtimer < 1)
+					maxtimer = 1;
+				maxtimer = ((mobj->threshold*movespeed)/(2*maxtimer));
+				movespeed -= maxtimer;
+			}
+		}
+
+		mobj->movecount += movespeed + 360*FRACUNIT;
+		mobj->movecount %= 360*FRACUNIT;
+
+		if (((oldmovecount>>FRACBITS)%120 >= 60) && !((mobj->movecount>>FRACBITS)%120 >= 60))
+			S_StartSound(NULL, sfx_mswing);
+	}
+
 	// movedir == battle stage:
 	//   0: initialization
 	//   1: phase 1 forward
@@ -4822,17 +4844,17 @@ static void P_Boss4Thinker(mobj_t *mobj)
 				P_SetTarget(&seg->target, mobj);
 				for (i = 0; i < 9; i++)
 				{
-					seg->hnext = P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE);
-					seg->hnext->hprev = seg;
+					P_SetTarget(&seg->hnext, P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE));
+					P_SetTarget(&seg->hnext->hprev, seg);
 					seg = seg->hnext;
 				}
 			}
 			// Move the cage up to the sky.
 			mobj->movecount = 800*FRACUNIT;
-			if (!P_Boss4MoveCage(mobj->movecount))
+			if (!P_Boss4MoveCage(mobj, mobj->movecount))
 			{
 				mobj->movecount = 0;
-				mobj->threshold = 3*TICRATE;
+				//mobj->threshold = 3*TICRATE;
 				mobj->extravalue1 = 1;
 				mobj->movedir++; // We don't have a cage, just continue.
 			}
@@ -4844,17 +4866,13 @@ static void P_Boss4Thinker(mobj_t *mobj)
 			fixed_t oldz = mobj->movecount;
 			mobj->threshold -= 5*FRACUNIT;
 			mobj->movecount += mobj->threshold;
-			if (mobj->movecount < 0)
-				mobj->movecount = 0;
-			P_Boss4MoveCage(mobj->movecount - oldz);
-			P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount);
-			if (mobj->movecount == 0)
+			if (mobj->movecount <= 0)
 			{
-				mobj->threshold = 3*TICRATE;
-				mobj->extravalue1 = 1;
-				P_LinedefExecute(LE_BOSS4DROP, mobj, NULL);
+				mobj->movecount = 0;
 				mobj->movedir++; // Initialization complete, next phase!
 			}
+			P_Boss4MoveCage(mobj, mobj->movecount - oldz);
+			P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount);
 		}
 		return;
 	}
@@ -4868,17 +4886,18 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	case 3:
 	{
 		fixed_t z;
-		if (mobj->z < mobj->watertop+(512<<FRACBITS))
+		if (mobj->z < mobj->watertop+(400<<FRACBITS))
 			mobj->momz = 8*FRACUNIT;
 		else
 		{
-			mobj->momz = 0;
+			mobj->momz = mobj->movefactor = 0;
+			mobj->threshold = 1110<<FRACBITS;
+			S_StartSound(NULL, sfx_s3k60);
 			mobj->movedir++;
 		}
-		mobj->movecount += 400<<(FRACBITS>>1);
-		mobj->movecount %= 360*FRACUNIT;
+
 		z = mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2;
-		if (z < 0) // We haven't risen high enough to pull the spikeballs along yet
+		if (z < (8<<FRACBITS)) // We haven't risen high enough to pull the spikeballs along yet
 			P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0); // So don't pull the spikeballs along yet.
 		else
 			P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), z);
@@ -4886,18 +4905,32 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	}
 	// Pinch phase!
 	case 4:
+	case 5:
 	{
-		if (mobj->z < (mobj->watertop + ((512+128*(mobj->info->damage-mobj->health))<<FRACBITS)))
-			mobj->momz = 8*FRACUNIT;
-		else
-			mobj->momz = 0;
-		mobj->movecount += (800+800*(mobj->info->damage-mobj->health))<<(FRACBITS>>1);
-		mobj->movecount %= 360*FRACUNIT;
+		mobj->angle -= FixedAngle(movespeed/8);
+
+		if (mobj->movefactor != mobj->threshold)
+		{
+			if (mobj->threshold - mobj->movefactor < FRACUNIT)
+			{
+				mobj->movefactor = mobj->threshold;
+				mobj->flags2 &= ~MF2_FRET;
+			}
+			else
+				mobj->movefactor += (mobj->threshold - mobj->movefactor)/8;
+		}
+
+		if (mobj->spawnpoint)
+			P_TryMove(mobj,
+				(mobj->spawnpoint->x<<FRACBITS) - P_ReturnThrustX(mobj, mobj->angle, mobj->movefactor),
+				(mobj->spawnpoint->y<<FRACBITS) - P_ReturnThrustY(mobj, mobj->angle, mobj->movefactor),
+				true);
+
 		P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2);
 
 		if (!mobj->target || !mobj->target->health)
 			P_SupermanLook4Players(mobj);
-		A_FaceTarget(mobj);
+		//A_FaceTarget(mobj);
 		return;
 	}
 
@@ -4917,10 +4950,23 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	if (mobj->reactiontime == 1)
 	{
 		fixed_t oldz = mobj->movefactor;
-		mobj->movefactor += 8*FRACUNIT;
-		if (mobj->movefactor > 128*FRACUNIT)
-			mobj->movefactor = 128*FRACUNIT;
-		P_Boss4MoveCage(mobj->movefactor - oldz);
+		if (mobj->movefactor != 128*FRACUNIT)
+		{
+			if (mobj->movefactor < 128*FRACUNIT)
+			{
+				mobj->movefactor += 8*FRACUNIT;
+				if (!oldz)
+				{
+					// 5 -> 2.5 second timer
+					mobj->threshold = 5*TICRATE-(TICRATE*(mobj->info->spawnhealth-mobj->health)/2);
+					if (mobj->threshold < 1)
+						mobj->threshold = 1;
+				}
+			}
+			else
+				mobj->movefactor = 128*FRACUNIT;
+			P_Boss4MoveCage(mobj, mobj->movefactor - oldz);
+		}
 	}
 	// Drop the cage!
 	else if (mobj->movefactor)
@@ -4929,64 +4975,38 @@ static void P_Boss4Thinker(mobj_t *mobj)
 		mobj->movefactor -= 4*FRACUNIT;
 		if (mobj->movefactor < 0)
 			mobj->movefactor = 0;
-		P_Boss4MoveCage(mobj->movefactor - oldz);
+		P_Boss4MoveCage(mobj, mobj->movefactor - oldz);
 		if (!mobj->movefactor)
 		{
 			if (mobj->health <= mobj->info->damage)
 			{ // Proceed to pinch phase!
-				P_Boss4DestroyCage();
+				P_Boss4DestroyCage(mobj);
 				mobj->movedir = 3;
-				P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
+				P_LinedefExecute(LE_PINCHPHASE + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
+				P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0);
+				var1 = 3;
+				A_BossJetFume(mobj);
 				return;
 			}
-			P_LinedefExecute(LE_BOSS4DROP, mobj, NULL);
+			P_LinedefExecute(LE_BOSS4DROP - (mobj->info->spawnhealth-mobj->health) + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
+			// 1 -> 1.5 second timer
+			mobj->threshold = TICRATE+(TICRATE*(mobj->info->spawnhealth-mobj->health)/10);
+			if (mobj->threshold < 1)
+				mobj->threshold = 1;
 		}
 	}
 
-	{
-		fixed_t movespeed = 170<<(FRACBITS>>1);
-		if (mobj->reactiontime == 2)
-			movespeed *= 3;
-		if (mobj->movedir == 2)
-			mobj->movecount -= movespeed;
-		else
-			mobj->movecount += movespeed;
-	}
-	mobj->movecount %= 360*FRACUNIT;
 	P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->movefactor);
 
 	// Check for attacks, always tick the timer even while animating!!
-	if (!(mobj->flags2 & MF2_FRET) // but pause for pain so we don't interrupt pinch phase, eep!
-	&& mobj->threshold-- == 0)
+	if (mobj->threshold)
 	{
-		// 5 -> 2.5 second timer
-		mobj->threshold = 5*TICRATE-(TICRATE/2)*(mobj->info->spawnhealth-mobj->health);
-		if (mobj->threshold < 1)
-			mobj->threshold = 1;
-
-		if (mobj->extravalue1-- == 0)
-		{
-			P_SetMobjState(mobj, mobj->info->raisestate);
-			mobj->extravalue1 = 3;
-		}
-		else
+		if (!(mobj->flags2 & MF2_FRET) && !(--mobj->threshold)) // but pause for pain so we don't interrupt pinch phase, eep!
 		{
 			if (mobj->reactiontime == 1) // Cage is raised?
+			{
+				P_SetMobjState(mobj, mobj->info->spawnstate);
 				mobj->reactiontime = 0; // Drop it!
-			switch(P_RandomKey(10))
-			{
-				// Telegraph Right (Speed Up!!)
-				case 1:
-				case 3:
-				case 4:
-				case 5:
-				case 6:
-					P_SetMobjState(mobj, mobj->info->missilestate);
-					break;
-				// Telegraph Left (Reverse Direction)
-				default:
-					P_SetMobjState(mobj, mobj->info->meleestate);
-					break;
 			}
 		}
 	}
@@ -4998,13 +5018,11 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	// Map allows us to get killed despite cage being down?
 	if (mobj->health <= mobj->info->damage)
 	{ // Proceed to pinch phase!
-		P_Boss4DestroyCage();
-		// spawn jet's flame now you're flying upwards
-		// tracer is already used, so if this ever gets reached again we've got problems
+		P_Boss4DestroyCage(mobj);
+		mobj->movedir = 3;
+		P_LinedefExecute(LE_PINCHPHASE + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
 		var1 = 3;
 		A_BossJetFume(mobj);
-		mobj->movedir = 3;
-		P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
 		return;
 	}
 
@@ -5218,6 +5236,7 @@ static void P_Boss7Thinker(mobj_t *mobj)
 		INT32 i;
 		boolean foundgoop = false;
 		INT32 closestNum;
+		UINT8 extrainfo = (mobj->spawnpoint ? mobj->spawnpoint->extrainfo : 0);
 
 		// Looks for players in goop. If you find one, try to jump on him.
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -5237,23 +5256,29 @@ static void P_Boss7Thinker(mobj_t *mobj)
 				closestdist = INT32_MAX; // Just in case...
 
 				// Find waypoint he is closest to
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
-					if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint)
-					{
-						dist = P_AproxDistance(players[i].mo->x - mo2->x, players[i].mo->y - mo2->y);
+					if (mo2->type != MT_BOSS3WAYPOINT)
+						continue;
+					if (!mo2->spawnpoint)
+						continue;
+					if (mo2->spawnpoint->extrainfo != extrainfo)
+						continue;
+					if (mobj->health <= mobj->info->damage && !(mo2->spawnpoint->options & 7))
+						continue; // don't jump to center
 
-						if (closestNum == -1 || dist < closestdist)
-						{
-							closestNum = (mo2->spawnpoint->options & 7);
-							closestdist = dist;
-							foundgoop = true;
-						}
-					}
+					dist = P_AproxDistance(players[i].mo->x - mo2->x, players[i].mo->y - mo2->y);
+
+					if (!(closestNum == -1 || dist < closestdist))
+						continue;
+
+					closestNum = (mo2->spawnpoint->options & 7);
+					closestdist = dist;
+					foundgoop = true;
 				}
 				waypointNum = closestNum;
 				break;
@@ -5262,17 +5287,14 @@ static void P_Boss7Thinker(mobj_t *mobj)
 
 		if (!foundgoop)
 		{
-			if (mobj->z > 1056*FRACUNIT)
-				waypointNum = 0;
-			else
+			// Don't jump to the center when health is low.
+			// Force the player to beat you with missiles.
+			if (mobj->z <= 1056*FRACUNIT || mobj->health <= mobj->info->damage)
 				waypointNum = 1 + P_RandomKey(4);
+			else
+				waypointNum = 0;
 		}
 
-		// Don't jump to the center when health is low.
-		// Force the player to beat you with missiles.
-		if (mobj->health <= mobj->info->damage && waypointNum == 0)
-			waypointNum = 1 + P_RandomKey(4);
-
 		if (mobj->tracer && mobj->tracer->type == MT_BOSS3WAYPOINT
 			&& mobj->tracer->spawnpoint && (mobj->tracer->spawnpoint->options & 7) == waypointNum)
 		{
@@ -5281,28 +5303,31 @@ static void P_Boss7Thinker(mobj_t *mobj)
 			else
 				waypointNum--;
 
-			waypointNum %= 5;
-
-			if (waypointNum < 0)
-				waypointNum = 0;
+			if (mobj->health <= mobj->info->damage)
+				waypointNum = ((waypointNum + 3) % 4) + 1; // plus four to avoid modulo being negative, minus one to avoid waypoint #0
+			else
+				waypointNum = ((waypointNum + 5) % 5);
 		}
 
-		if (waypointNum == 0 && mobj->health <= mobj->info->damage)
-			waypointNum = 1 + (P_RandomFixed() & 1);
-
 		// scan the thinkers to find
 		// the waypoint to use
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
-			if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint && (mo2->spawnpoint->options & 7) == waypointNum)
-			{
-				hitspot = mo2;
-				break;
-			}
+			if (mo2->type != MT_BOSS3WAYPOINT)
+				continue;
+			if (!mo2->spawnpoint)
+				continue;
+			if ((mo2->spawnpoint->options & 7) != waypointNum)
+				continue;
+			if (mo2->spawnpoint->extrainfo != extrainfo)
+				continue;
+
+			hitspot = mo2;
+			break;
 		}
 
 		if (hitspot == NULL)
@@ -5399,7 +5424,8 @@ static void P_Boss7Thinker(mobj_t *mobj)
 				if (mobj->info->activesound)\
 					S_StartSound(mobj, mobj->info->activesound);\
 				if (mobj->info->painchance)\
-					P_SetMobjState(mobj, mobj->info->painchance)
+					P_SetMobjState(mobj, mobj->info->painchance);\
+				mobj->flags2 &= ~MF2_INVERTAIMABLE;\
 
 // Metal Sonic battle boss
 // You CAN put multiple Metal Sonics in a single map
@@ -5424,19 +5450,19 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 		// Run through the thinkers ONCE and find all of the MT_BOSS9GATHERPOINT in the map.
 		// Build a hoop linked list of 'em!
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
 			if (mo2->type == MT_BOSS9GATHERPOINT)
 			{
 				if (last)
-					last->hnext = mo2;
+					P_SetTarget(&last->hnext, mo2);
 				else
-					mobj->hnext = mo2;
-				mo2->hprev = last;
+					P_SetTarget(&mobj->hnext, mo2);
+				P_SetTarget(&mo2->hprev, last);
 				last = mo2;
 			}
 		}
@@ -5488,27 +5514,17 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 	// AI goes here.
 	{
-		boolean danger = true;
 		angle_t angle;
-		if (mobj->threshold)
+		if (mobj->threshold || mobj->movecount)
 			mobj->momz = (mobj->watertop-mobj->z)/16; // Float to your desired position FASTER
 		else
 			mobj->momz = (mobj->watertop-mobj->z)/40; // Float to your desired position
 
-		if (mobj->movecount == 2) {
+		if (mobj->movecount == 2)
+		{
 			mobj_t *spawner;
 			fixed_t dist = 0;
-			angle = 0x06000000*leveltime;
-
-			// Alter your energy bubble's size/position
-			if (mobj->health > 3) {
-				mobj->tracer->destscale = FRACUNIT + (4*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2);
-				P_SetScale(mobj->tracer, mobj->tracer->destscale);
-				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;
-				mobj->tracer->momz = mobj->momz;
-			}
+			angle = 0x06000000*leveltime; // wtf?
 
 			// Face your target
 			P_BossTargetPlayer(mobj, true);
@@ -5519,27 +5535,150 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			else
 				mobj->angle -= InvAngle(angle)/8;
 
+			// Alter your energy bubble's size/position
+			if (mobj->health > 3)
+			{
+				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;
+			mobj->tracer->momz = mobj->momz;
+
+				// Firin' mah lazors - INDICATOR
+				if (mobj->fuse > TICRATE/2)
+				{
+					tic_t shoottime, worktime, calctime;
+					shoottime = (TICRATE/((mobj->extravalue1 == 3) ? 8 : 4));
+					shoottime += (shoottime>>1);
+					worktime = shoottime*(mobj->threshold/2);
+					calctime = mobj->fuse-(TICRATE/2);
+
+					if (calctime <= worktime && (calctime % shoottime == 0))
+					{
+						mobj_t *missile;
+
+						missile = P_SpawnMissile(mobj, mobj->target, MT_MSGATHER);
+						S_StopSound(missile);
+						if (mobj->extravalue1 >= 2)
+							P_SetScale(missile, FRACUNIT>>1);
+						missile->destscale = missile->scale>>1;
+						missile->fuse = TICRATE/2;
+						missile->scalespeed = abs(missile->destscale - missile->scale)/missile->fuse;
+						missile->z -= missile->height/2;
+						missile->momx *= -1;
+						missile->momy *= -1;
+						missile->momz *= -1;
+
+						if (mobj->extravalue1 == 2)
+						{
+							UINT8 i;
+							mobj_t *spread;
+							for (i = 0; i < 5; i++)
+							{
+								if (i == 2)
+									continue;
+								spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
+								spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
+								P_InstaThrust(spread,spread->angle,-spread->info->speed);
+								spread->momz = missile->momz;
+								P_SetScale(spread, missile->scale);
+								spread->destscale = missile->destscale;
+								spread->scalespeed = missile->scalespeed;
+								spread->fuse = missile->fuse;
+								P_UnsetThingPosition(spread);
+								spread->x -= spread->fuse*spread->momx;
+								spread->y -= spread->fuse*spread->momy;
+								spread->z -= spread->fuse*spread->momz;
+								P_SetThingPosition(spread);
+							}
+							P_InstaThrust(missile,missile->angle,-missile->info->speed);
+						}
+						else if (mobj->extravalue1 >= 3)
+						{
+							UINT8 i;
+							mobj_t *spread;
+							mobj->target->z -= (4*missile->height);
+							for (i = 0; i < 5; i++)
+							{
+								if (i != 2)
+								{
+									spread = P_SpawnMissile(mobj, mobj->target, missile->type);
+									P_SetScale(spread, missile->scale);
+									spread->destscale = missile->destscale;
+									spread->fuse = missile->fuse;
+									spread->z -= spread->height/2;
+									spread->momx *= -1;
+									spread->momy *= -1;
+									spread->momz *= -1;
+									P_UnsetThingPosition(spread);
+									spread->x -= spread->fuse*spread->momx;
+									spread->y -= spread->fuse*spread->momy;
+									spread->z -= spread->fuse*spread->momz;
+									P_SetThingPosition(spread);
+								}
+								mobj->target->z += missile->height*2;
+							}
+							mobj->target->z -= (6*missile->height);
+						}
+
+						P_UnsetThingPosition(missile);
+						missile->x -= missile->fuse*missile->momx;
+						missile->y -= missile->fuse*missile->momy;
+						missile->z -= missile->fuse*missile->momz;
+						P_SetThingPosition(missile);
+
+						S_StartSound(mobj, sfx_s3kb3);
+					}
+				}
+
+			// up...
+			mobj->z += mobj->height/2;
+
 			// Spawn energy particles
-			for (spawner = mobj->hnext; spawner; spawner = spawner->hnext) {
+			for (spawner = mobj->hnext; spawner; spawner = spawner->hnext)
+			{
 				dist = P_AproxDistance(spawner->x - mobj->x, spawner->y - mobj->y);
 				if (P_RandomRange(1,(dist>>FRACBITS)/16) == 1)
 					break;
 			}
-			if (spawner) {
+			if (spawner)
+			{
 				mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER);
-				if (mobj->health > mobj->info->damage)
-					missile->momz = FixedDiv(missile->momz, 7*FRACUNIT/5);
+
 				if (dist == 0)
 					missile->fuse = 0;
 				else
 					missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
+
 				if (missile->fuse > mobj->fuse)
 					P_RemoveMobj(missile);
+
+				if (mobj->health > mobj->info->damage)
+				{
+					P_SetScale(missile, FRACUNIT/2);
+					missile->color = SKINCOLOR_GOLD; // sonic cd electric power
+				}
+				else
+				{
+					P_SetScale(missile, FRACUNIT/4);
+					missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power
+				}
+				missile->destscale = missile->scale*2;
+				missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse;
+				missile->colorized = true;
 			}
+
+			// ...then down. easier than changing the missile's momz after-the-fact
+			mobj->z -= mobj->height/2;
 		}
 
 		// Pre-threshold reactiontime stuff for attack phases
-		if (mobj->reactiontime && mobj->movecount == 3) {
+		if (mobj->reactiontime && mobj->movecount == 3)
+		{
 			mobj->reactiontime--;
 
 			if (mobj->movedir == 0 || mobj->movedir == 2) { // Pausing between bounces in the pinball phase
@@ -5560,13 +5699,15 @@ static void P_Boss9Thinker(mobj_t *mobj)
 		}
 
 		// threshold is used for attacks/maneuvers.
-		if (mobj->threshold) {
+		if (mobj->threshold && mobj->movecount != 2) {
 			fixed_t speed = 20*FRACUNIT + FixedMul(40*FRACUNIT, FixedDiv((mobj->info->spawnhealth - mobj->health)<<FRACBITS, mobj->info->spawnhealth<<FRACBITS));
-			int tries = 0;
+			UINT8 tries = 0;
 
 			// Firin' mah lazors
-			if (mobj->movecount == 3 && mobj->movedir == 1) {
-				if (!(mobj->threshold&1)) {
+			if (mobj->movecount == 3 && mobj->movedir == 1)
+			{
+				if (!(mobj->threshold & 1))
+				{
 					mobj_t *missile;
 					if (mobj->info->seesound)
 						S_StartSound(mobj, mobj->info->seesound);
@@ -5578,18 +5719,20 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 					A_FaceTarget(mobj);
 					missile = P_SpawnMissile(mobj, mobj->target, mobj->info->speed);
-					if (mobj->extravalue1 == 2 || mobj->extravalue1 == 3) {
+					if (mobj->extravalue1 >= 2)
+					{
 						missile->destscale = FRACUNIT>>1;
 						P_SetScale(missile, missile->destscale);
 					}
 					missile->fuse = 3*TICRATE;
 					missile->z -= missile->height/2;
 
-					if (mobj->extravalue1 == 2) {
-						int i;
+					if (mobj->extravalue1 == 2)
+					{
+						UINT8 i;
 						mobj_t *spread;
-						missile->flags |= MF_MISSILE;
-						for (i = 0; i < 5; i++) {
+						for (i = 0; i < 5; i++)
+						{
 							if (i == 2)
 								continue;
 							spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
@@ -5598,11 +5741,32 @@ static void P_Boss9Thinker(mobj_t *mobj)
 							spread->momz = missile->momz;
 							spread->destscale = FRACUNIT>>1;
 							P_SetScale(spread, spread->destscale);
-							spread->fuse = 3*TICRATE;
+							spread->fuse = missile->fuse;
 						}
-						missile->flags &= ~MF_MISSILE;
+						P_InstaThrust(missile,missile->angle,missile->info->speed);
 					}
-				} else {
+					else if (mobj->extravalue1 >= 3)
+					{
+						UINT8 i;
+						mobj_t *spread;
+						mobj->target->z -= (2*missile->height);
+						for (i = 0; i < 5; i++)
+						{
+							if (i != 2)
+							{
+								spread = P_SpawnMissile(mobj, mobj->target, missile->type);
+								spread->destscale = FRACUNIT>>1;
+								P_SetScale(spread, spread->destscale);
+								spread->fuse = missile->fuse;
+								spread->z -= spread->height/2;
+							}
+							mobj->target->z += missile->height;
+						}
+						mobj->target->z -= (3*missile->height);
+					}
+				}
+				else
+				{
 					P_SetMobjState(mobj, mobj->state->nextstate);
 					if (mobj->extravalue1 == 3)
 						mobj->reactiontime = TICRATE/8;
@@ -5616,7 +5780,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			P_SpawnGhostMobj(mobj);
 
 			// Pinball attack!
-			if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2)) {
+			if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2))
+			{
 				if ((statenum_t)(mobj->state-states) != mobj->info->seestate)
 					P_SetMobjState(mobj, mobj->info->seestate);
 				if (mobj->movedir == 0) // mobj health == 1
@@ -5625,7 +5790,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 					P_InstaThrust(mobj, mobj->angle, 22*FRACUNIT);
 				else // mobj health == 2
 					P_InstaThrust(mobj, mobj->angle, 30*FRACUNIT);
-				if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true)) { // Hit a wall? Find a direction to bounce
+				if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true))
+				{ // Hit a wall? Find a direction to bounce
 					mobj->threshold--;
 					P_SetMobjState(mobj, mobj->state->nextstate);
 					if (!mobj->threshold) { // failed bounce!
@@ -5638,11 +5804,15 @@ static void P_Boss9Thinker(mobj_t *mobj)
 						mobj->movecount = 0;
 						P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CYBRAKDEMON_VILE_EXPLOSION);
 						P_SetMobjState(mobj, mobj->info->meleestate);
-					} else if (!(mobj->threshold%4)) { // We've decided to lock onto the player this bounce.
+					}
+					else if (!(mobj->threshold%4))
+					{ // We've decided to lock onto the player this bounce.
 						S_StartSound(mobj, sfx_s3k5a);
 						mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4);
 						mobj->reactiontime = TICRATE - 5*(mobj->info->damage - mobj->health); // targetting time
-					} else { // No homing, just use P_BounceMove
+					}
+					else
+					{ // No homing, just use P_BounceMove
 						S_StartSound(mobj, sfx_s3kaa); // make the bounces distinct...
 						P_BounceMove(mobj);
 						mobj->angle = R_PointToAngle2(0,0,mobj->momx,mobj->momy);
@@ -5656,7 +5826,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			// Vector form dodge!
 			mobj->angle += mobj->movedir;
 			P_InstaThrust(mobj, mobj->angle, -speed);
-			while (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true) && tries++ < 16) {
+			while (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true) && tries++ < 16)
+			{
 				S_StartSound(mobj, sfx_mspogo);
 				P_BounceMove(mobj);
 				mobj->angle = R_PointToAngle2(mobj->momx, mobj->momy,0,0);
@@ -5713,7 +5884,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 		if (mobj->flags2 & MF2_FRET)
 			return;
 
-		if (mobj->state == &states[mobj->info->raisestate])
+		if (mobj->movecount == 1 || mobj->movecount == 2)
 		{ // Charging energy
 			if (mobj->momx != 0 || mobj->momy != 0) { // Apply the air breaks
 				if (abs(mobj->momx)+abs(mobj->momy) < FRACUNIT)
@@ -5721,11 +5892,13 @@ static void P_Boss9Thinker(mobj_t *mobj)
 				else
 					P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -6*FRACUNIT/8);
 			}
-			return;
+			if (mobj->state == states+mobj->info->raisestate)
+				return;
 		}
 
 		if (mobj->fuse == 0)
 		{
+			mobj->flags2 &= ~MF2_INVERTAIMABLE;
 			// It's time to attack! What are we gonna do?!
 			switch(mobj->movecount)
 			{
@@ -5733,6 +5906,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			default:
 				// Fly up and prepare for an attack!
 				// We have to charge up first, so let's go up into the air
+				S_StartSound(mobj, sfx_beflap);
 				P_SetMobjState(mobj, mobj->info->raisestate);
 				if (mobj->floorz >= mobj->target->floorz)
 					mobj->watertop = mobj->floorz + 256*FRACUNIT;
@@ -5740,33 +5914,70 @@ static void P_Boss9Thinker(mobj_t *mobj)
 					mobj->watertop = mobj->target->floorz + 256*FRACUNIT;
 				break;
 
-			case 1: {
+			case 1:
 				// Okay, we're up? Good, time to gather energy...
 				if (mobj->health > mobj->info->damage)
 				{ // No more bubble if we're broken (pinch phase)
 					mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
 					P_SetTarget(&mobj->tracer, shield);
 					P_SetTarget(&shield->target, mobj);
+
+					// Attack 2: Energy shot!
+					switch (mobj->health)
+					{
+						case 8: // shoot once
+						default:
+							mobj->extravalue1 = 0;
+							mobj->threshold = 2;
+							break;
+						case 7: // spread shot (vertical)
+							mobj->extravalue1 = 4;
+							mobj->threshold = 2;
+							break;
+						case 6: // three shots
+							mobj->extravalue1 = 1;
+							mobj->threshold = 3*2;
+							break;
+						case 5: // spread shot (horizontal)
+							mobj->extravalue1 = 2;
+							mobj->threshold = 2;
+							break;
+						case 4: // machine gun
+							mobj->extravalue1 = 3;
+							mobj->threshold = 5*2;
+							break;
+					}
 				}
 				else
-					P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
+				{
+					mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
+					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_LinedefExecute(LE_PINCHPHASE, mobj, NULL); -- why does this happen twice? see case 2...
+				}
 				mobj->fuse = 4*TICRATE;
 				mobj->flags |= MF_PAIN;
 				if (mobj->info->attacksound)
 					S_StartSound(mobj, mobj->info->attacksound);
 				A_FaceTarget(mobj);
+
 				break;
-			}
 
 			case 2:
+			{
 				// We're all charged and ready now! Unleash the fury!!
-				if (mobj->health > mobj->info->damage)
+				mobj_t *removemobj = mobj->tracer;
+				S_StopSound(mobj);
+				P_SetTarget(&mobj->tracer, mobj->hnext);
+				P_RemoveMobj(removemobj);
+				if (mobj->health <= mobj->info->damage)
 				{
-					mobj_t *removemobj = mobj->tracer;
-					P_SetTarget(&mobj->tracer, mobj->hnext);
-					P_RemoveMobj(removemobj);
-				}
-				if (mobj->health <= mobj->info->damage) {
+					mobj_t *whoosh;
+
 					// Attack 1: Pinball dash!
 					if (mobj->health == 1)
 						mobj->movedir = 0;
@@ -5781,35 +5992,26 @@ static void P_Boss9Thinker(mobj_t *mobj)
 						mobj->threshold = 24; // bounce 24 times
 					mobj->watertop = mobj->target->floorz + 16*FRACUNIT;
 					P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
-				} else {
+
+					whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
+					whoosh->frame = FF_FULLBRIGHT;
+					whoosh->sprite = SPR_ARMA;
+					whoosh->destscale = whoosh->scale<<1;
+					whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
+					whoosh->height = 38*whoosh->scale;
+					whoosh->fuse = 10;
+					whoosh->color = SKINCOLOR_MAGENTA;
+					whoosh->colorized = true;
+					whoosh->flags |= MF_NOCLIPHEIGHT;
+				}
+				else
+				{
 					// Attack 2: Energy shot!
 					mobj->movedir = 1;
-
-					if (mobj->health >= 8)
-						mobj->extravalue1 = 0;
-					else if (mobj->health >= 5)
-						mobj->extravalue1 = 2;
-					else if (mobj->health >= 4)
-						mobj->extravalue1 = 1;
-					else
-						mobj->extravalue1 = 3;
-
-					switch(mobj->extravalue1) {
-					case 0: // shoot once
-					case 2: // spread-shot
-					default:
-						mobj->threshold = 2;
-						break;
-					case 1: // shoot 3 times
-						mobj->threshold = 3*2;
-						break;
-					case 3: // shoot like a goddamn machinegun
-						mobj->threshold = 8*2;
-						break;
-					}
+					// looking for the number of things to fire? that's done in case 1 now
 				}
 				break;
-
+			}
 			case 3:
 				// Return to idle.
 				mobj->watertop = mobj->target->floorz + 32*FRACUNIT;
@@ -5840,38 +6042,44 @@ static void P_Boss9Thinker(mobj_t *mobj)
 				mobj->angle -= InvAngle(angle)/8;
 			//A_FaceTarget(mobj);
 
-			// Check if we're being attacked
-			if (!(mobj->target->player->pflags & (PF_JUMPED|PF_SPINNING)
-			|| mobj->target->player->powers[pw_tailsfly]
-			|| mobj->target->player->powers[pw_invulnerability]
-			|| mobj->target->player->powers[pw_super]))
-				danger = false;
-			if (mobj->target->x+mobj->target->radius+abs(mobj->target->momx*2) < mobj->x-mobj->radius)
-				danger = false;
-			if (mobj->target->x-mobj->target->radius-abs(mobj->target->momx*2) > mobj->x+mobj->radius)
-				danger = false;
-			if (mobj->target->y+mobj->target->radius+abs(mobj->target->momy*2) < mobj->y-mobj->radius)
-				danger = false;
-			if (mobj->target->y-mobj->target->radius-abs(mobj->target->momy*2) > mobj->y+mobj->radius)
-				danger = false;
-			if (mobj->target->z+mobj->target->height+mobj->target->momz*2 < mobj->z)
-				danger = false;
-			if (mobj->target->z+mobj->target->momz*2 > mobj->z+mobj->height)
-				danger = false;
-			if (danger) {
-				// An incoming attack is detected! What should we do?!
-				// Go into vector form!
-				vectorise;
-				return;
-			}
+			if (mobj->flags2 & MF2_CLASSICPUSH)
+				mobj->flags2 &= ~MF2_CLASSICPUSH; // a missile caught us in PIT_CheckThing!
+			else
+			{
+				// Check if we're being attacked
+				if (!mobj->target || !mobj->target->player || !P_PlayerCanDamage(mobj->target->player, mobj))
+					goto nodanger;
+				if (mobj->target->x+mobj->target->radius+abs(mobj->target->momx*2) < mobj->x-mobj->radius)
+					goto nodanger;
+				if (mobj->target->x-mobj->target->radius-abs(mobj->target->momx*2) > mobj->x+mobj->radius)
+					goto nodanger;
+				if (mobj->target->y+mobj->target->radius+abs(mobj->target->momy*2) < mobj->y-mobj->radius)
+					goto nodanger;
+				if (mobj->target->y-mobj->target->radius-abs(mobj->target->momy*2) > mobj->y+mobj->radius)
+					goto nodanger;
+				if (mobj->target->z+mobj->target->height+mobj->target->momz*2 < mobj->z)
+					goto nodanger;
+				if (mobj->target->z+mobj->target->momz*2 > mobj->z+mobj->height)
+					goto nodanger;
+			}
+
+			// An incoming attack is detected! What should we do?!
+			// Go into vector form!
+			vectorise;
+			return;
+nodanger:
+
+			mobj->flags2 |= MF2_INVERTAIMABLE;
 
 			// Move normally: Approach the player using normal thrust and simulated friction.
 			dist = P_AproxDistance(mobj->x-mobj->target->x, mobj->y-mobj->target->y);
 			P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -3*FRACUNIT/8);
-			if (dist < 64*FRACUNIT)
+			if (dist < 64*FRACUNIT && !(mobj->target->player && mobj->target->player->homing))
 				P_Thrust(mobj, mobj->angle, -4*FRACUNIT);
 			else if (dist > 180*FRACUNIT)
 				P_Thrust(mobj, mobj->angle, FRACUNIT);
+			else
+				P_Thrust(mobj, mobj->angle + ANGLE_90, FINECOSINE((((angle_t)(leveltime*ANG1))>>ANGLETOFINESHIFT) & FINEMASK)>>1);
 			mobj->momz += P_AproxDistance(mobj->momx, mobj->momy)/12; // Move up higher the faster you're going.
 		}
 	}
@@ -5890,9 +6098,9 @@ mobj_t *P_GetClosestAxis(mobj_t *source)
 	fixed_t dist1, dist2 = 0;
 
 	// scan the thinkers to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -6591,7 +6799,7 @@ void P_RunOverlays(void)
 		{
 			angle_t viewingangle;
 
-			if (players[displayplayer].awayviewtics)
+			if (players[displayplayer].awayviewtics && players[displayplayer].awayviewmobj != NULL && !P_MobjWasRemoved(players[displayplayer].awayviewmobj))
 				viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].awayviewmobj->x, players[displayplayer].awayviewmobj->y);
 			else if (!camera.chase && players[displayplayer].mo)
 				viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].mo->x, players[displayplayer].mo->y);
@@ -6664,12 +6872,14 @@ static void P_RemoveOverlay(mobj_t *thing)
 {
 	mobj_t *mo;
 	for (mo = overlaycap; mo; mo = mo->hnext)
-		if (mo->hnext == thing)
-		{
-			P_SetTarget(&mo->hnext, thing->hnext);
-			P_SetTarget(&thing->hnext, NULL);
-			return;
-		}
+	{
+		if (mo->hnext != thing)
+			continue;
+
+		P_SetTarget(&mo->hnext, thing->hnext);
+		P_SetTarget(&thing->hnext, NULL);
+		return;
+	}
 }
 
 void A_BossDeath(mobj_t *mo);
@@ -6801,6 +7011,9 @@ void P_MobjThinker(mobj_t *mobj)
 	if (mobj->flags & MF_NOTHINK)
 		return;
 
+	if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<<mobj->spawnpoint->extrainfo)))
+		return;
+
 	// Remove dead target/tracer.
 	if (mobj->target && P_MobjWasRemoved(mobj->target))
 		P_SetTarget(&mobj->target, NULL);
@@ -7170,6 +7383,9 @@ void P_MobjThinker(mobj_t *mobj)
 					P_RemoveMobj(mobj);
 					return;
 				}
+
+				mobj->flags2 &= ~MF2_DONTDRAW;
+
 				mobj->x = mobj->target->x;
 				mobj->y = mobj->target->y;
 
@@ -7183,6 +7399,17 @@ void P_MobjThinker(mobj_t *mobj)
 				else
 					mobj->z = mobj->target->z - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale) - mobj->height;
 				break;
+			case MT_LOCKONINF:
+				if (!(mobj->flags2 & MF2_STRONGBOX))
+				{
+					mobj->threshold = mobj->z;
+					mobj->flags2 |= MF2_STRONGBOX;
+				}
+				if (!(mobj->eflags & MFE_VERTICALFLIP))
+					mobj->z = mobj->threshold + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
+				else
+					mobj->z = mobj->threshold - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
+				break;
 			case MT_DROWNNUMBERS:
 				if (!mobj->target)
 				{
@@ -7333,6 +7560,7 @@ void P_MobjThinker(mobj_t *mobj)
 			case MT_ROCKCRUMBLE15:
 			case MT_ROCKCRUMBLE16:
 			case MT_WOODDEBRIS:
+			case MT_BRICKDEBRIS:
 				if (mobj->z <= P_FloorzAtPos(mobj->x, mobj->y, mobj->z, mobj->height)
 					&& mobj->state != &states[mobj->info->deathstate])
 				{
@@ -7520,6 +7748,34 @@ void P_MobjThinker(mobj_t *mobj)
 				return;
 			}
 			break;
+		case MT_FAKEMOBILE:
+			if (mobj->scale == mobj->destscale)
+			{
+				if (!mobj->fuse)
+				{
+					S_StartSound(mobj, sfx_s3k77);
+					mobj->flags2 |= MF2_DONTDRAW;
+					mobj->fuse = TICRATE;
+				}
+				return;
+			}
+			if (!mobj->reactiontime)
+			{
+				if (P_RandomChance(FRACUNIT/2))
+					mobj->movefactor = FRACUNIT;
+				else
+					mobj->movefactor = -FRACUNIT;
+				if (P_RandomChance(FRACUNIT/2))
+					mobj->movedir = ANG20;
+				else
+					mobj->movedir = -ANG20;
+				mobj->reactiontime = 5;
+			}
+			mobj->momz += mobj->movefactor;
+			mobj->angle += mobj->movedir;
+			P_InstaThrust(mobj, mobj->angle, -mobj->info->speed);
+			mobj->reactiontime--;
+			break;
 		case MT_EGGSHIELD:
 			mobj->flags2 ^= MF2_DONTDRAW;
 			break;
@@ -7826,7 +8082,8 @@ void P_MobjThinker(mobj_t *mobj)
 						mobj->tracer->z += mobj->height;
 				}
 				break;
-			case MT_WAVINGFLAG:
+			case MT_WAVINGFLAG1:
+			case MT_WAVINGFLAG2:
 				{
 					fixed_t base = (leveltime<<(FRACBITS+1));
 					mobj_t *seg = mobj->tracer, *prev = mobj;
@@ -7921,6 +8178,10 @@ void P_MobjThinker(mobj_t *mobj)
 					}
 				}
 				break;
+			case MT_LHRT:
+				mobj->momx = FixedMul(mobj->momx, mobj->extravalue2);
+				mobj->momy = FixedMul(mobj->momy, mobj->extravalue2);
+				break;
 			case MT_EGGCAPSULE:
 				if (!mobj->reactiontime)
 				{
@@ -8084,7 +8345,166 @@ void P_MobjThinker(mobj_t *mobj)
 					P_UnsetThingPosition(mobj);
 					mobj->x = mobj->target->x;
 					mobj->y = mobj->target->y;
-					mobj->z = mobj->target->z - FixedMul(50*FRACUNIT, mobj->target->scale);
+					mobj->z = mobj->target->z - 50*mobj->target->scale;
+					mobj->floorz = mobj->z;
+					mobj->ceilingz = mobj->z+mobj->height;
+					P_SetThingPosition(mobj);
+				}
+				break;
+			case MT_EGGROBO1:
+#define SPECTATORRADIUS (96*mobj->scale)
+				{
+					if (!(mobj->flags2 & MF2_STRONGBOX))
+					{
+						mobj->cusval = mobj->x; // eat my SOCs, p_mobj.h warning, we have lua now
+						mobj->cvmem = mobj->y; // ditto
+						mobj->movedir = mobj->angle;
+						mobj->threshold = P_MobjFlip(mobj)*10*mobj->scale;
+						if (mobj->threshold < 0)
+							mobj->threshold += (mobj->ceilingz - mobj->height);
+						else
+							mobj->threshold += mobj->floorz;
+						var1 = 4;
+						A_BossJetFume(mobj);
+						mobj->flags2 |= MF2_STRONGBOX;
+					}
+
+					if (mobj->state == &states[mobj->info->deathstate]) // todo: make map actually set health to 0 for these
+					{
+						if (mobj->movecount)
+						{
+							if (!(--mobj->movecount))
+								S_StartSound(mobj, mobj->info->deathsound);
+						}
+						else
+						{
+							mobj->momz += P_MobjFlip(mobj)*mobj->scale;
+							if (mobj->momz > 0)
+							{
+								if (mobj->z + mobj->momz > mobj->ceilingz + (1000<<FRACBITS))
+								{
+									P_RemoveMobj(mobj);
+									return;
+								}
+							}
+							else if (mobj->z + mobj->height + mobj->momz < mobj->floorz - (1000<<FRACBITS))
+							{
+								P_RemoveMobj(mobj);
+								return;
+							}
+						}
+					}
+					else
+					{
+						mobj->z = mobj->threshold + FixedMul(FINESINE(((leveltime + mobj->movecount)*ANG2>>(ANGLETOFINESHIFT-2)) & FINEMASK), 8*mobj->scale);
+						if (mobj->state != &states[mobj->info->meleestate])
+						{
+							boolean didmove = false;
+
+							if (mobj->state == &states[mobj->info->spawnstate])
+							{
+								UINT8 i;
+								fixed_t dist = INT32_MAX;
+
+								for (i = 0; i < MAXPLAYERS; i++)
+								{
+									fixed_t compdist;
+									if (!playeringame[i])
+										continue;
+									if (players[i].spectator)
+										continue;
+									if (!players[i].mo)
+										continue;
+									if (!players[i].mo->health)
+										continue;
+									if (P_PlayerInPain(&players[i]))
+										continue;
+									if (players[i].mo->z > mobj->z + mobj->height + 8*mobj->scale)
+										continue;
+									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);
+									if (compdist >= dist)
+										continue;
+									dist = compdist;
+									P_SetTarget(&mobj->target, players[i].mo);
+								}
+
+								if (dist < (SPECTATORRADIUS<<1))
+								{
+									didmove = true;
+									mobj->frame = 3 + ((leveltime & 2)>>1);
+									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->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),
+										mobj->z);
+								}
+								else
+								{
+									angle_t diff = (mobj->movedir - mobj->angle);
+									if (diff > ANGLE_180)
+										diff = InvAngle(InvAngle(diff)/8);
+									else
+										diff /= 8;
+									mobj->angle += diff;
+
+									dist = FINECOSINE(((leveltime + mobj->movecount)*ANG2>>(ANGLETOFINESHIFT-2)) & FINEMASK);
+
+									if (abs(dist) < FRACUNIT/2)
+										mobj->frame = 0;
+									else
+										mobj->frame = (dist > 0) ? 1 : 2;
+								}
+							}
+
+							if (!didmove)
+							{
+								if (P_AproxDistance(
+										mobj->x - mobj->cusval,
+										mobj->y - mobj->cvmem)
+										< mobj->scale)
+									P_TeleportMove(mobj,
+										mobj->cusval,
+										mobj->cvmem,
+										mobj->z);
+								else
+									P_TeleportMove(mobj,
+										(15*(mobj->x>>4)) + (mobj->cusval>>4),
+										(15*(mobj->y>>4)) + (mobj->cvmem>>4),
+										mobj->z);
+							}
+						}
+					}
+				}
+				break;
+#undef SPECTATORRADIUS
+			case MT_EGGROBO1JET:
+				{
+					if (!mobj->target || P_MobjWasRemoved(mobj->target) // if you have no target
+					|| (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;
+					}
+
+					mobj->flags2 ^= MF2_DONTDRAW;
+
+					P_UnsetThingPosition(mobj);
+					mobj->x = mobj->target->x + P_ReturnThrustX(mobj, mobj->target->angle+ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustX(mobj, mobj->target->angle, 19*mobj->target->scale);
+					mobj->y = mobj->target->y + P_ReturnThrustY(mobj, mobj->target->angle+ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustY(mobj, mobj->target->angle, 19*mobj->target->scale);
+					mobj->z = mobj->target->z;
+					if (mobj->target->eflags & MFE_VERTICALFLIP)
+						mobj->z += (mobj->target->height - mobj->height);
 					mobj->floorz = mobj->z;
 					mobj->ceilingz = mobj->z+mobj->height;
 					P_SetThingPosition(mobj);
@@ -8718,6 +9138,9 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 				case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
 					P_SetMobjState(mobj, mobj->info->deathstate);
 					break;
+				case MT_LHRT:
+					P_KillMobj(mobj, NULL, NULL, 0);
+					break;
 				case MT_BLUEFLAG:
 				case MT_REDFLAG:
 					if (mobj->spawnpoint)
@@ -9074,6 +9497,7 @@ void P_SceneryThinker(mobj_t *mobj)
 mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 {
 	const mobjinfo_t *info = &mobjinfo[type];
+	SINT8 sc = -1;
 	state_t *st;
 	mobj_t *mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
 
@@ -9184,6 +9608,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		case MT_ALTVIEWMAN:
 			if (titlemapinaction) mobj->flags &= ~MF_NOTHINK;
 			break;
+		case MT_LOCKONINF:
+			P_SetScale(mobj, (mobj->destscale = 3*mobj->scale));
+			break;
 		case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
 			mobj->fuse = mobj->info->painchance;
 			break;
@@ -9259,15 +9686,14 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		case MT_BIGMINE:
 			mobj->extravalue1 = FixedHypot(mobj->x, mobj->y)>>FRACBITS;
 			break;
-		case MT_WAVINGFLAG:
+		case MT_WAVINGFLAG1:
+		case MT_WAVINGFLAG2:
 			{
 				mobj_t *prev = mobj, *cur;
 				UINT8 i;
-				mobj->destscale <<= 2;
-				P_SetScale(mobj, mobj->destscale);
 				for (i = 0; i <= 16; i++) // probably should be < but staying authentic to the Lua version
 				{
-					cur = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_WAVINGFLAGSEG);
+					cur = P_SpawnMobjFromMobj(mobj, 0, 0, 0, ((mobj->type == MT_WAVINGFLAG1) ? MT_WAVINGFLAGSEG1 : MT_WAVINGFLAGSEG2));;
 					P_SetTarget(&prev->tracer, cur);
 					cur->extravalue1 = i;
 					prev = cur;
@@ -9278,12 +9704,19 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			// Special condition for the 2nd boss.
 			mobj->watertop = mobj->info->speed;
 			break;
+		case MT_EGGMOBILE3:
+			mobj->movefactor = -512*FRACUNIT;
+			mobj->flags2 |= MF2_CLASSICPUSH;
+			break;
 		case MT_FLICKY_08:
 			mobj->color = (P_RandomChance(FRACUNIT/2) ? SKINCOLOR_RED : SKINCOLOR_AQUA);
 			break;
 		case MT_BALLOON:
 			mobj->color = SKINCOLOR_RED;
 			break;
+		case MT_EGGROBO1:
+			mobj->movecount = P_RandomKey(13);
+			mobj->color = SKINCOLOR_RUBY + P_RandomKey(MAXSKINCOLORS - SKINCOLOR_RUBY);
 		case MT_HIVEELEMENTAL:
 			mobj->extravalue1 = 5;
 			break;
@@ -9330,6 +9763,13 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			if (nummaprings >= 0)
 				nummaprings++;
 			break;
+		case MT_METALSONIC_BATTLE:
+		case MT_METALSONIC_RACE:
+			sc = 3;
+			break;
+		case MT_FANG:
+			sc = 4;
+			break;
 		case MT_CORK:
 			mobj->flags2 |= MF2_SUPERFIRE;
 			break;
@@ -9347,15 +9787,32 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			mobj->momx = 1; //stack hack
 			break;
 		case MT_MINECARTEND:
-			mobj->tracer = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID);
+			P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID));
 			mobj->tracer->angle = mobj->angle + ANGLE_90;
 			break;
 		default:
 			break;
 	}
 
+	if (sc != -1)
+	{
+		UINT8 i;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (!playeringame[i] || players[i].spectator)
+				continue;
+
+			if (players[i].skin == sc)
+			{
+				mobj->color = SKINCOLOR_SILVER;
+				mobj->colorized = true;
+				break;
+			}
+		}
+	}
+
 	if (!(mobj->flags & MF_NOTHINK))
-		P_AddThinker(&mobj->thinker);
+		P_AddThinker(THINK_MOBJ, &mobj->thinker);
 
 	// Call action functions when the state is set
 	if (st->action.acp1 && (mobj->flags & MF_RUNSPAWNFUNC))
@@ -9430,7 +9887,7 @@ static precipmobj_t *P_SpawnPrecipMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype
 	mobj->momz = mobjinfo[type].speed;
 
 	mobj->thinker.function.acp1 = (actionf_p1)P_NullPrecipThinker;
-	P_AddThinker(&mobj->thinker);
+	P_AddThinker(THINK_PRECIP, &mobj->thinker);
 
 	CalculatePrecipFloor(mobj);
 
@@ -9539,37 +9996,28 @@ void P_RemoveMobj(mobj_t *mobj)
 
 	P_SetTarget(&mobj->hnext, P_SetTarget(&mobj->hprev, NULL));
 
-	// free block
 	// DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
-	if (mobj->flags & MF_NOTHINK && !mobj->thinker.next)
+#ifdef SCRAMBLE_REMOVED
+	// Invalidate mobj_t data to cause crashes if accessed!
+	memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t));
+#endif
+
+	// free block
+	if (!mobj->thinker.next)
 	{ // Uh-oh, the mobj doesn't think, P_RemoveThinker would never go through!
+		INT32 prevreferences;
 		if (!mobj->thinker.references)
 		{
-#ifdef SCRAMBLE_REMOVED
-			// Invalidate mobj_t data to cause crashes if accessed!
-			memset(mobj, 0xff, sizeof(mobj_t));
-#endif
-			Z_Free(mobj); // No refrences? Can be removed immediately! :D
-		}
-		else
-		{ // Add thinker just to delay removing it until refrences are gone.
-			mobj->flags &= ~MF_NOTHINK;
-			P_AddThinker((thinker_t *)mobj);
-#ifdef SCRAMBLE_REMOVED
-			// Invalidate mobj_t data to cause crashes if accessed!
-			memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t));
-#endif
-			P_RemoveThinker((thinker_t *)mobj);
+			Z_Free(mobj); // No refrrences? Can be removed immediately! :D
+			return;
 		}
+
+		prevreferences = mobj->thinker.references;
+		P_AddThinker(THINK_MOBJ, (thinker_t *)mobj);
+		mobj->thinker.references = prevreferences;
 	}
-	else
-	{
-#ifdef SCRAMBLE_REMOVED
-		// Invalidate mobj_t data to cause crashes if accessed!
-		memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t));
-#endif
-		P_RemoveThinker((thinker_t *)mobj);
-	}
+
+	P_RemoveThinker((thinker_t *)mobj);
 }
 
 // This does not need to be added to Lua.
@@ -9629,7 +10077,7 @@ void P_SpawnPrecipitation(void)
 	subsector_t *precipsector = NULL;
 	precipmobj_t *rainmo = NULL;
 
-	if (dedicated || /*!cv_precipdensity*/!cv_drawdist_precip.value || curWeather == PRECIP_NONE)
+	if (dedicated || !(cv_drawdist_precip.value) || curWeather == PRECIP_NONE)
 		return;
 
 	// Use the blockmap to narrow down our placing patterns
@@ -9638,50 +10086,47 @@ void P_SpawnPrecipitation(void)
 		basex = bmaporgx + (i % bmapwidth) * MAPBLOCKSIZE;
 		basey = bmaporgy + (i / bmapwidth) * MAPBLOCKSIZE;
 
-		//for (j = 0; j < cv_precipdensity.value; ++j) -- density is 1 for us always
-		{
-			x = basex + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
-			y = basey + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
-
-			precipsector = R_IsPointInSubsector(x, y);
+		x = basex + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
+		y = basey + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
 
-			// No sector? Stop wasting time,
-			// move on to the next entry in the blockmap
-			if (!precipsector)
-				break;
+		precipsector = R_IsPointInSubsector(x, y);
 
-			// Exists, but is too small for reasonable precipitation.
-			if (!(precipsector->sector->floorheight <= precipsector->sector->ceilingheight - (32<<FRACBITS)))
-				continue;
+		// No sector? Stop wasting time,
+		// move on to the next entry in the blockmap
+		if (!precipsector)
+			continue;
 
-			// Don't set height yet...
-			height = precipsector->sector->ceilingheight;
+		// Exists, but is too small for reasonable precipitation.
+		if (!(precipsector->sector->floorheight <= precipsector->sector->ceilingheight - (32<<FRACBITS)))
+			continue;
 
-			if (curWeather == PRECIP_SNOW)
-			{
-				// Not in a sector with visible sky -- exception for NiGHTS.
-				if (!(maptol & TOL_NIGHTS) && precipsector->sector->ceilingpic != skyflatnum)
-					continue;
+		// Don't set height yet...
+		height = precipsector->sector->ceilingheight;
 
-				rainmo = P_SpawnSnowMobj(x, y, height, MT_SNOWFLAKE);
-				mrand = M_RandomByte();
-				if (mrand < 64)
-					P_SetPrecipMobjState(rainmo, S_SNOW3);
-				else if (mrand < 144)
-					P_SetPrecipMobjState(rainmo, S_SNOW2);
-			}
-			else // everything else.
-			{
-				// Not in a sector with visible sky.
-				if (precipsector->sector->ceilingpic != skyflatnum)
-					continue;
+		if (curWeather == PRECIP_SNOW)
+		{
+			// Not in a sector with visible sky -- exception for NiGHTS.
+			if (!(maptol & TOL_NIGHTS) && precipsector->sector->ceilingpic != skyflatnum)
+				continue;
 
-				rainmo = P_SpawnRainMobj(x, y, height, MT_RAIN);
-			}
+			rainmo = P_SpawnSnowMobj(x, y, height, MT_SNOWFLAKE);
+			mrand = M_RandomByte();
+			if (mrand < 64)
+				P_SetPrecipMobjState(rainmo, S_SNOW3);
+			else if (mrand < 144)
+				P_SetPrecipMobjState(rainmo, S_SNOW2);
+		}
+		else // everything else.
+		{
+			// Not in a sector with visible sky.
+			if (precipsector->sector->ceilingpic != skyflatnum)
+				continue;
 
-			// Randomly assign a height, now that floorz is set.
-			rainmo->z = M_RandomRange(rainmo->floorz>>FRACBITS, rainmo->ceilingz>>FRACBITS)<<FRACBITS;
+			rainmo = P_SpawnRainMobj(x, y, height, MT_RAIN);
 		}
+
+		// Randomly assign a height, now that floorz is set.
+		rainmo->z = M_RandomRange(rainmo->floorz>>FRACBITS, rainmo->ceilingz>>FRACBITS)<<FRACBITS;
 	}
 
 	if (curWeather == PRECIP_BLANK)
@@ -10113,7 +10558,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
 	{
 		x = mthing->x << FRACBITS;
 		y = mthing->y << FRACBITS;
-		angle = FixedAngle(mthing->angle*FRACUNIT);
+		angle = FixedAngle(mthing->angle<<FRACBITS);
 	}
 	//spawn at the origin as a desperation move if there is no mapthing
 
@@ -10665,6 +11110,15 @@ You should think about modifying the deathmatch starts to take full advantage of
 		else
 			skyboxviewpnts[mthing->extrainfo] = mobj;
 		break;
+	case MT_EGGSTATUE:
+		if (tutorialmode != (mthing->options & MTF_OBJECTSPECIAL))
+		{
+			mobj->color = SKINCOLOR_GOLD;
+			mobj->colorized = true;
+		}
+	case MT_EGGMOBILE3:
+		mobj->cusval = mthing->extrainfo;
+		break;
 	case MT_FAN:
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
@@ -11316,13 +11770,14 @@ ML_EFFECT5 : Don't stop thinking when too far away
 			P_SpawnMobjFromMobj(mobj, -1*FRACUNIT, 0,          0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_270;
 		}
 		break;
-	case MT_CEZPOLE:
+	case MT_CEZPOLE1:
+	case MT_CEZPOLE2:
 		{ // Spawn the banner
 			angle_t mobjangle = FixedAngle(mthing->angle<<FRACBITS);
 			P_SpawnMobjFromMobj(mobj,
 				P_ReturnThrustX(mobj, mobjangle, 4<<FRACBITS),
 				P_ReturnThrustY(mobj, mobjangle, 4<<FRACBITS),
-				0, MT_CEZBANNER)->angle = mobjangle + ANGLE_90;
+				0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2))->angle = mobjangle + ANGLE_90;
 		}
 			break;
 	case MT_HHZTREE_TOP:
@@ -11398,9 +11853,9 @@ ML_EFFECT5 : Don't stop thinking when too far away
 		mobj->health = (mthing->angle / 360) + 1;
 
 		// See if other starposts exist in this level that have the same value.
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -11546,6 +12001,15 @@ ML_EFFECT5 : Don't stop thinking when too far away
 			if (i == MT_YELLOWDIAG || i == MT_REDDIAG)
 				mobj->angle += ANGLE_22h;
 
+			if (i == MT_YELLOWHORIZ || i == MT_REDHORIZ || i == MT_BLUEHORIZ)
+			{
+				if (mthing->options & MTF_OBJECTFLIP)
+					mobj->z -= 16*FRACUNIT;
+				else
+					mobj->z += 16*FRACUNIT;
+			}
+
+
 			if (mobj->flags & MF_NIGHTSITEM)
 			{
 				// Spawn already displayed
@@ -11573,6 +12037,9 @@ ML_EFFECT5 : Don't stop thinking when too far away
 
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
+			if (i == MT_YELLOWDIAG || i == MT_REDDIAG)
+				mobj->flags |= MF_NOGRAVITY;
+
 			if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
 			{
 				// flag for strong/weak random boxes
@@ -11706,11 +12173,11 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime)
 			// Link all the sprites in the hoop together
 			if (nextmobj)
 			{
-				mobj->hprev = nextmobj;
-				mobj->hprev->hnext = mobj;
+				P_SetTarget(&mobj->hprev, nextmobj);
+				P_SetTarget(&mobj->hprev->hnext, mobj);
 			}
 			else
-				mobj->hprev = mobj->hnext = NULL;
+				P_SetTarget(&mobj->hprev, P_SetTarget(&mobj->hnext, NULL));
 
 			nextmobj = mobj;
 		}
@@ -11736,9 +12203,9 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime)
 			mobj->z -= mobj->height/2;
 
 			// Link all the collision sprites together.
-			mobj->hnext = NULL;
-			mobj->hprev = nextmobj;
-			mobj->hprev->hnext = mobj;
+			P_SetTarget(&mobj->hnext, NULL);
+			P_SetTarget(&mobj->hprev, nextmobj);
+			P_SetTarget(&mobj->hprev->hnext, mobj);
 
 			nextmobj = mobj;
 		}
@@ -11763,9 +12230,9 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime)
 			mobj->z -= mobj->height/2;
 
 			// Link all the collision sprites together.
-			mobj->hnext = NULL;
-			mobj->hprev = nextmobj;
-			mobj->hprev->hnext = mobj;
+			P_SetTarget(&mobj->hnext, NULL);
+			P_SetTarget(&mobj->hprev, nextmobj);
+			P_SetTarget(&mobj->hprev->hnext, mobj);
 
 			nextmobj = mobj;
 		}
@@ -11848,11 +12315,11 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime)
 			// Link all the sprites in the hoop together
 			if (nextmobj)
 			{
-				mobj->hprev = nextmobj;
-				mobj->hprev->hnext = mobj;
+				P_SetTarget(&mobj->hprev, nextmobj);
+				P_SetTarget(&mobj->hprev->hnext, mobj);
 			}
 			else
-				mobj->hprev = mobj->hnext = NULL;
+				P_SetTarget(&mobj->hprev, P_SetTarget(&mobj->hnext, NULL));
 
 			nextmobj = mobj;
 		}
@@ -11889,9 +12356,9 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime)
 				mobj->z -= mobj->height/2;
 
 				// Link all the collision sprites together.
-				mobj->hnext = NULL;
-				mobj->hprev = nextmobj;
-				mobj->hprev->hnext = mobj;
+				P_SetTarget(&mobj->hnext, NULL);
+				P_SetTarget(&mobj->hprev, nextmobj);
+				P_SetTarget(&mobj->hprev->hnext, mobj);
 
 				nextmobj = mobj;
 			}
@@ -11916,28 +12383,33 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime)
 		if (nightsreplace)
 			ringthing = MT_NIGHTSSTAR;
 
-		for (r = 1; r <= 5; r++)
+		if (mthing->options & MTF_OBJECTFLIP)
 		{
-			if (mthing->options & MTF_OBJECTFLIP)
-			{
-				z = (
+			z = (
 #ifdef ESLOPE
-					sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
+				sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
 #endif
-					sec->ceilingheight) - mobjinfo[ringthing].height - dist*r;
-				if (mthing->options >> ZSHIFT)
-					z -= ((mthing->options >> ZSHIFT) << FRACBITS);
+				sec->ceilingheight) - mobjinfo[ringthing].height;
+			if (mthing->options >> ZSHIFT)
+				z -= ((mthing->options >> ZSHIFT) << FRACBITS);
 			}
-			else
-			{
-				z = (
+		else
+		{
+			z = (
 #ifdef ESLOPE
-					sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
+				sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
 #endif
-					sec->floorheight) + dist*r;
-				if (mthing->options >> ZSHIFT)
-					z += ((mthing->options >> ZSHIFT) << FRACBITS);
-			}
+				sec->floorheight);
+			if (mthing->options >> ZSHIFT)
+				z += ((mthing->options >> ZSHIFT) << FRACBITS);
+		}
+
+		for (r = 1; r <= 5; r++)
+		{
+			if (mthing->options & MTF_OBJECTFLIP)
+				z -= dist;
+			else
+				z += dist;
 
 			mobj = P_SpawnMobj(x, y, z, ringthing);
 
@@ -11971,31 +12443,36 @@ void P_SpawnHoopsAndRings(mapthing_t *mthing, boolean bonustime)
 		closestangle = FixedAngle(mthing->angle*FRACUNIT);
 		fa = (closestangle >> ANGLETOFINESHIFT);
 
+		if (mthing->options & MTF_OBJECTFLIP)
+		{
+			z = (
+#ifdef ESLOPE
+				sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
+#endif
+				sec->ceilingheight) - mobjinfo[ringthing].height;
+			if (mthing->options >> ZSHIFT)
+				z -= ((mthing->options >> ZSHIFT) << FRACBITS);
+			}
+		else
+		{
+			z = (
+#ifdef ESLOPE
+				sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
+#endif
+				sec->floorheight);
+			if (mthing->options >> ZSHIFT)
+				z += ((mthing->options >> ZSHIFT) << FRACBITS);
+		}
+
 		for (r = 1; r <= iterations; r++)
 		{
 			x += FixedMul(64*FRACUNIT, FINECOSINE(fa));
 			y += FixedMul(64*FRACUNIT, FINESINE(fa));
 
 			if (mthing->options & MTF_OBJECTFLIP)
-			{
-				z = (
-#ifdef ESLOPE
-					sec->c_slope ? P_GetZAt(sec->c_slope, x, y) :
-#endif
-					sec->ceilingheight) - mobjinfo[ringthing].height - 64*FRACUNIT*r;
-				if (mthing->options >> ZSHIFT)
-					z -= ((mthing->options >> ZSHIFT) << FRACBITS);
-			}
+				z -= 64*FRACUNIT;
 			else
-			{
-				z = (
-#ifdef ESLOPE
-					sec->f_slope ? P_GetZAt(sec->f_slope, x, y) :
-#endif
-					sec->floorheight) + 64*FRACUNIT*r;
-				if (mthing->options >> ZSHIFT)
-					z += ((mthing->options >> ZSHIFT) << FRACBITS);
-			}
+				z += 64*FRACUNIT;
 
 			mobj = P_SpawnMobj(x, y, z, ringthing);
 
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 936be3bb09e5efff8fc7ce812a22c7e127969386..77791f928165ac188ca5d6e3c952dbc39791dc56 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -371,6 +371,8 @@ typedef struct mobj_s
 	struct pslope_s *standingslope; // The slope that the object is standing on (shouldn't need synced in savegames, right?)
 #endif
 
+	boolean colorized; // Whether the mobj uses the rainbow colormap
+
 	// WARNING: New fields must be added separately to savegame and Lua.
 } mobj_t;
 
@@ -468,4 +470,5 @@ extern INT32 numhuntemeralds;
 extern boolean runemeraldmanager;
 extern UINT16 emeraldspawndelay;
 extern INT32 numstarposts;
+extern UINT16 bossdisabled;
 #endif
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index dfa9906ba76d51b9eed428b1ccf98547f3f62cce..040bdca2a92143b6353476701d5cb8c527427cba 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -146,16 +146,6 @@ FUNCINLINE static ATTRINLINE void Polyobj_vecSub2(vertex_t *dst, vertex_t *v1, v
 	dst->y = v1->y - v2->y;
 }
 
-// Add the polyobject's thinker to the thinker list
-// Unlike P_AddThinker, this adds it to the front of the list instead of the back, so that carrying physics can work right. -Red
-FUNCINLINE static ATTRINLINE void PolyObj_AddThinker(thinker_t *th)
-{
-	thinkercap.next->prev = th;
-	th->next = thinkercap.next;
-	th->prev = &thinkercap;
-	thinkercap.next = th;
-}
-
 //
 // P_PointInsidePolyobj
 //
@@ -1505,6 +1495,7 @@ void Polyobj_InitLevel(void)
 	mqueue_t    anchorqueue;
 	mobjqitem_t *qitem;
 	INT32 i, numAnchors = 0;
+	mobj_t *mo;
 
 	M_QueueInit(&spawnqueue);
 	M_QueueInit(&anchorqueue);
@@ -1518,31 +1509,31 @@ void Polyobj_InitLevel(void)
 
 	// run down the thinker list, count the number of spawn points, and save
 	// the mobj_t pointers on a queue for use below.
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			mobj_t *mo = (mobj_t *)th;
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
-			if (mo->info->doomednum == POLYOBJ_SPAWN_DOOMEDNUM ||
-				mo->info->doomednum == POLYOBJ_SPAWNCRUSH_DOOMEDNUM)
-			{
-				++numPolyObjects;
+		mo = (mobj_t *)th;
 
-				qitem = malloc(sizeof(mobjqitem_t));
-				memset(qitem, 0, sizeof(mobjqitem_t));
-				qitem->mo = mo;
-				M_QueueInsert(&(qitem->mqitem), &spawnqueue);
-			}
-			else if (mo->info->doomednum == POLYOBJ_ANCHOR_DOOMEDNUM)
-			{
-				++numAnchors;
+		if (mo->info->doomednum == POLYOBJ_SPAWN_DOOMEDNUM ||
+			mo->info->doomednum == POLYOBJ_SPAWNCRUSH_DOOMEDNUM)
+		{
+			++numPolyObjects;
 
-				qitem = malloc(sizeof(mobjqitem_t));
-				memset(qitem, 0, sizeof(mobjqitem_t));
-				qitem->mo = mo;
-				M_QueueInsert(&(qitem->mqitem), &anchorqueue);
-			}
+			qitem = malloc(sizeof(mobjqitem_t));
+			memset(qitem, 0, sizeof(mobjqitem_t));
+			qitem->mo = mo;
+			M_QueueInsert(&(qitem->mqitem), &spawnqueue);
+		}
+		else if (mo->info->doomednum == POLYOBJ_ANCHOR_DOOMEDNUM)
+		{
+			++numAnchors;
+
+			qitem = malloc(sizeof(mobjqitem_t));
+			memset(qitem, 0, sizeof(mobjqitem_t));
+			qitem->mo = mo;
+			M_QueueInsert(&(qitem->mqitem), &anchorqueue);
 		}
 	}
 
@@ -1657,7 +1648,7 @@ void T_PolyObjRotate(polyrotate_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjRotate: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -1742,7 +1733,7 @@ void T_PolyObjMove(polymove_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjMove: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -1815,7 +1806,7 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjWaypoint: thinker with invalid id %d removed.", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -1826,9 +1817,9 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 
 	// Find out target first.
 	// We redo this each tic to make savegame compatibility easier.
-	for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+	for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 	{
-		if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+		if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)wp;
@@ -1882,7 +1873,8 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 		po->lines[0]->backsector->floorheight = target->z - amtz;
 		po->lines[0]->backsector->ceilingheight = target->z + amtz;
 		// Sal: Remember to check your sectors!
-		P_CheckSector(po->lines[0]->frontsector, (boolean)(po->damage));
+		// Monster Iestyn: we only need to bother with the back sector, now that P_CheckSector automatically checks the blockmap
+		//  updating objects in the front one too just added teleporting to ground bugs
 		P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage));
 		// Apply action to mirroring polyobjects as well
 		start = 0;
@@ -1896,7 +1888,8 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 			po->lines[0]->backsector->floorheight += diffz; // move up/down by same amount as the parent did
 			po->lines[0]->backsector->ceilingheight += diffz;
 			// Sal: Remember to check your sectors!
-			P_CheckSector(po->lines[0]->frontsector, (boolean)(po->damage));
+			// Monster Iestyn: we only need to bother with the back sector, now that P_CheckSector automatically checks the blockmap
+			//  updating objects in the front one too just added teleporting to ground bugs
 			P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage));
 		}
 
@@ -1907,9 +1900,9 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 			CONS_Debug(DBG_POLYOBJ, "Looking for next waypoint...\n");
 
 			// Find next waypoint
-			for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+			for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 			{
-				if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)wp;
@@ -1917,23 +1910,23 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 				if (mo2->type != MT_TUBEWAYPOINT)
 					continue;
 
-				if (mo2->threshold == th->sequence)
+				if (mo2->threshold != th->sequence)
+					continue;
+
+				if (th->direction == -1)
 				{
-					if (th->direction == -1)
+					if (mo2->health == target->health - 1)
 					{
-						if (mo2->health == target->health - 1)
-						{
-							waypoint = mo2;
-							break;
-						}
+						waypoint = mo2;
+						break;
 					}
-					else
+				}
+				else
+				{
+					if (mo2->health == target->health + 1)
 					{
-						if (mo2->health == target->health + 1)
-						{
-							waypoint = mo2;
-							break;
-						}
+						waypoint = mo2;
+						break;
 					}
 				}
 			}
@@ -1946,9 +1939,9 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 					th->stophere = true;
 				}
 
-				for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+				for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 				{
-					if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+					if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)wp;
@@ -1956,22 +1949,22 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 					if (mo2->type != MT_TUBEWAYPOINT)
 						continue;
 
-					if (mo2->threshold == th->sequence)
+					if (mo2->threshold != th->sequence)
+						continue;
+
+					if (th->direction == -1)
 					{
-						if (th->direction == -1)
-						{
-							if (waypoint == NULL)
-								waypoint = mo2;
-							else if (mo2->health > waypoint->health)
-								waypoint = mo2;
-						}
-						else
+						if (waypoint == NULL)
+							waypoint = mo2;
+						else if (mo2->health > waypoint->health)
+							waypoint = mo2;
+					}
+					else
+					{
+						if (mo2->health == 0)
 						{
-							if (mo2->health == 0)
-							{
-								waypoint = mo2;
-								break;
-							}
+							waypoint = mo2;
+							break;
 						}
 					}
 				}
@@ -1983,9 +1976,9 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 				if (!th->continuous)
 					th->comeback = false;
 
-				for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+				for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 				{
-					if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+					if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)wp;
@@ -1993,23 +1986,23 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 					if (mo2->type != MT_TUBEWAYPOINT)
 						continue;
 
-					if (mo2->threshold == th->sequence)
+					if (mo2->threshold != th->sequence)
+						continue;
+
+					if (th->direction == -1)
 					{
-						if (th->direction == -1)
+						if (mo2->health == target->health - 1)
 						{
-							if (mo2->health == target->health - 1)
-							{
-								waypoint = mo2;
-								break;
-							}
+							waypoint = mo2;
+							break;
 						}
-						else
+					}
+					else
+					{
+						if (mo2->health == target->health + 1)
 						{
-							if (mo2->health == target->health + 1)
-							{
-								waypoint = mo2;
-								break;
-							}
+							waypoint = mo2;
+							break;
 						}
 					}
 				}
@@ -2059,8 +2052,9 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 	po->lines[0]->backsector->floorheight += momz;
 	po->lines[0]->backsector->ceilingheight += momz;
 	// Sal: Remember to check your sectors!
-	P_CheckSector(po->lines[0]->frontsector, (boolean)(po->damage)); // frontsector is NEEDED for crushing
-	P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage)); // backsector may not be necessary, but just in case
+	// Monster Iestyn: we only need to bother with the back sector, now that P_CheckSector automatically checks the blockmap
+	//  updating objects in the front one too just added teleporting to ground bugs
+	P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage));
 
 	// Apply action to mirroring polyobjects as well
 	start = 0;
@@ -2074,7 +2068,8 @@ void T_PolyObjWaypoint(polywaypoint_t *th)
 		po->lines[0]->backsector->floorheight += momz;
 		po->lines[0]->backsector->ceilingheight += momz;
 		// Sal: Remember to check your sectors!
-		P_CheckSector(po->lines[0]->frontsector, (boolean)(po->damage));
+		// Monster Iestyn: we only need to bother with the back sector, now that P_CheckSector automatically checks the blockmap
+		//  updating objects in the front one too just added teleporting to ground bugs
 		P_CheckSector(po->lines[0]->backsector, (boolean)(po->damage));
 	}
 }
@@ -2089,7 +2084,7 @@ void T_PolyDoorSlide(polyslidedoor_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyDoorSlide: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -2194,7 +2189,7 @@ void T_PolyDoorSwing(polyswingdoor_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyDoorSwing: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -2293,7 +2288,7 @@ void T_PolyObjDisplace(polydisplace_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjDisplace: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -2333,7 +2328,7 @@ void T_PolyObjRotDisplace(polyrotdisplace_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjRotDisplace: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -2390,7 +2385,7 @@ INT32 EV_DoPolyObjRotate(polyrotdata_t *prdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polyrotate_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjRotate;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2455,7 +2450,7 @@ INT32 EV_DoPolyObjMove(polymovedata_t *pmdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjMove;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2516,7 +2511,7 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polywaypoint_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjWaypoint;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2534,9 +2529,9 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 	th->stophere = false;
 
 	// Find the first waypoint we need to use
-	for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+	for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 	{
-		if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+		if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)wp;
@@ -2544,31 +2539,31 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 		if (mo2->type != MT_TUBEWAYPOINT)
 			continue;
 
-		if (mo2->threshold == th->sequence)
+		if (mo2->threshold != th->sequence)
+			continue;
+
+		if (th->direction == -1) // highest waypoint #
 		{
-			if (th->direction == -1) // highest waypoint #
+			if (mo2->health == 0)
+				last = mo2;
+			else
 			{
-				if (mo2->health == 0)
-					last = mo2;
-				else
-				{
-					if (first == NULL)
-						first = mo2;
-					else if (mo2->health > first->health)
-						first = mo2;
-				}
+				if (first == NULL)
+					first = mo2;
+				else if (mo2->health > first->health)
+					first = mo2;
 			}
-			else // waypoint 0
+		}
+		else // waypoint 0
+		{
+			if (mo2->health == 0)
+				first = mo2;
+			else
 			{
-				if (mo2->health == 0)
-					first = mo2;
-				else
-				{
-					if (last == NULL)
-						last = mo2;
-					else if (mo2->health > last->health)
-						last = mo2;
-				}
+				if (last == NULL)
+					last = mo2;
+				else if (mo2->health > last->health)
+					last = mo2;
 			}
 		}
 	}
@@ -2605,9 +2600,9 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 
 	// Find the actual target movement waypoint
 	target = first;
-	/*for (wp = thinkercap.next; wp != &thinkercap; wp = wp->next)
+	/*for (wp = thlist[THINK_MOBJ].next; wp != &thlist[THINK_MOBJ]; wp = wp->next)
 	{
-		if (wp->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+		if (wp->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)wp;
@@ -2615,23 +2610,23 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 		if (mo2->type != MT_TUBEWAYPOINT)
 			continue;
 
-		if (mo2->threshold == th->sequence)
+		if (mo2->threshold != th->sequence)
+			continue;
+
+		if (th->direction == -1) // highest waypoint #
 		{
-			if (th->direction == -1) // highest waypoint #
+			if (mo2->health == first->health - 1)
 			{
-				if (mo2->health == first->health - 1)
-				{
-					target = mo2;
-					break;
-				}
+				target = mo2;
+				break;
 			}
-			else // waypoint 0
+		}
+		else // waypoint 0
+		{
+			if (mo2->health == first->health + 1)
 			{
-				if (mo2->health == first->health + 1)
-				{
-					target = mo2;
-					break;
-				}
+				target = mo2;
+				break;
 			}
 		}
 	}*/
@@ -2662,7 +2657,7 @@ static void Polyobj_doSlideDoor(polyobj_t *po, polydoordata_t *doordata)
 	// allocate and add a new slide door thinker
 	th = Z_Malloc(sizeof(polyslidedoor_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSlide;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 
 	// point the polyobject to this thinker
 	po->thinker = &th->thinker;
@@ -2710,7 +2705,7 @@ static void Polyobj_doSwingDoor(polyobj_t *po, polydoordata_t *doordata)
 	// allocate and add a new swing door thinker
 	th = Z_Malloc(sizeof(polyswingdoor_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSwing;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 
 	// point the polyobject to this thinker
 	po->thinker = &th->thinker;
@@ -2792,7 +2787,7 @@ INT32 EV_DoPolyObjDisplace(polydisplacedata_t *prdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polydisplace_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjDisplace;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2838,7 +2833,7 @@ INT32 EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *prdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polyrotdisplace_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjRotDisplace;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2875,7 +2870,7 @@ void T_PolyObjFlag(polymove_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjFlag: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -2939,7 +2934,7 @@ INT32 EV_DoPolyObjFlag(line_t *pfdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjFlag;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
@@ -2978,7 +2973,7 @@ void T_PolyObjFade(polyfade_t *th)
 #else
 	{
 		CONS_Debug(DBG_POLYOBJ, "T_PolyObjFade: thinker with invalid id %d removed.\n", th->polyObjNum);
-		P_RemoveThinkerDelayed(&th->thinker);
+		P_RemoveThinker(&th->thinker);
 		return;
 	}
 #endif
@@ -3089,7 +3084,7 @@ INT32 EV_DoPolyObjFade(polyfadedata_t *pfdata)
 	// create a new thinker
 	th = Z_Malloc(sizeof(polyfade_t), PU_LEVSPEC, NULL);
 	th->thinker.function.acp1 = (actionf_p1)T_PolyObjFade;
-	PolyObj_AddThinker(&th->thinker);
+	P_AddThinker(THINK_POLYOBJ, &th->thinker);
 	po->thinker = &th->thinker;
 
 	// set fields
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 0d58387b9e6be03187077ebf3642cda67cd7365b..24b68b97120ec81dec179beef0babe26ab473fb4 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1262,12 +1262,11 @@ typedef enum
 	MD2_HNEXT       = 1<<7,
 	MD2_HPREV       = 1<<8,
 	MD2_FLOORROVER  = 1<<9,
-#ifdef ESLOPE
 	MD2_CEILINGROVER = 1<<10,
-	MD2_SLOPE        = 1<<11
-#else
-	MD2_CEILINGROVER = 1<<10
+#ifdef ESLOPE
+	MD2_SLOPE        = 1<<11,
 #endif
+	MD2_COLORIZED    = 1<<12,
 } mobj_diff2_t;
 
 typedef enum
@@ -1304,6 +1303,10 @@ typedef enum
 	tc_fade,
 	tc_fadecolormap,
 	tc_planedisplace,
+#ifdef ESLOPE
+	tc_dynslopeline,
+	tc_dynslopevert,
+#endif // ESLOPE
 #ifdef POLYOBJECTS
 	tc_polyrotate, // haleyjd 03/26/06: polyobjects
 	tc_polymove,
@@ -1342,6 +1345,14 @@ static inline UINT32 SavePlayer(const player_t *player)
 	return 0xFFFFFFFF;
 }
 
+#ifdef ESLOPE
+static UINT32 SaveSlope(const pslope_t *slope)
+{
+	if (slope) return (UINT32)(slope->id);
+	return 0xFFFFFFFF;
+}
+#endif // ESLOPE
+
 //
 // SaveMobjThinker
 //
@@ -1473,6 +1484,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	if (mobj->standingslope)
 		diff2 |= MD2_SLOPE;
 #endif
+	if (mobj->colorized)
+		diff2 |= MD2_COLORIZED;
 	if (diff2 != 0)
 		diff |= MD_MORE;
 
@@ -1635,6 +1648,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	if (diff2 & MD2_SLOPE)
 		WRITEUINT16(save_p, mobj->standingslope->id);
 #endif
+	if (diff2 & MD2_COLORIZED)
+		WRITEUINT8(save_p, mobj->colorized);
 
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
@@ -1979,6 +1994,23 @@ static void SavePlaneDisplaceThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->speed);
 	WRITEUINT8(save_p, ht->type);
 }
+#ifdef ESLOPE
+/// Save a dynamic slope thinker.
+static inline void SaveDynamicSlopeThinker(const thinker_t *th, const UINT8 type)
+{
+	const dynplanethink_t* ht = (const void*)th;
+
+	WRITEUINT8(save_p, type);
+	WRITEUINT8(save_p, ht->type);
+	WRITEUINT32(save_p, SaveSlope(ht->slope));
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEFIXED(save_p, ht->extent);
+
+	WRITEMEM(save_p, ht->tags, sizeof(ht->tags));
+    WRITEMEM(save_p, ht->vex, sizeof(ht->vex));
+}
+#endif // ESLOPE
+
 #ifdef POLYOBJECTS
 
 //
@@ -2135,236 +2167,252 @@ static inline void SaveWhatThinker(const thinker_t *th, const UINT8 type)
 static void P_NetArchiveThinkers(void)
 {
 	const thinker_t *th;
-	UINT32 numsaved = 0;
+	UINT32 i;
 
 	WRITEUINT32(save_p, ARCHIVEBLOCK_THINKERS);
 
-	// save off the current thinkers
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		if (!(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed
-		 || th->function.acp1 == (actionf_p1)P_NullPrecipThinker))
-			numsaved++;
-
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			SaveMobjThinker(th, tc_mobj);
-			continue;
-		}
-#ifdef PARANOIA
-		else if (th->function.acp1 == (actionf_p1)P_NullPrecipThinker);
-#endif
-		else if (th->function.acp1 == (actionf_p1)T_MoveCeiling)
-		{
-			SaveCeilingThinker(th, tc_ceiling);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_CrushCeiling)
-		{
-			SaveCeilingThinker(th, tc_crushceiling);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_MoveFloor)
-		{
-			SaveFloormoveThinker(th, tc_floor);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_LightningFlash)
-		{
-			SaveLightflashThinker(th, tc_flash);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_StrobeFlash)
-		{
-			SaveStrobeThinker(th, tc_strobe);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Glow)
-		{
-			SaveGlowThinker(th, tc_glow);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_FireFlicker)
-		{
-			SaveFireflickerThinker(th, tc_fireflicker);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_MoveElevator)
-		{
-			SaveElevatorThinker(th, tc_elevator);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_ContinuousFalling)
-		{
-			SaveSpecialLevelThinker(th, tc_continuousfalling);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_ThwompSector)
-		{
-			SaveSpecialLevelThinker(th, tc_thwomp);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_NoEnemiesSector)
-		{
-			SaveSpecialLevelThinker(th, tc_noenemies);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_EachTimeThinker)
-		{
-			SaveSpecialLevelThinker(th, tc_eachtime);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_RaiseSector)
-		{
-			SaveSpecialLevelThinker(th, tc_raisesector);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_CameraScanner)
-		{
-			SaveElevatorThinker(th, tc_camerascanner);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Scroll)
-		{
-			SaveScrollThinker(th, tc_scroll);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Friction)
-		{
-			SaveFrictionThinker(th, tc_friction);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Pusher)
-		{
-			SavePusherThinker(th, tc_pusher);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_BounceCheese)
-		{
-			SaveSpecialLevelThinker(th, tc_bouncecheese);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_StartCrumble)
-		{
-			SaveElevatorThinker(th, tc_startcrumble);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_MarioBlock)
-		{
-			SaveSpecialLevelThinker(th, tc_marioblock);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_MarioBlockChecker)
-		{
-			SaveSpecialLevelThinker(th, tc_marioblockchecker);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_SpikeSector)
-		{
-			SaveSpecialLevelThinker(th, tc_spikesector);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_FloatSector)
-		{
-			SaveSpecialLevelThinker(th, tc_floatsector);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_BridgeThinker)
-		{
-			SaveSpecialLevelThinker(th, tc_bridgethinker);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_LaserFlash)
-		{
-			SaveLaserThinker(th, tc_laserflash);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_LightFade)
-		{
-			SaveLightlevelThinker(th, tc_lightfade);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_ExecutorDelay)
-		{
-			SaveExecutorThinker(th, tc_executor);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Disappear)
-		{
-			SaveDisappearThinker(th, tc_disappear);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_Fade)
-		{
-			SaveFadeThinker(th, tc_fade);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_FadeColormap)
-		{
-			SaveFadeColormapThinker(th, tc_fadecolormap);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PlaneDisplace)
+		UINT32 numsaved = 0;
+		// save off the current thinkers
+		for (th = thlist[i].next; th != &thlist[i]; th = th->next)
 		{
-			SavePlaneDisplaceThinker(th, tc_planedisplace);
-			continue;
-		}
+			if (!(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed
+			 || th->function.acp1 == (actionf_p1)P_NullPrecipThinker))
+				numsaved++;
+
+			if (th->function.acp1 == (actionf_p1)P_MobjThinker)
+			{
+				SaveMobjThinker(th, tc_mobj);
+				continue;
+			}
+	#ifdef PARANOIA
+			else if (th->function.acp1 == (actionf_p1)P_NullPrecipThinker);
+	#endif
+			else if (th->function.acp1 == (actionf_p1)T_MoveCeiling)
+			{
+				SaveCeilingThinker(th, tc_ceiling);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_CrushCeiling)
+			{
+				SaveCeilingThinker(th, tc_crushceiling);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_MoveFloor)
+			{
+				SaveFloormoveThinker(th, tc_floor);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_LightningFlash)
+			{
+				SaveLightflashThinker(th, tc_flash);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_StrobeFlash)
+			{
+				SaveStrobeThinker(th, tc_strobe);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Glow)
+			{
+				SaveGlowThinker(th, tc_glow);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_FireFlicker)
+			{
+				SaveFireflickerThinker(th, tc_fireflicker);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_MoveElevator)
+			{
+				SaveElevatorThinker(th, tc_elevator);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_ContinuousFalling)
+			{
+				SaveSpecialLevelThinker(th, tc_continuousfalling);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_ThwompSector)
+			{
+				SaveSpecialLevelThinker(th, tc_thwomp);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_NoEnemiesSector)
+			{
+				SaveSpecialLevelThinker(th, tc_noenemies);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_EachTimeThinker)
+			{
+				SaveSpecialLevelThinker(th, tc_eachtime);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_RaiseSector)
+			{
+				SaveSpecialLevelThinker(th, tc_raisesector);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_CameraScanner)
+			{
+				SaveElevatorThinker(th, tc_camerascanner);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Scroll)
+			{
+				SaveScrollThinker(th, tc_scroll);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Friction)
+			{
+				SaveFrictionThinker(th, tc_friction);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Pusher)
+			{
+				SavePusherThinker(th, tc_pusher);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_BounceCheese)
+			{
+				SaveSpecialLevelThinker(th, tc_bouncecheese);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_StartCrumble)
+			{
+				SaveElevatorThinker(th, tc_startcrumble);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_MarioBlock)
+			{
+				SaveSpecialLevelThinker(th, tc_marioblock);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_MarioBlockChecker)
+			{
+				SaveSpecialLevelThinker(th, tc_marioblockchecker);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_SpikeSector)
+			{
+				SaveSpecialLevelThinker(th, tc_spikesector);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_FloatSector)
+			{
+				SaveSpecialLevelThinker(th, tc_floatsector);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_BridgeThinker)
+			{
+				SaveSpecialLevelThinker(th, tc_bridgethinker);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_LaserFlash)
+			{
+				SaveLaserThinker(th, tc_laserflash);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_LightFade)
+			{
+				SaveLightlevelThinker(th, tc_lightfade);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_ExecutorDelay)
+			{
+				SaveExecutorThinker(th, tc_executor);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Disappear)
+			{
+				SaveDisappearThinker(th, tc_disappear);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_Fade)
+			{
+				SaveFadeThinker(th, tc_fade);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_FadeColormap)
+			{
+				SaveFadeColormapThinker(th, tc_fadecolormap);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PlaneDisplace)
+			{
+				SavePlaneDisplaceThinker(th, tc_planedisplace);
+				continue;
+			}
 #ifdef POLYOBJECTS
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjRotate)
-		{
-			SavePolyrotatetThinker(th, tc_polyrotate);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjMove)
-		{
-			SavePolymoveThinker(th, tc_polymove);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjWaypoint)
-		{
-			SavePolywaypointThinker(th, tc_polywaypoint);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyDoorSlide)
-		{
-			SavePolyslidedoorThinker(th, tc_polyslidedoor);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyDoorSwing)
-		{
-			SavePolyswingdoorThinker(th, tc_polyswingdoor);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjFlag)
-		{
-			SavePolymoveThinker(th, tc_polyflag);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjDisplace)
-		{
-			SavePolydisplaceThinker(th, tc_polydisplace);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjRotDisplace)
-		{
-			SavePolyrotdisplaceThinker(th, tc_polyrotdisplace);
-			continue;
-		}
-		else if (th->function.acp1 == (actionf_p1)T_PolyObjFade)
-		{
-			SavePolyfadeThinker(th, tc_polyfade);
-			continue;
-		}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjRotate)
+			{
+				SavePolyrotatetThinker(th, tc_polyrotate);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjMove)
+			{
+				SavePolymoveThinker(th, tc_polymove);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjWaypoint)
+			{
+				SavePolywaypointThinker(th, tc_polywaypoint);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyDoorSlide)
+			{
+				SavePolyslidedoorThinker(th, tc_polyslidedoor);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyDoorSwing)
+			{
+				SavePolyswingdoorThinker(th, tc_polyswingdoor);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjFlag)
+			{
+				SavePolymoveThinker(th, tc_polyflag);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjDisplace)
+			{
+				SavePolydisplaceThinker(th, tc_polydisplace);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjRotDisplace)
+			{
+				SavePolyrotdisplaceThinker(th, tc_polyrotdisplace);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_PolyObjFade)
+			{
+				SavePolyfadeThinker(th, tc_polyfade);
+				continue;
+			}
 #endif
+#ifdef ESLOPE
+			else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeLine)
+			{
+				SaveDynamicSlopeThinker(th, tc_dynslopeline);
+				continue;
+			}
+			else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeVert)
+			{
+				SaveDynamicSlopeThinker(th, tc_dynslopevert);
+				continue;
+			}
+#endif // ESLOPE
 #ifdef PARANOIA
-		else if (th->function.acv != P_RemoveThinkerDelayed) // wait garbage collection
-			I_Error("unknown thinker type %p", th->function.acp1);
+			else if (th->function.acp1 != P_RemoveThinkerDelayed) // wait garbage collection
+				I_Error("unknown thinker type %p", th->function.acp1);
 #endif
-	}
+		}
 
-	CONS_Debug(DBG_NETPLAY, "%u thinkers saved\n", numsaved);
+		CONS_Debug(DBG_NETPLAY, "%u thinkers saved in list %d\n", numsaved, i);
 
-	WRITEUINT8(save_p, tc_end);
+		WRITEUINT8(save_p, tc_end);
+	}
 }
 
 // Now save the pointers, tracer and target, but at load time we must
@@ -2376,14 +2424,16 @@ mobj_t *P_FindNewPosition(UINT32 oldposition)
 	thinker_t *th;
 	mobj_t *mobj;
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mobj = (mobj_t *)th;
-		if (mobj->mobjnum == oldposition)
-			return mobj;
+		if (mobj->mobjnum != oldposition)
+			continue;
+
+		return mobj;
 	}
 	CONS_Debug(DBG_GAMELOGIC, "mobj not found\n");
 	return NULL;
@@ -2413,12 +2463,26 @@ static inline player_t *LoadPlayer(UINT32 player)
 	return &players[player];
 }
 
+#ifdef ESLOPE
+static inline pslope_t *LoadSlope(UINT32 slopeid)
+{
+	pslope_t *p = slopelist;
+	if (slopeid > slopecount) return NULL;
+	do
+	{
+		if (p->id == slopeid)
+			return p;
+	} while ((p = p->next));
+	return NULL;
+}
+#endif // ESLOPE
+
 //
 // LoadMobjThinker
 //
 // Loads a mobj_t from a save game
 //
-static void LoadMobjThinker(actionf_p1 thinker)
+static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 {
 	thinker_t *next;
 	mobj_t *mobj;
@@ -2479,7 +2543,7 @@ static void LoadMobjThinker(actionf_p1 thinker)
 		if (mapthings[spawnpointnum].type == 1705 || mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case
 		{
 			P_SpawnHoopsAndRings(&mapthings[spawnpointnum], false);
-			return;
+			return NULL;
 		}
 
 		mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
@@ -2660,7 +2724,8 @@ static void LoadMobjThinker(actionf_p1 thinker)
 	if (diff2 & MD2_SLOPE)
 		mobj->standingslope = P_SlopeById(READUINT16(save_p));
 #endif
-
+	if (diff2 & MD2_COLORIZED)
+		mobj->colorized = READUINT8(save_p);
 
 	if (diff & MD_REDFLAG)
 	{
@@ -2686,9 +2751,9 @@ static void LoadMobjThinker(actionf_p1 thinker)
 			mobj->player->viewz = mobj->player->mo->z + mobj->player->viewheight;
 	}
 
-	P_AddThinker(&mobj->thinker);
-
 	mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function
+
+	return &mobj->thinker;
 }
 
 //
@@ -2702,7 +2767,7 @@ static void LoadMobjThinker(actionf_p1 thinker)
 //		2 - Ceiling Only
 //		3 - Both
 //
-static void LoadSpecialLevelThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
+static thinker_t* LoadSpecialLevelThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 {
 	levelspecthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	size_t i;
@@ -2723,7 +2788,7 @@ static void LoadSpecialLevelThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 			ht->sector->floordata = ht;
 	}
 
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2731,7 +2796,7 @@ static void LoadSpecialLevelThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 //
 // Loads a ceiling_t from a save game
 //
-static void LoadCeilingThinker(actionf_p1 thinker)
+static thinker_t* LoadCeilingThinker(actionf_p1 thinker)
 {
 	ceiling_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2752,7 +2817,7 @@ static void LoadCeilingThinker(actionf_p1 thinker)
 	ht->sourceline = READFIXED(save_p);
 	if (ht->sector)
 		ht->sector->ceilingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2760,7 +2825,7 @@ static void LoadCeilingThinker(actionf_p1 thinker)
 //
 // Loads a floormove_t from a save game
 //
-static void LoadFloormoveThinker(actionf_p1 thinker)
+static thinker_t* LoadFloormoveThinker(actionf_p1 thinker)
 {
 	floormove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2776,7 +2841,7 @@ static void LoadFloormoveThinker(actionf_p1 thinker)
 	ht->delaytimer = READFIXED(save_p);
 	if (ht->sector)
 		ht->sector->floordata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2784,7 +2849,7 @@ static void LoadFloormoveThinker(actionf_p1 thinker)
 //
 // Loads a lightflash_t from a save game
 //
-static void LoadLightflashThinker(actionf_p1 thinker)
+static thinker_t* LoadLightflashThinker(actionf_p1 thinker)
 {
 	lightflash_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2793,7 +2858,7 @@ static void LoadLightflashThinker(actionf_p1 thinker)
 	ht->minlight = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2801,7 +2866,7 @@ static void LoadLightflashThinker(actionf_p1 thinker)
 //
 // Loads a strobe_t from a save game
 //
-static void LoadStrobeThinker(actionf_p1 thinker)
+static thinker_t* LoadStrobeThinker(actionf_p1 thinker)
 {
 	strobe_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2813,7 +2878,7 @@ static void LoadStrobeThinker(actionf_p1 thinker)
 	ht->brighttime = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2821,7 +2886,7 @@ static void LoadStrobeThinker(actionf_p1 thinker)
 //
 // Loads a glow_t from a save game
 //
-static void LoadGlowThinker(actionf_p1 thinker)
+static thinker_t* LoadGlowThinker(actionf_p1 thinker)
 {
 	glow_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2832,14 +2897,14 @@ static void LoadGlowThinker(actionf_p1 thinker)
 	ht->speed = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 //
 // LoadFireflickerThinker
 //
 // Loads a fireflicker_t from a save game
 //
-static void LoadFireflickerThinker(actionf_p1 thinker)
+static thinker_t* LoadFireflickerThinker(actionf_p1 thinker)
 {
 	fireflicker_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2850,14 +2915,14 @@ static void LoadFireflickerThinker(actionf_p1 thinker)
 	ht->minlight = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 //
 // LoadElevatorThinker
 //
 // Loads a elevator_t from a save game
 //
-static void LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
+static thinker_t* LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 {
 	elevator_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2887,7 +2952,7 @@ static void LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 			ht->sector->floordata = ht;
 	}
 
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2895,7 +2960,7 @@ static void LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 //
 // Loads a scroll_t from a save game
 //
-static void LoadScrollThinker(actionf_p1 thinker)
+static thinker_t* LoadScrollThinker(actionf_p1 thinker)
 {
 	scroll_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2909,7 +2974,7 @@ static void LoadScrollThinker(actionf_p1 thinker)
 	ht->accel = READINT32(save_p);
 	ht->exclusive = READINT32(save_p);
 	ht->type = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2917,7 +2982,7 @@ static void LoadScrollThinker(actionf_p1 thinker)
 //
 // Loads a friction_t from a save game
 //
-static inline void LoadFrictionThinker(actionf_p1 thinker)
+static inline thinker_t* LoadFrictionThinker(actionf_p1 thinker)
 {
 	friction_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2926,7 +2991,7 @@ static inline void LoadFrictionThinker(actionf_p1 thinker)
 	ht->affectee = READINT32(save_p);
 	ht->referrer = READINT32(save_p);
 	ht->roverfriction = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2934,7 +2999,7 @@ static inline void LoadFrictionThinker(actionf_p1 thinker)
 //
 // Loads a pusher_t from a save game
 //
-static void LoadPusherThinker(actionf_p1 thinker)
+static thinker_t* LoadPusherThinker(actionf_p1 thinker)
 {
 	pusher_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2952,7 +3017,7 @@ static void LoadPusherThinker(actionf_p1 thinker)
 	ht->exclusive = READINT32(save_p);
 	ht->slider = READINT32(save_p);
 	ht->source = P_GetPushThing(ht->affectee);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2960,7 +3025,7 @@ static void LoadPusherThinker(actionf_p1 thinker)
 //
 // Loads a laserthink_t from a save game
 //
-static inline void LoadLaserThinker(actionf_p1 thinker)
+static inline thinker_t* LoadLaserThinker(actionf_p1 thinker)
 {
 	laserthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ffloor_t *rover = NULL;
@@ -2972,7 +3037,7 @@ static inline void LoadLaserThinker(actionf_p1 thinker)
 		if (rover->secnum == (size_t)(ht->sec - sectors)
 		&& rover->master == ht->sourceline)
 			ht->ffloor = rover;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -2980,7 +3045,7 @@ static inline void LoadLaserThinker(actionf_p1 thinker)
 //
 // Loads a lightlevel_t from a save game
 //
-static inline void LoadLightlevelThinker(actionf_p1 thinker)
+static inline thinker_t* LoadLightlevelThinker(actionf_p1 thinker)
 {
 	lightlevel_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2992,7 +3057,7 @@ static inline void LoadLightlevelThinker(actionf_p1 thinker)
 	ht->timer = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3000,7 +3065,7 @@ static inline void LoadLightlevelThinker(actionf_p1 thinker)
 //
 // Loads a executor_t from a save game
 //
-static inline void LoadExecutorThinker(actionf_p1 thinker)
+static inline thinker_t* LoadExecutorThinker(actionf_p1 thinker)
 {
 	executor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3008,7 +3073,7 @@ static inline void LoadExecutorThinker(actionf_p1 thinker)
 	ht->caller = LoadMobj(READUINT32(save_p));
 	ht->sector = LoadSector(READUINT32(save_p));
 	ht->timer = READINT32(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3016,7 +3081,7 @@ static inline void LoadExecutorThinker(actionf_p1 thinker)
 //
 // Loads a disappear_t thinker
 //
-static inline void LoadDisappearThinker(actionf_p1 thinker)
+static inline thinker_t* LoadDisappearThinker(actionf_p1 thinker)
 {
 	disappear_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3027,7 +3092,7 @@ static inline void LoadDisappearThinker(actionf_p1 thinker)
 	ht->affectee = READINT32(save_p);
 	ht->sourceline = READINT32(save_p);
 	ht->exists = READINT32(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3035,7 +3100,7 @@ static inline void LoadDisappearThinker(actionf_p1 thinker)
 //
 // Loads a fade_t thinker
 //
-static inline void LoadFadeThinker(actionf_p1 thinker)
+static inline thinker_t* LoadFadeThinker(actionf_p1 thinker)
 {
 	sector_t *ss;
 	fade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3074,14 +3139,14 @@ static inline void LoadFadeThinker(actionf_p1 thinker)
 			j++;
 		}
 	}
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 // LoadFadeColormapThinker
 //
 // Loads a fadecolormap_t from a save game
 //
-static inline void LoadFadeColormapThinker(actionf_p1 thinker)
+static inline thinker_t* LoadFadeColormapThinker(actionf_p1 thinker)
 {
 	fadecolormap_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3093,7 +3158,7 @@ static inline void LoadFadeColormapThinker(actionf_p1 thinker)
 	ht->timer = READINT32(save_p);
 	if (ht->sector)
 		ht->sector->fadecolormapdata = ht;
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3101,18 +3166,36 @@ static inline void LoadFadeColormapThinker(actionf_p1 thinker)
 //
 // Loads a planedisplace_t thinker
 //
-static inline void LoadPlaneDisplaceThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPlaneDisplaceThinker(actionf_p1 thinker)
 {
 	planedisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
+
 	ht->affectee = READINT32(save_p);
 	ht->control = READINT32(save_p);
 	ht->last_height = READFIXED(save_p);
 	ht->speed = READFIXED(save_p);
 	ht->type = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
+#ifdef ESLOPE
+/// Save a dynamic slope thinker.
+static inline thinker_t* LoadDynamicSlopeThinker(actionf_p1 thinker)
+{
+	dynplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+
+	ht->type = READUINT8(save_p);
+	ht->slope = LoadSlope(READUINT32(save_p));
+	ht->sourceline = LoadLine(READUINT32(save_p));
+	ht->extent = READFIXED(save_p);
+	READMEM(save_p, ht->tags, sizeof(ht->tags));
+	READMEM(save_p, ht->vex, sizeof(ht->vex));
+	return &ht->thinker;
+}
+#endif // ESLOPE
+
 #ifdef POLYOBJECTS
 
 //
@@ -3120,14 +3203,14 @@ static inline void LoadPlaneDisplaceThinker(actionf_p1 thinker)
 //
 // Loads a polyrotate_t thinker
 //
-static inline void LoadPolyrotatetThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolyrotatetThinker(actionf_p1 thinker)
 {
 	polyrotate_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
 	ht->polyObjNum = READINT32(save_p);
 	ht->speed = READINT32(save_p);
 	ht->distance = READINT32(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3135,7 +3218,7 @@ static inline void LoadPolyrotatetThinker(actionf_p1 thinker)
 //
 // Loads a polymovet_t thinker
 //
-static void LoadPolymoveThinker(actionf_p1 thinker)
+static thinker_t* LoadPolymoveThinker(actionf_p1 thinker)
 {
 	polymove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3145,7 +3228,7 @@ static void LoadPolymoveThinker(actionf_p1 thinker)
 	ht->momy = READFIXED(save_p);
 	ht->distance = READINT32(save_p);
 	ht->angle = READANGLE(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3153,7 +3236,7 @@ static void LoadPolymoveThinker(actionf_p1 thinker)
 //
 // Loads a polywaypoint_t thinker
 //
-static inline void LoadPolywaypointThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolywaypointThinker(actionf_p1 thinker)
 {
 	polywaypoint_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3169,7 +3252,7 @@ static inline void LoadPolywaypointThinker(actionf_p1 thinker)
 	ht->diffx = READFIXED(save_p);
 	ht->diffy = READFIXED(save_p);
 	ht->diffz = READFIXED(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3177,7 +3260,7 @@ static inline void LoadPolywaypointThinker(actionf_p1 thinker)
 //
 // loads a polyslidedoor_t thinker
 //
-static inline void LoadPolyslidedoorThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolyslidedoorThinker(actionf_p1 thinker)
 {
 	polyslidedoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3194,7 +3277,7 @@ static inline void LoadPolyslidedoorThinker(actionf_p1 thinker)
 	ht->momx = READFIXED(save_p);
 	ht->momy = READFIXED(save_p);
 	ht->closing = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3202,7 +3285,7 @@ static inline void LoadPolyslidedoorThinker(actionf_p1 thinker)
 //
 // Loads a polyswingdoor_t thinker
 //
-static inline void LoadPolyswingdoorThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolyswingdoorThinker(actionf_p1 thinker)
 {
 	polyswingdoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3214,7 +3297,7 @@ static inline void LoadPolyswingdoorThinker(actionf_p1 thinker)
 	ht->initDistance = READINT32(save_p);
 	ht->distance = READINT32(save_p);
 	ht->closing = READUINT8(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3222,7 +3305,7 @@ static inline void LoadPolyswingdoorThinker(actionf_p1 thinker)
 //
 // Loads a polydisplace_t thinker
 //
-static inline void LoadPolydisplaceThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolydisplaceThinker(actionf_p1 thinker)
 {
 	polydisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3231,10 +3314,10 @@ static inline void LoadPolydisplaceThinker(actionf_p1 thinker)
 	ht->dx = READFIXED(save_p);
 	ht->dy = READFIXED(save_p);
 	ht->oldHeights = READFIXED(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
-static inline void LoadPolyrotdisplaceThinker(actionf_p1 thinker)
+static inline thinker_t* LoadPolyrotdisplaceThinker(actionf_p1 thinker)
 {
 	polyrotdisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3243,7 +3326,7 @@ static inline void LoadPolyrotdisplaceThinker(actionf_p1 thinker)
 	ht->rotscale = READFIXED(save_p);
 	ht->turnobjs = READUINT8(save_p);
 	ht->oldHeights = READFIXED(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 
 //
@@ -3251,7 +3334,7 @@ static inline void LoadPolyrotdisplaceThinker(actionf_p1 thinker)
 //
 // Loads a polyfadet_t thinker
 //
-static void LoadPolyfadeThinker(actionf_p1 thinker)
+static thinker_t* LoadPolyfadeThinker(actionf_p1 thinker)
 {
 	polyfade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3263,7 +3346,7 @@ static void LoadPolyfadeThinker(actionf_p1 thinker)
 	ht->ticbased = (boolean)READUINT8(save_p);
 	ht->duration = READINT32(save_p);
 	ht->timer = READINT32(save_p);
-	P_AddThinker(&ht->thinker);
+	return &ht->thinker;
 }
 #endif
 
@@ -3296,15 +3379,18 @@ static void P_NetUnArchiveThinkers(void)
 		I_Error("Bad $$$.sav at archive block Thinkers");
 
 	// remove all the current thinkers
-	currentthinker = thinkercap.next;
-	for (currentthinker = thinkercap.next; currentthinker != &thinkercap; currentthinker = next)
+	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		next = currentthinker->next;
+		currentthinker = thlist[i].next;
+		for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = next)
+		{
+			next = currentthinker->next;
 
-		if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
-			P_RemoveSavegameMobj((mobj_t *)currentthinker); // item isn't saved, don't remove it
-		else
-			Z_Free(currentthinker);
+			if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
+				P_RemoveSavegameMobj((mobj_t *)currentthinker); // item isn't saved, don't remove it
+			else
+				Z_Free(currentthinker);
+		}
 	}
 
 	// we don't want the removed mobjs to come back
@@ -3318,203 +3404,219 @@ static void P_NetUnArchiveThinkers(void)
 	}
 
 	// read in saved thinkers
-	for (;;)
+	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		tclass = READUINT8(save_p);
+		for (;;)
+		{
+			thinker_t* th = NULL;
+			tclass = READUINT8(save_p);
 
-		if (tclass == tc_end)
-			break; // leave the saved thinker reading loop
-		numloaded++;
+			if (tclass == tc_end)
+				break; // leave the saved thinker reading loop
+			numloaded++;
 
-		switch (tclass)
-		{
-			case tc_mobj:
-				LoadMobjThinker((actionf_p1)P_MobjThinker);
-				break;
+			switch (tclass)
+			{
+				case tc_mobj:
+					th = LoadMobjThinker((actionf_p1)P_MobjThinker);
+					break;
 
-			case tc_ceiling:
-				LoadCeilingThinker((actionf_p1)T_MoveCeiling);
-				break;
+				case tc_ceiling:
+					th = LoadCeilingThinker((actionf_p1)T_MoveCeiling);
+					break;
 
-			case tc_crushceiling:
-				LoadCeilingThinker((actionf_p1)T_CrushCeiling);
-				break;
+				case tc_crushceiling:
+					th = LoadCeilingThinker((actionf_p1)T_CrushCeiling);
+					break;
 
-			case tc_floor:
-				LoadFloormoveThinker((actionf_p1)T_MoveFloor);
-				break;
+				case tc_floor:
+					th = LoadFloormoveThinker((actionf_p1)T_MoveFloor);
+					break;
 
-			case tc_flash:
-				LoadLightflashThinker((actionf_p1)T_LightningFlash);
-				break;
+				case tc_flash:
+					th = LoadLightflashThinker((actionf_p1)T_LightningFlash);
+					break;
 
-			case tc_strobe:
-				LoadStrobeThinker((actionf_p1)T_StrobeFlash);
-				break;
+				case tc_strobe:
+					th = LoadStrobeThinker((actionf_p1)T_StrobeFlash);
+					break;
 
-			case tc_glow:
-				LoadGlowThinker((actionf_p1)T_Glow);
-				break;
+				case tc_glow:
+					th = LoadGlowThinker((actionf_p1)T_Glow);
+					break;
 
-			case tc_fireflicker:
-				LoadFireflickerThinker((actionf_p1)T_FireFlicker);
-				break;
+				case tc_fireflicker:
+					th = LoadFireflickerThinker((actionf_p1)T_FireFlicker);
+					break;
 
-			case tc_elevator:
-				LoadElevatorThinker((actionf_p1)T_MoveElevator, 3);
-				break;
+				case tc_elevator:
+					th = LoadElevatorThinker((actionf_p1)T_MoveElevator, 3);
+					break;
 
-			case tc_continuousfalling:
-				LoadSpecialLevelThinker((actionf_p1)T_ContinuousFalling, 3);
-				break;
+				case tc_continuousfalling:
+					th = LoadSpecialLevelThinker((actionf_p1)T_ContinuousFalling, 3);
+					break;
 
-			case tc_thwomp:
-				LoadSpecialLevelThinker((actionf_p1)T_ThwompSector, 3);
-				break;
+				case tc_thwomp:
+					th = LoadSpecialLevelThinker((actionf_p1)T_ThwompSector, 3);
+					break;
 
-			case tc_noenemies:
-				LoadSpecialLevelThinker((actionf_p1)T_NoEnemiesSector, 0);
-				break;
+				case tc_noenemies:
+					th = LoadSpecialLevelThinker((actionf_p1)T_NoEnemiesSector, 0);
+					break;
 
-			case tc_eachtime:
-				LoadSpecialLevelThinker((actionf_p1)T_EachTimeThinker, 0);
-				break;
+				case tc_eachtime:
+					th = LoadSpecialLevelThinker((actionf_p1)T_EachTimeThinker, 0);
+					break;
 
-			case tc_raisesector:
-				LoadSpecialLevelThinker((actionf_p1)T_RaiseSector, 0);
-				break;
+				case tc_raisesector:
+					th = LoadSpecialLevelThinker((actionf_p1)T_RaiseSector, 0);
+					break;
 
-			/// \todo rewrite all the code that uses an elevator_t but isn't an elevator
-			/// \note working on it!
-			case tc_camerascanner:
-				LoadElevatorThinker((actionf_p1)T_CameraScanner, 0);
-				break;
+				/// \todo rewrite all the code that uses an elevator_t but isn't an elevator
+				/// \note working on it!
+				case tc_camerascanner:
+					th = LoadElevatorThinker((actionf_p1)T_CameraScanner, 0);
+					break;
 
-			case tc_bouncecheese:
-				LoadSpecialLevelThinker((actionf_p1)T_BounceCheese, 2);
-				break;
+				case tc_bouncecheese:
+					th = LoadSpecialLevelThinker((actionf_p1)T_BounceCheese, 2);
+					break;
 
-			case tc_startcrumble:
-				LoadElevatorThinker((actionf_p1)T_StartCrumble, 1);
-				break;
+				case tc_startcrumble:
+					th = LoadElevatorThinker((actionf_p1)T_StartCrumble, 1);
+					break;
 
-			case tc_marioblock:
-				LoadSpecialLevelThinker((actionf_p1)T_MarioBlock, 3);
-				break;
+				case tc_marioblock:
+					th = LoadSpecialLevelThinker((actionf_p1)T_MarioBlock, 3);
+					break;
 
-			case tc_marioblockchecker:
-				LoadSpecialLevelThinker((actionf_p1)T_MarioBlockChecker, 0);
-				break;
+				case tc_marioblockchecker:
+					th = LoadSpecialLevelThinker((actionf_p1)T_MarioBlockChecker, 0);
+					break;
 
-			case tc_spikesector:
-				LoadSpecialLevelThinker((actionf_p1)T_SpikeSector, 0);
-				break;
+				case tc_spikesector:
+					th = LoadSpecialLevelThinker((actionf_p1)T_SpikeSector, 0);
+					break;
 
-			case tc_floatsector:
-				LoadSpecialLevelThinker((actionf_p1)T_FloatSector, 0);
-				break;
+				case tc_floatsector:
+					th = LoadSpecialLevelThinker((actionf_p1)T_FloatSector, 0);
+					break;
 
-			case tc_bridgethinker:
-				LoadSpecialLevelThinker((actionf_p1)T_BridgeThinker, 3);
-				break;
+				case tc_bridgethinker:
+					th = LoadSpecialLevelThinker((actionf_p1)T_BridgeThinker, 3);
+					break;
 
-			case tc_laserflash:
-				LoadLaserThinker((actionf_p1)T_LaserFlash);
-				break;
+				case tc_laserflash:
+					th = LoadLaserThinker((actionf_p1)T_LaserFlash);
+					break;
 
-			case tc_lightfade:
-				LoadLightlevelThinker((actionf_p1)T_LightFade);
-				break;
+				case tc_lightfade:
+					th = LoadLightlevelThinker((actionf_p1)T_LightFade);
+					break;
 
-			case tc_executor:
-				LoadExecutorThinker((actionf_p1)T_ExecutorDelay);
-				restoreNum = true;
-				break;
+				case tc_executor:
+					th = LoadExecutorThinker((actionf_p1)T_ExecutorDelay);
+					restoreNum = true;
+					break;
 
-			case tc_disappear:
-				LoadDisappearThinker((actionf_p1)T_Disappear);
-				break;
+				case tc_disappear:
+					th = LoadDisappearThinker((actionf_p1)T_Disappear);
+					break;
 
-			case tc_fade:
-				LoadFadeThinker((actionf_p1)T_Fade);
-				break;
+				case tc_fade:
+					th = LoadFadeThinker((actionf_p1)T_Fade);
+					break;
 
-			case tc_fadecolormap:
-				LoadFadeColormapThinker((actionf_p1)T_FadeColormap);
-				break;
+				case tc_fadecolormap:
+					th = LoadFadeColormapThinker((actionf_p1)T_FadeColormap);
+					break;
 
-			case tc_planedisplace:
-				LoadPlaneDisplaceThinker((actionf_p1)T_PlaneDisplace);
-				break;
+				case tc_planedisplace:
+					th = LoadPlaneDisplaceThinker((actionf_p1)T_PlaneDisplace);
+					break;
 #ifdef POLYOBJECTS
-			case tc_polyrotate:
-				LoadPolyrotatetThinker((actionf_p1)T_PolyObjRotate);
-				break;
+				case tc_polyrotate:
+					th = LoadPolyrotatetThinker((actionf_p1)T_PolyObjRotate);
+					break;
 
-			case tc_polymove:
-				LoadPolymoveThinker((actionf_p1)T_PolyObjMove);
-				break;
+				case tc_polymove:
+					th = LoadPolymoveThinker((actionf_p1)T_PolyObjMove);
+					break;
 
-			case tc_polywaypoint:
-				LoadPolywaypointThinker((actionf_p1)T_PolyObjWaypoint);
-				break;
+				case tc_polywaypoint:
+					th = LoadPolywaypointThinker((actionf_p1)T_PolyObjWaypoint);
+					break;
 
-			case tc_polyslidedoor:
-				LoadPolyslidedoorThinker((actionf_p1)T_PolyDoorSlide);
-				break;
+				case tc_polyslidedoor:
+					th = LoadPolyslidedoorThinker((actionf_p1)T_PolyDoorSlide);
+					break;
 
-			case tc_polyswingdoor:
-				LoadPolyswingdoorThinker((actionf_p1)T_PolyDoorSwing);
-				break;
+				case tc_polyswingdoor:
+					th = LoadPolyswingdoorThinker((actionf_p1)T_PolyDoorSwing);
+					break;
 
-			case tc_polyflag:
-				LoadPolymoveThinker((actionf_p1)T_PolyObjFlag);
-				break;
+				case tc_polyflag:
+					th = LoadPolymoveThinker((actionf_p1)T_PolyObjFlag);
+					break;
 
-			case tc_polydisplace:
-				LoadPolydisplaceThinker((actionf_p1)T_PolyObjDisplace);
-				break;
+				case tc_polydisplace:
+					th = LoadPolydisplaceThinker((actionf_p1)T_PolyObjDisplace);
+					break;
 
-			case tc_polyrotdisplace:
-				LoadPolyrotdisplaceThinker((actionf_p1)T_PolyObjRotDisplace);
-				break;
+				case tc_polyrotdisplace:
+					th = LoadPolyrotdisplaceThinker((actionf_p1)T_PolyObjRotDisplace);
+					break;
 
-			case tc_polyfade:
-				LoadPolyfadeThinker((actionf_p1)T_PolyObjFade);
-				break;
+				case tc_polyfade:
+					th = LoadPolyfadeThinker((actionf_p1)T_PolyObjFade);
+					break;
 #endif
-			case tc_scroll:
-				LoadScrollThinker((actionf_p1)T_Scroll);
-				break;
+#ifdef ESLOPE
+				case tc_dynslopeline:
+					th = LoadDynamicSlopeThinker((actionf_p1)T_DynamicSlopeLine);
+					break;
 
-			case tc_friction:
-				LoadFrictionThinker((actionf_p1)T_Friction);
-				break;
+				case tc_dynslopevert:
+					th = LoadDynamicSlopeThinker((actionf_p1)T_DynamicSlopeVert);
+					break;
+#endif // ESLOPE
 
-			case tc_pusher:
-				LoadPusherThinker((actionf_p1)T_Pusher);
-				break;
+				case tc_scroll:
+					th = LoadScrollThinker((actionf_p1)T_Scroll);
+					break;
+
+				case tc_friction:
+					th = LoadFrictionThinker((actionf_p1)T_Friction);
+					break;
+
+				case tc_pusher:
+					th = LoadPusherThinker((actionf_p1)T_Pusher);
+					break;
 
-			default:
-				I_Error("P_UnarchiveSpecials: Unknown tclass %d in savegame", tclass);
+				default:
+					I_Error("P_UnarchiveSpecials: Unknown tclass %d in savegame", tclass);
+			}
+			if (th)
+				P_AddThinker(i, th);
 		}
-	}
 
-	CONS_Debug(DBG_NETPLAY, "%u thinkers loaded\n", numloaded);
+		CONS_Debug(DBG_NETPLAY, "%u thinkers loaded in list %d\n", numloaded, i);
+	}
 
 	if (restoreNum)
 	{
 		executor_t *delay = NULL;
 		UINT32 mobjnum;
-		for (currentthinker = thinkercap.next; currentthinker != &thinkercap;
-			currentthinker = currentthinker->next)
+		for (currentthinker = thlist[i].next; currentthinker != &thlist[i];
+		currentthinker = currentthinker->next)
 		{
-			if (currentthinker->function.acp1 == (actionf_p1)T_ExecutorDelay)
-			{
-				delay = (void *)currentthinker;
-				if ((mobjnum = (UINT32)(size_t)delay->caller))
-					delay->caller = P_FindNewPosition(mobjnum);
-			}
+			if (currentthinker->function.acp1 != (actionf_p1)T_ExecutorDelay)
+				continue;
+			delay = (void *)currentthinker;
+			if (!(mobjnum = (UINT32)(size_t)delay->caller))
+				continue;
+			delay->caller = P_FindNewPosition(mobjnum);
 		}
 	}
 }
@@ -3622,14 +3724,14 @@ static inline void P_FinishMobjs(void)
 	mobj_t *mobj;
 
 	// put info field there real value
-	for (currentthinker = thinkercap.next; currentthinker != &thinkercap;
+	for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ];
 		currentthinker = currentthinker->next)
 	{
-		if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			mobj = (mobj_t *)currentthinker;
-			mobj->info = &mobjinfo[mobj->type];
-		}
+		if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		mobj = (mobj_t *)currentthinker;
+		mobj->info = &mobjinfo[mobj->type];
 	}
 }
 
@@ -3640,86 +3742,86 @@ static void P_RelinkPointers(void)
 	UINT32 temp;
 
 	// use info field (value = oldposition) to relink mobjs
-	for (currentthinker = thinkercap.next; currentthinker != &thinkercap;
+	for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ];
 		currentthinker = currentthinker->next)
 	{
-		if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			mobj = (mobj_t *)currentthinker;
+		if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
-			if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
-				continue;
+		mobj = (mobj_t *)currentthinker;
 
-			if (mobj->tracer)
-			{
-				temp = (UINT32)(size_t)mobj->tracer;
-				mobj->tracer = NULL;
-				if (!P_SetTarget(&mobj->tracer, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "tracer not found on %d\n", mobj->type);
-			}
-			if (mobj->target)
-			{
-				temp = (UINT32)(size_t)mobj->target;
-				mobj->target = NULL;
-				if (!P_SetTarget(&mobj->target, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "target not found on %d\n", mobj->type);
-			}
-			if (mobj->hnext)
-			{
-				temp = (UINT32)(size_t)mobj->hnext;
-				mobj->hnext = NULL;
-				if (!(mobj->hnext = P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "hnext not found on %d\n", mobj->type);
-			}
-			if (mobj->hprev)
-			{
-				temp = (UINT32)(size_t)mobj->hprev;
-				mobj->hprev = NULL;
-				if (!(mobj->hprev = P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->capsule)
-			{
-				temp = (UINT32)(size_t)mobj->player->capsule;
-				mobj->player->capsule = NULL;
-				if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "capsule not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->axis1)
-			{
-				temp = (UINT32)(size_t)mobj->player->axis1;
-				mobj->player->axis1 = NULL;
-				if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "axis1 not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->axis2)
-			{
-				temp = (UINT32)(size_t)mobj->player->axis2;
-				mobj->player->axis2 = NULL;
-				if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "axis2 not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->awayviewmobj)
-			{
-				temp = (UINT32)(size_t)mobj->player->awayviewmobj;
-				mobj->player->awayviewmobj = NULL;
-				if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->followmobj)
-			{
-				temp = (UINT32)(size_t)mobj->player->followmobj;
-				mobj->player->followmobj = NULL;
-				if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type);
-			}
-			if (mobj->player && mobj->player->drone)
-			{
-				temp = (UINT32)(size_t)mobj->player->drone;
-				mobj->player->drone = NULL;
-				if (!P_SetTarget(&mobj->player->drone, P_FindNewPosition(temp)))
-					CONS_Debug(DBG_GAMELOGIC, "drone not found on %d\n", mobj->type);
-			}
+		if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
+			continue;
+
+		if (mobj->tracer)
+		{
+			temp = (UINT32)(size_t)mobj->tracer;
+			mobj->tracer = NULL;
+			if (!P_SetTarget(&mobj->tracer, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "tracer not found on %d\n", mobj->type);
+		}
+		if (mobj->target)
+		{
+			temp = (UINT32)(size_t)mobj->target;
+			mobj->target = NULL;
+			if (!P_SetTarget(&mobj->target, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "target not found on %d\n", mobj->type);
+		}
+		if (mobj->hnext)
+		{
+			temp = (UINT32)(size_t)mobj->hnext;
+			mobj->hnext = NULL;
+			if (!(mobj->hnext = P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "hnext not found on %d\n", mobj->type);
+		}
+		if (mobj->hprev)
+		{
+			temp = (UINT32)(size_t)mobj->hprev;
+			mobj->hprev = NULL;
+			if (!(mobj->hprev = P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->capsule)
+		{
+			temp = (UINT32)(size_t)mobj->player->capsule;
+			mobj->player->capsule = NULL;
+			if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "capsule not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->axis1)
+		{
+			temp = (UINT32)(size_t)mobj->player->axis1;
+			mobj->player->axis1 = NULL;
+			if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "axis1 not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->axis2)
+		{
+			temp = (UINT32)(size_t)mobj->player->axis2;
+			mobj->player->axis2 = NULL;
+			if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "axis2 not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->awayviewmobj)
+		{
+			temp = (UINT32)(size_t)mobj->player->awayviewmobj;
+			mobj->player->awayviewmobj = NULL;
+			if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->followmobj)
+		{
+			temp = (UINT32)(size_t)mobj->player->followmobj;
+			mobj->player->followmobj = NULL;
+			if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type);
+		}
+		if (mobj->player && mobj->player->drone)
+		{
+			temp = (UINT32)(size_t)mobj->player->drone;
+			mobj->player->drone = NULL;
+			if (!P_SetTarget(&mobj->player->drone, P_FindNewPosition(temp)))
+				CONS_Debug(DBG_GAMELOGIC, "drone not found on %d\n", mobj->type);
 		}
 	}
 }
@@ -3888,6 +3990,7 @@ static void P_NetArchiveMisc(void)
 	WRITEUINT32(save_p, leveltime);
 	WRITEUINT32(save_p, ssspheres);
 	WRITEINT16(save_p, lastmap);
+	WRITEUINT16(save_p, bossdisabled);
 
 	WRITEUINT16(save_p, emeralds);
 	WRITEUINT8(save_p, stagefailed);
@@ -3965,6 +4068,7 @@ static inline boolean P_NetUnArchiveMisc(void)
 	leveltime = READUINT32(save_p);
 	ssspheres = READUINT32(save_p);
 	lastmap = READINT16(save_p);
+	bossdisabled = READUINT16(save_p);
 
 	emeralds = READUINT16(save_p);
 	stagefailed = READUINT8(save_p);
@@ -4021,15 +4125,15 @@ void P_SaveNetGame(void)
 	P_NetArchiveMisc();
 
 	// Assign the mobjnumber for pointer tracking
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-		{
-			mobj = (mobj_t *)th;
-			if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
-				continue;
-			mobj->mobjnum = i++;
-		}
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		mobj = (mobj_t *)th;
+		if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
+			continue;
+		mobj->mobjnum = i++;
 	}
 
 	P_NetArchivePlayers();
diff --git a/src/p_setup.c b/src/p_setup.c
index 82713c3cb7b354eeb14b3ac8c747e8fdec9bbcd0..a089e0a8f2292751ac0e69d520d1d93d8ecd4e01 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -102,6 +102,7 @@ line_t *lines;
 side_t *sides;
 mapthing_t *mapthings;
 INT32 numstarposts;
+UINT16 bossdisabled;
 boolean levelloading;
 UINT8 levelfadecol;
 
@@ -815,9 +816,9 @@ void P_ReloadRings(void)
 	mapthing_t *mt = mapthings;
 
 	// scan the thinkers to find rings/spheres/hoops to unset
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo = (mobj_t *)th;
@@ -859,12 +860,7 @@ void P_ReloadRings(void)
 			mt->z = (INT16)(R_PointInSubsector(mt->x << FRACBITS, mt->y << FRACBITS)
 				->sector->floorheight>>FRACBITS);
 
-			P_SpawnHoopsAndRings(mt,
-#ifdef MANIASPHERES
-				true);
-#else
-				!G_IsSpecialStage(gamemap)); // prevent flashing spheres in special stages
-#endif
+			P_SpawnHoopsAndRings(mt, true);
 		}
 	}
 	for (i = 0; i < numHoops; i++)
@@ -878,15 +874,10 @@ void P_SwitchSpheresBonusMode(boolean bonustime)
 	mobj_t *mo;
 	thinker_t *th;
 
-#ifndef MANIASPHERES
-	if (G_IsSpecialStage(gamemap)) // prevent flashing spheres in special stages
-		return;
-#endif
-
 	// scan the thinkers to find spheres to switch
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo = (mobj_t *)th;
@@ -1288,6 +1279,9 @@ static void P_LoadLineDefs2(void)
 		// Compile linedef 'text' from both sidedefs 'text' for appropriate specials.
 		switch(ld->special)
 		{
+		case 331: // Trigger linedef executor: Skin - Continuous
+		case 332: // Trigger linedef executor: Skin - Each time
+		case 333: // Trigger linedef executor: Skin - Once
 		case 443: // Calls a named Lua function
 			if (sides[ld->sidenum[0]].text)
 			{
@@ -1498,6 +1492,9 @@ static void P_LoadRawSideDefs2(void *data)
 				break;
 			}
 
+			case 331: // Trigger linedef executor: Skin - Continuous
+			case 332: // Trigger linedef executor: Skin - Each time
+			case 333: // Trigger linedef executor: Skin - Once
 			case 443: // Calls a named Lua function
 			case 459: // Control text prompt (named tag)
 			{
@@ -2209,7 +2206,7 @@ static void P_LevelInitStuff(void)
 	ssspheres = timeinmap = 0;
 
 	// special stage
-	stagefailed = false;
+	stagefailed = true; // assume failed unless proven otherwise - P_GiveEmerald or emerald touchspecial
 	// Reset temporary record data
 	memset(&ntemprecords, 0, sizeof(nightsdata_t));
 
@@ -2228,7 +2225,7 @@ static void P_LevelInitStuff(void)
 		}
 	}
 
-	countdown = countdown2 = 0;
+	countdown = countdown2 = exitfadestarted = 0;
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -2284,7 +2281,6 @@ static void P_LevelInitStuff(void)
 void P_LoadThingsOnly(void)
 {
 	// Search through all the thinkers.
-	mobj_t *mo;
 	thinker_t *think;
 	INT32 i, viewid = -1, centerid = -1; // for skyboxes
 
@@ -2299,15 +2295,11 @@ void P_LoadThingsOnly(void)
 		}
 
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
-
-		mo = (mobj_t *)think;
-
-		if (mo)
-			P_RemoveMobj(mo);
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+		P_RemoveMobj((mobj_t *)think);
 	}
 
 	P_LevelInitStuff();
@@ -2880,7 +2872,10 @@ boolean P_SetupLevel(boolean skipprecip)
 
 		// reset the player starts
 		for (i = 0; i < MAXPLAYERS; i++)
-			playerstarts[i] = NULL;
+			playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL;
+
+		for (i = 0; i < MAX_DM_STARTS; i++)
+			deathmatchstarts[i] = NULL;
 
 		for (i = 0; i < 2; i++)
 			skyboxmo[i] = NULL;
@@ -2916,7 +2911,10 @@ boolean P_SetupLevel(boolean skipprecip)
 
 		// reset the player starts
 		for (i = 0; i < MAXPLAYERS; i++)
-			playerstarts[i] = NULL;
+			playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL;
+
+		for (i = 0; i < MAX_DM_STARTS; i++)
+			deathmatchstarts[i] = NULL;
 
 		for (i = 0; i < 2; i++)
 			skyboxmo[i] = NULL;
@@ -2934,7 +2932,7 @@ boolean P_SetupLevel(boolean skipprecip)
 	P_InitSpecials();
 
 #ifdef ESLOPE
-	P_ResetDynamicSlopes();
+	P_ResetDynamicSlopes(fromnetsave);
 #endif
 
 	P_LoadThings(loademblems);
@@ -3132,7 +3130,7 @@ boolean P_SetupLevel(boolean skipprecip)
 		R_PrecacheLevel();
 
 	nextmapoverride = 0;
-	skipstats = false;
+	skipstats = 0;
 
 	if (!(netgame || multiplayer) && (!modifiedgame || savemoddata))
 		mapvisited[gamemap-1] |= MV_VISITED;
@@ -3443,13 +3441,13 @@ boolean P_AddWadFile(const char *wadfilename)
 	ST_UnloadGraphics();
 	HU_LoadGraphics();
 	ST_LoadGraphics();
-	ST_ReloadSkinFaceGraphics();
 
 	//
 	// look for skins
 	//
 	R_AddSkins(wadnum); // faB: wadfile index in wadfiles[]
 	R_PatchSkins(wadnum); // toast: PATCH PATCH
+	ST_ReloadSkinFaceGraphics();
 
 	//
 	// search for maps
diff --git a/src/p_slopes.c b/src/p_slopes.c
index 3cdbd687efefb7cc674cf858468472f5bf9e68cd..d6080c15d5ff66597b1f9729aa07e3cee6f8dceb 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -25,68 +25,66 @@
 
 #ifdef ESLOPE
 
-static pslope_t *slopelist = NULL;
-static UINT16 slopecount = 0;
+pslope_t *slopelist = NULL;
+UINT16 slopecount = 0;
 
 // Calculate line normal
 void P_CalculateSlopeNormal(pslope_t *slope) {
 	slope->normal.z = FINECOSINE(slope->zangle>>ANGLETOFINESHIFT);
-	slope->normal.x = -FixedMul(FINESINE(slope->zangle>>ANGLETOFINESHIFT), slope->d.x);
-	slope->normal.y = -FixedMul(FINESINE(slope->zangle>>ANGLETOFINESHIFT), slope->d.y);
+	slope->normal.x = FixedMul(FINESINE(slope->zangle>>ANGLETOFINESHIFT), slope->d.x);
+	slope->normal.y = FixedMul(FINESINE(slope->zangle>>ANGLETOFINESHIFT), slope->d.y);
 }
 
-// With a vertex slope that has its vertices set, configure relevant slope info
-static void P_ReconfigureVertexSlope(pslope_t *slope)
+/// Setup slope via 3 vertexes.
+static void ReconfigureViaVertexes (pslope_t *slope, const vector3_t v1, const vector3_t v2, const vector3_t v3)
 {
 	vector3_t vec1, vec2;
 
-	// Set slope normal
-	vec1.x = (slope->vertices[1]->x - slope->vertices[0]->x) << FRACBITS;
-	vec1.y = (slope->vertices[1]->y - slope->vertices[0]->y) << FRACBITS;
-	vec1.z = (slope->vertices[1]->z - slope->vertices[0]->z) << FRACBITS;
-
-	vec2.x = (slope->vertices[2]->x - slope->vertices[0]->x) << FRACBITS;
-	vec2.y = (slope->vertices[2]->y - slope->vertices[0]->y) << FRACBITS;
-	vec2.z = (slope->vertices[2]->z - slope->vertices[0]->z) << FRACBITS;
-
-	// ugggggggh fixed-point maaaaaaath
-	slope->extent = max(
-		max(max(abs(vec1.x), abs(vec1.y)), abs(vec1.z)),
-		max(max(abs(vec2.x), abs(vec2.y)), abs(vec2.z))
-	) >> (FRACBITS+5);
-	vec1.x /= slope->extent;
-	vec1.y /= slope->extent;
-	vec1.z /= slope->extent;
-	vec2.x /= slope->extent;
-	vec2.y /= slope->extent;
-	vec2.z /= slope->extent;
-
-	FV3_Cross(&vec1, &vec2, &slope->normal);
-
-	slope->extent = R_PointToDist2(0, 0, R_PointToDist2(0, 0, slope->normal.x, slope->normal.y), slope->normal.z);
-	if (slope->normal.z < 0)
-		slope->extent = -slope->extent;
-
-	slope->normal.x = FixedDiv(slope->normal.x, slope->extent);
-	slope->normal.y = FixedDiv(slope->normal.y, slope->extent);
-	slope->normal.z = FixedDiv(slope->normal.z, slope->extent);
-
-	// Set origin
-	slope->o.x = slope->vertices[0]->x << FRACBITS;
-	slope->o.y = slope->vertices[0]->y << FRACBITS;
-	slope->o.z = slope->vertices[0]->z << FRACBITS;
-
-	if (slope->normal.x == 0 && slope->normal.y == 0) { // Set some defaults for a non-sloped "slope"
+	// Set origin.
+	FV3_Copy(&slope->o, &v1);
+
+	// Get slope's normal.
+	FV3_SubEx(&v2, &v1, &vec1);
+	FV3_SubEx(&v3, &v1, &vec2);
+
+	// Set some defaults for a non-sloped "slope"
+	if (vec1.z == 0 && vec2.z == 0)
+	{
+		/// \todo Fix fully flat cases.
+
 		slope->zangle = slope->xydirection = 0;
 		slope->zdelta = slope->d.x = slope->d.y = 0;
-	} else {
+	}
+	else
+	{
+		/// \note Using fixed point for vectorial products easily leads to overflows so we work around by downscaling them.
+		fixed_t m = max(
+			max(max(abs(vec1.x), abs(vec1.y)), abs(vec1.z)),
+			max(max(abs(vec2.x), abs(vec2.y)), abs(vec2.z))
+		) >> 5; // shifting right by 5 is good enough.
+
+		FV3_Cross(
+				FV3_Divide(&vec1, m),
+				FV3_Divide(&vec2, m),
+				&slope->normal
+				);
+
+		// NOTE: FV3_Magnitude() doesn't work properly in some cases, and chaining FixedHypot() seems to give worse results.
+		m = R_PointToDist2(0, 0, R_PointToDist2(0, 0, slope->normal.x, slope->normal.y), slope->normal.z);
+
+		// Invert normal if it's facing down.
+		if (slope->normal.z < 0)
+			m = -m;
+
+		FV3_Divide(&slope->normal, m);
+
 		// Get direction vector
-		slope->extent = R_PointToDist2(0, 0, slope->normal.x, slope->normal.y);
-		slope->d.x = -FixedDiv(slope->normal.x, slope->extent);
-		slope->d.y = -FixedDiv(slope->normal.y, slope->extent);
+		m = FixedHypot(slope->normal.x, slope->normal.y);
+		slope->d.x = -FixedDiv(slope->normal.x, m);
+		slope->d.y = -FixedDiv(slope->normal.y, m);
 
 		// Z delta
-		slope->zdelta = FixedDiv(slope->extent, slope->normal.z);
+		slope->zdelta = FixedDiv(m, slope->normal.z);
 
 		// Get angles
 		slope->xydirection = R_PointToAngle2(0, 0, slope->d.x, slope->d.y)+ANGLE_180;
@@ -94,88 +92,95 @@ static void P_ReconfigureVertexSlope(pslope_t *slope)
 	}
 }
 
-// Recalculate dynamic slopes
-void P_RunDynamicSlopes(void) {
-	pslope_t *slope;
+/// Recalculate dynamic slopes.
+void T_DynamicSlopeLine (dynplanethink_t* th)
+{
+	pslope_t* slope = th->slope;
+	line_t* srcline = th->sourceline;
 
-	for (slope = slopelist; slope; slope = slope->next) {
-		fixed_t zdelta;
+	fixed_t zdelta;
 
-		if (slope->flags & SL_NODYNAMIC)
-			continue;
+	switch(th->type) {
+	case DP_FRONTFLOOR:
+		zdelta = srcline->backsector->floorheight - srcline->frontsector->floorheight;
+		slope->o.z = srcline->frontsector->floorheight;
+		break;
 
-		switch(slope->refpos) {
-		case 1: // front floor
-			zdelta = slope->sourceline->backsector->floorheight - slope->sourceline->frontsector->floorheight;
-			slope->o.z = slope->sourceline->frontsector->floorheight;
-			break;
-		case 2: // front ceiling
-			zdelta = slope->sourceline->backsector->ceilingheight - slope->sourceline->frontsector->ceilingheight;
-			slope->o.z = slope->sourceline->frontsector->ceilingheight;
-			break;
-		case 3: // back floor
-			zdelta = slope->sourceline->frontsector->floorheight - slope->sourceline->backsector->floorheight;
-			slope->o.z = slope->sourceline->backsector->floorheight;
-			break;
-		case 4: // back ceiling
-			zdelta = slope->sourceline->frontsector->ceilingheight - slope->sourceline->backsector->ceilingheight;
-			slope->o.z = slope->sourceline->backsector->ceilingheight;
-			break;
-		case 5: // vertices
-			{
-				mapthing_t *mt;
-				size_t i;
-				INT32 l;
-				line_t *line;
-
-				for (i = 0; i < 3; i++) {
-					mt = slope->vertices[i];
-					l = P_FindSpecialLineFromTag(799, mt->angle, -1);
-					if (l != -1) {
-						line = &lines[l];
-						mt->z = line->frontsector->floorheight >> FRACBITS;
-					}
-				}
-
-				P_ReconfigureVertexSlope(slope);
-			}
-			continue; // TODO
-
-		default:
-			I_Error("P_RunDynamicSlopes: slope has invalid type!");
-		}
+	case DP_FRONTCEIL:
+		zdelta = srcline->backsector->ceilingheight - srcline->frontsector->ceilingheight;
+		slope->o.z = srcline->frontsector->ceilingheight;
+		break;
+
+	case DP_BACKFLOOR:
+		zdelta = srcline->frontsector->floorheight - srcline->backsector->floorheight;
+		slope->o.z = srcline->backsector->floorheight;
+		break;
+
+	case DP_BACKCEIL:
+		zdelta = srcline->frontsector->ceilingheight - srcline->backsector->ceilingheight;
+		slope->o.z = srcline->backsector->ceilingheight;
+		break;
+
+	default:
+		return;
+	}
+
+	if (slope->zdelta != FixedDiv(zdelta, th->extent)) {
+		slope->zdelta = FixedDiv(zdelta, th->extent);
+		slope->zangle = R_PointToAngle2(0, 0, th->extent, -zdelta);
+		P_CalculateSlopeNormal(slope);
+	}
+}
+
+/// Mapthing-defined
+void T_DynamicSlopeVert (dynplanethink_t* th)
+{
+	pslope_t* slope = th->slope;
+
+	size_t i;
+	INT32 l;
 
-		if (slope->zdelta != FixedDiv(zdelta, slope->extent)) {
-			slope->zdelta = FixedDiv(zdelta, slope->extent);
-			slope->zangle = R_PointToAngle2(0, 0, slope->extent, -zdelta);
-			P_CalculateSlopeNormal(slope);
+	for (i = 0; i < 3; i++) {
+		l = P_FindSpecialLineFromTag(799, th->tags[i], -1);
+		if (l != -1) {
+			th->vex[i].z = lines[l].frontsector->floorheight;
 		}
+		else
+			th->vex[i].z = 0;
 	}
+
+	ReconfigureViaVertexes(slope, th->vex[0], th->vex[1], th->vex[2]);
 }
 
-//
-// P_MakeSlope
-//
-// Alocates and fill the contents of a slope structure.
-//
-static pslope_t *P_MakeSlope(const vector3_t *o, const vector2_t *d,
-                             const fixed_t zdelta, UINT8 flags)
+static inline void P_AddDynSlopeThinker (pslope_t* slope, dynplanetype_t type, line_t* sourceline, fixed_t extent, const INT16 tags[3], const vector3_t vx[3])
 {
-	pslope_t *ret = Z_Malloc(sizeof(pslope_t), PU_LEVEL, NULL);
-	memset(ret, 0, sizeof(*ret));
+	dynplanethink_t* th = Z_Calloc(sizeof (*th), PU_LEVSPEC, NULL);
+	switch (type)
+	{
+	case DP_VERTEX:
+		th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeVert;
+		memcpy(th->tags, tags, sizeof(th->tags));
+		memcpy(th->vex, vx, sizeof(th->vex));
+		break;
+	default:
+		th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeLine;
+		th->sourceline = sourceline;
+		th->extent = extent;
+	}
 
-	ret->o.x = o->x;
-	ret->o.y = o->y;
-	ret->o.z = o->z;
+	th->slope = slope;
+	th->type = type;
 
-	ret->d.x = d->x;
-	ret->d.y = d->y;
+	P_AddThinker(THINK_DYNSLOPE, &th->thinker);
+}
 
-	ret->zdelta = zdelta;
 
+/// Create a new slope and add it to the slope list.
+static inline pslope_t* Slope_Add (const UINT8 flags)
+{
+	pslope_t *ret = Z_Calloc(sizeof(pslope_t), PU_LEVEL, NULL);
 	ret->flags = flags;
 
-	// Add to the slope list
 	ret->next = slopelist;
 	slopelist = ret;
 
@@ -185,13 +190,24 @@ static pslope_t *P_MakeSlope(const vector3_t *o, const vector2_t *d,
 	return ret;
 }
 
-//
-// P_GetExtent
-//
-// Returns the distance to the first line within the sector that
-// is intersected by a line parallel to the plane normal with the point (ox, oy)
-//
-static fixed_t P_GetExtent(sector_t *sector, line_t *line)
+/// Alocates and fill the contents of a slope structure.
+static pslope_t *MakeViaVectors(const vector3_t *o, const vector2_t *d,
+                             const fixed_t zdelta, UINT8 flags)
+{
+	pslope_t *ret = Slope_Add(flags);
+
+	FV3_Copy(&ret->o, o);
+	FV2_Copy(&ret->d, d);
+
+	ret->zdelta = zdelta;
+
+	ret->flags = flags;
+
+	return ret;
+}
+
+/// Get furthest perpendicular distance from all vertexes in a sector for a given line.
+static fixed_t GetExtent(sector_t *sector, line_t *line)
 {
 	// ZDoom code reference: v3float_t = vertex_t
 	fixed_t fardist = -FRACUNIT;
@@ -224,14 +240,8 @@ static fixed_t P_GetExtent(sector_t *sector, line_t *line)
 	return fardist;
 }
 
-
-//
-// P_SpawnSlope_Line
-//
-// Creates one or more slopes based on the given line type and front/back
-// sectors.
-//
-void P_SpawnSlope_Line(int linenum)
+/// Creates one or more slopes based on the given line type and front/back sectors.
+static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 {
 	// With dynamic slopes, it's fine to just leave this function as normal,
 	// because checking to see if a slope had changed will waste more memory than
@@ -249,12 +259,10 @@ void P_SpawnSlope_Line(int linenum)
 	boolean backceil   = (special == 711 || special == 712 || special == 703);
 
 	UINT8 flags = 0; // Slope flags
-	if (line->flags & ML_NOSONIC)
+	if (line->flags & ML_NETONLY)
 		flags |= SL_NOPHYSICS;
-	if (!(line->flags & ML_NOTAILS))
-		flags |= SL_NODYNAMIC;
-	if (line->flags & ML_NOKNUX)
-		flags |= SL_ANCHORVERTEX;
+	if (line->flags & ML_NONET)
+		flags |= SL_DYNAMIC;
 
 	if(!frontfloor && !backfloor && !frontceil && !backceil)
 	{
@@ -274,6 +282,7 @@ void P_SpawnSlope_Line(int linenum)
 		ny = -FixedDiv(line->dx, len);
 	}
 
+	// Set origin to line's center.
 	origin.x = line->v1->x + (line->v2->x - line->v1->x)/2;
 	origin.y = line->v1->y + (line->v2->y - line->v1->y)/2;
 
@@ -286,7 +295,7 @@ void P_SpawnSlope_Line(int linenum)
 		direction.x = nx;
 		direction.y = ny;
 
-		extent = P_GetExtent(line->frontsector, line);
+		extent = GetExtent(line->frontsector, line);
 
 		if(extent < 0)
 		{
@@ -304,104 +313,43 @@ void P_SpawnSlope_Line(int linenum)
 
 		if(frontfloor)
 		{
-			fixed_t highest, lowest;
-			size_t l;
 			point.z = line->frontsector->floorheight; // Startz
 			dz = FixedDiv(origin.z - point.z, extent); // Destinationz
 
 			// In P_SpawnSlopeLine the origin is the centerpoint of the sourcelinedef
 
 			fslope = line->frontsector->f_slope =
-            P_MakeSlope(&point, &direction, dz, flags);
-
-            // Set up some shit
-            fslope->extent = extent;
-            fslope->refpos = 1;
+            MakeViaVectors(&point, &direction, dz, flags);
 
 			// Now remember that f_slope IS a vector
 			// fslope->o = origin      3D point 1 of the vector
 			// fslope->d = destination 3D point 2 of the vector
 			// fslope->normal is a 3D line perpendicular to the 3D vector
 
-			// Sync the linedata of the line that started this slope
-			// TODO: Anything special for control sector based slopes later?
-			fslope->sourceline = line;
-
-			// To find the real highz/lowz of a slope, you need to check all the vertexes
-			// in the slope's sector with P_GetZAt to get the REAL lowz & highz
-			// Although these slopes are set by floorheights the ANGLE is what a slope is,
-			// so technically any slope can extend on forever (they are just bound by sectors)
-			// *You can use sourceline as a reference to see if two slopes really are the same
-
-			// Default points for high and low
-			highest = point.z > origin.z ? point.z : origin.z;
-			lowest = point.z < origin.z ? point.z : origin.z;
-
-			// Now check to see what the REAL high and low points of the slope inside the sector
-			// TODO: Is this really needed outside of FOFs? -Red
-
-			for (l = 0; l < line->frontsector->linecount; l++)
-			{
-				fixed_t height = P_GetZAt(line->frontsector->f_slope, line->frontsector->lines[l]->v1->x, line->frontsector->lines[l]->v1->y);
-
-				if (height > highest)
-					highest = height;
-
-				if (height < lowest)
-					lowest = height;
-			}
-
-			// Sets extra clipping data for the frontsector's slope
-			fslope->highz = highest;
-			fslope->lowz = lowest;
-
 			fslope->zangle = R_PointToAngle2(0, origin.z, extent, point.z);
 			fslope->xydirection = R_PointToAngle2(origin.x, origin.y, point.x, point.y);
 
 			P_CalculateSlopeNormal(fslope);
+
+			if (spawnthinker && (flags & SL_DYNAMIC))
+				P_AddDynSlopeThinker(fslope, DP_FRONTFLOOR, line, extent, NULL, NULL);
 		}
 		if(frontceil)
 		{
-			fixed_t highest, lowest;
-			size_t l;
 			origin.z = line->backsector->ceilingheight;
 			point.z = line->frontsector->ceilingheight;
 			dz = FixedDiv(origin.z - point.z, extent);
 
 			cslope = line->frontsector->c_slope =
-            P_MakeSlope(&point, &direction, dz, flags);
-
-            // Set up some shit
-            cslope->extent = extent;
-            cslope->refpos = 2;
-
-			// Sync the linedata of the line that started this slope
-			// TODO: Anything special for control sector based slopes later?
-			cslope->sourceline = line;
-
-			// Remember the way the slope is formed
-			highest = point.z > origin.z ? point.z : origin.z;
-			lowest = point.z < origin.z ? point.z : origin.z;
-
-			for (l = 0; l < line->frontsector->linecount; l++)
-			{
-				fixed_t height = P_GetZAt(line->frontsector->c_slope, line->frontsector->lines[l]->v1->x, line->frontsector->lines[l]->v1->y);
-
-				if (height > highest)
-					highest = height;
-
-				if (height < lowest)
-					lowest = height;
-			}
-
-			// This line special sets extra clipping data for the frontsector's slope
-			cslope->highz = highest;
-			cslope->lowz = lowest;
+            MakeViaVectors(&point, &direction, dz, flags);
 
 			cslope->zangle = R_PointToAngle2(0, origin.z, extent, point.z);
 			cslope->xydirection = R_PointToAngle2(origin.x, origin.y, point.x, point.y);
 
 			P_CalculateSlopeNormal(cslope);
+
+			if (spawnthinker && (flags & SL_DYNAMIC))
+				P_AddDynSlopeThinker(cslope, DP_FRONTCEIL, line, extent, NULL, NULL);
 		}
 	}
 	if(backfloor || backceil)
@@ -413,7 +361,7 @@ void P_SpawnSlope_Line(int linenum)
 		direction.x = -nx;
 		direction.y = -ny;
 
-		extent = P_GetExtent(line->backsector, line);
+		extent = GetExtent(line->backsector, line);
 
 		if(extent < 0)
 		{
@@ -429,88 +377,36 @@ void P_SpawnSlope_Line(int linenum)
 
 		if(backfloor)
 		{
-			fixed_t highest, lowest;
-			size_t l;
 			point.z = line->backsector->floorheight;
 			dz = FixedDiv(origin.z - point.z, extent);
 
 			fslope = line->backsector->f_slope =
-            P_MakeSlope(&point, &direction, dz, flags);
-
-            // Set up some shit
-            fslope->extent = extent;
-            fslope->refpos = 3;
-
-			// Sync the linedata of the line that started this slope
-			// TODO: Anything special for control sector based slopes later?
-			fslope->sourceline = line;
-
-			// Remember the way the slope is formed
-			highest = point.z > origin.z ? point.z : origin.z;
-			lowest = point.z < origin.z ? point.z : origin.z;
-
-			for (l = 0; l < line->backsector->linecount; l++)
-			{
-				fixed_t height = P_GetZAt(line->backsector->f_slope, line->backsector->lines[l]->v1->x, line->backsector->lines[l]->v1->y);
-
-				if (height > highest)
-					highest = height;
-
-				if (height < lowest)
-					lowest = height;
-			}
-
-			// This line special sets extra clipping data for the frontsector's slope
-			fslope->highz = highest;
-			fslope->lowz = lowest;
+            MakeViaVectors(&point, &direction, dz, flags);
 
 			fslope->zangle = R_PointToAngle2(0, origin.z, extent, point.z);
 			fslope->xydirection = R_PointToAngle2(origin.x, origin.y, point.x, point.y);
 
 			P_CalculateSlopeNormal(fslope);
+
+			if (spawnthinker && (flags & SL_DYNAMIC))
+				P_AddDynSlopeThinker(fslope, DP_BACKFLOOR, line, extent, NULL, NULL);
 		}
 		if(backceil)
 		{
-			fixed_t highest, lowest;
-			size_t l;
 			origin.z = line->frontsector->ceilingheight;
 			point.z = line->backsector->ceilingheight;
 			dz = FixedDiv(origin.z - point.z, extent);
 
 			cslope = line->backsector->c_slope =
-            P_MakeSlope(&point, &direction, dz, flags);
-
-            // Set up some shit
-            cslope->extent = extent;
-            cslope->refpos = 4;
-
-			// Sync the linedata of the line that started this slope
-			// TODO: Anything special for control sector based slopes later?
-			cslope->sourceline = line;
-
-			// Remember the way the slope is formed
-			highest = point.z > origin.z ? point.z : origin.z;
-			lowest = point.z < origin.z ? point.z : origin.z;
-
-			for (l = 0; l < line->backsector->linecount; l++)
-			{
-				fixed_t height = P_GetZAt(line->backsector->c_slope, line->backsector->lines[l]->v1->x, line->backsector->lines[l]->v1->y);
-
-				if (height > highest)
-					highest = height;
-
-				if (height < lowest)
-					lowest = height;
-			}
-
-			// This line special sets extra clipping data for the backsector's slope
-			cslope->highz = highest;
-			cslope->lowz = lowest;
+            MakeViaVectors(&point, &direction, dz, flags);
 
 			cslope->zangle = R_PointToAngle2(0, origin.z, extent, point.z);
 			cslope->xydirection = R_PointToAngle2(origin.x, origin.y, point.x, point.y);
 
 			P_CalculateSlopeNormal(cslope);
+
+			if (spawnthinker && (flags & SL_DYNAMIC))
+				P_AddDynSlopeThinker(cslope, DP_BACKCEIL, line, extent, NULL, NULL);
 		}
 	}
 
@@ -518,63 +414,99 @@ void P_SpawnSlope_Line(int linenum)
 		return;
 }
 
-//
-// P_NewVertexSlope
-//
-// Creates a new slope from three vertices with the specified IDs
-//
-static pslope_t *P_NewVertexSlope(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flags)
+/// Creates a new slope from three mapthings with the specified IDs
+static pslope_t *MakeViaMapthings(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flags, const boolean spawnthinker)
 {
 	size_t i;
-	mapthing_t *mt = mapthings;
+	mapthing_t* mt = mapthings;
+	mapthing_t* vertices[3] = {0};
+	INT16 tags[3] = {tag1, tag2, tag3};
 
-	pslope_t *ret = Z_Malloc(sizeof(pslope_t), PU_LEVEL, NULL);
-	memset(ret, 0, sizeof(*ret));
-
-	// Start by setting flags
-	ret->flags = flags;
-
-	// Now set up the vertex list
-	ret->vertices = Z_Malloc(3*sizeof(mapthing_t), PU_LEVEL, NULL);
-	memset(ret->vertices, 0, 3*sizeof(mapthing_t));
+	vector3_t vx[3];
+	pslope_t* ret = Slope_Add(flags);
 
 	// And... look for the vertices in question.
 	for (i = 0; i < nummapthings; i++, mt++) {
 		if (mt->type != 750) // Haha, I'm hijacking the old Chaos Spawn thingtype for something!
 			continue;
 
-		if (!ret->vertices[0] && mt->angle == tag1)
-			ret->vertices[0] = mt;
-		else if (!ret->vertices[1] && mt->angle == tag2)
-			ret->vertices[1] = mt;
-		else if (!ret->vertices[2] && mt->angle == tag3)
-			ret->vertices[2] = mt;
+		if (!vertices[0] && mt->angle == tag1)
+			vertices[0] = mt;
+		else if (!vertices[1] && mt->angle == tag2)
+			vertices[1] = mt;
+		else if (!vertices[2] && mt->angle == tag3)
+			vertices[2] = mt;
 	}
 
 	// Now set heights for each vertex, because they haven't been set yet
 	for (i = 0; i < 3; i++) {
-		mt = ret->vertices[i];
+		mt = vertices[i];
 		if (!mt) // If a vertex wasn't found, it's game over. There's nothing you can do to recover (except maybe try and kill the slope instead - TODO?)
-			I_Error("P_NewVertexSlope: Slope vertex %s (for linedef tag %d) not found!", sizeu1(i), tag1);
+			I_Error("MakeViaMapthings: Slope vertex %s (for linedef tag %d) not found!", sizeu1(i), tag1);
+		vx[i].x = mt->x << FRACBITS;
+		vx[i].y = mt->y << FRACBITS;
 		if (mt->extrainfo)
-			mt->z = mt->options;
+			vx[i].z = mt->options << FRACBITS;
 		else
-			mt->z = (R_PointInSubsector(mt->x << FRACBITS, mt->y << FRACBITS)->sector->floorheight >> FRACBITS) + (mt->options >> ZSHIFT);
+			vx[i].z = (R_PointInSubsector(mt->x << FRACBITS, mt->y << FRACBITS)->sector->floorheight) + ((mt->options >> ZSHIFT) << FRACBITS);
 	}
 
-	P_ReconfigureVertexSlope(ret);
-	ret->refpos = 5;
-
-	// Add to the slope list
-	ret->next = slopelist;
-	slopelist = ret;
+	ReconfigureViaVertexes(ret, vx[0], vx[1], vx[2]);
 
-	slopecount++;
-	ret->id = slopecount;
+	if (spawnthinker && (flags & SL_DYNAMIC))
+		P_AddDynSlopeThinker(ret, DP_VERTEX, NULL, 0, tags, vx);
 
 	return ret;
 }
 
+/// Create vertex based slopes.
+static void line_SpawnViaVertexes(const int linenum, const boolean spawnthinker)
+{
+	line_t *line = lines + linenum;
+	side_t *side;
+	pslope_t **slopetoset;
+	UINT16 tag1, tag2, tag3;
+
+	UINT8 flags = 0;
+	if (line->flags & ML_NETONLY)
+		flags |= SL_NOPHYSICS;
+	if (line->flags & ML_NONET)
+		flags |= SL_DYNAMIC;
+
+	switch(line->special)
+	{
+	case 704:
+		slopetoset = &line->frontsector->f_slope;
+		side = &sides[line->sidenum[0]];
+		break;
+	case 705:
+		slopetoset = &line->frontsector->c_slope;
+		side = &sides[line->sidenum[0]];
+		break;
+	case 714:
+		slopetoset = &line->backsector->f_slope;
+		side = &sides[line->sidenum[1]];
+		break;
+	case 715:
+		slopetoset = &line->backsector->c_slope;
+		side = &sides[line->sidenum[1]];
+	default:
+		return;
+	}
+
+	if (line->flags & ML_EFFECT6)
+	{
+		tag1 = line->tag;
+		tag2 = side->textureoffset >> FRACBITS;
+		tag3 = side->rowoffset >> FRACBITS;
+	}
+	else
+		tag1 = tag2 = tag3 = line->tag;
+
+	*slopetoset = MakeViaMapthings(tag1, tag2, tag3, flags, spawnthinker);
+
+	side->sector->hasslope = true;
+}
 
 
 //
@@ -620,56 +552,20 @@ pslope_t *P_SlopeById(UINT16 id)
 	return ret;
 }
 
-// Reset the dynamic slopes pointer, and read all of the fancy schmancy slopes
-void P_ResetDynamicSlopes(void) {
+/// Reset slopes and read them from special lines.
+void P_ResetDynamicSlopes(const UINT32 fromsave) {
 	size_t i;
-#ifdef ESLOPE_TYPESHIM // Rewrite old specials to new ones, and give a console warning
-	boolean warned = false;
-#endif
+
+	boolean spawnthinkers = !(boolean)fromsave;
 
 	slopelist = NULL;
 	slopecount = 0;
 
-	// We'll handle copy slopes later, after all the tag lists have been made.
-	// Yes, this means copied slopes won't affect things' spawning heights. Too bad for you.
+	/// Generates line special-defined slopes.
 	for (i = 0; i < numlines; i++)
 	{
 		switch (lines[i].special)
 		{
-#ifdef ESLOPE_TYPESHIM // Rewrite old specials to new ones, and give a console warning
-#define WARNME if (!warned) {warned = true; CONS_Alert(CONS_WARNING, "This level uses old slope specials.\nA conversion will be needed before 2.2's release.\n");}
-			case 386:
-			case 387:
-			case 388:
-				lines[i].special += 700-386;
-				WARNME
-				P_SpawnSlope_Line(i);
-				break;
-
-			case 389:
-			case 390:
-			case 391:
-			case 392:
-				lines[i].special += 710-389;
-				WARNME
-				P_SpawnSlope_Line(i);
-				break;
-
-			case 393:
-				lines[i].special = 703;
-				WARNME
-				P_SpawnSlope_Line(i);
-				break;
-
-			case 394:
-			case 395:
-			case 396:
-				lines[i].special += 720-394;
-				WARNME
-				break;
-
-#endif
-
 			case 700:
 			case 701:
 			case 702:
@@ -678,62 +574,34 @@ void P_ResetDynamicSlopes(void) {
 			case 711:
 			case 712:
 			case 713:
-				P_SpawnSlope_Line(i);
+				line_SpawnViaLine(i, spawnthinkers);
 				break;
 
 			case 704:
 			case 705:
 			case 714:
 			case 715:
-				{
-					pslope_t **slopetoset;
-					size_t which = lines[i].special;
-
-					UINT8 flags = SL_VERTEXSLOPE;
-					if (lines[i].flags & ML_NOSONIC)
-						flags |= SL_NOPHYSICS;
-					if (!(lines[i].flags & ML_NOTAILS))
-						flags |= SL_NODYNAMIC;
-
-					if (which == 704)
-					{
-						slopetoset = &lines[i].frontsector->f_slope;
-						which = 0;
-					}
-					else if (which == 705)
-					{
-						slopetoset = &lines[i].frontsector->c_slope;
-						which = 0;
-					}
-					else if (which == 714)
-					{
-						slopetoset = &lines[i].backsector->f_slope;
-						which = 1;
-					}
-					else // 715
-					{
-						slopetoset = &lines[i].backsector->c_slope;
-						which = 1;
-					}
-
-					if (lines[i].flags & ML_NOKNUX)
-						*slopetoset = P_NewVertexSlope(lines[i].tag, sides[lines[i].sidenum[which]].textureoffset >> FRACBITS,
-																			sides[lines[i].sidenum[which]].rowoffset >> FRACBITS, flags);
-					else
-						*slopetoset = P_NewVertexSlope(lines[i].tag, lines[i].tag, lines[i].tag, flags);
-
-					sides[lines[i].sidenum[which]].sector->hasslope = true;
-				}
+				line_SpawnViaVertexes(i, spawnthinkers);
 				break;
 
 			default:
 				break;
 		}
 	}
-}
-
-
 
+	/// Copies slopes from tagged sectors via line specials.
+	/// \note Doesn't actually copy, but instead they share the same pointers.
+	for (i = 0; i < numlines; i++)
+		switch (lines[i].special)
+		{
+			case 720:
+			case 721:
+			case 722:
+				P_CopySectorSlope(&lines[i]);
+			default:
+				break;
+		}
+}
 
 // ============================================================================
 //
diff --git a/src/p_slopes.h b/src/p_slopes.h
index b802ec25f6a4e5cb820641d58dc85c231d90727f..8f9e7d61e5bf135e82b42c56593465a0f5fc73e0 100644
--- a/src/p_slopes.h
+++ b/src/p_slopes.h
@@ -13,14 +13,17 @@
 #ifndef P_SLOPES_H__
 #define P_SLOPES_H__
 
+#include "m_fixed.h" // Vectors
+
 #ifdef ESLOPE
+
+extern pslope_t *slopelist;
+extern UINT16 slopecount;
+
+void P_LinkSlopeThinkers (void);
+
 void P_CalculateSlopeNormal(pslope_t *slope);
-void P_ResetDynamicSlopes(void);
-void P_RunDynamicSlopes(void);
-// P_SpawnSlope_Line
-// Creates one or more slopes based on the given line type and front/back
-// sectors.
-void P_SpawnSlope_Line(int linenum);
+void P_ResetDynamicSlopes(const UINT32 fromsave);
 
 //
 // P_CopySectorSlope
@@ -42,7 +45,34 @@ fixed_t P_GetWallTransferMomZ(mobj_t *mo, pslope_t *slope);
 void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope);
 void P_ButteredSlope(mobj_t *mo);
 
-#endif
 
-// EOF
+/// Dynamic plane type enum for the thinker. Will have a different functionality depending on this.
+typedef enum {
+	DP_FRONTFLOOR,
+	DP_FRONTCEIL,
+	DP_BACKFLOOR,
+	DP_BACKCEIL,
+	DP_VERTEX
+} dynplanetype_t;
+
+/// Permit slopes to be dynamically altered through a thinker.
+typedef struct
+{
+	thinker_t thinker;
+
+	pslope_t* slope;
+	dynplanetype_t type;
+
+	// Used by line slopes.
+	line_t* sourceline;
+	fixed_t extent;
+
+	// Used by mapthing vertex slopes.
+	INT16 tags[3];
+	vector3_t vex[3];
+} dynplanethink_t;
+
+void T_DynamicSlopeLine (dynplanethink_t* th);
+void T_DynamicSlopeVert (dynplanethink_t* th);
 #endif // #ifdef ESLOPE
+#endif // #ifndef P_SLOPES_H__
diff --git a/src/p_spec.c b/src/p_spec.c
index 4bb03ca1a20018c143d4df33bc6afa1c0fa94077..23ab04fc7644048203a9bd9ef5bac0d18e67a5ec 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -36,6 +36,7 @@
 #include "m_cond.h" //unlock triggers
 #include "lua_hook.h" // LUAh_LinedefExecute
 #include "f_finale.h" // control text prompt
+#include "r_things.h" // skins
 
 #ifdef HW3SOUND
 #include "hardware/hw3sound.h"
@@ -98,7 +99,6 @@ typedef struct
 	thinker_t **thinkers;
 } thinkerlist_t;
 
-static void P_SearchForDisableLinedefs(void);
 static void P_SpawnScrollers(void);
 static void P_SpawnFriction(void);
 static void P_SpawnPushers(void);
@@ -1644,7 +1644,7 @@ static void P_AddExecutorDelay(line_t *line, mobj_t *mobj, sector_t *sector)
 	e->sector = sector;
 	e->timer = (line->backsector->ceilingheight>>FRACBITS)+(line->backsector->floorheight>>FRACBITS);
 	P_SetTarget(&e->caller, mobj); // Use P_SetTarget to make sure the mobj doesn't get freed while we're delaying.
-	P_AddThinker(&e->thinker);
+	P_AddThinker(THINK_MAIN, &e->thinker);
 }
 
 /** Used by P_RunTriggerLinedef to check a NiGHTS trigger linedef's conditions
@@ -2008,7 +2008,12 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			if (!P_CheckNightsTriggerLine(triggerline, actor))
 				return false;
 			break;
-
+		case 331: // continuous
+		case 332: // each time
+		case 333: // once
+			if (!(actor && actor->player && ((stricmp(triggerline->text, skins[actor->player->skin].name) == 0) ^ ((triggerline->flags & ML_NOCLIMB) == ML_NOCLIMB))))
+				return false;
+			break;
 		default:
 			break;
 	}
@@ -2141,6 +2146,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 	 || specialtype == 326 // DeNightserize - Once
 	 || specialtype == 328 // Nights lap - Once
 	 || specialtype == 330 // Nights Bonus Time - Once
+	 || specialtype == 333 // Skin - Once
 	 || specialtype == 399) // Level Load
 		triggerline->special = 0; // Clear it out
 
@@ -2181,7 +2187,8 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
 		 || lines[masterline].special == 306 // Character ability - Each time
 		 || lines[masterline].special == 310 // CTF Red team - Each time
 		 || lines[masterline].special == 312 // CTF Blue team - Each time
-		 || lines[masterline].special == 322) // Trigger on X calls - Each Time
+		 || lines[masterline].special == 322 // Trigger on X calls - Each Time
+		 || lines[masterline].special == 332)// Skin - Each time
 			continue;
 
 		if (lines[masterline].special < 300
@@ -2253,7 +2260,7 @@ void P_SwitchWeather(INT32 weathernum)
 		thinker_t *think;
 		precipmobj_t *precipmobj;
 
-		for (think = thinkercap.next; think != &thinkercap; think = think->next)
+		for (think = thlist[THINK_PRECIP].next; think != &thlist[THINK_PRECIP]; think = think->next)
 		{
 			if (think->function.acp1 != (actionf_p1)P_NullPrecipThinker)
 				continue; // not a precipmobj thinker
@@ -2269,7 +2276,7 @@ void P_SwitchWeather(INT32 weathernum)
 		precipmobj_t *precipmobj;
 		state_t *st;
 
-		for (think = thinkercap.next; think != &thinkercap; think = think->next)
+		for (think = thlist[THINK_PRECIP].next; think != &thlist[THINK_PRECIP]; think = think->next)
 		{
 			if (think->function.acp1 != (actionf_p1)P_NullPrecipThinker)
 				continue; // not a precipmobj thinker
@@ -3144,7 +3151,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				scroll_t *scroller;
 				thinker_t *th;
 
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
 				{
 					if (th->function.acp1 != (actionf_p1)T_Scroll)
 						continue;
@@ -3382,7 +3389,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 							// if flags changed, reset sector's light list
 							if (rover->flags != oldflags)
+							{
 								sec->moved = true;
+								P_RecalcPrecipInSector(sec);
+							}
 						}
 					}
 
@@ -3544,6 +3554,29 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
+		case 449: // Enable bosses with parameter
+		{
+			INT32 bossid = sides[line->sidenum[0]].textureoffset>>FRACBITS;
+			if (bossid & ~15) // if any bits other than first 16 are set
+			{
+				CONS_Alert(CONS_WARNING,
+					M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
+					line->tag);
+				break;
+			}
+			if (line->flags & ML_NOCLIMB)
+			{
+				bossdisabled |= (1<<bossid);
+				CONS_Debug(DBG_GAMELOGIC, "Line type 449 Executor: bossid disabled = %d", bossid);
+			}
+			else
+			{
+				bossdisabled &= ~(1<<bossid);
+				CONS_Debug(DBG_GAMELOGIC, "Line type 449 Executor: bossid enabled = %d", bossid);
+			}
+			break;
+		}
+
 		case 450: // Execute Linedef Executor - for recursion
 			P_LinedefExecute(line->tag, mo, NULL);
 			break;
@@ -3908,6 +3941,18 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
+		case 460: // Award rings
+			{
+				INT16 rings = (sides[line->sidenum[0]].textureoffset>>FRACBITS);
+				INT32 delay = (sides[line->sidenum[0]].rowoffset>>FRACBITS);
+				if (mo && mo->player)
+				{
+					if (delay <= 0 || !(leveltime % delay))
+						P_GivePlayerRings(mo->player, rings);
+				}
+			}
+			break;
+
 #ifdef POLYOBJECTS
 		case 480: // Polyobj_DoorSlide
 		case 481: // Polyobj_DoorSwing
@@ -3980,10 +4025,10 @@ void P_SetupSignExit(player_t *player)
 
 	// didn't find any signposts in the exit sector.
 	// spin all signposts in the level then.
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		thing = (mobj_t *)think;
 		if (thing->type != MT_SIGN)
@@ -4010,23 +4055,18 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 {
 	thinker_t *think;
 	mobj_t *mo;
-	INT32 specialnum = 0;
+	INT32 specialnum = (flag == MT_REDFLAG) ? 3 : 4;
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
 
 		if (mo->type != flag)
 			continue;
 
-		if (mo->type == MT_REDFLAG)
-			specialnum = 3;
-		else if (mo->type == MT_BLUEFLAG)
-			specialnum = 4;
-
 		if (GETSECSPECIAL(mo->subsector->sector->special, 4) == specialnum)
 			return true;
 		else if (mo->subsector->sector->ffloors) // Check the 3D floors
@@ -4041,9 +4081,11 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 				if (GETSECSPECIAL(rover->master->frontsector->special, 4) != specialnum)
 					continue;
 
-				if (mo->z <= P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector)
-					&& mo->z >= P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector))
-					return true;
+				if (!(mo->z <= P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector)
+					&& mo->z >= P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector)))
+					continue;
+
+				return true;
 			}
 		}
 	}
@@ -4445,14 +4487,14 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 
 			// Find the center of the Eggtrap and release all the pretty animals!
 			// The chimps are my friends.. heeheeheheehehee..... - LouisJM
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
-
 				mo2 = (mobj_t *)th;
-				if (mo2->type == MT_EGGTRAP)
-					P_KillMobj(mo2, NULL, player->mo, 0);
+				if (mo2->type != MT_EGGTRAP)
+					continue;
+				P_KillMobj(mo2, NULL, player->mo, 0);
 			}
 
 			// clear the special so you can't push the button twice.
@@ -4594,7 +4636,10 @@ DoneSection2:
 			if (player->bot)
 				break;
 			if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && player->nightstime > 6)
+			{
 				player->nightstime = 6; // Just let P_Ticker take care of the rest.
+				return;
+			}
 
 			// Exit (for FOF exits; others are handled in P_PlayerThink in p_user.c)
 			{
@@ -4617,7 +4662,7 @@ DoneSection2:
 						nextmapoverride = (INT16)(lines[lineindex].frontsector->floorheight>>FRACBITS);
 
 					if (lines[lineindex].flags & ML_NOCLIMB)
-						skipstats = true;
+						skipstats = 1;
 				}
 			}
 			break;
@@ -4751,19 +4796,22 @@ DoneSection2:
 
 				// scan the thinkers
 				// to find the first waypoint
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
 
-					if (mo2->type == MT_TUBEWAYPOINT && mo2->threshold == sequence
-						&& mo2->health == 0)
-					{
-						waypoint = mo2;
-						break;
-					}
+					if (mo2->type != MT_TUBEWAYPOINT)
+						continue;
+					if (mo2->threshold != sequence)
+						continue;
+					if (mo2->health != 0)
+						continue;
+
+					waypoint = mo2;
+					break;
 				}
 
 				if (!waypoint)
@@ -4830,20 +4878,22 @@ DoneSection2:
 
 				// scan the thinkers
 				// to find the last waypoint
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
 
-					if (mo2->type == MT_TUBEWAYPOINT && mo2->threshold == sequence)
-					{
-						if (!waypoint)
-							waypoint = mo2;
-						else if (mo2->health > waypoint->health)
-							waypoint = mo2;
-					}
+					if (mo2->type != MT_TUBEWAYPOINT)
+						continue;
+					if (mo2->threshold != sequence)
+						continue;
+
+					if (!waypoint)
+						waypoint = mo2;
+					else if (mo2->health > waypoint->health)
+						waypoint = mo2;
 				}
 
 				if (!waypoint)
@@ -4982,9 +5032,9 @@ DoneSection2:
 
 				// scan the thinkers
 				// to find the first waypoint
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -5020,9 +5070,9 @@ DoneSection2:
 				}
 
 				// Find waypoint before this one (waypointlow)
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -5047,9 +5097,9 @@ DoneSection2:
 				}
 
 				// Find waypoint after this one (waypointhigh)
-				for (th = thinkercap.next; th != &thinkercap; th = th->next)
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -5566,11 +5616,6 @@ void P_UpdateSpecials(void)
 	// POINT LIMIT
 	P_CheckPointLimit();
 
-#ifdef ESLOPE
-	// Dynamic slopeness
-	P_RunDynamicSlopes();
-#endif
-
 	// ANIMATE TEXTURES
 	for (anim = anims; anim < lastanim; anim++)
 	{
@@ -5640,26 +5685,26 @@ ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id)
 /** Adds a newly formed 3Dfloor structure to a sector's ffloors list.
   *
   * \param sec    Target sector.
-  * \param ffloor Newly formed 3Dfloor structure.
+  * \param fflr   Newly formed 3Dfloor structure.
   * \sa P_AddFakeFloor
   */
-static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *ffloor)
+static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *fflr)
 {
 	ffloor_t *rover;
 
 	if (!sec->ffloors)
 	{
-		sec->ffloors = ffloor;
-		ffloor->next = 0;
-		ffloor->prev = 0;
+		sec->ffloors = fflr;
+		fflr->next = 0;
+		fflr->prev = 0;
 		return;
 	}
 
 	for (rover = sec->ffloors; rover->next; rover = rover->next);
 
-	rover->next = ffloor;
-	ffloor->prev = rover;
-	ffloor->next = 0;
+	rover->next = fflr;
+	fflr->prev = rover;
+	fflr->next = 0;
 }
 
 /** Adds a 3Dfloor.
@@ -5674,7 +5719,7 @@ static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *ffloor)
   */
 static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, ffloortype_e flags, thinkerlist_t *secthinkers)
 {
-	ffloor_t *ffloor;
+	ffloor_t *fflr;
 	thinker_t *th;
 	friction_t *f;
 	pusher_t *p;
@@ -5684,8 +5729,8 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 
 	if (sec == sec2)
 		return NULL; //Don't need a fake floor on a control sector.
-	if ((ffloor = (P_GetFFloorBySec(sec, sec2))))
-		return ffloor; // If this ffloor already exists, return it
+	if ((fflr = (P_GetFFloorBySec(sec, sec2))))
+		return fflr; // If this ffloor already exists, return it
 
 	if (sec2->ceilingheight < sec2->floorheight)
 	{
@@ -5724,27 +5769,27 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	}
 
 	// Add the floor
-	ffloor = Z_Calloc(sizeof (*ffloor), PU_LEVEL, NULL);
-	ffloor->secnum = sec2 - sectors;
-	ffloor->target = sec;
-	ffloor->bottomheight = &sec2->floorheight;
-	ffloor->bottompic = &sec2->floorpic;
-	ffloor->bottomxoffs = &sec2->floor_xoffs;
-	ffloor->bottomyoffs = &sec2->floor_yoffs;
-	ffloor->bottomangle = &sec2->floorpic_angle;
+	fflr = Z_Calloc(sizeof (*fflr), PU_LEVEL, NULL);
+	fflr->secnum = sec2 - sectors;
+	fflr->target = sec;
+	fflr->bottomheight = &sec2->floorheight;
+	fflr->bottompic = &sec2->floorpic;
+	fflr->bottomxoffs = &sec2->floor_xoffs;
+	fflr->bottomyoffs = &sec2->floor_yoffs;
+	fflr->bottomangle = &sec2->floorpic_angle;
 
 	// Add the ceiling
-	ffloor->topheight = &sec2->ceilingheight;
-	ffloor->toppic = &sec2->ceilingpic;
-	ffloor->toplightlevel = &sec2->lightlevel;
-	ffloor->topxoffs = &sec2->ceiling_xoffs;
-	ffloor->topyoffs = &sec2->ceiling_yoffs;
-	ffloor->topangle = &sec2->ceilingpic_angle;
+	fflr->topheight = &sec2->ceilingheight;
+	fflr->toppic = &sec2->ceilingpic;
+	fflr->toplightlevel = &sec2->lightlevel;
+	fflr->topxoffs = &sec2->ceiling_xoffs;
+	fflr->topyoffs = &sec2->ceiling_yoffs;
+	fflr->topangle = &sec2->ceilingpic_angle;
 
 #ifdef ESLOPE
 	// Add slopes
-	ffloor->t_slope = &sec2->c_slope;
-	ffloor->b_slope = &sec2->f_slope;
+	fflr->t_slope = &sec2->c_slope;
+	fflr->b_slope = &sec2->f_slope;
 	// mark the target sector as having slopes, if the FOF has any of its own
 	// (this fixes FOF slopes glitching initially at level load in software mode)
 	if (sec2->hasslope)
@@ -5757,10 +5802,10 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	if ((flags & FF_SOLID) && (master->flags & ML_EFFECT2)) // Block all BUT player
 		flags &= ~FF_BLOCKPLAYER;
 
-	ffloor->spawnflags = ffloor->flags = flags;
-	ffloor->master = master;
-	ffloor->norender = INFTICS;
-	ffloor->fadingdata = NULL;
+	fflr->spawnflags = fflr->flags = flags;
+	fflr->master = master;
+	fflr->norender = INFTICS;
+	fflr->fadingdata = NULL;
 
 	// Scan the thinkers to check for special conditions applying to this FOF.
 	// If we have thinkers sorted by sector, just check the relevant ones;
@@ -5770,7 +5815,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 
 	// Just initialise both of these to placate the compiler.
 	i = 0;
-	th = thinkercap.next;
+	th = thlist[THINK_MAIN].next;
 
 	for(;;)
 	{
@@ -5780,7 +5825,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 				th = secthinkers[sec2num].thinkers[i];
 			else break;
 		}
-		else if (th == &thinkercap)
+		else if (th == &thlist[THINK_MAIN])
 			break;
 
 		// Should this FOF have spikeness?
@@ -5816,14 +5861,14 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	if (flags & FF_TRANSLUCENT)
 	{
 		if (sides[master->sidenum[0]].toptexture > 0)
-			ffloor->alpha = sides[master->sidenum[0]].toptexture; // for future reference, "#0" is 1, and "#255" is 256. Be warned
+			fflr->alpha = sides[master->sidenum[0]].toptexture; // for future reference, "#0" is 1, and "#255" is 256. Be warned
 		else
-			ffloor->alpha = 0x80;
+			fflr->alpha = 0x80;
 	}
 	else
-		ffloor->alpha = 0xff;
+		fflr->alpha = 0xff;
 
-	ffloor->spawnalpha = ffloor->alpha; // save for netgames
+	fflr->spawnalpha = fflr->alpha; // save for netgames
 
 	if (flags & FF_QUICKSAND)
 		CheckForQuicksand = true;
@@ -5847,9 +5892,9 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 		CheckForFloatBob = true;
 	}
 
-	P_AddFFloorToList(sec, ffloor);
+	P_AddFFloorToList(sec, fflr);
 
-	return ffloor;
+	return fflr;
 }
 
 //
@@ -5870,7 +5915,7 @@ static void P_AddSpikeThinker(sector_t *sec, INT32 referrer)
 
 	// create and initialize new thinker
 	spikes = Z_Calloc(sizeof (*spikes), PU_LEVSPEC, NULL);
-	P_AddThinker(&spikes->thinker);
+	P_AddThinker(THINK_MAIN, &spikes->thinker);
 
 	spikes->thinker.function.acp1 = (actionf_p1)T_SpikeSector;
 
@@ -5892,7 +5937,7 @@ static void P_AddFloatThinker(sector_t *sec, INT32 tag, line_t *sourceline)
 
 	// create and initialize new thinker
 	floater = Z_Calloc(sizeof (*floater), PU_LEVSPEC, NULL);
-	P_AddThinker(&floater->thinker);
+	P_AddThinker(THINK_MAIN, &floater->thinker);
 
 	floater->thinker.function.acp1 = (actionf_p1)T_FloatSector;
 
@@ -5916,7 +5961,7 @@ static inline void P_AddBridgeThinker(line_t *sourceline, sector_t *sec)
 
 	// create an initialize new thinker
 	bridge = Z_Calloc(sizeof (*bridge), PU_LEVSPEC, NULL);
-	P_AddThinker(&bridge->thinker);
+	P_AddThinker(THINK_MAIN, &bridge->thinker);
 
 	bridge->thinker.function.acp1 = (actionf_p1)T_BridgeThinker;
 
@@ -5952,7 +5997,7 @@ static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control,
 
 	// create and initialize new displacement thinker
 	displace = Z_Calloc(sizeof (*displace), PU_LEVSPEC, NULL);
-	P_AddThinker(&displace->thinker);
+	P_AddThinker(THINK_MAIN, &displace->thinker);
 
 	displace->thinker.function.acp1 = (actionf_p1)T_PlaneDisplace;
 	displace->affectee = affectee;
@@ -5979,7 +6024,7 @@ static void P_AddBlockThinker(sector_t *sec, line_t *sourceline)
 
 	// create and initialize new elevator thinker
 	block = Z_Calloc(sizeof (*block), PU_LEVSPEC, NULL);
-	P_AddThinker(&block->thinker);
+	P_AddThinker(THINK_MAIN, &block->thinker);
 
 	block->thinker.function.acp1 = (actionf_p1)T_MarioBlockChecker;
 	block->sourceline = sourceline;
@@ -6008,7 +6053,7 @@ static void P_AddRaiseThinker(sector_t *sec, line_t *sourceline)
 	levelspecthink_t *raise;
 
 	raise = Z_Calloc(sizeof (*raise), PU_LEVSPEC, NULL);
-	P_AddThinker(&raise->thinker);
+	P_AddThinker(THINK_MAIN, &raise->thinker);
 
 	raise->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
 
@@ -6047,7 +6092,7 @@ static void P_AddOldAirbob(sector_t *sec, line_t *sourceline, boolean noadjust)
 	levelspecthink_t *airbob;
 
 	airbob = Z_Calloc(sizeof (*airbob), PU_LEVSPEC, NULL);
-	P_AddThinker(&airbob->thinker);
+	P_AddThinker(THINK_MAIN, &airbob->thinker);
 
 	airbob->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
 
@@ -6108,7 +6153,7 @@ static inline void P_AddThwompThinker(sector_t *sec, sector_t *actionsector, lin
 
 	// create and initialize new elevator thinker
 	thwomp = Z_Calloc(sizeof (*thwomp), PU_LEVSPEC, NULL);
-	P_AddThinker(&thwomp->thinker);
+	P_AddThinker(THINK_MAIN, &thwomp->thinker);
 
 	thwomp->thinker.function.acp1 = (actionf_p1)T_ThwompSector;
 
@@ -6144,7 +6189,7 @@ static inline void P_AddNoEnemiesThinker(sector_t *sec, line_t *sourceline)
 
 	// create and initialize new thinker
 	nobaddies = Z_Calloc(sizeof (*nobaddies), PU_LEVSPEC, NULL);
-	P_AddThinker(&nobaddies->thinker);
+	P_AddThinker(THINK_MAIN, &nobaddies->thinker);
 
 	nobaddies->thinker.function.acp1 = (actionf_p1)T_NoEnemiesSector;
 
@@ -6160,13 +6205,13 @@ static inline void P_AddNoEnemiesThinker(sector_t *sec, line_t *sourceline)
   * \sa P_SpawnSpecials, T_EachTimeThinker
   * \author SSNTails <http://www.ssntails.org>
   */
-static inline void P_AddEachTimeThinker(sector_t *sec, line_t *sourceline)
+static void P_AddEachTimeThinker(sector_t *sec, line_t *sourceline)
 {
 	levelspecthink_t *eachtime;
 
 	// create and initialize new thinker
 	eachtime = Z_Calloc(sizeof (*eachtime), PU_LEVSPEC, NULL);
-	P_AddThinker(&eachtime->thinker);
+	P_AddThinker(THINK_MAIN, &eachtime->thinker);
 
 	eachtime->thinker.function.acp1 = (actionf_p1)T_EachTimeThinker;
 
@@ -6188,7 +6233,7 @@ static inline void P_AddCameraScanner(sector_t *sourcesec, sector_t *actionsecto
 
 	// create and initialize new elevator thinker
 	elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
-	P_AddThinker(&elevator->thinker);
+	P_AddThinker(THINK_MAIN, &elevator->thinker);
 
 	elevator->thinker.function.acp1 = (actionf_p1)T_CameraScanner;
 	elevator->type = elevateBounce;
@@ -6212,30 +6257,30 @@ void T_LaserFlash(laserthink_t *flash)
 	msecnode_t *node;
 	mobj_t *thing;
 	sector_t *sourcesec;
-	ffloor_t *ffloor = flash->ffloor;
+	ffloor_t *fflr = flash->ffloor;
 	sector_t *sector = flash->sector;
 	fixed_t top, bottom;
 
-	if (!ffloor || !(ffloor->flags & FF_EXISTS))
+	if (!fflr || !(fflr->flags & FF_EXISTS))
 		return;
 
 	if (leveltime & 2)
-		//ffloor->flags |= FF_RENDERALL;
-		ffloor->alpha = 0xB0;
+		//fflr->flags |= FF_RENDERALL;
+		fflr->alpha = 0xB0;
 	else
-		//ffloor->flags &= ~FF_RENDERALL;
-		ffloor->alpha = 0x90;
+		//fflr->flags &= ~FF_RENDERALL;
+		fflr->alpha = 0x90;
 
-	sourcesec = ffloor->master->frontsector; // Less to type!
+	sourcesec = fflr->master->frontsector; // Less to type!
 
 #ifdef ESLOPE
-	top = (*ffloor->t_slope) ? P_GetZAt(*ffloor->t_slope, sector->soundorg.x, sector->soundorg.y)
-			: *ffloor->topheight;
-	bottom = (*ffloor->b_slope) ? P_GetZAt(*ffloor->b_slope, sector->soundorg.x, sector->soundorg.y)
-			: *ffloor->bottomheight;
+	top = (*fflr->t_slope) ? P_GetZAt(*fflr->t_slope, sector->soundorg.x, sector->soundorg.y)
+			: *fflr->topheight;
+	bottom = (*fflr->b_slope) ? P_GetZAt(*fflr->b_slope, sector->soundorg.x, sector->soundorg.y)
+			: *fflr->bottomheight;
 	sector->soundorg.z = (top + bottom)/2;
 #else
-	sector->soundorg.z = (*ffloor->topheight + *ffloor->bottomheight)/2;
+	sector->soundorg.z = (*fflr->topheight + *fflr->bottomheight)/2;
 #endif
 	S_StartSound(&sector->soundorg, sfx_laser);
 
@@ -6244,7 +6289,7 @@ void T_LaserFlash(laserthink_t *flash)
 	{
 		thing = node->m_thing;
 
-		if ((ffloor->master->flags & ML_EFFECT1)
+		if ((fflr->master->flags & ML_EFFECT1)
 			&& thing->flags & MF_BOSS)
 			continue; // Don't hurt bosses
 
@@ -6268,7 +6313,7 @@ void T_LaserFlash(laserthink_t *flash)
 
 /** Adds a laser thinker to a 3Dfloor.
   *
-  * \param ffloor      3Dfloor to turn into a laser block.
+  * \param fflr      3Dfloor to turn into a laser block.
   * \param sector      Target sector.
   * \param secthkiners Lists of thinkers sorted by sector. May be NULL.
   * \sa T_LaserFlash
@@ -6277,17 +6322,17 @@ void T_LaserFlash(laserthink_t *flash)
 static inline void EV_AddLaserThinker(sector_t *sec, sector_t *sec2, line_t *line, thinkerlist_t *secthinkers)
 {
 	laserthink_t *flash;
-	ffloor_t *ffloor = P_AddFakeFloor(sec, sec2, line, laserflags, secthinkers);
+	ffloor_t *fflr = P_AddFakeFloor(sec, sec2, line, laserflags, secthinkers);
 
-	if (!ffloor)
+	if (!fflr)
 		return;
 
 	flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
 
-	P_AddThinker(&flash->thinker);
+	P_AddThinker(THINK_MAIN, &flash->thinker);
 
 	flash->thinker.function.acp1 = (actionf_p1)T_LaserFlash;
-	flash->ffloor = ffloor;
+	flash->ffloor = fflr;
 	flash->sector = sec; // For finding mobjs
 	flash->sec = sec2;
 	flash->sourceline = line;
@@ -6351,7 +6396,7 @@ void P_InitSpecials(void)
 
 static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs)
 {
-	if (!(master->flags & ML_NOSONIC)) // Modify floor flat alignment unless NOSONIC flag is set
+	if (!(master->flags & ML_NETONLY)) // Modify floor flat alignment unless ML_NETONLY flag is set
 	{
 		sector->spawn_flrpic_angle = sector->floorpic_angle = flatangle;
 		sector->floor_xoffs += xoffs;
@@ -6361,7 +6406,7 @@ static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flata
 		sector->spawn_flr_yoffs = sector->floor_yoffs;
 	}
 
-	if (!(master->flags & ML_NOTAILS)) // Modify ceiling flat alignment unless NOTAILS flag is set
+	if (!(master->flags & ML_NONET)) // Modify ceiling flat alignment unless ML_NONET flag is set
 	{
 		sector->spawn_ceilpic_angle = sector->ceilingpic_angle = flatangle;
 		sector->ceiling_xoffs += xoffs;
@@ -6394,6 +6439,9 @@ void P_SpawnSpecials(INT32 fromnetsave)
 	// but currently isn't.
 	(void)fromnetsave;
 
+	// yep, we do this here - "bossdisabled" is considered an apparatus of specials.
+	bossdisabled = 0;
+
 	// Init special SECTORs.
 	sector = sectors;
 	for (i = 0; i < numsectors; i++, sector++)
@@ -6442,8 +6490,6 @@ void P_SpawnSpecials(INT32 fromnetsave)
 		}
 	}
 
-	P_SearchForDisableLinedefs(); // Disable linedefs are now allowed to disable *any* line
-
 	P_SpawnScrollers(); // Add generalized scrollers
 	P_SpawnFriction();  // Friction model using linedefs
 	P_SpawnPushers();   // Pusher model using linedefs
@@ -6453,7 +6499,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 	secthinkers = Z_Calloc(numsectors * sizeof(thinkerlist_t), PU_STATIC, NULL);
 
 	// Firstly, find out how many there are in each sector
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
 	{
 		if (th->function.acp1 == (actionf_p1)T_SpikeSector)
 			secthinkers[((levelspecthink_t *)th)->sector - sectors].count++;
@@ -6473,7 +6519,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 		}
 
 	// Finally, populate the lists.
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
 	{
 		size_t secnum = (size_t)-1;
 
@@ -6492,28 +6538,22 @@ void P_SpawnSpecials(INT32 fromnetsave)
 	// Init line EFFECTs
 	for (i = 0; i < numlines; i++)
 	{
-		if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment with arbitrary skin setups...
+		if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment in netgames...
 		{
 			// set line specials to 0 here too, same reason as above
 			if (netgame || multiplayer)
 			{
-				// future: nonet flag?
-			}
-			else if ((lines[i].flags & ML_NETONLY) == ML_NETONLY)
-			{
-				lines[i].special = 0;
-				continue;
-			}
-			else
-			{
-				if ((players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
-				|| (players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
-				|| (players[consoleplayer].charability == CA_GLIDEANDCLIMB && (lines[i].flags & ML_NOKNUX)))
+				if (lines[i].flags & ML_NONET)
 				{
 					lines[i].special = 0;
 					continue;
 				}
 			}
+			else if (lines[i].flags & ML_NETONLY)
+			{
+				lines[i].special = 0;
+				continue;
+			}
 		}
 
 		switch (lines[i].special)
@@ -6552,20 +6592,14 @@ void P_SpawnSpecials(INT32 fromnetsave)
 					P_AddCameraScanner(&sectors[sec], &sectors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y));
 				break;
 
-#ifdef PARANOIA
-			case 6: // Disable tags if level not cleared
-				I_Error("Failed to catch a disable linedef");
-				break;
-#endif
-
 			case 7: // Flat alignment - redone by toast
-				if ((lines[i].flags & (ML_NOSONIC|ML_NOTAILS)) != (ML_NOSONIC|ML_NOTAILS)) // If you can do something...
+				if ((lines[i].flags & (ML_NETONLY|ML_NONET)) != (ML_NETONLY|ML_NONET)) // If you can do something...
 				{
 					angle_t flatangle = InvAngle(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y));
 					fixed_t xoffs;
 					fixed_t yoffs;
 
-					if (lines[i].flags & ML_NOKNUX) // Set offset through x and y texture offsets if NOKNUX flag is set
+					if (lines[i].flags & ML_EFFECT6) // Set offset through x and y texture offsets if ML_EFFECT6 flag is set
 					{
 						xoffs = sides[lines[i].sidenum[0]].textureoffset;
 						yoffs = sides[lines[i].sidenum[0]].rowoffset;
@@ -7204,6 +7238,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 301:
 			case 310:
 			case 312:
+			case 332:
 				sec = sides[*lines[i].sidenum].sector - sectors;
 				P_AddEachTimeThinker(&sectors[sec], &lines[i]);
 				break;
@@ -7252,6 +7287,11 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 330:
 				break;
 
+			// Skin trigger executors
+			case 331:
+			case 333:
+				break;
+
 			case 399: // Linedef execute on map load
 				// This is handled in P_RunLevelLoadExecutors.
 				break;
@@ -7290,6 +7330,24 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 431:
 				break;
 
+			case 449: // Enable bosses with parameter
+			{
+				INT32 bossid = sides[*lines[i].sidenum].textureoffset>>FRACBITS;
+				if (bossid & ~15) // if any bits other than first 16 are set
+				{
+					CONS_Alert(CONS_WARNING,
+						M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
+						lines[i].tag);
+					break;
+				}
+				if (!(lines[i].flags & ML_NOCLIMB))
+				{
+					bossdisabled |= (1<<bossid); // gotta disable in the first place to enable
+					CONS_Debug(DBG_GAMELOGIC, "Line type 449 spawn effect: bossid disabled = %d", bossid);
+				}
+				break;
+			}
+
 			// 500 is used for a scroller
 			// 501 is used for a scroller
 			// 502 is used for a scroller
@@ -7368,14 +7426,6 @@ void P_SpawnSpecials(INT32 fromnetsave)
 					sectors[s].extra_colormap = sectors[s].spawn_extra_colormap = sides[lines[i].sidenum[0]].colormap_data;
 				break;
 
-#ifdef ESLOPE // Slope copy specials. Handled here for sanity.
-			case 720:
-			case 721:
-			case 722:
-				P_CopySectorSlope(&lines[i]);
-				break;
-#endif
-
 			default:
 				break;
 		}
@@ -7735,7 +7785,7 @@ static void Add_Scroller(INT32 type, fixed_t dx, fixed_t dy, INT32 control, INT3
 	if ((s->control = control) != -1)
 		s->last_height = sectors[control].floorheight + sectors[control].ceilingheight;
 	s->affectee = affectee;
-	P_AddThinker(&s->thinker);
+	P_AddThinker(THINK_MAIN, &s->thinker);
 }
 
 /** Initializes the scrollers.
@@ -7869,7 +7919,7 @@ static void Add_MasterDisappearer(tic_t appeartime, tic_t disappeartime, tic_t o
 	d->exists = true;
 	d->timer = 1;
 
-	P_AddThinker(&d->thinker);
+	P_AddThinker(THINK_MAIN, &d->thinker);
 }
 
 /** Makes a FOF appear/disappear
@@ -7916,6 +7966,7 @@ void T_Disappear(disappear_t *d)
 				}
 			}
 			sectors[s].moved = true;
+			P_RecalcPrecipInSector(&sectors[s]);
 		}
 
 		if (d->exists)
@@ -8264,7 +8315,7 @@ static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloor
 	d->ffloornum = (UINT32)ffloornum;
 
 	d->alpha = d->sourcevalue = rover->alpha;
-	d->destvalue = max(1, min(256, relative ? rover->alpha + destvalue : destvalue)); // ffloor->alpha is 1-256
+	d->destvalue = max(1, min(256, relative ? rover->alpha + destvalue : destvalue)); // rover->alpha is 1-256
 
 	if (ticbased)
 	{
@@ -8358,7 +8409,7 @@ static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloor
 				FixedFloor(FixedDiv(abs(d->destvalue - d->alpha), d->speed))/FRACUNIT);
 	}
 
-	P_AddThinker(&d->thinker);
+	P_AddThinker(THINK_MAIN, &d->thinker);
 }
 
 /** Makes a FOF fade
@@ -8428,7 +8479,7 @@ static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, ext
 	}
 
 	sector->fadecolormapdata = d;
-	P_AddThinker(&d->thinker); // add thinker
+	P_AddThinker(THINK_MAIN, &d->thinker);
 }
 
 void T_FadeColormap(fadecolormap_t *d)
@@ -8547,7 +8598,7 @@ static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32
 	else
 		f->roverfriction = false;
 
-	P_AddThinker(&f->thinker);
+	P_AddThinker(THINK_MAIN, &f->thinker);
 }
 
 /** Applies friction to all things in a sector.
@@ -8713,7 +8764,7 @@ static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *
 		p->z = p->source->z;
 	}
 	p->affectee = affectee;
-	P_AddThinker(&p->thinker);
+	P_AddThinker(THINK_MAIN, &p->thinker);
 }
 
 
@@ -9214,40 +9265,4 @@ static void P_SpawnPushers(void)
 					Add_Pusher(p_downwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 		}
-}
-
-static void P_SearchForDisableLinedefs(void)
-{
-	size_t i;
-	INT32 j;
-
-	// Look for disable linedefs
-	for (i = 0; i < numlines; i++)
-	{
-		if (lines[i].special == 6)
-		{
-			// Remove special
-			// Do *not* remove tag. That would mess with the tag lists
-			// that P_InitTagLists literally just created!
-			lines[i].special = 0;
-
-			// Ability flags can disable disable linedefs now, lol
-			if (netgame || multiplayer)
-			{
-				// future: nonet flag?
-			}
-			else if ((lines[i].flags & ML_NETONLY) == ML_NETONLY)
-				continue; // Net-only never triggers in single player
-			else if (players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
-				continue;
-			else if (players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
-				continue;
-			else if (players[consoleplayer].charability == CA_GLIDEANDCLIMB && (lines[i].flags & ML_NOKNUX))
-				continue;
-
-			// Disable any linedef specials with our tag.
-			for (j = -1; (j = P_FindLineFromLineTag(&lines[i], j)) >= 0;)
-				lines[j].special = 0;
-		}
-	}
-}
+}
\ No newline at end of file
diff --git a/src/p_tick.c b/src/p_tick.c
index 6f7c96ead28bf5a7ca1061345959ba46d874ddd3..cfdd54eb2091bcb144d195e6eb5968e9192543ea 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -35,8 +35,8 @@ tic_t leveltime;
 // but the first element must be thinker_t.
 //
 
-// Both the head and tail of the thinker list.
-thinker_t thinkercap;
+// The entries will behave like both the head and tail of the lists.
+thinker_t thlist[NUM_THINKERLISTS];
 
 void Command_Numthinkers_f(void)
 {
@@ -44,6 +44,9 @@ void Command_Numthinkers_f(void)
 	INT32 count = 0;
 	actionf_p1 action;
 	thinker_t *think;
+	thinklistnum_t start = 0;
+	thinklistnum_t end = NUM_THINKERLISTS - 1;
+	thinklistnum_t i;
 
 	if (gamestate != GS_LEVEL)
 	{
@@ -70,6 +73,7 @@ void Command_Numthinkers_f(void)
 	switch (num)
 	{
 		case 1:
+			start = end = THINK_MOBJ;
 			action = (actionf_p1)P_MobjThinker;
 			CONS_Printf(M_GetText("Number of %s: "), "P_MobjThinker");
 			break;
@@ -82,14 +86,17 @@ void Command_Numthinkers_f(void)
 			CONS_Printf(M_GetText("Number of %s: "), "P_SnowThinker");
 			break;*/
 		case 2:
+			start = end = THINK_PRECIP;
 			action = (actionf_p1)P_NullPrecipThinker;
 			CONS_Printf(M_GetText("Number of %s: "), "P_NullPrecipThinker");
 			break;
 		case 3:
+			start = end = THINK_MAIN;
 			action = (actionf_p1)T_Friction;
 			CONS_Printf(M_GetText("Number of %s: "), "T_Friction");
 			break;
 		case 4:
+			start = end = THINK_MAIN;
 			action = (actionf_p1)T_Pusher;
 			CONS_Printf(M_GetText("Number of %s: "), "T_Pusher");
 			break;
@@ -102,12 +109,15 @@ void Command_Numthinkers_f(void)
 			return;
 	}
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (i = start; i <= end; i++)
 	{
-		if (think->function.acp1 != action)
-			continue;
+		for (think = thlist[i].next; think != &thlist[i]; think = think->next)
+		{
+			if (think->function.acp1 != action)
+				continue;
 
-		count++;
+			count++;
+		}
 	}
 
 	CONS_Printf("%d\n", count);
@@ -139,9 +149,9 @@ void Command_CountMobjs_f(void)
 
 			count = 0;
 
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				if (((mobj_t *)th)->type == i)
@@ -159,9 +169,9 @@ void Command_CountMobjs_f(void)
 	{
 		count = 0;
 
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			if (((mobj_t *)th)->type == i)
@@ -178,19 +188,22 @@ void Command_CountMobjs_f(void)
 //
 void P_InitThinkers(void)
 {
-	thinkercap.prev = thinkercap.next = &thinkercap;
+	UINT8 i;
+	for (i = 0; i < NUM_THINKERLISTS; i++)
+		thlist[i].prev = thlist[i].next = &thlist[i];
 }
 
-//
-// P_AddThinker
 // Adds a new thinker at the end of the list.
-//
-void P_AddThinker(thinker_t *thinker)
+void P_AddThinker(const thinklistnum_t n, thinker_t *thinker)
 {
-	thinkercap.prev->next = thinker;
-	thinker->next = &thinkercap;
-	thinker->prev = thinkercap.prev;
-	thinkercap.prev = thinker;
+#ifdef PARANOIA
+	I_Assert(n >= 0 && n < NUM_THINKERLISTS);
+#endif
+
+	thlist[n].prev->next = thinker;
+	thinker->next = &thlist[n];
+	thinker->prev = thlist[n].prev;
+	thlist[n].prev = thinker;
 
 	thinker->references = 0;    // killough 11/98: init reference counter to 0
 }
@@ -213,22 +226,33 @@ static thinker_t *currentthinker;
 // remove it, and set currentthinker to one node preceeding it, so
 // that the next step in P_RunThinkers() will get its successor.
 //
-void P_RemoveThinkerDelayed(void *pthinker)
+void P_RemoveThinkerDelayed(thinker_t *thinker)
 {
-	thinker_t *thinker = pthinker;
-	if (!thinker->references)
+	thinker_t *next;
+#ifdef PARANOIA
+#define BEENAROUNDBIT (0x40000000) // has to be sufficiently high that it's unlikely to happen in regular gameplay. If you change this, pay attention to the bit pattern of INT32_MIN.
+	if (thinker->references & ~BEENAROUNDBIT)
 	{
-		{
-			/* Remove from main thinker list */
-			thinker_t *next = thinker->next;
-			/* Note that currentthinker is guaranteed to point to us,
-			 * and since we're freeing our memory, we had better change that. So
-			 * point it to thinker->prev, so the iterator will correctly move on to
-			 * thinker->prev->next = thinker->next */
-			(next->prev = currentthinker = thinker->prev)->next = next;
-		}
-		Z_Free(thinker);
+		if (thinker->references & BEENAROUNDBIT) // Usually gets cleared up in one frame; what's going on here, then?
+			CONS_Printf("Number of potentially faulty references: %d\n", (thinker->references & ~BEENAROUNDBIT));
+		thinker->references |= BEENAROUNDBIT;
+		return;
 	}
+#undef BEENAROUNDBIT
+#else
+	if (thinker->references)
+		return;
+#endif
+
+	/* Remove from main thinker list */
+	next = thinker->next;
+	/* Note that currentthinker is guaranteed to point to us,
+	* and since we're freeing our memory, we had better change that. So
+	* point it to thinker->prev, so the iterator will correctly move on to
+	* thinker->prev->next = thinker->next */
+	(next->prev = currentthinker = thinker->prev)->next = next;
+
+	Z_Free(thinker);
 }
 
 //
@@ -248,7 +272,7 @@ void P_RemoveThinker(thinker_t *thinker)
 #ifdef HAVE_BLUA
 	LUA_InvalidateUserdata(thinker);
 #endif
-	thinker->function.acp1 = P_RemoveThinkerDelayed;
+	thinker->function.acp1 = (actionf_p1)P_RemoveThinkerDelayed;
 }
 
 /*
@@ -296,11 +320,18 @@ if ((*mop = targ) != NULL) // Set new target and if non-NULL, increase its count
 //
 static inline void P_RunThinkers(void)
 {
-	for (currentthinker = thinkercap.next; currentthinker != &thinkercap; currentthinker = currentthinker->next)
+	size_t i;
+	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		if (currentthinker->function.acp1)
+		for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = currentthinker->next)
+		{
+#ifdef PARANOIA
+			I_Assert(currentthinker->function.acp1 != NULL)
+#endif
 			currentthinker->function.acp1(currentthinker);
+		}
 	}
+
 }
 
 //
@@ -487,10 +518,7 @@ static inline void P_DoSpecialStageStuff(void)
 			}
 		}
 		else
-		{
 			sstimer = 0;
-			stagefailed = true;
-		}
 	}
 }
 
diff --git a/src/p_tick.h b/src/p_tick.h
index 169c54c8e798f099d7bbca00e720dacdc86f42ef..8551ccc7606015b895c928c197328f2ee1247289 100644
--- a/src/p_tick.h
+++ b/src/p_tick.h
@@ -27,7 +27,7 @@ void Command_CountMobjs_f(void);
 void P_Ticker(boolean run);
 void P_PreTicker(INT32 frames);
 void P_DoTeamscrambling(void);
-void P_RemoveThinkerDelayed(void *pthinker); //killed
+void P_RemoveThinkerDelayed(thinker_t *thinker); //killed
 mobj_t *P_SetTarget(mobj_t **mo, mobj_t *target);   // killough 11/98
 
 #endif
diff --git a/src/p_user.c b/src/p_user.c
index 54ca6c78cda7b3c529333a4fcbbc218cfd7edda6..e14e922accd091ce12857300f2432409fb891d58 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -302,15 +302,39 @@ void P_GiveEmerald(boolean spawnObj)
 
 	S_StartSound(NULL, sfx_cgot); // Got the emerald!
 	emeralds |= (1 << em);
+	stagefailed = false;
 
-	if (spawnObj && playeringame[consoleplayer])
+	if (spawnObj)
 	{
 		// The Chaos Emerald begins to orbit us!
-		// Only give it to ONE person!
-		mobj_t *emmo = P_SpawnMobjFromMobj(players[consoleplayer].mo, 0, 0, players[consoleplayer].mo->height, MT_GOTEMERALD);
-		P_SetTarget(&emmo->target, players[consoleplayer].mo);
-		P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
-		P_SetTarget(&players[consoleplayer].mo->tracer, emmo);
+		// Only visibly give it to ONE person!
+		UINT8 i, pnum = ((playeringame[consoleplayer]) && (!players[consoleplayer].spectator) && (players[consoleplayer].mo)) ? consoleplayer : 255;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			mobj_t *emmo;
+			if (!playeringame[i])
+				continue;
+			if (players[i].spectator)
+				continue;
+			if (!players[i].mo)
+				continue;
+
+			emmo = P_SpawnMobjFromMobj(players[i].mo, 0, 0, players[i].mo->height, MT_GOTEMERALD);
+			P_SetTarget(&emmo->target, players[i].mo);
+			P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
+			P_SetTarget(&players[i].mo->tracer, emmo);
+
+			if (pnum == 255)
+			{
+				i = pnum;
+				continue;
+			}
+
+			if (i == pnum)
+				continue;
+
+			emmo->flags2 |= MF2_DONTDRAW;
+		}
 	}
 }
 
@@ -343,14 +367,18 @@ UINT8 P_FindLowestMare(void)
 
 	// scan the thinkers
 	// to find the egg capsule with the lowest mare
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
 
-		if (mo2->type == MT_EGGCAPSULE && mo2->health > 0)
+		if (mo2->type != MT_EGGCAPSULE)
+			continue;
+		if (mo2->health <= 0)
+			continue;
+
 		{
 			const UINT8 threshold = (UINT8)mo2->threshold;
 			if (mare == 255)
@@ -392,34 +420,34 @@ boolean P_TransferToNextMare(player_t *player)
 
 	// scan the thinkers
 	// to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
 
-		if (mo2->type == MT_AXIS)
+		if (mo2->type != MT_AXIS)
+			continue;
+
+		if (mo2->threshold != mare)
+			continue;
+
+		if (closestaxis == NULL)
 		{
-			if (mo2->threshold == mare)
-			{
-				if (closestaxis == NULL)
-				{
-					closestaxis = mo2;
-					lowestaxisnum = mo2->health;
-					dist2 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y)-mo2->radius;
-				}
-				else if (mo2->health < lowestaxisnum)
-				{
-					dist1 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y)-mo2->radius;
+			closestaxis = mo2;
+			lowestaxisnum = mo2->health;
+			dist2 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y) - mo2->radius;
+		}
+		else if (mo2->health < lowestaxisnum)
+		{
+			dist1 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y) - mo2->radius;
 
-					if (dist1 < dist2)
-					{
-						closestaxis = mo2;
-						lowestaxisnum = mo2->health;
-						dist2 = dist1;
-					}
-				}
+			if (dist1 < dist2)
+			{
+				closestaxis = mo2;
+				lowestaxisnum = mo2->health;
+				dist2 = dist1;
 			}
 		}
 	}
@@ -443,9 +471,9 @@ static mobj_t *P_FindAxis(INT32 mare, INT32 axisnum)
 
 	// scan the thinkers
 	// to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -454,11 +482,13 @@ static mobj_t *P_FindAxis(INT32 mare, INT32 axisnum)
 		if (!(mo2->flags2 & MF2_AXIS))
 			return NULL;
 
-		if (mo2->type == MT_AXIS)
-		{
-			if (mo2->health == axisnum && mo2->threshold == mare)
-				return mo2;
-		}
+		if (mo2->type != MT_AXIS)
+			continue;
+		if (mo2->health != axisnum)
+			continue;
+		if (mo2->threshold != mare)
+			continue;
+		return mo2;
 	}
 
 	return NULL;
@@ -476,9 +506,9 @@ static mobj_t *P_FindAxisTransfer(INT32 mare, INT32 axisnum, mobjtype_t type)
 
 	// scan the thinkers
 	// to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -487,11 +517,13 @@ static mobj_t *P_FindAxisTransfer(INT32 mare, INT32 axisnum, mobjtype_t type)
 		if (!(mo2->flags2 & MF2_AXIS))
 			return NULL;
 
-		if (mo2->type == type)
-		{
-			if (mo2->health == axisnum && mo2->threshold == mare)
-				return mo2;
-		}
+		if (mo2->type != type)
+			continue;
+		if (mo2->health != axisnum)
+			continue;
+		if (mo2->threshold != mare)
+			continue;
+		return mo2;
 	}
 
 	return NULL;
@@ -515,32 +547,33 @@ void P_TransferToAxis(player_t *player, INT32 axisnum)
 
 	// scan the thinkers
 	// to find the closest axis point
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
 
-		if (mo2->type == MT_AXIS)
+		if (mo2->type != MT_AXIS)
+			continue;
+		if (mo2->health != axisnum)
+			continue;
+		if (mo2->threshold != mare)
+			continue;
+
+		if (closestaxis == NULL)
 		{
-			if (mo2->health == axisnum && mo2->threshold == mare)
-			{
-				if (closestaxis == NULL)
-				{
-					closestaxis = mo2;
-					dist2 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y)-mo2->radius;
-				}
-				else
-				{
-					dist1 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y)-mo2->radius;
+			closestaxis = mo2;
+			dist2 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y) - mo2->radius;
+		}
+		else
+		{
+			dist1 = R_PointToDist2(player->mo->x, player->mo->y, mo2->x, mo2->y) - mo2->radius;
 
-					if (dist1 < dist2)
-					{
-						closestaxis = mo2;
-						dist2 = dist1;
-					}
-				}
+			if (dist1 < dist2)
+			{
+				closestaxis = mo2;
+				dist2 = dist1;
 			}
 		}
 	}
@@ -588,7 +621,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->mo->skin = &skins[player->skin];
 	player->followitem = skins[player->skin].followitem;
 	player->mo->color = player->skincolor;
-	G_GhostAddColor(GHC_NORMAL);
+	G_GhostAddColor(GHC_RETURNSKIN);
 
 	// Restore aiming angle
 	if (player == &players[consoleplayer])
@@ -606,7 +639,6 @@ static void P_DeNightserizePlayer(player_t *player)
 			if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 				players[i].nightstime = 1; // force everyone else to fall too.
 		player->exiting = 3*TICRATE;
-		stagefailed = true; // NIGHT OVER
 
 		// If you screwed up, kiss your score and ring bonus goodbye.
 		// But only do this in special stage (and instakill!) In regular stages, wait til we hit the ground.
@@ -615,9 +647,9 @@ static void P_DeNightserizePlayer(player_t *player)
 	}
 
 	// Check to see if the player should be killed.
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -707,6 +739,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		if (!(cv_debug || devparm) && !(netgame || multiplayer || demoplayback))
 			player->mo->color = skins[DEFAULTNIGHTSSKIN].prefcolor;
 		player->followitem = skins[DEFAULTNIGHTSSKIN].followitem;
+		G_GhostAddColor(GHC_NIGHTSSKIN);
 	}
 
 	player->nightstime = player->startedtime = player->lapstartedtime = nighttime*TICRATE;
@@ -866,6 +899,9 @@ boolean P_PlayerInPain(player_t *player)
 	if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate] && player->powers[pw_flashing])
 		return true;
 
+	if (player->mo->state == &states[S_PLAY_STUN])
+		return true;
+
 	return false;
 }
 
@@ -977,6 +1013,68 @@ void P_ResetPlayer(player_t *player)
 		CV_SetValue(&cv_analog2, true);
 }
 
+// P_PlayerCanDamage
+//
+// Can player do damage?
+//
+boolean P_PlayerCanDamage(player_t *player, mobj_t *thing)
+{
+	if (!player->mo || player->spectator || !thing || P_MobjWasRemoved(thing))
+		return false;
+
+#ifdef HAVE_BLUA
+	{
+		UINT8 shouldCollide = LUAh_PlayerCanDamage(player, thing);
+		if (P_MobjWasRemoved(thing))
+			return false; // removed???
+		if (shouldCollide == 1)
+			return true; // force yes
+		else if (shouldCollide == 2)
+			return false; // force no
+	}
+#endif
+
+	// Invinc/super. Not for Monitors.
+	if (!(thing->flags & MF_MONITOR) && (player->powers[pw_invulnerability] || player->powers[pw_super]))
+		return true;
+
+	// NiGHTS drill. Wasn't originally for monitors, but that's more an oversight being corrected than anything else.
+	if ((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
+		return true;
+
+	// Jumping.
+	if ((player->pflags & PF_JUMPED)
+	&& (!(player->pflags & PF_NOJUMPDAMAGE)
+		|| (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
+		return true;
+
+	// Spinning.
+	if (player->pflags & PF_SPINNING)
+		return true;
+
+	// From the front.
+	if (((player->pflags & PF_GLIDING) || (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+	&& (player->drawangle - R_PointToAngle2(player->mo->x - player->mo->momx, player->mo->y - player->mo->momy, thing->x, thing->y) +  + ANGLE_90) < ANGLE_180)
+		return true;
+
+	// From the top/bottom.
+	if (P_MobjFlip(player->mo)*(player->mo->z - (thing->z + thing->height/2)) > 0)
+	{
+		if ((player->charflags & SF_STOMPDAMAGE || player->pflags & PF_BOUNCING) && (P_MobjFlip(player->mo)*player->mo->momz < 0))
+			return true;
+	}
+	else if (player->charability == CA_FLY && player->panim == PA_ABILITY)
+		return true;
+
+	// Shield stomp.
+	if (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (player->pflags & PF_SHIELDABILITY))
+		return true;
+
+	return false;
+}
+
+
+
 //
 // P_GivePlayerRings
 //
@@ -1551,6 +1649,7 @@ void P_SpawnShieldOrb(player_t *player)
 		orbtype = MT_ARMAGEDDON_ORB;
 		break;
 	case SH_PITY:
+	case SH_PINK: // PITY IN PINK
 		orbtype = MT_PITY_ORB;
 		break;
 	case SH_FLAMEAURA:
@@ -1567,9 +1666,9 @@ void P_SpawnShieldOrb(player_t *player)
 	}
 
 	// blaze through the thinkers to see if an orb already exists!
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		shieldobj = (mobj_t *)th;
@@ -1581,7 +1680,13 @@ void P_SpawnShieldOrb(player_t *player)
 	shieldobj = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, orbtype);
 	shieldobj->flags2 |= MF2_SHIELD;
 	P_SetTarget(&shieldobj->target, player->mo);
-	shieldobj->color = (UINT8)shieldobj->info->painchance;
+	if ((player->powers[pw_shield] & SH_NOSTACK) == SH_PINK)
+	{
+		shieldobj->color = SKINCOLOR_PINK;
+		shieldobj->colorized = true;
+	}
+	else
+		shieldobj->color = (UINT8)shieldobj->info->painchance;
 	shieldobj->threshold = (player->powers[pw_shield] & SH_FORCE) ? SH_FORCE : (player->powers[pw_shield] & SH_NOSTACK);
 
 	if (shieldobj->info->seestate)
@@ -1687,6 +1792,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 	}
 
 	ghost->color = mobj->color;
+	ghost->colorized = mobj->colorized; // alternatively, "true" for sonic advance style colourisation
 
 	ghost->angle = (mobj->player ? mobj->player->drawangle : mobj->angle);
 	ghost->sprite = mobj->sprite;
@@ -1729,6 +1835,9 @@ void P_SpawnThokMobj(player_t *player)
 	if (player->spectator)
 		return;
 
+	if (!type)
+		return;
+
 	if (type == MT_GHOST)
 		mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us
 	else
@@ -1789,6 +1898,9 @@ void P_SpawnSpinMobj(player_t *player, mobjtype_t type)
 	if (player->spectator)
 		return;
 
+	if (!type)
+		return;
+
 	if (type == MT_GHOST)
 		mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us
 	else
@@ -1919,7 +2031,7 @@ boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 //
 // Handles player hitting floor surface.
 // Returns whether to clip momz.
-boolean P_PlayerHitFloor(player_t *player)
+boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 {
 	boolean clipmomz;
 
@@ -1927,57 +2039,104 @@ boolean P_PlayerHitFloor(player_t *player)
 
 	if ((clipmomz = !(P_CheckDeathPitCollide(player->mo))) && player->mo->health && !player->spectator)
 	{
-		if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_THOKKED) && (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
+		if (dorollstuff)
 		{
-			player->pflags |= PF_SPINNING;
-			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-			S_StartSound(player->mo, sfx_spin);
+			if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_THOKKED) && (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
+			{
+				player->pflags |= PF_SPINNING;
+				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+				S_StartSound(player->mo, sfx_spin);
+			}
+			else
+				player->pflags &= ~PF_SPINNING;
 		}
-		else
-		{
-			player->pflags &= ~PF_SPINNING;
 
-			if (player->pflags & PF_GLIDING) // ground gliding
+		if (player->pflags & PF_GLIDING) // ground gliding
+		{
+			if (dorollstuff)
 			{
 				player->skidtime = TICRATE;
 				player->mo->tics = -1;
 			}
-			else if (player->charability2 == CA2_MELEE && (player->panim == PA_ABILITY2 && player->mo->state-states != S_PLAY_MELEE_LANDING))
+			else
+				player->pflags &= ~PF_GLIDING;
+		}
+		else if (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
+		{
+			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 if (player->pflags & PF_JUMPED || !(player->pflags & PF_SPINNING)
+		}
+		else if (player->charability2 == CA2_GUNSLINGER && player->panim == PA_ABILITY2)
+			;
+		else if (player->pflags & PF_JUMPED || !(player->pflags & PF_SPINNING)
 			|| player->powers[pw_tailsfly] || player->mo->state-states == S_PLAY_FLY_TIRED)
+		{
+			if (player->cmomx || player->cmomy)
 			{
-				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->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);
 			}
 		}
 
@@ -2197,7 +2356,7 @@ static void P_CheckBustableBlocks(player_t *player)
 					//if (metalrecording)
 					//	G_RecordBustup(rover);
 
-					EV_CrumbleChain(node->m_sector, rover);
+					EV_CrumbleChain(NULL, rover); // node->m_sector
 
 					// Run a linedef executor??
 					if (rover->master->flags & ML_EFFECT5)
@@ -2414,7 +2573,7 @@ static void P_CheckQuicksand(player_t *player)
 					player->mo->z = ceilingheight - player->mo->height;
 
 				if (player->mo->momz <= 0)
-					P_PlayerHitFloor(player);
+					P_PlayerHitFloor(player, false);
 			}
 			else
 			{
@@ -2426,7 +2585,7 @@ static void P_CheckQuicksand(player_t *player)
 					player->mo->z = floorheight;
 
 				if (player->mo->momz >= 0)
-					P_PlayerHitFloor(player);
+					P_PlayerHitFloor(player, false);
 			}
 
 			friction = abs(rover->master->v1->y - rover->master->v2->y)>>6;
@@ -3057,7 +3216,7 @@ static void P_DoClimbing(player_t *player)
 			angle_t sideangle;
 			fixed_t dx, dy;
 
-			for (think = thinkercap.next; think != &thinkercap; think = think->next)
+			for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
 			{
 				if (think->function.acp1 != (actionf_p1)T_Scroll)
 					continue;
@@ -3532,7 +3691,8 @@ static void P_DoTeeter(player_t *player)
 		if (teeter) // only bother with objects as a last resort if you were already teetering
 		{
 			mobj_t *oldtmthing = tmthing;
-			tmthing = teeterer = player->mo;
+			teeterer = player->mo;
+			P_SetTarget(&tmthing, teeterer);
 			teeterxl = teeterxh = player->mo->x;
 			teeteryl = teeteryh = player->mo->y;
 			couldteeter = false;
@@ -3546,7 +3706,7 @@ static void P_DoTeeter(player_t *player)
 				}
 teeterdone:
 			teeter = solidteeter;
-			tmthing = oldtmthing; // restore old tmthing, goodness knows what the game does with this before mobj thinkers
+			P_SetTarget(&tmthing, oldtmthing); // restore old tmthing, goodness knows what the game does with this before mobj thinkers
 		}
 	}
 	if (teeter)
@@ -4221,7 +4381,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 							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
-								visual->target = lockon;
+								P_SetTarget(&visual->target, lockon);
 							}
 						}
 						if ((cmd->buttons & BT_USE) && !(player->pflags & PF_USEDOWN))
@@ -4286,9 +4446,17 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					{
 						player->mo->z += P_MobjFlip(player->mo);
 						P_SetObjectMomZ(player->mo, player->mindash, false);
+						if (P_MobjFlip(player->mo)*player->mo->pmomz > 0)
+							player->mo->momz += player->mo->pmomz; // Add the platform's momentum to your jump.
+						else
+							player->mo->pmomz = 0;
 						if (player->mo->eflags & MFE_UNDERWATER)
 							player->mo->momz >>= 1;
+#if 0
 						if (FixedMul(player->speed, FINECOSINE(((player->mo->angle - R_PointToAngle2(0, 0, player->rmomx, player->rmomy)) >> ANGLETOFINESHIFT) & FINEMASK)) < FixedMul(player->maxdash, player->mo->scale))
+#else
+						if (player->speed < FixedMul(player->maxdash, player->mo->scale))
+#endif
 						{
 							player->drawangle = player->mo->angle;
 							P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->maxdash, player->mo->scale));
@@ -4362,6 +4530,7 @@ void P_DoJumpShield(player_t *player)
 			P_InstaThrust(spark, travelangle + i*(ANGLE_MAX/numangles), FixedMul(4*FRACUNIT, spark->scale));
 			if (i % 2)
 				P_SetObjectMomZ(spark, -4*FRACUNIT, false);
+			spark->fuse = 18;
 		}
 #undef limitangle
 #undef numangles
@@ -4422,6 +4591,57 @@ void P_DoAbilityBounce(player_t *player, boolean changemomz)
 	player->pflags |= PF_BOUNCING|PF_THOKKED;
 }
 
+//
+// P_TwinSpinRejuvenate
+//
+// CA_TWINSPIN landing handling
+//
+void P_TwinSpinRejuvenate(player_t *player, mobjtype_t type)
+{
+	fixed_t actionspd;
+	angle_t movang, ang, fa;
+	fixed_t v, h;
+	UINT8 i;
+
+	if (!player->mo || !type)
+		return;
+
+	actionspd = FixedMul(player->actionspd, player->mo->scale);
+
+	fa = (R_PointToAngle2(0, 0, player->mo->momz, FixedHypot(player->mo->momx, player->mo->momy))>>ANGLETOFINESHIFT) & FINEMASK;
+	movang = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
+	ang = 0;
+
+	v = FixedMul(actionspd, FINESINE(fa));
+	h = actionspd - FixedMul(actionspd, FINECOSINE(fa));
+
+	// hearticles
+	for (i = 0; i <= 7; i++)
+	{
+		fixed_t side = actionspd - FixedMul(h, abs(FINESINE((ang>>ANGLETOFINESHIFT) & FINEMASK)));
+		fixed_t xo = P_ReturnThrustX(NULL, ang + movang, side);
+		fixed_t yo = P_ReturnThrustY(NULL, ang + movang, side);
+		fixed_t zo = -FixedMul(FINECOSINE(((ang>>ANGLETOFINESHIFT) & FINEMASK)), v);
+		mobj_t *missile = P_SpawnMobjFromMobj(player->mo,
+			xo,
+			yo,
+			player->mo->height/2 + zo,
+			type);
+		P_SetTarget(&missile->target, player->mo);
+		P_SetScale(missile, (missile->destscale >>= 1));
+		missile->angle = ang + movang;
+		missile->fuse = TICRATE/2;
+		missile->extravalue2 = (99*FRACUNIT)/100;
+		missile->momx = xo;
+		missile->momy = yo;
+		missile->momz = zo;
+
+		ang += ANGLE_45;
+	}
+
+	player->pflags &= ~PF_THOKKED;
+}
+
 //
 // P_Telekinesis
 //
@@ -4438,9 +4658,9 @@ void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range)
 	if (player->powers[pw_super]) // increase range when super
 		range *= 2;
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -4491,7 +4711,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		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
-			visual->target = lockon;
+			P_SetTarget(&visual->target, lockon);
 		}
 	}
 
@@ -4637,10 +4857,10 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							player->mo->momx /= 2;
 							player->mo->momy /= 2;
 						}
-						else if (player->charability == CA_HOMINGTHOK)
+						if (player->charability == CA_HOMINGTHOK)
 						{
-							player->mo->momx /= 3;
-							player->mo->momy /= 3;
+							player->mo->momx /= 2;
+							player->mo->momy /= 2;
 						}
 
 						if (player->charability == CA_HOMINGTHOK)
@@ -5635,9 +5855,9 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 		fixed_t truexspeed = xspeed*(!(player->pflags & PF_TRANSFERTOCLOSEST) && player->mo->target->flags2 & MF2_AMBUSH ? -1 : 1);
 
 		// Find next waypoint
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -5645,24 +5865,24 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 			// Axis things are only at beginning of list.
 			if (!(mo2->flags2 & MF2_AXIS))
 				break;
+			if (!(mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+				continue;
+			if (mo2->threshold != sequence)
+				continue;
 
-			if ((mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE)
-				&& mo2->threshold == sequence)
+			if (player->pflags & PF_TRANSFERTOCLOSEST)
 			{
-				if (player->pflags & PF_TRANSFERTOCLOSEST)
-				{
-					if (mo2->health == player->axis1->health)
-						transfer1 = mo2;
-					else if (mo2->health == player->axis2->health)
-						transfer2 = mo2;
-				}
-				else
-				{
-					if (mo2->health == player->mo->target->health)
-						transfer1 = mo2;
-					else if (mo2->health == player->mo->target->health + 1)
-						transfer2 = mo2;
-				}
+				if (mo2->health == player->axis1->health)
+					transfer1 = mo2;
+				else if (mo2->health == player->axis2->health)
+					transfer2 = mo2;
+			}
+			else
+			{
+				if (mo2->health == player->mo->target->health)
+					transfer1 = mo2;
+				else if (mo2->health == player->mo->target->health + 1)
+					transfer2 = mo2;
 			}
 		}
 
@@ -5671,9 +5891,9 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 		// Look for a wrapper point.
 		if (!transfer1)
 		{
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -5681,27 +5901,28 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 				// Axis things are only at beginning of list.
 				if (!(mo2->flags2 & MF2_AXIS))
 					break;
+				if (!(mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+					continue;
+				if (mo2->threshold != sequence)
+					continue;
 
-				if (mo2->threshold == sequence && (mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+				if (!transfer1)
 				{
-					if (!transfer1)
-					{
-						transfer1 = mo2;
-						transfer1last = true;
-					}
-					else if (mo2->health > transfer1->health)
-					{
-						transfer1 = mo2;
-						transfer1last = true;
-					}
+					transfer1 = mo2;
+					transfer1last = true;
+				}
+				else if (mo2->health > transfer1->health)
+				{
+					transfer1 = mo2;
+					transfer1last = true;
 				}
 			}
 		}
 		if (!transfer2)
 		{
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -5709,19 +5930,20 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 				// Axis things are only at beginning of list.
 				if (!(mo2->flags2 & MF2_AXIS))
 					break;
+				if (!(mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+					continue;
+				if (mo2->threshold != sequence)
+					continue;
 
-				if (mo2->threshold == sequence && (mo2->type == MT_AXISTRANSFER || mo2->type == MT_AXISTRANSFERLINE))
+				if (!transfer2)
 				{
-					if (!transfer2)
-					{
-						transfer2 = mo2;
-						transfer2last = true;
-					}
-					else if (mo2->health > transfer2->health)
-					{
-						transfer2 = mo2;
-						transfer2last = true;
-					}
+					transfer2 = mo2;
+					transfer2last = true;
+				}
+				else if (mo2->health > transfer2->health)
+				{
+					transfer2 = mo2;
+					transfer2last = true;
 				}
 			}
 		}
@@ -6406,36 +6628,34 @@ static void P_NiGHTSMovement(player_t *player)
 
 		// scan the thinkers
 		// to find the closest axis point
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
 
-			if (mo2->type == MT_AXIS)
+			if (mo2->type != MT_AXIS)
+				continue;
+			if (mo2->threshold != player->mare)
+				continue;
+
+			if (closestaxis == NULL)
 			{
-				if (mo2->threshold == player->mare)
-				{
-					if (closestaxis == NULL)
-					{
-						closestaxis = mo2;
-						dist2 = R_PointToDist2(newx, newy, mo2->x, mo2->y)-mo2->radius;
-					}
-					else
-					{
-						dist1 = R_PointToDist2(newx, newy, mo2->x, mo2->y)-mo2->radius;
+				closestaxis = mo2;
+				dist2 = R_PointToDist2(newx, newy, mo2->x, mo2->y) - mo2->radius;
+			}
+			else
+			{
+				dist1 = R_PointToDist2(newx, newy, mo2->x, mo2->y) - mo2->radius;
 
-						if (dist1 < dist2)
-						{
-							closestaxis = mo2;
-							dist2 = dist1;
-						}
-					}
+				if (dist1 < dist2)
+				{
+					closestaxis = mo2;
+					dist2 = dist1;
 				}
 			}
 		}
-
 		P_SetTarget(&player->mo->target, closestaxis);
 	}
 
@@ -7216,9 +7436,9 @@ static void P_MovePlayer(player_t *player)
 			thinker_t *th;
 			mobj_t *mo2;
 
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -7724,7 +7944,7 @@ static void P_MovePlayer(player_t *player)
 				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
-					visual->target = lockon;
+					P_SetTarget(&visual->target, lockon);
 					P_SetMobjStateNF(visual, visual->info->spawnstate+1);
 				}
 			}
@@ -7757,7 +7977,7 @@ static void P_MovePlayer(player_t *player)
 				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 shield activation
+					// Force stop
 					if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
 					{
 						player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
@@ -7773,17 +7993,17 @@ static void P_MovePlayer(player_t *player)
 								if (P_SuperReady(player))
 									P_DoSuperTransformation(player, false);
 								break;
-							// Whirlwind/Thundercoin shield activation
+							// Whirlwind jump/Thunder jump
 							case SH_WHIRLWIND:
 							case SH_THUNDERCOIN:
 								P_DoJumpShield(player);
 								break;
-							// Armageddon shield activation
+							// Armageddon pow
 							case SH_ARMAGEDDON:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								P_BlackOw(player);
 								break;
-							// Attract shield activation
+							// Attraction blast
 							case SH_ATTRACT:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
 								player->homing = 2;
@@ -7792,13 +8012,14 @@ static void P_MovePlayer(player_t *player)
 								{
 									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/Bubblewrap shield activation
+							// Elemental stomp/Bubble bounce
 							case SH_ELEMENTAL:
 							case SH_BUBBLEWRAP:
 								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
@@ -7812,7 +8033,7 @@ static void P_MovePlayer(player_t *player)
 									? sfx_s3k43
 									: sfx_s3k44);
 								break;
-							// Flame shield activation
+							// 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));
@@ -7833,8 +8054,7 @@ static void P_MovePlayer(player_t *player)
 	{
 		if (player->homing && player->mo->tracer)
 		{
-			P_HomingAttack(player->mo, player->mo->tracer);
-			if (player->mo->tracer->health <= 0 || (player->mo->tracer->flags2 & MF2_FRET))
+			if (!P_HomingAttack(player->mo, player->mo->tracer))
 			{
 				P_SetObjectMomZ(player->mo, 6*FRACUNIT, false);
 				if (player->mo->eflags & MFE_UNDERWATER)
@@ -7853,10 +8073,9 @@ static void P_MovePlayer(player_t *player)
 		if (player->homing && player->mo->tracer)
 		{
 			P_SpawnThokMobj(player);
-			P_HomingAttack(player->mo, player->mo->tracer);
 
 			// But if you don't, then stop homing.
-			if (player->mo->tracer->health <= 0 || (player->mo->tracer->flags2 & MF2_FRET))
+			if (!P_HomingAttack(player->mo, player->mo->tracer))
 			{
 				if (player->mo->eflags & MFE_UNDERWATER)
 					P_SetObjectMomZ(player->mo, FixedDiv(457*FRACUNIT,72*FRACUNIT), false);
@@ -8124,9 +8343,9 @@ static void P_DoZoomTube(player_t *player)
 		CONS_Debug(DBG_GAMELOGIC, "Looking for next waypoint...\n");
 
 		// Find next waypoint
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -8134,15 +8353,17 @@ static void P_DoZoomTube(player_t *player)
 			if (mo2->type != MT_TUBEWAYPOINT)
 				continue;
 
-			if (mo2->threshold == sequence)
-			{
-				if ((reverse && mo2->health == player->mo->tracer->health - 1)
-					|| (!reverse && mo2->health == player->mo->tracer->health + 1))
-				{
-					waypoint = mo2;
-					break;
-				}
-			}
+			if (mo2->threshold != sequence)
+				continue;
+
+			if (reverse && mo2->health != player->mo->tracer->health - 1)
+				continue;
+
+			if (!reverse && mo2->health != player->mo->tracer->health + 1)
+				continue;
+
+			waypoint = mo2;
+			break;
 		}
 
 		if (waypoint)
@@ -8258,9 +8479,9 @@ static void P_DoRopeHang(player_t *player)
 		CONS_Debug(DBG_GAMELOGIC, "Looking for next waypoint...\n");
 
 		// Find next waypoint
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -8268,14 +8489,14 @@ static void P_DoRopeHang(player_t *player)
 			if (mo2->type != MT_TUBEWAYPOINT)
 				continue;
 
-			if (mo2->threshold == sequence)
-			{
-				if (mo2->health == player->mo->tracer->health + 1)
-				{
-					waypoint = mo2;
-					break;
-				}
-			}
+			if (mo2->threshold != sequence)
+				continue;
+
+			if (mo2->health != player->mo->tracer->health + 1)
+				continue;
+
+			waypoint = mo2;
+			break;
 		}
 
 		if (!(player->mo->tracer->flags & MF_SLIDEME) && !waypoint)
@@ -8283,9 +8504,9 @@ static void P_DoRopeHang(player_t *player)
 			CONS_Debug(DBG_GAMELOGIC, "Next waypoint not found, wrapping to start...\n");
 
 			// Wrap around back to first waypoint
-			for (th = thinkercap.next; th != &thinkercap; th = th->next)
+			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 != (actionf_p1)P_MobjThinker) // Not a mobj thinker
+				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -8293,14 +8514,14 @@ static void P_DoRopeHang(player_t *player)
 				if (mo2->type != MT_TUBEWAYPOINT)
 					continue;
 
-				if (mo2->threshold == sequence)
-				{
-					if (mo2->health == 0)
-					{
-						waypoint = mo2;
-						break;
-					}
-				}
+				if (mo2->threshold != sequence)
+					continue;
+
+				if (mo2->health != 0)
+					continue;
+
+				waypoint = mo2;
+				break;
 			}
 		}
 
@@ -8347,25 +8568,22 @@ static void P_DoRopeHang(player_t *player)
 static void P_NukeAllPlayers(player_t *player)
 {
 	mobj_t *mo;
-	thinker_t *think;
+	UINT8 i;
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
-
-		mo = (mobj_t *)think;
-
-		if (!mo->player)
+		if (!playeringame[i])
 			continue;
-
-		if (mo->health <= 0) // dead
+		if (players[i].spectator)
 			continue;
-
-		if (mo == player->mo)
+		if (!players[i].mo)
+			continue;
+		if (players[i].mo == player->mo)
+			continue;
+		if (players[i].mo->health <= 0)
 			continue;
 
-		P_DamageMobj(mo, player->mo, player->mo, 1, 0);
+		P_DamageMobj(players[i].mo, player->mo, player->mo, 1, 0);
 	}
 
 	CONS_Printf(M_GetText("%s caused a world of pain.\n"), player_names[player-players]);
@@ -8397,10 +8615,10 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 		}
 	}
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
 
@@ -8448,13 +8666,13 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 	const angle_t span = (bullet ? ANG30 : ANGLE_90);
 	fixed_t dist, closestdist = 0;
 
-	for (think = thinkercap.next; think != &thinkercap; think = think->next)
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 != (actionf_p1)P_MobjThinker)
-			continue; // not a mobj thinker
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
 
 		mo = (mobj_t *)think;
-		if (!(mo->flags & (MF_ENEMY|MF_BOSS|MF_MONITOR|MF_SPRING)) == !(mo->flags2 & MF2_INVERTAIMABLE)) // allows if it has the flags desired XOR it has the invert aimable flag
+		if (!((mo->flags & (MF_ENEMY|MF_BOSS|MF_MONITOR) && (mo->flags & MF_SHOOTABLE)) || (mo->flags & MF_SPRING)) == !(mo->flags2 & MF2_INVERTAIMABLE)) // allows if it has the flags desired XOR it has the invert aimable flag
 			continue; // not a valid target
 
 		if (mo->health <= 0) // dead
@@ -8466,9 +8684,6 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 		if (mo->flags2 & MF2_FRET)
 			continue;
 
-		if ((mo->flags & (MF_ENEMY|MF_BOSS)) && !(mo->flags & MF_SHOOTABLE)) // don't aim at something you can't shoot at anyway (see Egg Guard or Minus)
-			continue;
-
 		if (!nonenemies && mo->flags & (MF_MONITOR|MF_SPRING))
 			continue;
 
@@ -8522,17 +8737,23 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 	return closestmo;
 }
 
-void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
+boolean P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 {
 	fixed_t zdist;
 	fixed_t dist;
 	fixed_t ns = 0;
 
 	if (!enemy)
-		return;
+		return false;
 
-	if (!(enemy->health))
-		return;
+	if (!enemy->health)
+		return false;
+
+	if (enemy->flags2 & MF2_FRET)
+		return false;
+
+	if (!(enemy->flags & (MF_SHOOTABLE|MF_SPRING)) == !(enemy->flags2 & MF2_INVERTAIMABLE)) // allows if it has the flags desired XOR it has the invert aimable flag
+		return false;
 
 	// change angle
 	source->angle = R_PointToAngle2(source->x, source->y, enemy->x, enemy->y);
@@ -8575,6 +8796,8 @@ void P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 	source->momx = FixedMul(FixedDiv(enemy->x - source->x, dist), ns);
 	source->momy = FixedMul(FixedDiv(enemy->y - source->y, dist), ns);
 	source->momz = FixedMul(FixedDiv(zdist, dist), ns);
+
+	return true;
 }
 
 // Search for emeralds
@@ -8587,9 +8810,9 @@ void P_FindEmerald(void)
 
 	// scan the remaining thinkers
 	// to find all emeralds
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -9744,12 +9967,12 @@ void P_DoPityCheck(player_t *player)
 	// Apply pity shield if available.
 	if ((player->pity >= 3 || player->pity < 0) && player->powers[pw_shield] == SH_NONE)
 	{
+		P_SwitchShield(player, SH_PITY);
+
 		if (player->pity > 0)
 			S_StartSound(player->mo, mobjinfo[MT_PITY_ICON].seesound);
 
 		player->pity = 0;
-		player->powers[pw_shield] = SH_PITY;
-		P_SpawnShieldOrb(player);
 	}
 }
 
@@ -9815,9 +10038,9 @@ static mobj_t *P_GetAxis(INT32 num)
 	thinker_t *th;
 	mobj_t *mobj;
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
 
 		mobj = (mobj_t *)th;
@@ -9832,6 +10055,7 @@ static mobj_t *P_GetAxis(INT32 num)
 
 		return mobj;
 	}
+
 	CONS_Alert(CONS_WARNING, "P_GetAxis: Track segment %d is missing!\n", num);
 	return NULL;
 }
@@ -10232,14 +10456,8 @@ void P_PlayerThink(player_t *player)
 	if (player->flashcount)
 		player->flashcount--;
 
-	// Re-fixed by Jimita (11-12-2018)
-	if (player->awayviewtics)
-	{
+	if (player->awayviewtics && player->awayviewtics != -1)
 		player->awayviewtics--;
-		if (!player->awayviewtics)
-			player->awayviewtics = -1;
-		// The timer might've reached zero, but we'll run the remote view camera anyway by setting it to -1.
-	}
 
 	/// \note do this in the cheat code
 	if (player->pflags & PF_NOCLIP)
@@ -10312,6 +10530,44 @@ void P_PlayerThink(player_t *player)
 	if (player->exiting && countdown2)
 		player->exiting = 5;
 
+	// Same check as below, just at 1 second before
+	// so we can fade music
+	if (!exitfadestarted &&
+		player->exiting > 0 && player->exiting <= 1*TICRATE &&
+		(!multiplayer || gametype == GT_COOP ? !mapheaderinfo[gamemap-1]->musinterfadeout : true) &&
+			// don't fade if we're fading during intermission. follows Y_StartIntermission intertype = int_coop
+		(gametype == GT_RACE || gametype == GT_COMPETITION ? countdown2 == 0 : true) && // don't fade on timeout
+		player->lives > 0 && // don't fade on game over (competition)
+		P_IsLocalPlayer(player))
+	{
+		if (cv_playersforexit.value)
+		{
+			INT32 i;
+
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i] || players[i].spectator || players[i].bot)
+					continue;
+				if (players[i].lives <= 0)
+					continue;
+
+				if (!players[i].exiting || players[i].exiting > 1*TICRATE)
+					break;
+			}
+
+			if (i == MAXPLAYERS)
+			{
+				exitfadestarted = true;
+				S_FadeOutStopMusic(1*MUSICRATE);
+			}
+		}
+		else
+		{
+			exitfadestarted = true;
+			S_FadeOutStopMusic(1*MUSICRATE);
+		}
+	}
+
 	if (player->exiting == 2 || countdown2 == 2)
 	{
 		if (cv_playersforexit.value) // Count to be sure everyone's exited
@@ -10424,9 +10680,9 @@ void P_PlayerThink(player_t *player)
 		fixed_t y = player->mo->y;
 		fixed_t z = player->mo->z;
 
-		for (th = thinkercap.next; th != &thinkercap; th = th->next)
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -11231,9 +11487,6 @@ void P_PlayerAfterThink(player_t *player)
 		}
 	}
 
-	if (player->awayviewtics < 0)
-		player->awayviewtics = 0;
-
 	// spectator invisibility and nogravity.
 	if ((netgame || multiplayer) && player->spectator)
 	{
@@ -11428,7 +11681,6 @@ void P_PlayerAfterThink(player_t *player)
 							player->followmobj->threshold = player->mo->z;
 							player->followmobj->movecount = player->panim;
 							player->followmobj->angle = horizangle;
-							player->followmobj->scale = player->mo->scale;
 							P_SetScale(player->followmobj, player->mo->scale);
 							player->followmobj->destscale = player->mo->destscale;
 							player->followmobj->radius = player->mo->radius;
diff --git a/src/r_data.c b/src/r_data.c
index 838bc160cdbf9942727ed8defe709cac25eece53..6889bdddebf587ce22b3526066fca43aec364ceb 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -578,6 +578,17 @@ void R_LoadTextures(void)
 	// but the alternative is to spend a ton of time checking and re-checking all previous entries just to skip any potentially patched textures.
 	for (w = 0, numtextures = 0; w < numwadfiles; w++)
 	{
+		// Count the textures from TEXTURES lumps
+
+		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
+		while (texturesLumpPos != INT16_MAX)
+		{
+			numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
+			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
+		}
+
+		// Count single-patch textures
+
 		if (wadfiles[w]->type == RET_PK3)
 		{
 			texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
@@ -585,29 +596,34 @@ void R_LoadTextures(void)
 		}
 		else
 		{
-			texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0) + 1;
+			texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0);
 			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
 		}
 
-		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
-		while (texturesLumpPos != INT16_MAX)
-		{
-			numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
-			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
-		}
+		if (texstart == INT16_MAX || texend == INT16_MAX)
+			continue;
+
+		texstart++; // Do not count the first marker
 
-		// Add all the textures between TX_START and TX_END
-		if (texstart != INT16_MAX && texend != INT16_MAX)
+		// PK3s have subfolders, so we can't just make a simple sum
+		if (wadfiles[w]->type == RET_PK3)
 		{
-			numtextures += (UINT32)(texend - texstart);
+			for (j = texstart; j < texend; j++)
+			{
+				if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
+					numtextures++;
+			}
 		}
-
-		// If no textures found by this point, bomb out
-		if (!numtextures && w == (numwadfiles - 1))
+		else // Add all the textures between TX_START and TX_END
 		{
-			I_Error("No textures detected in any WADs!\n");
+			numtextures += (UINT32)(texend - texstart);
 		}
 	}
+
+	// If no textures found by this point, bomb out
+	if (!numtextures)
+		I_Error("No textures detected in any WADs!\n");
+
 	// 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);
@@ -642,7 +658,7 @@ void R_LoadTextures(void)
 		}
 		else
 		{
-			texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0) + 1;
+			texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0);
 			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
 			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
 			if (texturesLumpPos != INT16_MAX)
@@ -652,9 +668,16 @@ void R_LoadTextures(void)
 		if (texstart == INT16_MAX || texend == INT16_MAX)
 			continue;
 
+		texstart++; // Do not count the first marker
+
 		// Work through each lump between the markers in the WAD.
-		for (j = 0; j < (texend - texstart); i++, j++)
+		for (j = 0; j < (texend - texstart); j++)
 		{
+			if (wadfiles[w]->type == RET_PK3)
+			{
+				if (W_IsLumpFolder((UINT16)w, texstart + j)) // Check if lump is a folder
+					continue; // If it is then SKIP IT
+			}
 			patchlump = W_CacheLumpNumPwad((UINT16)w, texstart + j, PU_CACHE);
 
 			//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
@@ -684,6 +707,7 @@ void R_LoadTextures(void)
 
 			texturewidthmask[i] = k - 1;
 			textureheight[i] = texture->height << FRACBITS;
+			i++;
 		}
 	}
 }
@@ -1293,14 +1317,23 @@ void R_ReInitColormaps(UINT16 num)
 {
 	char colormap[9] = "COLORMAP";
 	lumpnum_t lump;
+	const lumpnum_t basecolormaplump = W_GetNumForName(colormap);
 	if (num > 0 && num <= 10000)
 		snprintf(colormap, 8, "CLM%04u", num-1);
 
 	// Load in the light tables, now 64k aligned for smokie...
 	lump = W_GetNumForName(colormap);
 	if (lump == LUMPERROR)
-		lump = W_GetNumForName("COLORMAP");
-	W_ReadLump(lump, colormaps);
+		lump = basecolormaplump;
+	else
+	{
+		if (W_LumpLength(lump) != W_LumpLength(basecolormaplump))
+		{
+			CONS_Alert(CONS_WARNING, "%s lump size does not match COLORMAP, results may be unexpected.\n", colormap);
+		}
+	}
+
+	W_ReadLumpHeader(lump, colormaps, W_LumpLength(basecolormaplump), 0U);
 
 	// Init Boom colormaps.
 	R_ClearColormaps();
@@ -2242,8 +2275,8 @@ void R_PrecacheLevel(void)
 	spritepresent = calloc(numsprites, sizeof (*spritepresent));
 	if (spritepresent == NULL) I_Error("%s: Out of memory looking up sprites", "R_PrecacheLevel");
 
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
-		if (th->function.acp1 == (actionf_p1)P_MobjThinker)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		if (th->function.acp1 != (actionf_p1)P_RemoveThinkerDelayed)
 			spritepresent[((mobj_t *)th)->sprite] = 1;
 
 	spritememory = 0;
diff --git a/src/r_defs.h b/src/r_defs.h
index e7315b35cc1f698d3a9dd88692eb31c872937b5a..def7b46f3b8aa5ecdd81c9d1d359848b87268822 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -237,46 +237,27 @@ typedef struct linechain_s
 // Slopes
 #ifdef ESLOPE
 typedef enum {
-	SL_NOPHYSICS = 1, // Don't do momentum adjustment with this slope
-	SL_NODYNAMIC = 1<<1, // Slope will never need to move during the level, so don't fuss with recalculating it
-	SL_ANCHORVERTEX = 1<<2, // Slope is using a Slope Vertex Thing to anchor its position
-	SL_VERTEXSLOPE = 1<<3, // Slope is built from three Slope Vertex Things
+	SL_NOPHYSICS = 1, /// This plane will have no physics applied besides the positioning.
+	SL_DYNAMIC = 1<<1, /// This plane slope will be assigned a thinker to make it dynamic.
 } slopeflags_t;
 
 typedef struct pslope_s
 {
 	UINT16 id; // The number of the slope, mostly used for netgame syncing purposes
+	struct pslope_s *next; // Make a linked list of dynamic slopes, for easy reference later
 
-	// --- Information used in clipping/projection ---
-	// Origin vector for the plane
-	vector3_t o;
-
-	// 2-Dimentional vector (x, y) normalized. Used to determine distance from
-	// the origin in 2d mapspace. (Basically a thrust of FRACUNIT in xydirection angle)
-	vector2_t d;
-
-	// The rate at which z changes based on distance from the origin plane.
-	fixed_t zdelta;
-
-	// The normal of the slope; will always point upward, and thus be inverted on ceilings. I think it's only needed for physics? -Red
-	vector3_t normal;
+	// The plane's definition.
+	vector3_t o;		/// Plane origin.
+	vector3_t normal;	/// Plane normal.
 
-	// For comparing when a slope should be rendered
-	fixed_t lowz;
-	fixed_t highz;
+	vector2_t d;		/// Precomputed normalized projection of the normal over XY.
+	fixed_t zdelta;		/// Precomputed Z unit increase per XY unit.
 
 	// This values only check and must be updated if the slope itself is modified
-	angle_t zangle; // Angle of the plane going up from the ground (not mesured in degrees)
-	angle_t xydirection; // The direction the slope is facing (north, west, south, etc.)
-
-	struct line_s *sourceline; // The line that generated the slope
-	fixed_t extent; // Distance value used for recalculating zdelta
-	UINT8 refpos; // 1=front floor 2=front ceiling 3=back floor 4=back ceiling (used for dynamic sloping)
+	angle_t zangle;		/// Precomputed angle of the plane going up from the ground (not measured in degrees).
+	angle_t xydirection;/// Precomputed angle of the normal's projection on the XY plane.
 
 	UINT8 flags; // Slope options
-	mapthing_t **vertices; // List should be three long for slopes made by vertex things, or one long for slopes using one vertex thing to anchor
-
-	struct pslope_s *next; // Make a linked list of dynamic slopes, for easy reference later
 } pslope_t;
 #endif
 
diff --git a/src/r_draw.c b/src/r_draw.c
index 13ac691d5f9c63dbb8d5689cb08d170097d4031d..f8e4356244c73826bb4cfdc7cb05866e6eb8869e 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -126,10 +126,12 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 #define BOSS_TT_CACHE_INDEX (MAXSKINS + 1)
 #define METALSONIC_TT_CACHE_INDEX (MAXSKINS + 2)
 #define ALLWHITE_TT_CACHE_INDEX (MAXSKINS + 3)
+#define RAINBOW_TT_CACHE_INDEX (MAXSKINS + 4)
+#define BLINK_TT_CACHE_INDEX (MAXSKINS + 5)
 #define DEFAULT_STARTTRANSCOLOR 96
 #define NUM_PALETTE_ENTRIES 256
 
-static UINT8** translationtablecache[MAXSKINS + 4] = {NULL};
+static UINT8** translationtablecache[MAXSKINS + 6] = {NULL};
 
 const UINT8 Color_Index[MAXTRANSLATIONS-1][16] = {
 	// {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // SKINCOLOR_NONE
@@ -457,17 +459,82 @@ void R_InitTranslationTables(void)
 
 	\return	void
 */
+
+// Define for getting accurate color brightness readings according to how the human eye sees them.
+// https://en.wikipedia.org/wiki/Relative_luminance
+// 0.2126 to red
+// 0.7152 to green
+// 0.0722 to blue
+// (See this same define in hw_md2.c!)
+#define SETBRIGHTNESS(brightness,r,g,b) \
+	brightness = (UINT8)(((1063*((UINT16)r)/5000) + (3576*((UINT16)g)/5000) + (361*((UINT16)b)/5000)) / 3)
+
+/** \brief	Generates the rainbow colourmaps that are used when a player has the invincibility power... stolen from kart, with permission
+
+	\param	dest_colormap	colormap to populate
+	\param	skincolor		translation color
+*/
+static void R_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor)
+{
+	INT32 i;
+	RGBA_t color;
+	UINT8 brightness;
+	INT32 j;
+	UINT8 colorbrightnesses[16];
+	UINT16 brightdif;
+	INT32 temp;
+
+	// first generate the brightness of all the colours of that skincolour
+	for (i = 0; i < 16; i++)
+	{
+		color = V_GetColor(Color_Index[skincolor-1][i]);
+		SETBRIGHTNESS(colorbrightnesses[i], color.s.red, color.s.green, color.s.blue);
+	}
+
+	// next, for every colour in the palette, choose the transcolor that has the closest brightness
+	for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
+	{
+		if (i == 0 || i == 31) // pure black and pure white don't change
+		{
+			dest_colormap[i] = (UINT8)i;
+			continue;
+		}
+		color = V_GetColor(i);
+		SETBRIGHTNESS(brightness, color.s.red, color.s.green, color.s.blue);
+		brightdif = 256;
+		for (j = 0; j < 16; j++)
+		{
+			temp = abs((INT16)brightness - (INT16)colorbrightnesses[j]);
+			if (temp < brightdif)
+			{
+				brightdif = (UINT16)temp;
+				dest_colormap[i] = Color_Index[skincolor-1][j];
+			}
+		}
+	}
+}
+
+#undef SETBRIGHTNESS
+
 static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color)
 {
 	INT32 i, starttranscolor, skinramplength;
 
 	// Handle a couple of simple special cases
-	if (skinnum == TC_BOSS || skinnum == TC_ALLWHITE || skinnum == TC_METALSONIC || color == SKINCOLOR_NONE)
+	if (skinnum == TC_BOSS
+		|| skinnum == TC_ALLWHITE
+		|| skinnum == TC_METALSONIC
+		|| skinnum == TC_BLINK
+		|| color == SKINCOLOR_NONE)
 	{
-		for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
+		if (skinnum == TC_ALLWHITE)
+			memset(dest_colormap, 0, NUM_PALETTE_ENTRIES * sizeof(UINT8));
+		else if (skinnum == TC_BLINK && color != SKINCOLOR_NONE)
+			memset(dest_colormap, Color_Index[color-1][3], NUM_PALETTE_ENTRIES * sizeof(UINT8));
+		else
 		{
-			if (skinnum == TC_ALLWHITE) dest_colormap[i] = 0;
-			else dest_colormap[i] = (UINT8)i;
+			for (i = 0; i < NUM_PALETTE_ENTRIES; i++)
+				dest_colormap[i] = (UINT8)i;
 		}
 
 		// White!
@@ -478,6 +545,11 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 
 		return;
 	}
+	else if (skinnum == TC_RAINBOW)
+	{
+		R_RainbowColormap(dest_colormap, color);
+		return;
+	}
 
 	if (color >= MAXTRANSLATIONS)
 		I_Error("Invalid skin color #%hu.", (UINT16)color);
@@ -522,6 +594,8 @@ UINT8* R_GetTranslationColormap(INT32 skinnum, skincolors_t color, UINT8 flags)
 	else if (skinnum == TC_BOSS) skintableindex = BOSS_TT_CACHE_INDEX;
 	else if (skinnum == TC_METALSONIC) skintableindex = METALSONIC_TT_CACHE_INDEX;
 	else if (skinnum == TC_ALLWHITE) skintableindex = ALLWHITE_TT_CACHE_INDEX;
+	else if (skinnum == TC_RAINBOW) skintableindex = RAINBOW_TT_CACHE_INDEX;
+	else if (skinnum == TC_BLINK) skintableindex = BLINK_TT_CACHE_INDEX;
 	else skintableindex = skinnum;
 
 	if (flags & GTC_CACHE)
diff --git a/src/r_draw.h b/src/r_draw.h
index 0c04fee2a76e9e1cac286cdaf8cc82a0d7fa8c2d..82498eb11e22d30569e69b2baca059d1acbeb40e 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -105,6 +105,8 @@ extern lumpnum_t viewborderlump[8];
 #define TC_BOSS       -2
 #define TC_METALSONIC -3 // For Metal Sonic battle
 #define TC_ALLWHITE   -4 // For Cy-Brak-demon
+#define TC_RAINBOW    -5 // For single colour
+#define TC_BLINK      -6 // For item blinking, according to kart
 
 // Initialize color translation tables, for player rendering etc.
 void R_InitTranslationTables(void);
diff --git a/src/r_things.c b/src/r_things.c
index 50ee8e6ef598f62be38644e9ea4dab7cf8e12e8c..71e4c0214c4ca94099a67590a43f1ff2fb6644db 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -486,7 +486,11 @@ void R_InitSprites(void)
 	// it can be is do before loading config for skin cvar possible value
 	R_InitSkins();
 	for (i = 0; i < numwadfiles; i++)
+	{
 		R_AddSkins((UINT16)i);
+		R_PatchSkins((UINT16)i);
+	}
+	ST_ReloadSkinFaceGraphics();
 
 	//
 	// check if all sprites have frames
@@ -719,11 +723,11 @@ static void R_DrawVisSprite(vissprite_t *vis)
 
 	colfunc = basecolfunc; // hack: this isn't resetting properly somewhere.
 	dc_colormap = vis->colormap;
-	if (!(vis->cut & SC_PRECIP) && (vis->mobj->flags & (MF_ENEMY|MF_BOSS)) && (vis->mobj->flags2 & MF2_FRET) && (leveltime & 1)) // Bosses "flash"
+	if (!(vis->cut & SC_PRECIP) && (vis->mobj->flags & (MF_ENEMY|MF_BOSS)) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
 	{
 		// translate certain pixels to white
 		colfunc = transcolfunc;
-		if (vis->mobj->type == MT_CYBRAKDEMON)
+		if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized)
 			dc_translation = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
 		else if (vis->mobj->type == MT_METALSONIC_BATTLE)
 			dc_translation = R_GetTranslationColormap(TC_METALSONIC, 0, GTC_CACHE);
@@ -734,7 +738,9 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	{
 		colfunc = transtransfunc;
 		dc_transmap = vis->transmap;
-		if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
+		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
+			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
 		{
 			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
 			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
@@ -753,7 +759,9 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		colfunc = transcolfunc;
 
 		// New colormap stuff for skins Tails 06-07-2002
-		if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
+		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
+			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
 		{
 			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
 			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
@@ -2442,8 +2450,10 @@ static void R_DrawMaskedList (drawnode_t* head)
 
 void R_DrawMasked(maskcount_t* masks, UINT8 nummasks)
 {
-	drawnode_t heads[nummasks];	/**< Drawnode lists; as many as number of views/portals. */
-	INT8 i;
+	drawnode_t *heads;	/**< Drawnode lists; as many as number of views/portals. */
+	SINT8 i;
+
+	heads = calloc(nummasks, sizeof(drawnode_t));
 
 	for (i = 0; i < nummasks; i++)
 	{
@@ -2470,6 +2480,8 @@ void R_DrawMasked(maskcount_t* masks, UINT8 nummasks)
 		R_DrawMaskedList(&heads[nummasks - 1]);
 		R_ClearDrawNodes(&heads[nummasks - 1]);
 	}
+
+	free(heads);
 }
 
 // ==========================================================================
@@ -2499,6 +2511,9 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 	if (!skin)
 		return 0;
 
+	if ((spr2 & ~FF_SPR2SUPER) >= free_spr2)
+		return 0;
+
 	while (!(skin->sprites[spr2].numframes)
 		&& spr2 != SPR2_STND
 		&& ++i < 32) // recursion limiter
@@ -2521,8 +2536,10 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
 			break;
 		case SPR2_TIRE:
-			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
-			break;
+			spr2 = ((player
+					? player->charability
+					: skin->ability)
+					== CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
 
 		// Use the handy list, that's what it's there for!
 		default:
@@ -2533,6 +2550,9 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 		spr2 |= super;
 	}
 
+	if (i >= 32) // probably an infinite loop...
+		return 0;
+
 	return spr2;
 }
 
@@ -2552,9 +2572,6 @@ static void Sk_SetDefaultValue(skin_t *skin)
 
 	strcpy(skin->realname, "Someone");
 	strcpy(skin->hudname, "???");
-	strncpy(skin->charsel, "CHRSONIC", 9);
-	strncpy(skin->face, "MISSING", 8);
-	strncpy(skin->superface, "MISSING", 8);
 
 	skin->starttranscolor = 96;
 	skin->prefcolor = SKINCOLOR_GREEN;
@@ -2700,6 +2717,9 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 		player->revitem = skin->revitem < 0 ? (mobjtype_t)mobjinfo[MT_PLAYER].raisestate : (UINT32)skin->revitem;
 		player->followitem = skin->followitem;
 
+		if (((player->powers[pw_shield] & SH_NOSTACK) == SH_PINK) && (player->revitem == MT_LHRT || player->spinitem == MT_LHRT || player->thokitem == MT_LHRT)) // Healers can't keep their buff.
+			player->powers[pw_shield] &= SH_STACK;
+
 		player->actionspd = skin->actionspd;
 		player->mindash = skin->mindash;
 		player->maxdash = skin->maxdash;
@@ -2981,7 +3001,7 @@ void R_AddSkins(UINT16 wadnum)
 	char *value;
 	size_t size;
 	skin_t *skin;
-	boolean hudname, realname, superface;
+	boolean hudname, realname;
 
 	//
 	// search for all skin markers in pwad
@@ -3011,7 +3031,7 @@ void R_AddSkins(UINT16 wadnum)
 		skin = &skins[numskins];
 		Sk_SetDefaultValue(skin);
 		skin->wadnum = wadnum;
-		hudname = realname = superface = false;
+		hudname = realname = false;
 		// parse
 		stoken = strtok (buf2, "\r\n= ");
 		while (stoken)
@@ -3087,24 +3107,6 @@ void R_AddSkins(UINT16 wadnum)
 				if (!realname)
 					STRBUFCPY(skin->realname, skin->hudname);
 			}
-			else if (!stricmp(stoken, "charsel"))
-			{
-				strupr(value);
-				strncpy(skin->charsel, value, sizeof skin->charsel);
-			}
-			else if (!stricmp(stoken, "face"))
-			{
-				strupr(value);
-				strncpy(skin->face, value, sizeof skin->face);
-				if (!superface)
-					strncpy(skin->superface, value, sizeof skin->superface);
-			}
-			else if (!stricmp(stoken, "superface"))
-			{
-				superface = true;
-				strupr(value);
-				strncpy(skin->superface, value, sizeof skin->superface);
-			}
 			else if (!stricmp(stoken, "availability"))
 			{
 				skin->availability = atoi(value);
@@ -3123,6 +3125,7 @@ next_token:
 
 		// Add sprites
 		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
+		//ST_LoadFaceGraphics(numskins); -- nah let's do this elsewhere
 
 		R_FlushTranslationColormapCache();
 
@@ -3133,9 +3136,6 @@ next_token:
 		skin_cons_t[numskins].strvalue = skin->name;
 #endif
 
-		// add face graphics
-		ST_LoadFaceGraphics(skin->face, skin->superface, numskins);
-
 #ifdef HWRENDER
 		if (rendermode == render_opengl)
 			HWR_AddPlayerMD2(numskins);
@@ -3258,6 +3258,9 @@ next_token:
 
 		// Patch sprites
 		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
+		//ST_LoadFaceGraphics(skinnum); -- nah let's do this elsewhere
+
+		R_FlushTranslationColormapCache();
 
 		if (!skin->availability) // Safe to print...
 			CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name);
diff --git a/src/r_things.h b/src/r_things.h
index d287df8328293374265873a9d5e1602a3181f60f..9c3d16ab018b27abdb610e4ee5eefe7a6b5e66b3 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -88,7 +88,6 @@ typedef struct
 
 	char realname[SKINNAMESIZE+1]; // Display name for level completion.
 	char hudname[SKINNAMESIZE+1]; // HUD name to display (officially exactly 5 characters long)
-	char charsel[9], face[9], superface[9]; // Arbitrarily named patch lumps
 
 	UINT8 ability; // ability definition
 	UINT8 ability2; // secondary ability definition
diff --git a/src/s_sound.c b/src/s_sound.c
index c7dda5fefb12b7c52c11709074132ecdedfe513e..cb803259e667e01d312fa6b8cf5fc06a23770daa 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -519,6 +519,7 @@ void S_StartCaption(sfxenum_t sfx_id, INT32 cnum, UINT16 lifespan)
 void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
 {
 	INT32 sep, pitch, priority, cnum;
+	const sfxenum_t actual_id = sfx_id;
 	sfxinfo_t *sfx;
 
 	const mobj_t *origin = (const mobj_t *)origin_p;
@@ -657,7 +658,7 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
 #endif
 
 		// Handle closed caption input.
-		S_StartCaption(sfx_id, cnum, MAXCAPTIONTICS);
+		S_StartCaption(actual_id, cnum, MAXCAPTIONTICS);
 
 		// Assigns the handle to one of the channels in the
 		// mix/output buffer.
@@ -710,7 +711,7 @@ dontplay:
 #endif
 
 	// Handle closed caption input.
-	S_StartCaption(sfx_id, cnum, MAXCAPTIONTICS);
+	S_StartCaption(actual_id, cnum, MAXCAPTIONTICS);
 
 	// Assigns the handle to one of the channels in the
 	// mix/output buffer.
@@ -1683,7 +1684,17 @@ void S_StopMusic(void)
 	if (cv_closedcaptioning.value)
 	{
 		if (closedcaptions[0].s-S_sfx == sfx_None)
-			closedcaptions[0].t = CAPTIONFADETICS;
+		{
+			if (gamestate != wipegamestate)
+			{
+				closedcaptions[0].c = NULL;
+				closedcaptions[0].s = NULL;
+				closedcaptions[0].t = 0;
+				closedcaptions[0].b = 0;
+			}
+			else
+				closedcaptions[0].t = CAPTIONFADETICS;
+		}
 	}
 }
 
@@ -1989,8 +2000,10 @@ void GameMIDIMusic_OnChange(void)
 	}
 }
 
+#ifdef HAVE_OPENMPT
 void ModFilter_OnChange(void)
 {
 	if (openmpt_mhandle)
 		openmpt_module_set_render_param(openmpt_mhandle, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, cv_modfilter.value);
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/screen.c b/src/screen.c
index ac7878c4a8f8d82061007ac754b51f88d06cb656..fc3f5b8e87f863112cdd88a5e7d0cd1f9faea95f 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -438,7 +438,9 @@ void SCR_ClosedCaptions(void)
 
 	if (gamestate == GS_LEVEL)
 	{
-		if (splitscreen)
+		if (promptactive)
+			basey -= 28;
+		else if (splitscreen)
 			basey -= 8;
 		else if ((modeattacking == ATTACKING_NIGHTS)
 		|| (!(maptol & TOL_NIGHTS)
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index d05a0d32414e38ea9906ad8cef3cf0b24960e2f8..72c38b3dc090061f370d6822d7050912ed2f34ca 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -277,6 +277,7 @@
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
     <ClInclude Include="..\r_plane.h" />
+    <ClInclude Include="..\r_portal.h" />
     <ClInclude Include="..\r_segs.h" />
     <ClInclude Include="..\r_sky.h" />
     <ClInclude Include="..\r_splats.h" />
@@ -430,6 +431,7 @@
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
     <ClCompile Include="..\r_plane.c" />
+    <ClCompile Include="..\r_portal.c" />
     <ClCompile Include="..\r_segs.c" />
     <ClCompile Include="..\r_sky.c" />
     <ClCompile Include="..\r_splats.c" />
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index ca6bd38d211e4c4be9cbd853d5630e6492203694..9e442000fdc1a5ad0a2e6643cd06eb99ddd8cf11 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -453,6 +453,9 @@
     <ClInclude Include="..\hardware\hw_clip.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
+    <ClInclude Include="..\r_portal.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <CustomBuild Include="..\tmap.nas">
@@ -894,6 +897,10 @@
     <ClCompile Include="..\hardware\hw_clip.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
+    <ClCompile Include="..\apng.c" />
+    <ClCompile Include="..\r_portal.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <Image Include="Srb2SDL.ico">
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index 3932d23d3374b0c3e323e48ab623ac175bfc3c34..7bf67559ca2f0dc7c2f26152fd70452a907634ed 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -108,14 +108,16 @@ static UINT16 current_track;
 #endif
 
 #ifdef HAVE_OPENMPT
-int mod_err = OPENMPT_ERROR_OK;
+static int mod_err = OPENMPT_ERROR_OK;
 static const char *mod_err_str;
 static UINT16 current_subsong;
+static size_t probesize;
+static int result;
 #endif
 
 static void var_cleanup(void)
 {
-	loop_point = song_length = 0.0f;
+	song_length = loop_point = 0.0f;
 	music_bytes = fading_source = fading_target =\
 	 fading_timer = fading_duration = 0;
 
@@ -388,7 +390,7 @@ void *I_GetSfx(sfxinfo_t *sfx)
 					gme_track_info(emu, &info, 0);
 
 					len = (info->play_length * 441 / 10) << 2;
-					mem = malloc(len);
+					mem = Z_Malloc(len, PU_SOUND, 0);
 					gme_play(emu, len >> 1, mem);
 					gme_free_info(info);
 					gme_delete(emu);
@@ -461,7 +463,7 @@ void *I_GetSfx(sfxinfo_t *sfx)
 		gme_track_info(emu, &info, 0);
 
 		len = (info->play_length * 441 / 10) << 2;
-		mem = malloc(len);
+		mem = Z_Malloc(len, PU_SOUND, 0);
 		gme_play(emu, len >> 1, mem);
 		gme_free_info(info);
 		gme_delete(emu);
@@ -1112,6 +1114,36 @@ boolean I_LoadSong(char *data, size_t len)
 	}
 #endif
 
+#ifdef HAVE_OPENMPT
+	/*
+		If the size of the data to be checked is bigger than the recommended size (> 2048)
+		Let's just set the probe size to the recommended size
+		Otherwise let's give it the full data size
+	*/
+
+	if (len > openmpt_probe_file_header_get_recommended_size())
+		probesize = openmpt_probe_file_header_get_recommended_size();
+	else
+		probesize = len;
+
+	result = openmpt_probe_file_header(OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT, data, probesize, len, NULL, NULL, NULL, NULL, NULL, NULL);
+
+	if (result == OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS) // We only cared if it succeeded, continue on if not.
+	{
+		openmpt_mhandle = openmpt_module_create_from_memory2(data, len, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+		if (!openmpt_mhandle) // Failed to create module handle? Show error and return!
+		{
+			mod_err = openmpt_module_error_get_last(openmpt_mhandle);
+			mod_err_str = openmpt_error_string(mod_err);
+			CONS_Alert(CONS_ERROR, "openmpt_module_create_from_memory2: %s\n", mod_err_str);
+			return false;
+		}
+		else
+			return true; // All good and we're ready for music playback!
+	}
+#endif
+
+	// Let's see if Mixer is able to load this.
 	rw = SDL_RWFromMem(data, len);
 	if (rw != NULL)
 	{
@@ -1123,40 +1155,6 @@ boolean I_LoadSong(char *data, size_t len)
 		return false;
 	}
 
-#ifdef HAVE_OPENMPT
-	switch(Mix_GetMusicType(music))
-	{
-		case MUS_MODPLUG:
-		case MUS_MOD:
-			openmpt_mhandle = openmpt_module_create_from_memory2(data, len, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
-			if (!openmpt_mhandle)
-			{
-				mod_err = openmpt_module_error_get_last(openmpt_mhandle);
-				mod_err_str = openmpt_error_string(mod_err);
-				CONS_Alert(CONS_ERROR, "openmpt_module_create_from_memory2: %s\n", mod_err_str);
-				Mix_FreeMusic(music);
-				music = NULL;
-				return false;
-			}
-			else
-			{
-				Mix_FreeMusic(music);
-				music = NULL;
-				return true;
-			}
-			break;
-		case MUS_WAV:
-		case MUS_MID:
-		case MUS_OGG:
-		case MUS_MP3:
-		case MUS_FLAC:
-			Mix_HookMusic(NULL, NULL);
-			break;
-		default:
-			break;
-	}
-#endif
-
 	// Find the OGG loop point.
 	loop_point = 0.0f;
 	song_length = 0.0f;
diff --git a/src/sounds.c b/src/sounds.c
index 56de4a9c5bdbaf98bb5edba8cfc4c73fd2c5c542..11ba1e0bcb4cb78c7d2547d0634fcb42a170f6ba 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -137,7 +137,7 @@ sfxinfo_t S_sfx[NUMSFX] =
 
   // Game objects, etc
   {"appear", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Appearing platform"},
-  {"bkpoof", false,  70,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Armageddon explosion"},
+  {"bkpoof", false,  70,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Armageddon pow"},
   {"bnce1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bounce"}, // Boing!
   {"bnce2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Scatter"}, // Boing!
   {"cannon", false,  64,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powerful shot"},
@@ -197,6 +197,8 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"corkh",  false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Cork hit"},
   {"bowl",   false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bowling"},
   {"chuchu", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Train horn"},
+  {"bsnipe", false, 200,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Home-run smash"},
+  {"sprong", false, 112,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power spring"},
 
   // Menu, interface
   {"chchng", false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Score"},
@@ -303,7 +305,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k3e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flame Shield"},
   {"s3k3f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bubble Shield"},
   {"s3k40",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Attraction blast"},
-  {"s3k41",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning Shield"},
+  {"s3k41",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thunder Shield"},
   {"s3k42",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Twinspin"},
   {"s3k43",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flame burst"},
   {"s3k44",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bubble bounce"},
@@ -334,7 +336,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k5d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Heavy hit"},
   {"s3k5e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing laser"},
   {"s3k5f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crusher stomp"},
-  {"s3k60",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying away"},
+  {"s3k60",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Accelerating"},
   {"s3k61",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drilling"},
   {"s3k62",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jump"},
   {"s3k63",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starpost"},
@@ -430,8 +432,8 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3kbcl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // long version of previous
   {"s3kbds", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying fortress"},
   {"s3kbdl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying fortress"}, // ditto
-  {"s3kbes", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying away"},
-  {"s3kbel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying away"}, // ditto
+  {"s3kbes", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying"},
+  {"s3kbel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying"}, // ditto
   {"s3kbfs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"},
   {"s3kbfl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"}, // ditto
   {"s3kc0s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"},
diff --git a/src/sounds.h b/src/sounds.h
index 47530912133953fafcd166198a5166ab5bbb192a..20f89d9fbb241be125c057584634d618a3eda58e 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -263,6 +263,8 @@ typedef enum
 	sfx_corkh,
 	sfx_bowl,
 	sfx_chuchu,
+	sfx_bsnipe,
+	sfx_sprong,
 
 	// Menu, interface
 	sfx_chchng,
diff --git a/src/st_stuff.c b/src/st_stuff.c
index f0f40ed32cf350002025ae5c326d89c9f6bf0ad8..aefb4c53c70badbc2a9b85e178b4cdceb2b704dc 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -95,6 +95,7 @@ static patch_t *ringshield;
 static patch_t *watershield;
 static patch_t *bombshield;
 static patch_t *pityshield;
+static patch_t *pinkshield;
 static patch_t *flameshield;
 static patch_t *bubbleshield;
 static patch_t *thundershield;
@@ -109,6 +110,7 @@ static patch_t *orngstat;
 static patch_t *redstat;
 static patch_t *yelstat;
 static patch_t *nbracket;
+static patch_t *nring;
 static patch_t *nhud[12];
 static patch_t *nsshud;
 static patch_t *nbon[12];
@@ -225,7 +227,7 @@ void ST_doPaletteStuff(void)
 
 void ST_UnloadGraphics(void)
 {
-	Z_FreeTags(PU_HUDGFX, PU_HUDGFX);
+	Z_FreeTag(PU_HUDGFX);
 }
 
 void ST_LoadGraphics(void)
@@ -285,6 +287,7 @@ void ST_LoadGraphics(void)
 	watershield = W_CachePatchName("TVELICON", PU_HUDGFX);
 	bombshield = W_CachePatchName("TVARICON", PU_HUDGFX);
 	pityshield = W_CachePatchName("TVPIICON", PU_HUDGFX);
+	pinkshield = W_CachePatchName("TVPPICON", PU_HUDGFX);
 	flameshield = W_CachePatchName("TVFLICON", PU_HUDGFX);
 	bubbleshield = W_CachePatchName("TVBBICON", PU_HUDGFX);
 	thundershield = W_CachePatchName("TVZPICON", PU_HUDGFX);
@@ -309,6 +312,7 @@ void ST_LoadGraphics(void)
 	redstat = W_CachePatchName("REDSTAT", PU_HUDGFX);
 	yelstat = W_CachePatchName("YELSTAT", PU_HUDGFX);
 	nbracket = W_CachePatchName("NBRACKET", PU_HUDGFX);
+	nring = W_CachePatchName("NRNG1", PU_HUDGFX);
 	for (i = 0; i < 12; ++i)
 	{
 		nhud[i] = W_CachePatchName(va("NHUD%d", i+1), PU_HUDGFX);
@@ -339,10 +343,24 @@ void ST_LoadGraphics(void)
 }
 
 // made separate so that skins code can reload custom face graphics
-void ST_LoadFaceGraphics(char *facestr, char *superstr, INT32 skinnum)
+void ST_LoadFaceGraphics(INT32 skinnum)
 {
-	faceprefix[skinnum] = W_CachePatchName(facestr, PU_HUDGFX);
-	superprefix[skinnum] = W_CachePatchName(superstr, PU_HUDGFX);
+	if (skins[skinnum].sprites[SPR2_XTRA].numframes)
+	{
+		spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
+		spriteframe_t *sprframe = &sprdef->spriteframes[0];
+		faceprefix[skinnum] = W_CachePatchNum(sprframe->lumppat[0], PU_HUDGFX);
+		if (skins[skinnum].sprites[(SPR2_XTRA|FF_SPR2SUPER)].numframes)
+		{
+			sprdef = &skins[skinnum].sprites[SPR2_XTRA|FF_SPR2SUPER];
+			sprframe = &sprdef->spriteframes[0];
+			superprefix[skinnum] = W_CachePatchNum(sprframe->lumppat[0], PU_HUDGFX);
+		}
+		else
+			superprefix[skinnum] = faceprefix[skinnum]; // not manually freed, okay to set to same pointer
+	}
+	else
+		faceprefix[skinnum] = superprefix[skinnum] = W_CachePatchName("MISSING", PU_HUDGFX); // ditto
 	facefreed[skinnum] = false;
 }
 
@@ -351,7 +369,7 @@ void ST_ReloadSkinFaceGraphics(void)
 	INT32 i;
 
 	for (i = 0; i < numskins; i++)
-		ST_LoadFaceGraphics(skins[i].face, skins[i].superface, i);
+		ST_LoadFaceGraphics(i);
 }
 
 static inline void ST_InitData(void)
@@ -1250,6 +1268,7 @@ static void ST_drawPowerupHUD(void)
 				case SH_ARMAGEDDON:  p = bombshield;    break;
 				case SH_ATTRACT:     p = ringshield;    break;
 				case SH_PITY:        p = pityshield;    break;
+				case SH_PINK:        p = pinkshield;    break;
 				case SH_FLAMEAURA:   p = flameshield;   break;
 				case SH_BUBBLEWRAP:  p = bubbleshield;  break;
 				case SH_THUNDERCOIN: p = thundershield; break;
@@ -1542,7 +1561,7 @@ static void ST_drawNiGHTSLink(void)
 static void ST_drawNiGHTSHUD(void)
 {
 	INT32 origamount;
-	INT32 total_spherecount;
+	INT32 total_spherecount, total_ringcount;
 	const boolean oldspecialstage = (G_IsSpecialStage(gamemap) && !(maptol & TOL_NIGHTS));
 
 	// Drill meter
@@ -1614,33 +1633,28 @@ static void ST_drawNiGHTSHUD(void)
 #endif
 	ST_DrawTopLeftOverlayPatch(16, 8, nbracket);
 	if (G_IsSpecialStage(gamemap))
-#ifdef MANIASPHERES
 		ST_DrawTopLeftOverlayPatch(24, 16, (
-			(stplyr->bonustime && (leveltime & 4)) ? nssbon : nsshud));
-#else
-		ST_DrawTopLeftOverlayPatch(24, 16, (nsshud));
-#endif
+			(stplyr->bonustime && (leveltime & 4) && (states[S_BLUESPHEREBONUS].frame & FF_ANIMATE)) ? nssbon : nsshud));
 	else
 		ST_DrawTopLeftOverlayPatch(24, 16, *(((stplyr->bonustime) ? nbon : nhud)+((leveltime/2)%12)));
 
 	if (G_IsSpecialStage(gamemap))
 	{
 		INT32 i;
-		total_spherecount = 0;
+		total_spherecount = total_ringcount = 0;
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] /*&& players[i].powers[pw_carry] == CR_NIGHTSMODE*/ && players[i].spheres)
-				total_spherecount += players[i].spheres;
+		{
+			if (!playeringame[i])
+				continue;
+			total_spherecount += players[i].spheres;
+			total_ringcount += players[i].rings;
+		}
 	}
 	else
-		total_spherecount = stplyr->spheres;
-
-	/*if (oldspecialstage)
 	{
-		if (total_spherecount < ssspheres)
-			total_spherecount = ssspheres - total_spherecount;
-		else
-			total_spherecount = 0;
-	}*/
+		total_spherecount = stplyr->spheres;
+		total_ringcount = stplyr->spheres;
+	}
 
 	if (stplyr->capsule)
 	{
@@ -1728,6 +1742,27 @@ static void ST_drawNiGHTSHUD(void)
 	else
 		ST_DrawTopLeftOverlayPatch(40, 8 + 5, narrow[8]);
 
+	if (oldspecialstage)
+	{
+		// invert for s3k style junk
+		total_spherecount = ssspheres - total_spherecount;
+		if (total_spherecount < 0)
+			total_spherecount = 0;
+
+		if (nummaprings > 0) // don't count down if there ISN'T a valid maximum number of rings, like sonic 3
+		{
+			total_ringcount = nummaprings - total_ringcount;
+			if (total_ringcount < 0)
+				total_ringcount = 0;
+		}
+
+		// now rings! you know, for that perfect bonus.
+		V_DrawScaledPatch(272, 8, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT|V_HUDTRANS, nbracket);
+		V_DrawScaledPatch(280, 16+1, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT|V_HUDTRANS, nring);
+		V_DrawScaledPatch(280, 8+5, V_FLIP|V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT|V_HUDTRANS, narrow[8]);
+		V_DrawTallNum(272, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTORIGHT|V_HUDTRANS, total_ringcount);
+	}
+
 	if (total_spherecount >= 100)
 		V_DrawTallNum((total_spherecount >= 1000) ? 76 : 72, 8 + 11, V_PERPLAYER|V_SNAPTOTOP|V_SNAPTOLEFT|V_HUDTRANS, total_spherecount);
 	else
@@ -2303,31 +2338,33 @@ static void ST_doItemFinderIconsAndSound(void)
 		return;
 
 	// Scan thinkers to find emblem mobj with these ids
-	for (th = thinkercap.next; th != &thinkercap; th = th->next)
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
+
 		mo2 = (mobj_t *)th;
 
-		if (mo2->type == MT_EMBLEM)
-		{
-			if (!(mo2->flags & MF_SPECIAL))
-				continue;
+		if (mo2->type != MT_EMBLEM)
+			continue;
+
+		if (!(mo2->flags & MF_SPECIAL))
+			continue;
 
-			for (i = 0; i < stemblems; ++i)
+		for (i = 0; i < stemblems; ++i)
+		{
+			if (mo2->health == emblems[i] + 1)
 			{
-				if (mo2->health == emblems[i]+1)
-				{
-					soffset = (i * 20) - ((stemblems-1) * 10);
+				soffset = (i * 20) - ((stemblems - 1) * 10);
 
-					newinterval = ST_drawEmeraldHuntIcon(mo2, itemhoming, soffset);
-					if (newinterval && (!interval || newinterval < interval))
-						interval = newinterval;
+				newinterval = ST_drawEmeraldHuntIcon(mo2, itemhoming, soffset);
+				if (newinterval && (!interval || newinterval < interval))
+					interval = newinterval;
 
-					break;
-				}
+				break;
 			}
 		}
+
 	}
 
 	if (!(P_AutoPause() || paused) && interval > 0 && leveltime && leveltime % interval == 0)
diff --git a/src/st_stuff.h b/src/st_stuff.h
index aca4e60d2949aacc618d27a93c6b6fc3d92b291e..40574f46c1d07249318ee713fec74e1291778a4a 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -42,7 +42,7 @@ void ST_UnloadGraphics(void);
 void ST_LoadGraphics(void);
 
 // face load graphics, called when skin changes
-void ST_LoadFaceGraphics(char *facestr, char *superstr, INT32 playernum);
+void ST_LoadFaceGraphics(INT32 playernum);
 void ST_ReloadSkinFaceGraphics(void);
 
 void ST_doPaletteStuff(void);
diff --git a/src/v_video.c b/src/v_video.c
index df342e74b2e5092b906e0e62fdf1ff6774c27311..85f22eccb4d8f1dca07f0a4f371990eaccc38766 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -309,12 +309,14 @@ static boolean InitCube(void)
 	return true;
 }
 
+#ifdef BACKWARDSCOMPATCORRECTION
 /*
 So it turns out that the way gamma was implemented previously, the default
 colour profile of the game was messed up. Since this bad decision has been
 around for a long time, and the intent is to keep the base game looking the
 same, I'm not gonna be the one to remove this base modification.
 toast 20/04/17
+... welp yes i am (27/07/19, see the ifdef around it)
 */
 const UINT8 correctiontable[256] =
 	{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
@@ -333,6 +335,7 @@ const UINT8 correctiontable[256] =
 	208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
 	224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
 	240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255};
+#endif
 
 // keep a copy of the palette so that we can get the RGB value for a color index at any time.
 static void LoadPalette(const char *lumpname)
@@ -351,12 +354,18 @@ static void LoadPalette(const char *lumpname)
 	pal = W_CacheLumpNum(lumpnum, PU_CACHE);
 	for (i = 0; i < palsize; i++)
 	{
+#ifdef BACKWARDSCOMPATCORRECTION
 		pMasterPalette[i].s.red = pLocalPalette[i].s.red = correctiontable[*pal++];
 		pMasterPalette[i].s.green = pLocalPalette[i].s.green = correctiontable[*pal++];
 		pMasterPalette[i].s.blue = pLocalPalette[i].s.blue = correctiontable[*pal++];
+#else
+		pMasterPalette[i].s.red = pLocalPalette[i].s.red = *pal++;
+		pMasterPalette[i].s.green = pLocalPalette[i].s.green = *pal++;
+		pMasterPalette[i].s.blue = pLocalPalette[i].s.blue = *pal++;
+#endif
 		pMasterPalette[i].s.alpha = pLocalPalette[i].s.alpha = 0xFF;
 
-		// lerp of colour cubing!
+		// lerp of colour cubing! if you want, make it smoother yourself
 		if (cube)
 		{
 			float working[4][3];
@@ -732,12 +741,15 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 		// Center it if necessary
 		if (!(scrn & V_SCALEPATCHMASK))
 		{
-			// if it's meant to cover the whole screen, black out the rest
+			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
 			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
 			{
 				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
+				if (!column->topdelta)
+				{
+					source = (const UINT8 *)(column) + 3;
+					V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, source[0]);
+				}
 			}
 
 			if (vid.width != BASEVIDWIDTH * dupx)
@@ -984,12 +996,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 		if (!(scrn & V_SCALEPATCHMASK))
 		{
 			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
-			}
+			// no the patch is cropped do not do this ever
 
 			if (vid.width != BASEVIDWIDTH * dupx)
 			{
@@ -1349,13 +1356,16 @@ static UINT32 V_GetHWConsBackColor(void)
 
 
 // THANK YOU MPC!!!
+// and thanks toaster for cleaning it up.
 
 void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 {
 	UINT8 *dest;
-	INT32 u, v;
+	const UINT8 *deststop;
+	INT32 u;
 	UINT8 *fadetable;
 	UINT32 alphalevel = 0;
+	UINT8 perplayershuffle = 0;
 
 	if (rendermode == render_none)
 		return;
@@ -1369,15 +1379,90 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	}
 #endif
 
-	if (!(c & V_NOSCALESTART))
+	if ((alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT)))
 	{
-		INT32 dupx = vid.dupx, dupy = vid.dupy;
+		if (alphalevel == 13)
+			alphalevel = hudminusalpha[cv_translucenthud.value];
+		else if (alphalevel == 14)
+			alphalevel = 10 - cv_translucenthud.value;
+		else if (alphalevel == 15)
+			alphalevel = hudplusalpha[cv_translucenthud.value];
 
-		if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
-		{ // Clear the entire screen, from dest to deststop. Yes, this really works.
-			memset(screens[0], (UINT8)(c&255), vid.width * vid.height * vid.bpp);
-			return;
+		if (alphalevel >= 10)
+			return; // invis
+	}
+
+	if (splitscreen && (c & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				c &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP;
+			}
 		}
+	}
+
+	if (!(c & V_NOSCALESTART))
+	{
+		INT32 dupx = vid.dupx, dupy = vid.dupy;
 
 		x *= dupx;
 		y *= dupy;
@@ -1393,6 +1478,10 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 				x += (vid.width - (BASEVIDWIDTH * dupx));
 			else if (!(c & V_SNAPTOLEFT))
 				x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
 		}
 		if (vid.height != BASEVIDHEIGHT * dupy)
 		{
@@ -1401,6 +1490,10 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 				y += (vid.height - (BASEVIDHEIGHT * dupy));
 			else if (!(c & V_SNAPTOTOP))
 				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
 		}
 	}
 
@@ -1423,34 +1516,206 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 		h = vid.height-y;
 
 	dest = screens[0] + y*vid.width + x;
+	deststop = screens[0] + vid.rowbytes * vid.height;
 
-	if ((alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT)))
+	c &= 255;
+
+	// Jimita (12-04-2018)
+	if (alphalevel)
 	{
-		if (alphalevel == 13)
-			alphalevel = hudminusalpha[cv_translucenthud.value];
-		else if (alphalevel == 14)
-			alphalevel = 10 - cv_translucenthud.value;
-		else if (alphalevel == 15)
-			alphalevel = hudplusalpha[cv_translucenthud.value];
+		fadetable = ((UINT8 *)transtables + ((alphalevel-1)<<FF_TRANSSHIFT) + (c*256));
+		for (;(--h >= 0) && dest < deststop; dest += vid.width)
+		{
+			u = 0;
+			while (u < w)
+			{
+				dest[u] = fadetable[consolebgmap[dest[u]]];
+				u++;
+			}
+		}
+	}
+	else
+	{
+		for (;(--h >= 0) && dest < deststop; dest += vid.width)
+		{
+			u = 0;
+			while (u < w)
+			{
+				dest[u] = consolebgmap[dest[u]];
+				u++;
+			}
+		}
+	}
+}
 
-		if (alphalevel >= 10)
-			return; // invis
+//
+// If color is 0x00 to 0xFF, draw transtable (strength range 0-9).
+// Else, use COLORMAP lump (strength range 0-31).
+// c is not color, it is for flags only. transparency flags will be ignored.
+// IF YOU ARE NOT CAREFUL, THIS CAN AND WILL CRASH!
+// I have kept the safety checks for strength out of this function;
+// I don't trust Lua users with it, so it doesn't matter.
+//
+void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, UINT8 strength)
+{
+	UINT8 *dest;
+	const UINT8 *deststop;
+	INT32 u;
+	UINT8 *fadetable;
+	UINT8 perplayershuffle = 0;
+
+	if (rendermode == render_none)
+		return;
+
+#ifdef HWRENDER
+	if (rendermode != render_soft && rendermode != render_none)
+	{
+		// ughhhhh please can someone else do this? thanks ~toast 25/7/19 in 38 degrees centigrade w/o AC
+		HWR_DrawFadeFill(x, y, w, h, c, color, strength); // toast two days later - left above comment in 'cause it's funny
+		return;
 	}
+#endif
+
+	if (splitscreen && (c & V_PERPLAYER))
+	{
+		fixed_t adjusty = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+		h >>= 1;
+		y >>= 1;
+#ifdef QUADS
+		if (splitscreen > 1) // 3 or 4 players
+		{
+			fixed_t adjustx = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
+			w >>= 1;
+			x >>= 1;
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT;
+			}
+			else if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				c &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT;
+			}
+			else if (stplyr == &players[thirddisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 4;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTORIGHT;
+			}
+			else //if (stplyr == &players[fourthdisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				if (!(c & (V_SNAPTOLEFT|V_SNAPTORIGHT)))
+					perplayershuffle |= 8;
+				x += adjustx;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP|V_SNAPTOLEFT;
+			}
+		}
+		else
+#endif
+		// 2 players
+		{
+			if (stplyr == &players[displayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 1;
+				c &= ~V_SNAPTOBOTTOM;
+			}
+			else //if (stplyr == &players[secondarydisplayplayer])
+			{
+				if (!(c & (V_SNAPTOTOP|V_SNAPTOBOTTOM)))
+					perplayershuffle |= 2;
+				y += adjusty;
+				c &= ~V_SNAPTOTOP;
+			}
+		}
+	}
+
+	if (!(c & V_NOSCALESTART))
+	{
+		INT32 dupx = vid.dupx, dupy = vid.dupy;
+
+		x *= dupx;
+		y *= dupy;
+		w *= dupx;
+		h *= dupy;
+
+		// Center it if necessary
+		if (vid.width != BASEVIDWIDTH * dupx)
+		{
+			// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
+			// so center this imaginary screen
+			if (c & V_SNAPTORIGHT)
+				x += (vid.width - (BASEVIDWIDTH * dupx));
+			else if (!(c & V_SNAPTOLEFT))
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
+			if (perplayershuffle & 4)
+				x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+			else if (perplayershuffle & 8)
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 4;
+		}
+		if (vid.height != BASEVIDHEIGHT * dupy)
+		{
+			// same thing here
+			if (c & V_SNAPTOBOTTOM)
+				y += (vid.height - (BASEVIDHEIGHT * dupy));
+			else if (!(c & V_SNAPTOTOP))
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+			if (perplayershuffle & 1)
+				y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+			else if (perplayershuffle & 2)
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4;
+		}
+	}
+
+	if (x >= vid.width || y >= vid.height)
+		return; // off the screen
+	if (x < 0) {
+		w += x;
+		x = 0;
+	}
+	if (y < 0) {
+		h += y;
+		y = 0;
+	}
+
+	if (w <= 0 || h <= 0)
+		return; // zero width/height wouldn't draw anything
+	if (x + w > vid.width)
+		w = vid.width-x;
+	if (y + h > vid.height)
+		h = vid.height-y;
+
+	dest = screens[0] + y*vid.width + x;
+	deststop = screens[0] + vid.rowbytes * vid.height;
 
 	c &= 255;
 
-	// Jimita (12-04-2018)
-	w = min(w, vid.width);
-	h = min(h, vid.height);
-	fadetable = ((UINT8 *)transtables + ((alphalevel-1)<<FF_TRANSSHIFT) + (c*256));
-	for (v = 0; v < h; v++, dest += vid.width)
-		for (u = 0; u < w; u++)
+	fadetable = ((color & 0xFF00) // Color is not palette index?
+		? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
+		: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
+	for (;(--h >= 0) && dest < deststop; dest += vid.width)
+	{
+		u = 0;
+		while (u < w)
 		{
-			if (!alphalevel)
-				dest[u] = consolebgmap[dest[u]];
-			else
-				dest[u] = fadetable[consolebgmap[dest[u]]];
+			dest[u] = fadetable[dest[u]];
+			u++;
 		}
+	}
 }
 
 //
diff --git a/src/v_video.h b/src/v_video.h
index 43748692e706acd43098d840588dc85470394436..7eb990295de7c23d35eef3ebf29a243b74081eae 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -158,6 +158,8 @@ void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum);
 
 // fade down the screen buffer before drawing the menu over
 void V_DrawFadeScreen(UINT16 color, UINT8 strength);
+// available to lua over my dead body, which will probably happen in this heat
+void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, UINT8 strength);
 
 void V_DrawFadeConsBack(INT32 plines);
 void V_DrawPromptBack(INT32 boxheight, INT32 color);
diff --git a/src/w_wad.c b/src/w_wad.c
index 82ef21d7fd1123267f21c120b8ec0446f383af82..d02ce9ce6061e43f3fa229e549c4821680d8090c 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -162,9 +162,15 @@ FILE *W_OpenWadFile(const char **filename, boolean useerrors)
 {
 	FILE *handle;
 
-	strncpy(filenamebuf, *filename, MAX_WADPATH);
-	filenamebuf[MAX_WADPATH - 1] = '\0';
-	*filename = filenamebuf;
+	// Officially, strncpy should not have overlapping buffers, since W_VerifyNMUSlumps is called after this, and it
+	// changes filename to point at filenamebuf, it would technically be doing that. I doubt any issue will occur since
+	// they point to the same location, but it's better to be safe and this is a simple change.
+	if (filenamebuf != *filename)
+	{
+		strncpy(filenamebuf, *filename, MAX_WADPATH);
+		filenamebuf[MAX_WADPATH - 1] = '\0';
+		*filename = filenamebuf;
+	}
 
 	// open wad file
 	if ((handle = fopen(*filename, "rb")) == NULL)
@@ -1133,6 +1139,22 @@ boolean W_IsLumpWad(lumpnum_t lumpnum)
 	return false; // WADs should never be inside non-PK3s as far as SRB2 is concerned
 }
 
+//
+// W_IsLumpFolder
+// Is the lump a folder? (in a PK3 obviously)
+//
+boolean W_IsLumpFolder(UINT16 wad, UINT16 lump)
+{
+	if (wadfiles[wad]->type == RET_PK3)
+	{
+		const char *name = wadfiles[wad]->lumpinfo[lump].name2;
+
+		return (name[strlen(name)-1] == '/'); // folders end in '/'
+	}
+
+	return false; // non-PK3s don't have folders
+}
+
 #ifdef HAVE_ZLIB
 /* report a zlib or i/o error */
 void zerr(int ret)
diff --git a/src/w_wad.h b/src/w_wad.h
index 8ffcc1d03ce42f01f67cd6ebbce649f2ffd38b4b..6517388504d39b66945f284b9bd294aa1bcaae18 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -151,6 +151,7 @@ size_t W_LumpLengthPwad(UINT16 wad, UINT16 lump);
 size_t W_LumpLength(lumpnum_t lumpnum);
 
 boolean W_IsLumpWad(lumpnum_t lumpnum); // for loading maps from WADs in PK3s
+boolean W_IsLumpFolder(UINT16 wad, UINT16 lump); // for detecting folder "lumps"
 
 #ifdef HAVE_ZLIB
 void zerr(int ret); // zlib error checking
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index acab2507a37d995112b6402e7c83ac0d6f0dd57a..c0fe8eda9ad5f2d73f7a0c2cd7f1a2023a02dd0e 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -293,6 +293,7 @@
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
     <ClCompile Include="..\r_plane.c" />
+    <ClCompile Include="..\r_portal.c" />
     <ClCompile Include="..\r_segs.c" />
     <ClCompile Include="..\r_sky.c" />
     <ClCompile Include="..\r_splats.c" />
@@ -443,6 +444,7 @@
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
     <ClInclude Include="..\r_plane.h" />
+    <ClInclude Include="..\r_portal.h" />
     <ClInclude Include="..\r_segs.h" />
     <ClInclude Include="..\r_sky.h" />
     <ClInclude Include="..\r_splats.h" />
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index c21cedb8a4bf258af6efa2d9a2faba1de00da325..93806e3951066fff4172cdc9f60a0315171e74f4 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -456,6 +456,10 @@
     <ClCompile Include="..\hardware\hw_clip.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
+    <ClCompile Include="..\apng.c" />
+    <ClCompile Include="..\r_portal.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="afxres.h">
@@ -857,6 +861,10 @@
     <ClInclude Include="..\hardware\hw_clip.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
+    <ClInclude Include="..\apng.h" />
+    <ClInclude Include="..\r_portal.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Image Include="Srb2win.ico">
diff --git a/src/y_inter.c b/src/y_inter.c
index 99219396426e86ad8b40a3a088cabdf243f61a7d..975902ab089b2593f738bb14540cb2e812df1df8 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -86,8 +86,8 @@ typedef union
 		INT32 passedx3;
 		INT32 passedx4;
 
-		y_bonus_t bonus;
-		patch_t *bonuspatch;
+		y_bonus_t bonuses[2];
+		patch_t *bonuspatches[2];
 
 		patch_t *pscore; // SCORE
 		UINT32 score; // fake score
@@ -326,34 +326,36 @@ void Y_IntermissionDrawer(void)
 		// draw the header
 		if (intertic <= 2*TICRATE)
 			animatetic = 0;
-		else if (!animatetic && data.spec.bonus.points == 0 && data.spec.passed3[0] != '\0')
+		else if (!animatetic && data.spec.bonuses[0].points == 0 && data.spec.bonuses[1].points == 0 && data.spec.passed3[0] != '\0')
 			animatetic = intertic + TICRATE;
 
 		if (animatetic && (tic_t)intertic >= animatetic)
 		{
 			const INT32 scradjust = (vid.width/vid.dupx)>>3; // 40 for BASEVIDWIDTH
 			INT32 animatetimer = (intertic - animatetic);
-			if (animatetimer <= 14)
+			if (animatetimer <= 16)
 			{
-				xoffset1 = -(animatetimer     * scradjust);
-				xoffset2 = -((animatetimer-2) * scradjust);
-				xoffset3 = -((animatetimer-4) * scradjust);
-				xoffset4 = -((animatetimer-6) * scradjust);
-				xoffset5 = -((animatetimer-8) * scradjust);
+				xoffset1 = -(animatetimer      * scradjust);
+				xoffset2 = -((animatetimer- 2) * scradjust);
+				xoffset3 = -((animatetimer- 4) * scradjust);
+				xoffset4 = -((animatetimer- 6) * scradjust);
+				xoffset5 = -((animatetimer- 8) * scradjust);
+				xoffset6 = -((animatetimer-10) * scradjust);
 				if (xoffset2 > 0) xoffset2 = 0;
 				if (xoffset3 > 0) xoffset3 = 0;
 				if (xoffset4 > 0) xoffset4 = 0;
 				if (xoffset5 > 0) xoffset5 = 0;
+				if (xoffset6 > 0) xoffset6 = 0;
 			}
-			else if (animatetimer < 32)
+			else if (animatetimer < 34)
 			{
 				drawsection = 1;
-				xoffset1 = (22-animatetimer) * scradjust;
-				xoffset2 = (24-animatetimer) * scradjust;
-				xoffset3 = (26-animatetimer) * scradjust;
-				xoffset4 = (28-animatetimer) * scradjust;
-				xoffset5 = (30-animatetimer) * scradjust;
-				xoffset6 = (32-animatetimer) * scradjust;
+				xoffset1 = (24-animatetimer) * scradjust;
+				xoffset2 = (26-animatetimer) * scradjust;
+				xoffset3 = (28-animatetimer) * scradjust;
+				xoffset4 = (30-animatetimer) * scradjust;
+				xoffset5 = (32-animatetimer) * scradjust;
+				xoffset6 = (34-animatetimer) * scradjust;
 				if (xoffset1 < 0) xoffset1 = 0;
 				if (xoffset2 < 0) xoffset2 = 0;
 				if (xoffset3 < 0) xoffset3 = 0;
@@ -370,9 +372,9 @@ void Y_IntermissionDrawer(void)
 
 		if (drawsection == 1)
 		{
-			const char *ringtext = "\x86" "50 RINGS, NO SHIELD";
-			const char *tut1text = "\x86" "PRESS " "\x82" "SPIN";
-			const char *tut2text = "\x86" "MID-" "\x82" "JUMP";
+			const char *ringtext = "\x82" "50 RINGS, NO SHIELD";
+			const char *tut1text = "\x82" "PRESS " "\x80" "SPIN";
+			const char *tut2text = "\x82" "MID-" "\x80" "JUMP";
 			ttheight = 16;
 			V_DrawLevelTitle(data.spec.passedx1 + xoffset1, ttheight, 0, data.spec.passed1);
 			ttheight += V_LevelNameHeight(data.spec.passed3) + 2;
@@ -389,6 +391,7 @@ void Y_IntermissionDrawer(void)
 		}
 		else
 		{
+			INT32 yoffset = 0;
 			if (data.spec.passed1[0] != '\0')
 			{
 				ttheight = 24;
@@ -402,22 +405,31 @@ void Y_IntermissionDrawer(void)
 				V_DrawLevelTitle(data.spec.passedx2 + xoffset1, ttheight, 0, data.spec.passed2);
 			}
 
-			V_DrawScaledPatch(152 + xoffset3, 108, 0, data.spec.bonuspatch);
-			V_DrawTallNum(BASEVIDWIDTH + xoffset3 - 68, 109, 0, data.spec.bonus.points);
-			V_DrawScaledPatch(152 + xoffset4, 124, 0, data.spec.pscore);
-			V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125, 0, data.spec.score);
+			V_DrawScaledPatch(152 + xoffset3, 108, 0, data.spec.bonuspatches[0]);
+			V_DrawTallNum(BASEVIDWIDTH + xoffset3 - 68, 109, 0, data.spec.bonuses[0].points);
+			if (data.spec.bonuses[1].display)
+			{
+				V_DrawScaledPatch(152 + xoffset4, 124, 0, data.spec.bonuspatches[1]);
+				V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125, 0, data.spec.bonuses[1].points);
+				yoffset = 16;
+				// hack; pass the buck along...
+				xoffset4 = xoffset5;
+				xoffset5 = xoffset6;
+			}
+			V_DrawScaledPatch(152 + xoffset4, 124+yoffset, 0, data.spec.pscore);
+			V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125+yoffset, 0, data.spec.score);
 
 			// Draw continues!
 			if (!multiplayer /* && (data.spec.continues & 0x80) */) // Always draw outside of netplay
 			{
 				UINT8 continues = data.spec.continues & 0x7F;
 
-				V_DrawScaledPatch(152 + xoffset5, 150, 0, data.spec.pcontinues);
+				V_DrawScaledPatch(152 + xoffset5, 150+yoffset, 0, data.spec.pcontinues);
 				for (i = 0; i < continues; ++i)
 				{
 					if ((data.spec.continues & 0x80) && i == continues-1 && (endtic < 0 || intertic%20 < 10))
 						break;
-					V_DrawContinueIcon(246 + xoffset5 - (i*12), 162, 0, *data.spec.playerchar, *data.spec.playercolor);
+					V_DrawContinueIcon(246 + xoffset5 - (i*12), 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
 				}
 			}
 		}
@@ -904,7 +916,7 @@ void Y_Ticker(void)
 	{
 		INT32 i;
 		UINT32 oldscore = data.spec.score;
-		boolean skip = false, super = false;
+		boolean skip = false, super = false, anybonuses = false;
 
 		if (!intertic) // first time only
 		{
@@ -946,16 +958,26 @@ void Y_Ticker(void)
 			return;
 		}
 
-		// ring bonus counts down by 222 each tic
-		data.spec.bonus.points -= 222;
-		data.spec.score += 222;
-		if (data.spec.bonus.points < 0 || skip == true) // went too far
+		// bonuses count down by 222 each tic
+		for (i = 0; i < 2; ++i)
 		{
-			data.spec.score += data.spec.bonus.points;
-			data.spec.bonus.points = 0;
+			if (!data.spec.bonuses[i].points)
+				continue;
+
+			data.spec.bonuses[i].points -= 222;
+			data.spec.score += 222;
+			if (data.spec.bonuses[i].points < 0 || skip == true) // too far?
+			{
+				data.spec.score += data.spec.bonuses[i].points;
+				data.spec.bonuses[i].points = 0;
+			}
+			if (data.spec.score > MAXSCORE)
+				data.spec.score = MAXSCORE;
+			if (data.spec.bonuses[i].points > 0)
+				anybonuses = true;
 		}
 
-		if (!data.spec.bonus.points)
+		if (!anybonuses)
 		{
 			tallydonetic = intertic;
 			if (!((data.spec.continues & 0x80) || (super && ALL7EMERALDS(emeralds)))) // don't set endtic yet!
@@ -1301,7 +1323,9 @@ void Y_StartIntermission(void)
 			// give out ring bonuses
 			Y_AwardSpecialStageBonus();
 
-			data.spec.bonuspatch = W_CachePatchName(data.spec.bonus.patch, PU_STATIC);
+			for (i = 0; i < 2; ++i)
+				data.spec.bonuspatches[i] = W_CachePatchName(data.spec.bonuses[i].patch, PU_STATIC);
+
 			data.spec.pscore = W_CachePatchName("YB_SCORE", PU_STATIC);
 			data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_STATIC);
 
@@ -1831,7 +1855,7 @@ static void Y_SetPerfectBonus(player_t *player, y_bonus_t *bstruct)
 	memset(bstruct, 0, sizeof(y_bonus_t));
 	strncpy(bstruct->patch, "YB_PERFE", sizeof(bstruct->patch));
 
-	if (data.coop.gotperfbonus == -1)
+	if (intertype != int_coop || data.coop.gotperfbonus == -1)
 	{
 		INT32 sharedringtotal = 0;
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -1840,15 +1864,33 @@ static void Y_SetPerfectBonus(player_t *player, y_bonus_t *bstruct)
 			sharedringtotal += players[i].rings;
 		}
 		if (!sharedringtotal || nummaprings == -1 || sharedringtotal < nummaprings)
-			data.coop.gotperfbonus = 0;
+			bstruct->display = false;
 		else
-			data.coop.gotperfbonus = 1;
+		{
+			bstruct->display = true;
+			bstruct->points = 50000;
+		}
 	}
-	if (!data.coop.gotperfbonus)
+	if (intertype != int_coop)
 		return;
 
+	data.coop.gotperfbonus = (bstruct->display ? 1 : 0);
+}
+
+static void Y_SetSpecialRingBonus(player_t *player, y_bonus_t *bstruct)
+{
+	INT32 i, sharedringtotal = 0;
+
+	(void)player;
+	strncpy(bstruct->patch, "YB_RING", sizeof(bstruct->patch));
 	bstruct->display = true;
-	bstruct->points = 50000;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i]) continue;
+		sharedringtotal += players[i].rings;
+	}
+	bstruct->points = max(0, (sharedringtotal) * 100);
 }
 
 // This list can be extended in the future with SOC/Lua, perhaps.
@@ -1953,23 +1995,33 @@ static void Y_AwardCoopBonuses(void)
 static void Y_AwardSpecialStageBonus(void)
 {
 	INT32 i, oldscore, ptlives;
-	y_bonus_t localbonus;
+	y_bonus_t localbonuses[2];
 
 	data.spec.score = players[consoleplayer].score;
-	memset(&data.spec.bonus, 0, sizeof(data.spec.bonus));
-	data.spec.bonuspatch = NULL;
+	memset(data.spec.bonuses, 0, sizeof(data.spec.bonuses));
+	memset(data.spec.bonuspatches, 0, sizeof(data.coop.bonuspatches));
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		oldscore = players[i].score;
 
 		if (!playeringame[i] || players[i].lives < 1) // not active or game over
-			Y_SetNullBonus(&players[i], &localbonus);
-		else if (maptol & TOL_NIGHTS) // Mare score instead of Rings
-			Y_SetNightsBonus(&players[i], &localbonus);
+		{
+			Y_SetNullBonus(&players[i], &localbonuses[0]);
+			Y_SetNullBonus(&players[i], &localbonuses[1]);
+		}
+		else if (maptol & TOL_NIGHTS) // NiGHTS bonus score instead of Rings
+		{
+			Y_SetNightsBonus(&players[i], &localbonuses[0]);
+			Y_SetNullBonus(&players[i], &localbonuses[1]);
+		}
 		else
-			Y_SetRingBonus(&players[i], &localbonus);
-		players[i].score += localbonus.points;
+		{
+			Y_SetSpecialRingBonus(&players[i], &localbonuses[0]);
+			Y_SetPerfectBonus(&players[i], &localbonuses[1]);
+		}
+		players[i].score += localbonuses[0].points;
+		players[i].score += localbonuses[1].points;
 		if (players[i].score > MAXSCORE)
 			players[i].score = MAXSCORE;
 
@@ -1982,7 +2034,7 @@ static void Y_AwardSpecialStageBonus(void)
 		if (i == consoleplayer)
 		{
 			data.spec.gotlife = (((netgame || multiplayer) && gametype == GT_COOP && cv_cooplives.value == 0) ? 0 : ptlives);
-			M_Memcpy(&data.spec.bonus, &localbonus, sizeof(data.spec.bonus));
+			M_Memcpy(&data.spec.bonuses, &localbonuses, sizeof(data.spec.bonuses));
 
 			// Continues related
 			data.spec.continues = min(players[i].continues, 8);
@@ -2057,7 +2109,8 @@ static void Y_UnloadData(void)
 			// unload the special stage patches
 			//UNLOAD(data.spec.cemerald);
 			//UNLOAD(data.spec.nowsuper);
-			UNLOAD(data.spec.bonuspatch);
+			UNLOAD(data.spec.bonuspatches[1]);
+			UNLOAD(data.spec.bonuspatches[0]);
 			UNLOAD(data.spec.pscore);
 			UNLOAD(data.spec.pcontinues);
 			break;