diff --git a/CMakeLists.txt b/CMakeLists.txt
index abec11087e0ec11f24bb2228730ebd19af2ee2cf..1e46f5dc37fb5b650165ef49004b5f5177511483 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
 # DO NOT CHANGE THIS SRB2 STRING! Some variable names depend on this string.
 # Version change is fine.
 project(SRB2
-	VERSION 2.2.2
+	VERSION 2.2.4
 	LANGUAGES C)
 
 if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
diff --git a/appveyor.yml b/appveyor.yml
index a28935f6388649052389adbc70c88d2f853a7e75..5d599a516c4bcf78e67b4dc0b5e2f2505df1df48 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.2.2.{branch}-{build}
+version: 2.2.4.{branch}-{build}
 os: MinGW
 
 environment:
diff --git a/extras/conf/SRB2-22.cfg b/extras/conf/SRB2-22.cfg
index ec318321d75fcb1b100f67c80d93c8750cb06282..7b1b678f220d843f812b285ba13470bcb8b658ad 100644
--- a/extras/conf/SRB2-22.cfg
+++ b/extras/conf/SRB2-22.cfg
@@ -15,7 +15,7 @@
 	* Oogaland
 	* Rob
 	* Shadow Hog
-	* Spherallic
+	* sphere
 	* SRB2-Playah
 	* SSNTails
 	* SteelT
@@ -435,7 +435,7 @@ sectortypes
 	112 = "Trigger Line Ex. (NiGHTS Mare)";
 	128 = "Check for Linedef Executor on FOFs";
 	144 = "Egg Capsule";
-	160 = "Special Stage Time/Rings Parameters";
+	160 = "Special Stage Time/Spheres Parameters";
 	176 = "Custom Global Gravity";
 	512 = "Wind/Current";
 	1024 = "Conveyor Belt";
@@ -490,7 +490,7 @@ gen_sectortypes
 		112 = "Trigger Line Ex. (NiGHTS Mare)";
 		128 = "Check for Linedef Executor on FOFs";
 		144 = "Egg Capsule";
-		160 = "Special Stage Time/Rings Parameters";
+		160 = "Special Stage Time/Spheres Parameters";
 		176 = "Custom Global Gravity";
 	}
 
@@ -738,12 +738,6 @@ linedeftypes
 			flags2text = "[1] Use control sector tag";
 			flags64text = "[6] No sound effect";
 		}
-
-		65
-		{
-			title = "Bridge Thinker <disabled>";
-			prefix = "(65)";
-		}
 	}
 
 	polyobject
@@ -756,16 +750,12 @@ linedeftypes
 			prefix = "(20)";
 		}
 
-		21
-		{
-			title = "Explicitly Include Line <disabled>";
-			prefix = "(21)";
-		}
-
 		22
 		{
 			title = "Parameters";
 			prefix = "(22)";
+			flags8text = "[3] Set translucency by X offset";
+			flags32text = "[5] Render outer sides only";
 			flags64text = "[6] Trigger linedef executor";
 			flags128text = "[7] Intangible";
 			flags256text = "[8] Stopped by pushables";
@@ -788,7 +778,6 @@ linedeftypes
 		{
 			title = "Angular Displacement by Front Sector";
 			prefix = "(32)";
-			flags8text = "[3] Set delay by backside sector";
 			flags64text = "[6] Don't turn players";
 			flags512text = "[9] Turn all objects";
 		}
@@ -1136,7 +1125,6 @@ linedeftypes
 		{
 			title = "Goo Water, Translucent, No Sides";
 			prefix = "(125)";
-			flags8text = "[3] Slope skew sides";
 			flags64text = "[6] Use two light levels";
 			flags512text = "[9] Use target light level";
 			flags1024text = "[10] Ripple effect";
@@ -1227,6 +1215,18 @@ linedeftypes
 			3dfloorflags = "19F";
 		}
 
+		153
+		{
+			title = "Dynamically Sinking Platform";
+			prefix = "(153)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "19F";
+		}
+
 		160
 		{
 			title = "Floating, Bobbing";
@@ -1282,7 +1282,6 @@ linedeftypes
 			title = "Rising Platform, Solid, Invisible";
 			prefix = "(193)";
 			flags2text = "[1] Sink when stepped on";
-			flags8text = "[3] Slope skew sides";
 			flags32text = "[5] Only block player";
 			flags64text = "[6] Spindash to move";
 			flags128text = "[7] Only block non-players";
@@ -1488,16 +1487,22 @@ linedeftypes
 		{
 			title = "Mario Block";
 			prefix = "(250)";
+			flags8text = "[3] Slope skew sides";
 			flags32text = "[5] Invisible block";
 			flags64text = "[6] Brick block";
 			3dfloor = true;
 			3dfloorflags = "40019F";
+			flags323dfloorflagsremove = "19E";
+			flags643dfloorflagsadd = "200000";
 		}
 
 		251
 		{
 			title = "Thwomp Block";
 			prefix = "(251)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
 			flags512text = "[9] Custom crushing sound";
 			flags1024text = "[10] Custom speed";
 			3dfloor = true;
@@ -1513,8 +1518,8 @@ linedeftypes
 			flags512text = "[9] Shattered by pushables";
 			flags1024text = "[10] Trigger linedef executor";
 			3dfloor = true;
-			3dfloorflags = "8800019";
-			flags643dfloorflagsadd = "200006";
+			3dfloorflags = "880001D";
+			flags643dfloorflagsadd = "200002";
 		}
 
 		253
@@ -1525,7 +1530,7 @@ linedeftypes
 			flags512text = "[9] Shattered by pushables";
 			flags1024text = "[10] Trigger linedef executor";
 			3dfloor = true;
-			3dfloorflags = "8801019";
+			3dfloorflags = "880101D";
 		}
 
 		254
@@ -1533,6 +1538,7 @@ linedeftypes
 			title = "Bustable Block";
 			prefix = "(254)";
 			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
 			flags64text = "[6] Strong characters only";
 			flags128text = "[7] Only block non-players";
 			flags512text = "[9] Shattered by pushables";
@@ -1593,6 +1599,7 @@ linedeftypes
 		{
 			title = "Custom FOF";
 			prefix = "(259)";
+			flags8text = "[3] Slope skew sides";
 			flags32text = "[5] Only block player";
 			flags128text = "[7] Only block non-players";
 			flags512text = "[9] Shattered by pushables";
@@ -1896,6 +1903,27 @@ linedeftypes
 			prefix = "(333)";
 		}
 
+		334
+		{
+			title = "Object Dye - Continuous";
+			flags64text = "[6] Disable for this color";
+			prefix = "(334)";
+		}
+
+		335
+		{
+			title = "Object Dye - Each Time";
+			flags64text = "[6] Disable for this color";
+			prefix = "(335)";
+		}
+
+		336
+		{
+			title = "Object Dye - Once";
+			flags64text = "[6] Disable for this color";
+			prefix = "(336)";
+		}
+
 		399
 		{
 			title = "Level Load";
@@ -2161,6 +2189,14 @@ linedeftypes
 			flags8text = "[3] Set delay by backside sector";
 		}
 
+		449
+		{
+			title = "Enable Bosses with Parameter";
+			prefix = "(449)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Disable bosses";
+		}
+
 		457
 		{
 			title = "Track Object's Angle";
@@ -2180,12 +2216,15 @@ linedeftypes
 		{
 			title = "Award Rings";
 			prefix = "(460)";
+			flags8text = "[3] Set delay by backside sector";
 		}
 
 		461
 		{
 			title = "Spawn Object";
 			prefix = "(461)";
+			flags8text = "[3] Set delay by backside sector";
+			flags32text = "[5] Use line angle for object";
 			flags64text = "[6] Spawn inside a range";
 		}
 
@@ -2193,6 +2232,20 @@ linedeftypes
 		{
 			title = "Stop Timer/Exit Stage in Record Attack";
 			prefix = "(462)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		463
+		{
+			title = "Dye Object";
+			prefix = "(463)";
+		}
+
+		464
+		{
+			title = "Trigger Egg Capsule";
+			prefix = "(464)";
+			flags64text = "[6] Don't end level";
 		}
 	}
 
@@ -2206,7 +2259,7 @@ linedeftypes
 			prefix = "(413)";
 			flags2text = "[1] Keep after death";
 			flags8text = "[3] Set delay by backside sector";
-			flags32text = "[5] Seek to current song position";
+			flags32text = "[5] Seek from current position";
 			flags64text = "[6] For everyone";
 			flags128text = "[7] Fade to custom volume";
 			flags512text = "[9] Don't loop";
@@ -2220,7 +2273,7 @@ linedeftypes
 			flags2text = "[1] From calling sector";
 			flags8text = "[3] Set delay by backside sector";
 			flags64text = "[6] From nowhere for triggerer";
-			flags512text = "[9] For everyone";
+			flags512text = "[9] From nowhere for everyone";
 			flags1024text = "[10] From tagged sectors";
 		}
 
@@ -2298,7 +2351,6 @@ linedeftypes
 			flags8text = "[3] Set delay by backside sector";
 		}
 
-
 		445
 		{
 			title = "Make FOF Disappear/Reappear";
@@ -2325,8 +2377,8 @@ linedeftypes
 			flags32text = "[5] Subtract Red value";
 			flags64text = "[6] Subtract Green value";
 			flags128text = "[7] Subtract Blue value";
-			flags256text = "[8] Calc relative values";
-			flags32768text = "[15] Use back side colormap";
+			flags256text = "[8] Set relative to current";
+			flags32768text = "[15] Use backside colormap";
 		}
 
 		448
@@ -2359,7 +2411,7 @@ linedeftypes
 			prefix = "(452)";
 			flags8text = "[3] Set delay by backside sector";
 			flags64text = "[6] Do not handle FF_TRANS";
-			flags256text = "[8] Set relative to current val";
+			flags256text = "[8] Set relative to current";
 		}
 
 		453
@@ -2371,7 +2423,7 @@ linedeftypes
 			flags32text = "[5] No collision during fade";
 			flags64text = "[6] Do not handle FF_TRANS";
 			flags128text = "[7] Do not handle lighting";
-			flags256text = "[8] Set relative to current val";
+			flags256text = "[8] Set relative to current";
 			flags512text = "[9] Speed = Tic Duration";
 			flags1024text = "[10] Override existing fade";
 			flags16384text = "[14] Do not handle collision";
@@ -2395,11 +2447,11 @@ linedeftypes
 			flags32text = "[5] Subtract Red value";
 			flags64text = "[6] Subtract Green value";
 			flags128text = "[7] Subtract Blue value";
-			flags256text = "[8] Calc relative values";
+			flags256text = "[8] Set relative to current";
 			flags512text = "[9] Speed = Tic Duration";
 			flags1024text = "[10] Override existing fade";
 			flags16384text = "[14] Fade from invisible black";
-			flags32768text = "[15] Use back side colormap";
+			flags32768text = "[15] Use backside colormap";
 		}
 
 		456
@@ -2416,9 +2468,7 @@ linedeftypes
 			flags2text = "[1] Close text prompt";
 			flags8text = "[3] Set delay by backside sector";
 			flags32text = "[5] Run executor tag on close";
-			flags64text = "[6] For everyone";
-			flags128text = "[7] Do not block controls";
-			flags256text = "[8] Do not freeze time";
+			flags128text = "[7] Don't disable controls";
 			flags32768text = "[15] Find prompt by name";
 		}
 	}
@@ -2524,7 +2574,7 @@ linedeftypes
 			prefix = "(491)";
 			flags8text = "[3] Set delay by backside sector";
 			flags16text = "[4] Set raw alpha by Front X";
-			flags256text = "[8] Calc relative values";
+			flags256text = "[8] Set relative to current";
 		}
 
 		492
@@ -2534,7 +2584,7 @@ linedeftypes
 			flags8text = "[3] Set delay by backside sector";
 			flags16text = "[4] Set raw alpha by Front X";
 			flags32text = "[5] No collision during fade";
-			flags256text = "[8] Calc relative values";
+			flags256text = "[8] Set relative to current";
 			flags512text = "[9] Speed = Tic Duration";
 			flags1024text = "[10] Override existing fade";
 			flags16384text = "[14] Do not handle collision";
@@ -2632,76 +2682,84 @@ linedeftypes
 		{
 			title = "Carry Objects on Floor";
 			prefix = "(520)";
+			flags64text = "[6] Exclusive";
 		}
 
 		521
 		{
 			title = "Carry Objects on Floor (Accelerative)";
 			prefix = "(521)";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		522
 		{
 			title = "Carry Objects on Floor (Displacement)";
 			prefix = "(522)";
+			flags64text = "[6] Exclusive";
 		}
 
 		523
 		{
 			title = "Carry Objects on Ceiling";
 			prefix = "(523)";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		524
 		{
 			title = "Carry Objects on Ceiling (Accelerative)";
 			prefix = "(524)";
+			flags64text = "[6] Exclusive";
 		}
 
 		525
 		{
 			title = "Carry Objects on Ceiling (Displacement)";
 			prefix = "(525)";
+			flags64text = "[6] Exclusive";
 		}
 
 		530
 		{
 			title = "Scroll Floor Texture and Carry Objects";
 			prefix = "(530)";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		531
 		{
 			title = "Scroll Floor Texture and Carry Objects (Accelerative)";
 			prefix = "(531)";
+			flags64text = "[6] Exclusive";
 		}
 
 		532
 		{
 			title = "Scroll Floor Texture and Carry Objects (Displacement)";
 			prefix = "(532)";
+			flags64text = "[6] Exclusive";
 		}
 
 		533
 		{
 			title = "Scroll Ceiling Texture and Carry Objects";
 			prefix = "(533)";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		534
 		{
 			title = "Scroll Ceiling Texture and Carry Objects (Accelerative)";
 			prefix = "(534)";
+			flags64text = "[6] Exclusive";
 		}
 
 		535
 		{
 			title = "Scroll Ceiling Texture and Carry Objects (Displacement)";
 			prefix = "(535)";
+			flags64text = "[6] Exclusive";
 		}
 	}
 
@@ -2714,7 +2772,7 @@ linedeftypes
 			title = "Wind";
 			prefix = "(541)";
 			flags512text = "[9] Player slides";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		542
@@ -2722,7 +2780,7 @@ linedeftypes
 			title = "Upwards Wind";
 			prefix = "(542)";
 			flags512text = "[9] Player slides";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		543
@@ -2730,7 +2788,7 @@ linedeftypes
 			title = "Downwards Wind";
 			prefix = "(543)";
 			flags512text = "[9] Player slides";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		544
@@ -2738,7 +2796,7 @@ linedeftypes
 			title = "Current";
 			prefix = "(544)";
 			flags512text = "[9] Player slides";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		545
@@ -2746,7 +2804,7 @@ linedeftypes
 			title = "Upwards Current";
 			prefix = "(545)";
 			flags512text = "[9] Player slides";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		546
@@ -2754,13 +2812,14 @@ linedeftypes
 			title = "Downwards Current";
 			prefix = "(546)";
 			flags512text = "[9] Player slides";
-			flags64text = "[6] Even across edges";
+			flags64text = "[6] Exclusive";
 		}
 
 		547
 		{
 			title = "Push/Pull";
 			prefix = "(547)";
+			flags64text = "[6] Exclusive";
 		}
 	}
 
@@ -3407,8 +3466,8 @@ thingtypes
 			sprite = "ESHIA1";
 			width = 16;
 			height = 48;
-			flags1text = "[1] 90 degrees counter-clockwise";
-			flags4text = "[4] 90 degrees clockwise";
+			flags1text = "[1] 90 degrees clockwise";
+			flags4text = "[4] 90 degrees counter-clockwise";
 			flags8text = "[8] Double speed";
 		}
 		115
@@ -3674,6 +3733,7 @@ thingtypes
 			width = 8;
 			height = 16;
 			sprite = "internal:capsule";
+			angletext = "Tag";
 		}
 		292
 		{
@@ -3870,6 +3930,8 @@ thingtypes
 		{
 			title = "Emerald Hunt Location";
 			sprite = "SHRDA0";
+			flags8height = 24;
+			flags8text = "[8] Float";
 		}
 		321
 		{
@@ -3897,9 +3959,10 @@ thingtypes
 		title = "Monitors";
 		width = 18;
 		height = 40;
-		flags1text = "[1] Run Linedef Executor on pop";
+		flags1text = "[1] Run linedef executor on pop";
 		flags4text = "[4] Random (Strong)";
 		flags8text = "[8] Random (Weak)";
+		angletext = "Tag";
 
 		400
 		{
@@ -4029,7 +4092,8 @@ thingtypes
 		title = "Monitors (Respawning)";
 		width = 20;
 		height = 44;
-		flags1text = "[1] Run Linedef Executor on pop";
+		flags1text = "[1] Run linedef executor on pop";
+		angletext = "Tag";
 
 		431
 		{
@@ -4125,7 +4189,9 @@ thingtypes
 			sprite = "STPTA0M0";
 			width = 64;
 			height = 128;
+			flags4text = "[4] Respawn at center";
 			angletext = "Angle/Order";
+			parametertext = "Order";
 		}
 		520
 		{
@@ -4152,6 +4218,7 @@ thingtypes
 			sprite = "WSPKALAR";
 			width = 16;
 			height = 14;
+			arrow = 1;
 			flags1text = "[1] Start retracted";
 			flags4text = "[4] Retractable";
 			flags8text = "[8] Intangible";
@@ -4558,6 +4625,7 @@ thingtypes
 			sprite = "TOADA0";
 			width = 32;
 			height = 16;
+			angletext = "Tag";
 		}
 		757
 		{
@@ -5832,7 +5900,7 @@ thingtypes
 			sprite = "CAPSA0";
 			width = 72;
 			height = 144;
-			angletext = "Rings";
+			angletext = "Spheres";
 			parametertext = "Mare";
 		}
 	}
@@ -6273,7 +6341,7 @@ thingtypes
 			sprite = "PUMKA0";
 			width = 16;
 			height = 40;
-			flags1text = "Don't flicker";
+			flags1text = "[1] Don't flicker";
 		}
 		2007
 		{
@@ -6281,7 +6349,7 @@ thingtypes
 			sprite = "PUMKB0";
 			width = 16;
 			height = 40;
-			flags1text = "Don't flicker";
+			flags1text = "[1] Don't flicker";
 		}
 		2008
 		{
@@ -6289,7 +6357,7 @@ thingtypes
 			sprite = "PUMKC0";
 			width = 16;
 			height = 40;
-			flags1text = "Don't flicker";
+			flags1text = "[1] Don't flicker";
 		}
 		2009
 		{
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 372341291bc0fe6118ab20a81350ad658bd5a102..b0a593bb16d760950c71623f49e7349abd1df8ad 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -16,6 +16,7 @@ set(SRB2_CORE_SOURCES
 	f_finale.c
 	f_wipe.c
 	filesrch.c
+	g_demo.c
 	g_game.c
 	g_input.c
 	hu_stuff.c
@@ -71,6 +72,7 @@ set(SRB2_CORE_HEADERS
 	f_finale.h
 	fastcmp.h
 	filesrch.h
+	g_demo.h
 	g_game.h
 	g_input.h
 	g_state.h
diff --git a/src/Makefile b/src/Makefile
index 1ebd39ac40e4d417ec74c2cc6c6ff616463e7626..fdf9c78b774b513ee5650654c32913e468d77d83 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -422,6 +422,7 @@ OBJS:=$(i_main_o) \
 		$(OBJDIR)/z_zone.o   \
 		$(OBJDIR)/f_finale.o \
 		$(OBJDIR)/f_wipe.o   \
+		$(OBJDIR)/g_demo.o   \
 		$(OBJDIR)/g_game.o   \
 		$(OBJDIR)/g_input.o  \
 		$(OBJDIR)/am_map.o   \
diff --git a/src/android/i_video.c b/src/android/i_video.c
index b8bb4fefbf408970af9c23ace95f5217548dc43e..1909cd71afbb5e2acf1efdfd5a218b44cef09316 100644
--- a/src/android/i_video.c
+++ b/src/android/i_video.c
@@ -19,10 +19,10 @@ boolean allow_fullscreen = false;
 consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 void I_StartupGraphics(void){}
-void I_StartupHardwareGraphics(void){}
-
 void I_ShutdownGraphics(void){}
 
+void VID_StartupOpenGL(void){}
+
 void I_SetPalette(RGBA_t *palette)
 {
   (void)palette;
@@ -52,10 +52,8 @@ INT32 VID_SetMode(INT32 modenum)
   return 0;
 }
 
-void VID_CheckRenderer(void)
-{
-	// ..............
-}
+void VID_CheckRenderer(void) {}
+void VID_CheckGLLoaded(rendermode_t oldrender) {}
 
 const char *VID_GetModeName(INT32 modenum)
 {
diff --git a/src/b_bot.c b/src/b_bot.c
index 4fefbdcb61b78a4972cf729d55a054cd2497f7da..4397938c1ae42e90807493b8edbfac81225c12bd 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -459,6 +459,19 @@ boolean B_CheckRespawn(player_t *player)
 	if (!sonic || sonic->health <= 0)
 		return false;
 
+	// B_RespawnBot doesn't do anything if the condition above this isn't met
+	{
+		UINT8 shouldForce = LUAh_BotRespawn(sonic, tails);
+
+		if (P_MobjWasRemoved(sonic) || P_MobjWasRemoved(tails))
+			return (shouldForce == 1); // mobj was removed
+
+		if (shouldForce == 1)
+			return true;
+		else if (shouldForce == 2)
+			return false;
+	}
+
 	// Check if Sonic is busy first.
 	// If he's doing any of these things, he probably doesn't want to see us.
 	if (sonic->player->pflags & (PF_GLIDING|PF_SLIDING|PF_BOUNCING)
diff --git a/src/command.c b/src/command.c
index 72dc9f769908cf2e0f096caf9cc5413b14ed2388..f5c02d877dadb087efce2d0ecfcb825ae66acdb2 100644
--- a/src/command.c
+++ b/src/command.c
@@ -80,7 +80,7 @@ static boolean joyaxis2_default = false;
 static INT32 joyaxis_count = 0;
 static INT32 joyaxis2_count = 0;
 
-#define COM_BUF_SIZE 8192 // command buffer size
+#define COM_BUF_SIZE (32<<10) // command buffer size
 #define MAX_ALIAS_RECURSION 100 // max recursion allowed for aliases
 
 static INT32 com_wait; // one command per frame (for cmd sequences)
diff --git a/src/config.h.in b/src/config.h.in
index 4926f9a063ca706f36fcfd8398ce80820d312e39..3b25799657cdd10cec5e35f6e06775c6edf48b4a 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -28,12 +28,14 @@
 /* Manually defined asset hashes for non-CMake builds
  * Last updated 2020 / 02 / 15 - v2.2.1 - main assets
  * Last updated 2020 / 02 / 22 - v2.2.2 - patch.pk3
+ * Last updated 2020 / 05 / 10 - v2.2.3 - player.dta & patch.pk3
+ * Last updated 2020 / 05 / 11 - v2.2.4 - patch.pk3
  */
 #define ASSET_HASH_SRB2_PK3   "0277c9416756627004e83cbb5b2e3e28"
 #define ASSET_HASH_ZONES_PK3  "f7e88afb6af7996a834c7d663144bead"
-#define ASSET_HASH_PLAYER_DTA "ad49e07b17cc662f1ad70c454910b4ae"
+#define ASSET_HASH_PLAYER_DTA "8a4507ddf9bc0682c09174400f26ad65"
 #ifdef USE_PATCH_DTA
-#define ASSET_HASH_PATCH_PK3  "ee54330ecb743314c5f962af4db731ff"
+#define ASSET_HASH_PATCH_PK3  "bbbf6af3b20349612ee06e0b55979a76"
 #endif
 
 #endif
diff --git a/src/console.c b/src/console.c
index f8fa1314a7085179a8b99be769dad3fb9b186347..0f1ccbd33b02269e167ecfe9816b2164e77b09ca 100644
--- a/src/console.c
+++ b/src/console.c
@@ -97,6 +97,7 @@ static void CON_InputInit(void);
 static void CON_RecalcSize(void);
 static void CON_ChangeHeight(void);
 
+static void CON_DrawBackpic(void);
 static void CONS_hudlines_Change(void);
 static void CONS_backcolor_Change(void);
 
@@ -1530,6 +1531,51 @@ static void CON_DrawHudlines(void)
 	con_clearlines = y; // this is handled by HU_Erase();
 }
 
+// Lactozilla: Draws the console's background picture.
+static void CON_DrawBackpic(void)
+{
+	patch_t *con_backpic;
+	lumpnum_t piclump;
+	int x, w, h;
+
+	// Get the lumpnum for CONSBACK, or fallback into MISSING.
+	piclump = W_CheckNumForName("CONSBACK");
+	if (piclump == LUMPERROR)
+		piclump = W_GetNumForName("MISSING");
+
+	// Cache the Software patch.
+	con_backpic = W_CacheSoftwarePatchNum(piclump, PU_PATCH);
+
+	// Center the backpic, and draw a vertically cropped patch.
+	w = (con_backpic->width * vid.dupx);
+	x = (vid.width / 2) - (w / 2);
+	h = con_curlines/vid.dupy;
+
+	// If the patch doesn't fill the entire screen,
+	// then fill the sides with a solid color.
+	if (x > 0)
+	{
+		column_t *column = (column_t *)((UINT8 *)(con_backpic) + LONG(con_backpic->columnofs[0]));
+		if (!column->topdelta)
+		{
+			UINT8 *source = (UINT8 *)(column) + 3;
+			INT32 color = (source[0] | V_NOSCALESTART);
+			// left side
+			V_DrawFill(0, 0, x, con_curlines, color);
+			// right side
+			V_DrawFill((x + w), 0, (vid.width - w), con_curlines, color);
+		}
+	}
+
+	// Cache the patch normally.
+	con_backpic = W_CachePatchNum(piclump, PU_PATCH);
+	V_DrawCroppedPatch(x << FRACBITS, 0, FRACUNIT, V_NOSCALESTART, con_backpic,
+			0, ( BASEVIDHEIGHT - h ), BASEVIDWIDTH, h);
+
+	// Unlock the cached patch.
+	W_UnlockCachedPatch(con_backpic);
+}
+
 // draw the console background, text, and prompt if enough place
 //
 static void CON_DrawConsole(void)
@@ -1551,19 +1597,7 @@ static void CON_DrawConsole(void)
 
 	// draw console background
 	if (cons_backpic.value || con_forcepic)
-	{
-		patch_t *con_backpic = W_CachePatchName("CONSBACK", PU_PATCH);
-		int h;
-
-		h = con_curlines/vid.dupy;
-
-		// Jimita: CON_DrawBackpic just called V_DrawScaledPatch
-		//V_DrawScaledPatch(0, 0, 0, con_backpic);
-		V_DrawCroppedPatch(0, 0, FRACUNIT, 0, con_backpic,
-				0, ( BASEVIDHEIGHT - h ), BASEVIDWIDTH, h);
-
-		W_UnlockCachedPatch(con_backpic);
-	}
+		CON_DrawBackpic();
 	else
 	{
 		// inu: no more width (was always 0 and vid.width)
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index aa5d4cb9a9e11a51e3eca4fad9a0638b3b33d0a5..ed0b8e528ef1462ada824a9f7764168b18a96b1e 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -85,6 +85,10 @@ tic_t jointimeout = (10*TICRATE);
 static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame?
 static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout?
 
+// Incremented by cv_joindelay when a client joins, decremented each tic.
+// If higher than cv_joindelay * 2 (3 joins in a short timespan), joins are temporarily disabled.
+static tic_t joindelay = 0;
+
 UINT16 pingmeasurecount = 1;
 UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone.
 UINT32 playerpingtable[MAXPLAYERS]; //table of player latency values.
@@ -1338,7 +1342,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 	netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumPlayers();
 	netbuffer->u.serverinfo.maxplayer = (UINT8)cv_maxplayers.value;
 
-	if (FindRejoinerNum(node) != -1)
+	if (!node || FindRejoinerNum(node) != -1)
 		netbuffer->u.serverinfo.refusereason = 0;
 	else if (!cv_allownewplayer.value)
 		netbuffer->u.serverinfo.refusereason = 1;
@@ -1686,7 +1690,7 @@ static void CL_LoadReceivedSavegame(void)
 	// load a base level
 	if (P_LoadNetGame())
 	{
-		const INT32 actnum = mapheaderinfo[gamemap-1]->actnum;
+		const UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
 		CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap));
 		if (strcmp(mapheaderinfo[gamemap-1]->lvlttl, ""))
 		{
@@ -3077,6 +3081,8 @@ consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0,
 consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
 static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
 consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}};
+consvar_t cv_joindelay = {"joindelay", "10", CV_SAVE, joindelay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}};
 consvar_t cv_rejointimeout = {"rejointimeout", "Off", CV_SAVE|CV_FLOAT, rejointimeout_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -3164,6 +3170,8 @@ void SV_ResetServer(void)
 	neededtic = maketic;
 	tictoclear = maketic;
 
+	joindelay = 0;
+
 	for (i = 0; i < MAXNETNODES; i++)
 		ResetNode(i);
 
@@ -3613,6 +3621,9 @@ static void HandleConnect(SINT8 node)
 		SV_SendRefuse(node, M_GetText("No players from\nthis node."));
 	else if (luafiletransfers)
 		SV_SendRefuse(node, M_GetText("The server is broadcasting a file\nrequested by a Lua script.\nPlease wait a bit and then\ntry rejoining."));
+	else if (netgame && joindelay > 2 * (tic_t)cv_joindelay.value * TICRATE)
+		SV_SendRefuse(node, va(M_GetText("Too many people are connecting.\nPlease wait %d seconds and then\ntry rejoining."),
+			(joindelay - 2 * cv_joindelay.value * TICRATE) / TICRATE));
 	else
 	{
 #ifndef NONET
@@ -3670,6 +3681,7 @@ static void HandleConnect(SINT8 node)
 				DEBFILE("send savegame\n");
 			}
 			SV_AddWaitingPlayers(names[0], names[1]);
+			joindelay += cv_joindelay.value * TICRATE;
 			player_joining = true;
 		}
 #else
@@ -4875,7 +4887,8 @@ static inline void PingUpdate(void)
 	{
 		for (i = 1; i < MAXPLAYERS; i++)
 		{
-			if (playeringame[i] && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value))
+			if (playeringame[i] && !players[i].quittime
+			&& (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value))
 			{
 				if (players[i].jointime > 30 * TICRATE)
 					laggers[i] = true;
@@ -4894,8 +4907,8 @@ static inline void PingUpdate(void)
 				if (playeringame[i] && laggers[i])
 				{
 					pingtimeout[i]++;
+					// ok your net has been bad for too long, you deserve to die.
 					if (pingtimeout[i] > cv_pingtimeout.value)
-// ok your net has been bad for too long, you deserve to die.
 					{
 						pingtimeout[i] = 0;
 						SendKick(i, KICK_MSG_PING_HIGH | KICK_MSG_KEEP_BODY);
@@ -5037,12 +5050,21 @@ void NetUpdate(void)
 				hu_resynching = true;
 		}
 	}
+
 	Net_AckTicker();
+
 	// Handle timeouts to prevent definitive freezes from happenning
 	if (server)
+	{
 		for (i = 1; i < MAXNETNODES; i++)
 			if (nodeingame[i] && freezetimeout[i] < I_GetTime())
 				Net_ConnectionTimeout(i);
+
+		// In case the cvar value was lowered
+		if (joindelay)
+			joindelay = min(joindelay - 1, 3 * (tic_t)cv_joindelay.value * TICRATE);
+	}
+
 	nowtime /= NEWTICRATERATIO;
 	if (nowtime > resptime)
 	{
@@ -5050,6 +5072,7 @@ void NetUpdate(void)
 		M_Ticker();
 		CON_Ticker();
 	}
+
 	SV_FileSendTicker();
 }
 
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 30d562bed2e0c39c74b3b9851e89401e5e650c84..463240a2adc87289f8e00f319c7f90df062bf5f2 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -20,12 +20,9 @@
 #include "d_player.h"
 
 /*
-The 'packet version' may be used with packets whose
-format is expected to change between versions.
-
-This version is independent of the mod name, and standard
-version and subversion. It should only account for the
-basic fields of the packet, and change infrequently.
+The 'packet version' is used to distinguish packet formats.
+This version is independent of VERSION and SUBVERSION. Different
+applications may follow different packet versions.
 */
 #define PACKETVERSION 3
 
@@ -518,7 +515,7 @@ extern UINT32 realpingtable[MAXPLAYERS];
 extern UINT32 playerpingtable[MAXPLAYERS];
 extern tic_t servermaxping;
 
-extern consvar_t cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_rejointimeout;
+extern consvar_t cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_joindelay, cv_rejointimeout;
 extern consvar_t cv_resynchattempts, cv_blamecfail;
 extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed;
 
diff --git a/src/d_main.c b/src/d_main.c
index 40e7af22a76137170e8bd60cd0c6014d199fab59..07a7ecf91038f802742077a283c3c1691d545529 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -125,6 +125,8 @@ boolean advancedemo;
 INT32 debugload = 0;
 #endif
 
+char savegamename[256];
+
 char srb2home[256] = ".";
 char srb2path[256] = ".";
 boolean usehome = true;
@@ -310,7 +312,9 @@ static void D_Display(void)
 				F_WipeStartScreen();
 				// Check for Mega Genesis fade
 				wipestyleflags = WSF_FADEOUT;
-				if (F_TryColormapFade(31))
+				if (wipegamestate == (gamestate_t)FORCEWIPE)
+					F_WipeColorFill(31);
+				else if (F_TryColormapFade(31))
 					wipetypepost = -1; // Don't run the fade below this one
 				F_WipeEndScreen();
 				F_RunWipe(wipetypepre, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
@@ -876,6 +880,40 @@ static inline void D_CleanFile(void)
 	}
 }
 
+///\brief Checks if a netgame URL is being handled, and changes working directory to the EXE's if so.
+///       Done because browsers (at least, Firefox on Windows) launch the game from the browser's directory, which causes problems.
+static void ChangeDirForUrlHandler(void)
+{
+	// URL handlers are opened by web browsers (at least Firefox) from the browser's working directory, not the game's stored directory,
+	// so chdir to that directory unless overridden.
+	if (M_GetUrlProtocolArg() != NULL && !M_CheckParm("-nochdir"))
+	{
+		size_t i;
+
+		CONS_Printf("%s connect links load game files from the SRB2 application's stored directory. Switching to ", SERVER_URL_PROTOCOL);
+		strlcpy(srb2path, myargv[0], sizeof(srb2path));
+
+		// Get just the directory, minus the EXE name
+		for (i = strlen(srb2path)-1; i > 0; i--)
+		{
+			if (srb2path[i] == '/' || srb2path[i] == '\\')
+			{
+				srb2path[i] = '\0';
+				break;
+			}
+		}
+
+		CONS_Printf("%s\n", srb2path);
+
+#if defined (_WIN32)
+		SetCurrentDirectoryA(srb2path);
+#else
+		if (chdir(srb2path) == -1)
+			I_OutputMsg("Couldn't change working directory\n");
+#endif
+	}
+}
+
 // ==========================================================================
 // Identify the SRB2 version, and IWAD file to use.
 // ==========================================================================
@@ -957,6 +995,7 @@ static void IdentifyVersion(void)
 		}
 
 		MUSICTEST("music.dta")
+		MUSICTEST("patch_music.pk3")
 #ifdef DEVELOP // remove when music_new.dta is merged into music.dta
 		MUSICTEST("music_new.dta")
 #endif
@@ -1064,6 +1103,9 @@ void D_SRB2Main(void)
 	// Test Dehacked lists
 	DEH_Check();
 
+	// Netgame URL special case: change working dir to EXE folder.
+	ChangeDirForUrlHandler();
+
 	// identify the main IWAD file to use
 	IdentifyVersion();
 
@@ -1149,9 +1191,15 @@ void D_SRB2Main(void)
 	if (M_CheckParm("-password") && M_IsNextParm())
 		D_SetPassword(M_GetNextParm());
 
+	CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n");
+	Z_Init();
+
+	// Do this up here so that WADs loaded through the command line can use ExecCfg
+	COM_Init();
+
 	// add any files specified on the command line with -file wadfile
 	// to the wad list
-	if (!(M_CheckParm("-connect") && !M_CheckParm("-server")))
+	if (!((M_GetUrlProtocolArg() || M_CheckParm("-connect")) && !M_CheckParm("-server")))
 	{
 		if (M_CheckParm("-file"))
 		{
@@ -1176,9 +1224,6 @@ void D_SRB2Main(void)
 	if (M_CheckParm("-server") || dedicated)
 		netgame = server = true;
 
-	CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n");
-	Z_Init();
-
 	// adapt tables to SRB2's needs, including extra slots for dehacked file support
 	P_PatchInfoTables();
 
@@ -1186,7 +1231,7 @@ void D_SRB2Main(void)
 	M_InitMenuPresTables();
 
 	// init title screen display params
-	if (M_CheckParm("-connect"))
+	if (M_GetUrlProtocolArg() || M_CheckParm("-connect"))
 		F_InitMenuPresValues();
 
 	//---------------------------------------------------- READY TIME
@@ -1250,7 +1295,6 @@ void D_SRB2Main(void)
 	CONS_Printf("HU_Init(): Setting up heads up display.\n");
 	HU_Init();
 
-	COM_Init();
 	CON_Init();
 
 	D_RegisterServerCommands();
@@ -1284,11 +1328,10 @@ void D_SRB2Main(void)
 
 		// Set cv_renderer to the new render mode
 		VID_CheckRenderer();
-		SCR_ChangeRendererCVars(setrenderneeded);
+		SCR_ChangeRendererCVars(rendermode);
 
-		// check the renderer's state, and then clear setrenderneeded
+		// check the renderer's state
 		D_CheckRendererState();
-		setrenderneeded = 0;
 	}
 
 	wipegamestate = gamestate;
diff --git a/src/d_net.c b/src/d_net.c
index 1db75f3da13e7bcd357054362cdd49094ac78d45..a6768d75d3d1cfd550a77224ca83dba1dab33632 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -811,6 +811,7 @@ static const char *packettypename[NUMPACKETTYPE] =
 	"CLIENTJOIN",
 	"NODETIMEOUT",
 	"RESYNCHING",
+	"LOGIN",
 	"PING"
 };
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index aaa01c57f8ee7525eb752364641459c65154819c..dfc7351f5808ba8feff8ba0525d72774d4550993 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -573,6 +573,7 @@ void D_RegisterServerCommands(void)
 
 	// d_clisrv
 	CV_RegisterVar(&cv_maxplayers);
+	CV_RegisterVar(&cv_joindelay);
 	CV_RegisterVar(&cv_rejointimeout);
 	CV_RegisterVar(&cv_resynchattempts);
 	CV_RegisterVar(&cv_maxsend);
@@ -1252,7 +1253,7 @@ static void SendNameAndColor(void)
 
 		players[consoleplayer].skincolor = cv_playercolor.value;
 
-		if (players[consoleplayer].mo)
+		if (players[consoleplayer].mo && !players[consoleplayer].powers[pw_dye])
 			players[consoleplayer].mo->color = players[consoleplayer].skincolor;
 
 		if (metalrecording)
@@ -1364,8 +1365,9 @@ static void SendNameAndColor2(void)
 	if (botingame)
 	{
 		players[secondplaya].skincolor = botcolor;
-		if (players[secondplaya].mo)
+		if (players[secondplaya].mo && !players[secondplaya].powers[pw_dye])
 			players[secondplaya].mo->color = players[secondplaya].skincolor;
+
 		SetPlayerSkinByNum(secondplaya, botskin-1);
 		return;
 	}
@@ -1378,7 +1380,7 @@ static void SendNameAndColor2(void)
 
 		// don't use secondarydisplayplayer: the second player must be 1
 		players[secondplaya].skincolor = cv_playercolor2.value;
-		if (players[secondplaya].mo)
+		if (players[secondplaya].mo && !players[secondplaya].powers[pw_dye])
 			players[secondplaya].mo->color = players[secondplaya].skincolor;
 
 		if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player
diff --git a/src/d_player.h b/src/d_player.h
index 209ff766d6732715ad3cb1d5d27123310561ef21..e5c7e72980d644dc926ca8e792f1136a54fd3b16 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -48,6 +48,7 @@ typedef enum
 	SF_FASTEDGE         = 1<<12, // Faster edge teeter?
 	SF_MULTIABILITY     = 1<<13, // Revenge of Final Demo.
 	SF_NONIGHTSROTATION = 1<<14, // Disable sprite rotation for NiGHTS
+	SF_NONIGHTSSUPER    = 1<<15, // Disable super colors for NiGHTS (if you have SF_SUPER)
 	// free up to and including 1<<31
 } skinflags_t;
 
@@ -238,7 +239,8 @@ typedef enum
 	CR_MACESPIN,
 	CR_MINECART,
 	CR_ROLLOUT,
-	CR_PTERABYTE
+	CR_PTERABYTE,
+	CR_DUSTDEVIL
 } carrytype_t; // pw_carry
 
 // Player powers. (don't edit this comment)
@@ -278,6 +280,9 @@ typedef enum
 	pw_nights_linkfreeze,
 
 	pw_nocontrol, //for linedef exec 427
+
+	pw_dye, // for dyes
+
 	pw_justlaunched, // Launched off a slope this tic (0=none, 1=standard launch, 2=half-pipe launch)
 
 	NUMPOWERS
diff --git a/src/dehacked.c b/src/dehacked.c
index c6cd0b9e5e3a9f8fb5e90a18e85b92de4b1efc2d..d78a0d6c65f3a55a14240bac697ae240b10debaf 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1557,7 +1557,7 @@ static void readlevelheader(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "ACT"))
 			{
-				if (i >= 0 && i < 20) // 0 for no act number, TTL1 through TTL19
+				if (i >= 0 && i <= 99) // 0 for no act number
 					mapheaderinfo[num-1]->actnum = (UINT8)i;
 				else
 					deh_warning("Level header %d: invalid act number %d", num, i);
@@ -1863,6 +1863,12 @@ static void readlevelheader(MYFILE *f, INT32 num)
 			}
 			else if (fastcmp(word, "STARTRINGS"))
 				mapheaderinfo[num-1]->startrings = (UINT16)i;
+			else if (fastcmp(word, "SPECIALSTAGETIME"))
+				mapheaderinfo[num-1]->sstimer = i;
+			else if (fastcmp(word, "SPECIALSTAGESPHERES"))
+				mapheaderinfo[num-1]->ssspheres = i;
+			else if (fastcmp(word, "GRAVITY"))
+				mapheaderinfo[num-1]->gravity = FLOAT_TO_FIXED(atof(word2));
 			else
 				deh_warning("Level header %d: unknown word '%s'", num, word);
 		}
@@ -2807,7 +2813,7 @@ static actionpointer_t actionpointers[] =
 	{{A_ThrownRing},             "A_THROWNRING"},
 	{{A_SetSolidSteam},          "A_SETSOLIDSTEAM"},
 	{{A_UnsetSolidSteam},        "A_UNSETSOLIDSTEAM"},
-	{{A_SignSpin},               "S_SIGNSPIN"},
+	{{A_SignSpin},               "A_SIGNSPIN"},
 	{{A_SignPlayer},             "A_SIGNPLAYER"},
 	{{A_OverlayThink},           "A_OVERLAYTHINK"},
 	{{A_JetChase},               "A_JETCHASE"},
@@ -2909,6 +2915,7 @@ static actionpointer_t actionpointers[] =
 	{{A_SetRandomTics},          "A_SETRANDOMTICS"},
 	{{A_ChangeColorRelative},    "A_CHANGECOLORRELATIVE"},
 	{{A_ChangeColorAbsolute},    "A_CHANGECOLORABSOLUTE"},
+	{{A_Dye},                    "A_DYE"},
 	{{A_MoveRelative},           "A_MOVERELATIVE"},
 	{{A_MoveAbsolute},           "A_MOVEABSOLUTE"},
 	{{A_Thrust},                 "A_THRUST"},
@@ -3026,6 +3033,7 @@ static actionpointer_t actionpointers[] =
 	{{A_DragonbomberSpawn},      "A_DRAGONBOMERSPAWN"},
 	{{A_DragonWing},             "A_DRAGONWING"},
 	{{A_DragonSegment},          "A_DRAGONSEGMENT"},
+	{{A_ChangeHeight},           "A_CHANGEHEIGHT"},
 	{{NULL},                     "NONE"},
 
 	// This NULL entry must be the last in the list
@@ -3880,7 +3888,26 @@ static void readmaincfg(MYFILE *f)
 			value = atoi(word2); // used for numerical settings
 
 			if (fastcmp(word, "EXECCFG"))
-				COM_BufAddText(va("exec %s\n", word2));
+			{
+				if (strchr(word2, '.'))
+					COM_BufAddText(va("exec %s\n", word2));
+				else
+				{
+					lumpnum_t lumpnum;
+					char newname[9];
+
+					strncpy(newname, word2, 8);
+
+					newname[8] = '\0';
+
+					lumpnum = W_CheckNumForName(newname);
+
+					if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0)
+						CONS_Debug(DBG_SETUP, "SOC Error: script lump %s not found/not valid.\n", newname);
+					else
+						COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
+				}
+			}
 
 			else if (fastcmp(word, "SPSTAGE_START"))
 			{
@@ -4122,6 +4149,10 @@ static void readmaincfg(MYFILE *f)
 			{
 				maxXtraLife = (UINT8)get_number(word2);
 			}
+			else if (fastcmp(word, "USECONTINUES"))
+			{
+				useContinues = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
+			}
 
 			else if (fastcmp(word, "GAMEDATA"))
 			{
@@ -6201,6 +6232,14 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ROCKET",
 
 	"S_LASER",
+	"S_LASER2",
+	"S_LASERFLASH",
+
+	"S_LASERFLAME1",
+	"S_LASERFLAME2",
+	"S_LASERFLAME3",
+	"S_LASERFLAME4",
+	"S_LASERFLAME5",
 
 	"S_TORPEDO",
 
@@ -9132,7 +9171,11 @@ static const char *const POWERS_LIST[] = {
 
 	//for linedef exec 427
 	"NOCONTROL",
-	"JUSTLAUNCHED",
+
+	//for dyes
+	"DYE",
+
+	"JUSTLAUNCHED"
 };
 
 static const char *const HUDITEMS_LIST[] = {
@@ -9196,6 +9239,7 @@ static const char *const MENUTYPES_LIST[] = {
 	"MP_CONNECT",
 	"MP_ROOM",
 	"MP_PLAYERSETUP", // MP_PlayerSetupDef shared with SPLITSCREEN if #defined NONET
+	"MP_SERVER_OPTIONS",
 
 	// Options
 	"OP_MAIN",
@@ -9205,10 +9249,14 @@ static const char *const MENUTYPES_LIST[] = {
 	"OP_P1MOUSE",
 	"OP_P1JOYSTICK",
 	"OP_JOYSTICKSET", // OP_JoystickSetDef shared with P2
+	"OP_P1CAMERA",
 
 	"OP_P2CONTROLS",
 	"OP_P2MOUSE",
 	"OP_P2JOYSTICK",
+	"OP_P2CAMERA",
+
+	"OP_PLAYSTYLE",
 
 	"OP_VIDEO",
 	"OP_VIDEOMODE",
@@ -9440,6 +9488,7 @@ struct {
 	{"CR_MINECART",CR_MINECART},
 	{"CR_ROLLOUT",CR_ROLLOUT},
 	{"CR_PTERABYTE",CR_PTERABYTE},
+	{"CR_DUSTDEVIL",CR_DUSTDEVIL},
 
 	// Ring weapons (ringweapons_t)
 	// Useful for A_GiveWeapon
@@ -9467,6 +9516,7 @@ struct {
 	{"SF_FASTEDGE",SF_FASTEDGE},
 	{"SF_MULTIABILITY",SF_MULTIABILITY},
 	{"SF_NONIGHTSROTATION",SF_NONIGHTSROTATION},
+	{"SF_NONIGHTSSUPER",SF_NONIGHTSSUPER},
 
 	// Dashmode constants
 	{"DASHMODE_THRESHOLD",DASHMODE_THRESHOLD},
@@ -9648,11 +9698,11 @@ struct {
 	{"FF_CUTEXTRA",FF_CUTEXTRA},               ///< Cuts out hidden translucent pixels.
 	{"FF_CUTLEVEL",FF_CUTLEVEL},               ///< Cuts out all hidden pixels.
 	{"FF_CUTSPRITES",FF_CUTSPRITES},           ///< Final step in making 3D water.
-	{"FF_BOTHPLANES",FF_BOTHPLANES},           ///< Renders both planes all the time.
+	{"FF_BOTHPLANES",FF_BOTHPLANES},           ///< Render inside and outside planes.
 	{"FF_EXTRA",FF_EXTRA},                     ///< Gets cut by ::FF_CUTEXTRA.
 	{"FF_TRANSLUCENT",FF_TRANSLUCENT},         ///< See through!
 	{"FF_FOG",FF_FOG},                         ///< Fog "brush."
-	{"FF_INVERTPLANES",FF_INVERTPLANES},       ///< Reverse the plane visibility rules.
+	{"FF_INVERTPLANES",FF_INVERTPLANES},       ///< Only render inside planes.
 	{"FF_ALLSIDES",FF_ALLSIDES},               ///< Render inside and outside sides.
 	{"FF_INVERTSIDES",FF_INVERTSIDES},         ///< Only render inside sides.
 	{"FF_DOUBLESHADOW",FF_DOUBLESHADOW},       ///< Make two lightlist entries to reset light?
diff --git a/src/djgppdos/i_video.c b/src/djgppdos/i_video.c
index 02c7a842bc2c0aa77fb7d8d7180a04d9029878b5..f525b96ca6b19475148b8c22b213e21e88b3c8b0 100644
--- a/src/djgppdos/i_video.c
+++ b/src/djgppdos/i_video.c
@@ -339,7 +339,4 @@ void I_StartupGraphics(void)
 
 }
 
-void I_StartupHardwareGraphics(void)
-{
-	// oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo y
-}
+void VID_StartupOpenGL(void) {}
diff --git a/src/djgppdos/vid_vesa.c b/src/djgppdos/vid_vesa.c
index c8ce7dae52e645372455b76cbad40ffd03c53a0e..61ed18e4b6858eb8a9374b7edfe957f06975cce0 100644
--- a/src/djgppdos/vid_vesa.c
+++ b/src/djgppdos/vid_vesa.c
@@ -378,10 +378,8 @@ INT32 VID_SetMode (INT32 modenum)  //, UINT8 *palette)
 	return 1;
 }
 
-void VID_CheckRenderer(void)
-{
-	// ..............
-}
+void VID_CheckRenderer(void) {}
+void VID_CheckGLLoaded(rendermode_t oldrender) {}
 
 
 
diff --git a/src/doomdef.h b/src/doomdef.h
index 358d0d82c51d0860209aa690df4ac6daea3037fb..74086ef5d3a850d11060784960930b5154fb83ef 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -143,13 +143,16 @@ extern char logfilename[1024];
 // we use comprevision and compbranch instead.
 #else
 #define VERSION    202 // Game version
-#define SUBVERSION 2  // more precise version number
-#define VERSIONSTRING "v2.2.2"
-#define VERSIONSTRINGW L"v2.2.2"
+#define SUBVERSION 4  // more precise version number
+#define VERSIONSTRING "v2.2.4"
+#define VERSIONSTRINGW L"v2.2.4"
 // Hey! If you change this, add 1 to the MODVERSION below!
 // Otherwise we can't force updates!
 #endif
 
+/* A custom URL protocol for server links. */
+#define SERVER_URL_PROTOCOL "srb2://"
+
 // Does this version require an added patch file?
 // Comment or uncomment this as necessary.
 #define USE_PATCH_DTA
@@ -210,7 +213,7 @@ extern char logfilename[1024];
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.2.0 is not version "1".
-#define MODVERSION 42
+#define MODVERSION 44
 
 // To version config.cfg, MAJOREXECVERSION is set equal to MODVERSION automatically.
 // Increment MINOREXECVERSION whenever a config change is needed that does not correspond
@@ -455,7 +458,7 @@ void CONS_Debug(INT32 debugflags, const char *fmt, ...) FUNCDEBUG;
 
 // Things that used to be in dstrings.h
 #define SAVEGAMENAME "srb2sav"
-char savegamename[256];
+extern char savegamename[256];
 
 // m_misc.h
 #ifdef GETTEXT
@@ -571,9 +574,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 ///	Dumps the contents of a network save game upon consistency failure for debugging.
 //#define DUMPCONSISTENCY
 
-///	Polyobject fake flat code
-#define POLYOBJECTS_PLANES
-
 ///	See name of player in your crosshair
 #define SEENAMES
 
diff --git a/src/doomstat.h b/src/doomstat.h
index 893514b328be964abf78b3a545e088609967368a..0c2f1e975144d8ef9464afd57692539a56df4a3e 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -319,6 +319,9 @@ typedef struct
 
 	char selectheading[22]; ///< Level select heading. Allows for controllable grouping.
 	UINT16 startrings;      ///< Number of rings players start with.
+	INT32 sstimer;          ///< Timer for special stages.
+	UINT32 ssspheres;       ///< Sphere requirement in special stages.
+	fixed_t gravity;        ///< Map-wide gravity.
 
 	// Title card.
 	char ltzzpatch[8];      ///< Zig zag patch.
@@ -542,7 +545,7 @@ extern recorddata_t *mainrecords[NUMMAPS];
 extern UINT8 mapvisited[NUMMAPS];
 
 // Temporary holding place for nights data for the current map
-nightsdata_t ntemprecords;
+extern nightsdata_t ntemprecords;
 
 extern UINT32 token; ///< Number of tokens collected in a level
 extern UINT32 tokenlist; ///< List of tokens collected
@@ -575,6 +578,8 @@ extern UINT8 creditscutscene;
 
 extern UINT8 use1upSound;
 extern UINT8 maxXtraLife; // Max extra lives from rings
+extern UINT8 useContinues;
+#define continuesInSession (!multiplayer && (useContinues || ultimatemode || !(cursaveslot > 0)))
 
 extern mobj_t *hunt1, *hunt2, *hunt3; // Emerald hunt locations
 
diff --git a/src/dummy/i_video.c b/src/dummy/i_video.c
index fafeee0001400ef868b6c8d91042870c6c2e3773..56ead3672ae8ded9510814b20c766061a5d1d668 100644
--- a/src/dummy/i_video.c
+++ b/src/dummy/i_video.c
@@ -11,10 +11,10 @@ boolean allow_fullscreen = false;
 consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 void I_StartupGraphics(void){}
-void I_StartupHardwareGraphics(void){}
-
 void I_ShutdownGraphics(void){}
 
+void VID_StartupOpenGL(void){}
+
 void I_SetPalette(RGBA_t *palette)
 {
 	(void)palette;
@@ -40,10 +40,8 @@ INT32 VID_SetMode(INT32 modenum)
 	return 0;
 }
 
-void VID_CheckRenderer(void)
-{
-	// ..............
-}
+void VID_CheckRenderer(void) {}
+void VID_CheckGLLoaded(rendermode_t oldrender) {}
 
 const char *VID_GetModeName(INT32 modenum)
 {
diff --git a/src/f_finale.c b/src/f_finale.c
index 95535a7ea3b09becf96e6de832ea6f7f004edbf7..825f646b04755cbdce6222c2527dc7aed96143df 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -3618,7 +3618,7 @@ void F_StartContinue(void)
 {
 	I_Assert(!netgame && !multiplayer);
 
-	if (players[consoleplayer].continues <= 0)
+	if (continuesInSession && players[consoleplayer].continues <= 0)
 	{
 		Command_ExitGame_f();
 		return;
@@ -3725,7 +3725,9 @@ void F_ContinueDrawer(void)
 	}
 
 	// Draw the continue markers! Show continues.
-	if (ncontinues > 10)
+	if (!continuesInSession)
+		;
+	else if (ncontinues > 10)
 	{
 		if (!(continuetime & 1) || continuetime > 17)
 			V_DrawContinueIcon(x, 68, 0, players[consoleplayer].skin, players[consoleplayer].skincolor);
diff --git a/src/g_demo.c b/src/g_demo.c
new file mode 100644
index 0000000000000000000000000000000000000000..a901e8dea9b135f093940f3aa6d68896ffb592a8
--- /dev/null
+++ b/src/g_demo.c
@@ -0,0 +1,2505 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  g_demo.c
+/// \brief Demo recording and playback
+
+#include "doomdef.h"
+#include "console.h"
+#include "d_main.h"
+#include "d_player.h"
+#include "d_clisrv.h"
+#include "p_setup.h"
+#include "i_system.h"
+#include "m_random.h"
+#include "p_local.h"
+#include "r_draw.h"
+#include "r_main.h"
+#include "g_game.h"
+#include "g_demo.h"
+#include "m_misc.h"
+#include "m_menu.h"
+#include "m_argv.h"
+#include "hu_stuff.h"
+#include "z_zone.h"
+#include "i_video.h"
+#include "byteptr.h"
+#include "i_joy.h"
+#include "r_local.h"
+#include "r_skins.h"
+#include "y_inter.h"
+#include "v_video.h"
+#include "lua_hook.h"
+#include "md5.h" // demo checksums
+
+boolean timingdemo; // if true, exit with report on completion
+boolean nodrawers; // for comparative timing purposes
+boolean noblit; // for comparative timing purposes
+tic_t demostarttime; // for comparative timing purposes
+
+static char demoname[64];
+boolean demorecording;
+boolean demoplayback;
+boolean titledemo; // Title Screen demo can be cancelled by any key
+static UINT8 *demobuffer = NULL;
+static UINT8 *demo_p, *demotime_p;
+static UINT8 *demoend;
+static UINT8 demoflags;
+static UINT16 demoversion;
+boolean singledemo; // quit after playing a demo from cmdline
+boolean demo_start; // don't start playing demo right away
+boolean demosynced = true; // console warning message
+
+boolean metalrecording; // recording as metal sonic
+mobj_t *metalplayback;
+static UINT8 *metalbuffer = NULL;
+static UINT8 *metal_p;
+static UINT16 metalversion;
+
+// extra data stuff (events registered this frame while recording)
+static struct {
+	UINT8 flags; // EZT flags
+
+	// EZT_COLOR
+	UINT8 color, lastcolor;
+
+	// EZT_SCALE
+	fixed_t scale, lastscale;
+
+	// EZT_HIT
+	UINT16 hits;
+	mobj_t **hitlist;
+} ghostext;
+
+// Your naming conventions are stupid and useless.
+// There is no conflict here.
+typedef struct demoghost {
+	UINT8 checksum[16];
+	UINT8 *buffer, *p, color, fadein;
+	UINT16 version;
+	mobj_t oldmo, *mo;
+	struct demoghost *next;
+} demoghost;
+demoghost *ghosts = NULL;
+
+//
+// DEMO RECORDING
+//
+
+#define DEMOVERSION 0x000c
+#define DEMOHEADER  "\xF0" "SRB2Replay" "\x0F"
+
+#define DF_GHOST        0x01 // This demo contains ghost data too!
+#define DF_RECORDATTACK 0x02 // This demo is from record attack and contains its final completion time, score, and rings!
+#define DF_NIGHTSATTACK 0x04 // This demo is from NiGHTS attack and contains its time left, score, and mares!
+#define DF_ATTACKMASK   0x06 // This demo is from ??? attack and contains ???
+#define DF_ATTACKSHIFT  1
+
+// For demos
+#define ZT_FWD     0x01
+#define ZT_SIDE    0x02
+#define ZT_ANGLE   0x04
+#define ZT_BUTTONS 0x08
+#define ZT_AIMING  0x10
+#define DEMOMARKER 0x80 // demoend
+#define METALDEATH 0x44
+#define METALSNICE 0x69
+
+static ticcmd_t oldcmd;
+
+// For Metal Sonic and time attack ghosts
+#define GZT_XYZ    0x01
+#define GZT_MOMXY  0x02
+#define GZT_MOMZ   0x04
+#define GZT_ANGLE  0x08
+#define GZT_FRAME  0x10 // Animation frame
+#define GZT_SPR2   0x20 // Player animations
+#define GZT_EXTRA  0x40
+#define GZT_FOLLOW 0x80 // Followmobj
+
+// GZT_EXTRA flags
+#define EZT_THOK   0x01 // Spawned a thok object
+#define EZT_SPIN   0x02 // Because one type of thok object apparently wasn't enough
+#define EZT_REV    0x03 // And two types wasn't enough either yet
+#define EZT_THOKMASK 0x03
+#define EZT_COLOR  0x04 // Changed color (Super transformation, Mario fireflowers/invulnerability, etc.)
+#define EZT_FLIP   0x08 // Reversed gravity
+#define EZT_SCALE  0x10 // Changed size
+#define EZT_HIT    0x20 // Damaged a mobj
+#define EZT_SPRITE 0x40 // Changed sprite set completely out of PLAY (NiGHTS, SOCs, whatever)
+#define EZT_HEIGHT 0x80 // Changed height
+
+// GZT_FOLLOW flags
+#define FZT_SPAWNED 0x01 // just been spawned
+#define FZT_SKIN 0x02 // has skin
+#define FZT_LINKDRAW 0x04 // has linkdraw (combine with spawned only)
+#define FZT_COLORIZED 0x08 // colorized (ditto)
+#define FZT_SCALE 0x10 // different scale to object
+// spare FZT slots 0x20 to 0x80
+
+static mobj_t oldmetal, oldghost;
+
+void G_SaveMetal(UINT8 **buffer)
+{
+	I_Assert(buffer != NULL && *buffer != NULL);
+
+	WRITEUINT32(*buffer, metal_p - metalbuffer);
+}
+
+void G_LoadMetal(UINT8 **buffer)
+{
+	I_Assert(buffer != NULL && *buffer != NULL);
+
+	G_DoPlayMetal();
+	metal_p = metalbuffer + READUINT32(*buffer);
+}
+
+
+void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum)
+{
+	UINT8 ziptic;
+	(void)playernum;
+
+	if (!demo_p || !demo_start)
+		return;
+	ziptic = READUINT8(demo_p);
+
+	if (ziptic & ZT_FWD)
+		oldcmd.forwardmove = READSINT8(demo_p);
+	if (ziptic & ZT_SIDE)
+		oldcmd.sidemove = READSINT8(demo_p);
+	if (ziptic & ZT_ANGLE)
+		oldcmd.angleturn = READINT16(demo_p);
+	if (ziptic & ZT_BUTTONS)
+		oldcmd.buttons = (oldcmd.buttons & (BT_CAMLEFT|BT_CAMRIGHT)) | (READUINT16(demo_p) & ~(BT_CAMLEFT|BT_CAMRIGHT));
+	if (ziptic & ZT_AIMING)
+		oldcmd.aiming = READINT16(demo_p);
+
+	G_CopyTiccmd(cmd, &oldcmd, 1);
+
+	if (!(demoflags & DF_GHOST) && *demo_p == DEMOMARKER)
+	{
+		// end of demo data stream
+		G_CheckDemoStatus();
+		return;
+	}
+}
+
+void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum)
+{
+	char ziptic = 0;
+	UINT8 *ziptic_p;
+	(void)playernum;
+
+	if (!demo_p)
+		return;
+	ziptic_p = demo_p++; // the ziptic, written at the end of this function
+
+	if (cmd->forwardmove != oldcmd.forwardmove)
+	{
+		WRITEUINT8(demo_p,cmd->forwardmove);
+		oldcmd.forwardmove = cmd->forwardmove;
+		ziptic |= ZT_FWD;
+	}
+
+	if (cmd->sidemove != oldcmd.sidemove)
+	{
+		WRITEUINT8(demo_p,cmd->sidemove);
+		oldcmd.sidemove = cmd->sidemove;
+		ziptic |= ZT_SIDE;
+	}
+
+	if (cmd->angleturn != oldcmd.angleturn)
+	{
+		WRITEINT16(demo_p,cmd->angleturn);
+		oldcmd.angleturn = cmd->angleturn;
+		ziptic |= ZT_ANGLE;
+	}
+
+	if (cmd->buttons != oldcmd.buttons)
+	{
+		WRITEUINT16(demo_p,cmd->buttons);
+		oldcmd.buttons = cmd->buttons;
+		ziptic |= ZT_BUTTONS;
+	}
+
+	if (cmd->aiming != oldcmd.aiming)
+	{
+		WRITEINT16(demo_p,cmd->aiming);
+		oldcmd.aiming = cmd->aiming;
+		ziptic |= ZT_AIMING;
+	}
+
+	*ziptic_p = ziptic;
+
+	// attention here for the ticcmd size!
+	// latest demos with mouse aiming byte in ticcmd
+	if (!(demoflags & DF_GHOST) && ziptic_p > demoend - 9)
+	{
+		G_CheckDemoStatus(); // no more space
+		return;
+	}
+}
+
+void G_GhostAddThok(void)
+{
+	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
+		return;
+	ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_THOK;
+}
+
+void G_GhostAddSpin(void)
+{
+	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
+		return;
+	ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_SPIN;
+}
+
+void G_GhostAddRev(void)
+{
+	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
+		return;
+	ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_REV;
+}
+
+void G_GhostAddFlip(void)
+{
+	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
+		return;
+	ghostext.flags |= EZT_FLIP;
+}
+
+void G_GhostAddColor(ghostcolor_t color)
+{
+	if (!demorecording || !(demoflags & DF_GHOST))
+		return;
+	if (ghostext.lastcolor == (UINT8)color)
+	{
+		ghostext.flags &= ~EZT_COLOR;
+		return;
+	}
+	ghostext.flags |= EZT_COLOR;
+	ghostext.color = (UINT8)color;
+}
+
+void G_GhostAddScale(fixed_t scale)
+{
+	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
+		return;
+	if (ghostext.lastscale == scale)
+	{
+		ghostext.flags &= ~EZT_SCALE;
+		return;
+	}
+	ghostext.flags |= EZT_SCALE;
+	ghostext.scale = scale;
+}
+
+void G_GhostAddHit(mobj_t *victim)
+{
+	if (!demorecording || !(demoflags & DF_GHOST))
+		return;
+	ghostext.flags |= EZT_HIT;
+	ghostext.hits++;
+	ghostext.hitlist = Z_Realloc(ghostext.hitlist, ghostext.hits * sizeof(mobj_t *), PU_LEVEL, NULL);
+	ghostext.hitlist[ghostext.hits-1] = victim;
+}
+
+void G_WriteGhostTic(mobj_t *ghost)
+{
+	char ziptic = 0;
+	UINT8 *ziptic_p;
+	UINT32 i;
+	fixed_t height;
+
+	if (!demo_p)
+		return;
+	if (!(demoflags & DF_GHOST))
+		return; // No ghost data to write.
+
+	ziptic_p = demo_p++; // the ziptic, written at the end of this function
+
+	#define MAXMOM (0xFFFF<<8)
+
+	// GZT_XYZ is only useful if you've moved 256 FRACUNITS or more in a single tic.
+	if (abs(ghost->x-oldghost.x) > MAXMOM
+	|| abs(ghost->y-oldghost.y) > MAXMOM
+	|| abs(ghost->z-oldghost.z) > MAXMOM)
+	{
+		oldghost.x = ghost->x;
+		oldghost.y = ghost->y;
+		oldghost.z = ghost->z;
+		ziptic |= GZT_XYZ;
+		WRITEFIXED(demo_p,oldghost.x);
+		WRITEFIXED(demo_p,oldghost.y);
+		WRITEFIXED(demo_p,oldghost.z);
+	}
+	else
+	{
+		// For moving normally:
+		// Store one full byte of movement, plus one byte of fractional movement.
+		INT16 momx = (INT16)((ghost->x-oldghost.x)>>8);
+		INT16 momy = (INT16)((ghost->y-oldghost.y)>>8);
+		if (momx != oldghost.momx
+		|| momy != oldghost.momy)
+		{
+			oldghost.momx = momx;
+			oldghost.momy = momy;
+			ziptic |= GZT_MOMXY;
+			WRITEINT16(demo_p,momx);
+			WRITEINT16(demo_p,momy);
+		}
+		momx = (INT16)((ghost->z-oldghost.z)>>8);
+		if (momx != oldghost.momz)
+		{
+			oldghost.momz = momx;
+			ziptic |= GZT_MOMZ;
+			WRITEINT16(demo_p,momx);
+		}
+
+		// This SHOULD set oldghost.x/y/z to match ghost->x/y/z
+		// but it keeps the fractional loss of one byte,
+		// so it will hopefully be made up for in future tics.
+		oldghost.x += oldghost.momx<<8;
+		oldghost.y += oldghost.momy<<8;
+		oldghost.z += oldghost.momz<<8;
+	}
+
+	#undef MAXMOM
+
+	// Only store the 8 most relevant bits of angle
+	// because exact values aren't too easy to discern to begin with when only 8 angles have different sprites
+	// and it does not affect this mode of movement at all anyway.
+	if (ghost->player && ghost->player->drawangle>>24 != oldghost.angle)
+	{
+		oldghost.angle = ghost->player->drawangle>>24;
+		ziptic |= GZT_ANGLE;
+		WRITEUINT8(demo_p,oldghost.angle);
+	}
+
+	// Store the sprite frame.
+	if ((ghost->frame & FF_FRAMEMASK) != oldghost.frame)
+	{
+		oldghost.frame = (ghost->frame & FF_FRAMEMASK);
+		ziptic |= GZT_FRAME;
+		WRITEUINT8(demo_p,oldghost.frame);
+	}
+
+	if (ghost->sprite == SPR_PLAY
+	&& ghost->sprite2 != oldghost.sprite2)
+	{
+		oldghost.sprite2 = ghost->sprite2;
+		ziptic |= GZT_SPR2;
+		WRITEUINT8(demo_p,oldghost.sprite2);
+	}
+
+	// Check for sprite set changes
+	if (ghost->sprite != oldghost.sprite)
+	{
+		oldghost.sprite = ghost->sprite;
+		ghostext.flags |= EZT_SPRITE;
+	}
+
+	if ((height = FixedDiv(ghost->height, ghost->scale)) != oldghost.height)
+	{
+		oldghost.height = height;
+		ghostext.flags |= EZT_HEIGHT;
+	}
+
+	if (ghostext.flags)
+	{
+		ziptic |= GZT_EXTRA;
+
+		if (ghostext.color == ghostext.lastcolor)
+			ghostext.flags &= ~EZT_COLOR;
+		if (ghostext.scale == ghostext.lastscale)
+			ghostext.flags &= ~EZT_SCALE;
+
+		WRITEUINT8(demo_p,ghostext.flags);
+		if (ghostext.flags & EZT_COLOR)
+		{
+			WRITEUINT8(demo_p,ghostext.color);
+			ghostext.lastcolor = ghostext.color;
+		}
+		if (ghostext.flags & EZT_SCALE)
+		{
+			WRITEFIXED(demo_p,ghostext.scale);
+			ghostext.lastscale = ghostext.scale;
+		}
+		if (ghostext.flags & EZT_HIT)
+		{
+			WRITEUINT16(demo_p,ghostext.hits);
+			for (i = 0; i < ghostext.hits; i++)
+			{
+				mobj_t *mo = ghostext.hitlist[i];
+				//WRITEUINT32(demo_p,UINT32_MAX); // reserved for some method of determining exactly which mobj this is. (mobjnum doesn't work here.)
+				WRITEUINT32(demo_p,mo->type);
+				WRITEUINT16(demo_p,(UINT16)mo->health);
+				WRITEFIXED(demo_p,mo->x);
+				WRITEFIXED(demo_p,mo->y);
+				WRITEFIXED(demo_p,mo->z);
+				WRITEANGLE(demo_p,mo->angle);
+			}
+			Z_Free(ghostext.hitlist);
+			ghostext.hits = 0;
+			ghostext.hitlist = NULL;
+		}
+		if (ghostext.flags & EZT_SPRITE)
+			WRITEUINT16(demo_p,oldghost.sprite);
+		if (ghostext.flags & EZT_HEIGHT)
+		{
+			height >>= FRACBITS;
+			WRITEINT16(demo_p, height);
+		}
+		ghostext.flags = 0;
+	}
+
+	if (ghost->player && ghost->player->followmobj && !(ghost->player->followmobj->sprite == SPR_NULL || (ghost->player->followmobj->flags2 & MF2_DONTDRAW))) // bloats tails runs but what can ya do
+	{
+		INT16 temp;
+		UINT8 *followtic_p = demo_p++;
+		UINT8 followtic = 0;
+
+		ziptic |= GZT_FOLLOW;
+
+		if (ghost->player->followmobj->skin)
+			followtic |= FZT_SKIN;
+
+		if (!(oldghost.flags2 & MF2_AMBUSH))
+		{
+			followtic |= FZT_SPAWNED;
+			WRITEINT16(demo_p,ghost->player->followmobj->info->height>>FRACBITS);
+			if (ghost->player->followmobj->flags2 & MF2_LINKDRAW)
+				followtic |= FZT_LINKDRAW;
+			if (ghost->player->followmobj->colorized)
+				followtic |= FZT_COLORIZED;
+			if (followtic & FZT_SKIN)
+				WRITEUINT8(demo_p,(UINT8)(((skin_t *)(ghost->player->followmobj->skin))-skins));
+			oldghost.flags2 |= MF2_AMBUSH;
+		}
+
+		if (ghost->player->followmobj->scale != ghost->scale)
+		{
+			followtic |= FZT_SCALE;
+			WRITEFIXED(demo_p,ghost->player->followmobj->scale);
+		}
+
+		temp = (INT16)((ghost->player->followmobj->x-ghost->x)>>8);
+		WRITEINT16(demo_p,temp);
+		temp = (INT16)((ghost->player->followmobj->y-ghost->y)>>8);
+		WRITEINT16(demo_p,temp);
+		temp = (INT16)((ghost->player->followmobj->z-ghost->z)>>8);
+		WRITEINT16(demo_p,temp);
+		if (followtic & FZT_SKIN)
+			WRITEUINT8(demo_p,ghost->player->followmobj->sprite2);
+		WRITEUINT16(demo_p,ghost->player->followmobj->sprite);
+		WRITEUINT8(demo_p,(ghost->player->followmobj->frame & FF_FRAMEMASK));
+		WRITEUINT8(demo_p,ghost->player->followmobj->color);
+
+		*followtic_p = followtic;
+	}
+	else
+		oldghost.flags2 &= ~MF2_AMBUSH;
+
+	*ziptic_p = ziptic;
+
+	// attention here for the ticcmd size!
+	// latest demos with mouse aiming byte in ticcmd
+	if (demo_p >= demoend - (13 + 9 + 9))
+	{
+		G_CheckDemoStatus(); // no more space
+		return;
+	}
+}
+
+// Uses ghost data to do consistency checks on your position.
+// This fixes desynchronising demos when fighting eggman.
+void G_ConsGhostTic(void)
+{
+	UINT8 ziptic;
+	UINT16 px,py,pz,gx,gy,gz;
+	mobj_t *testmo;
+
+	if (!demo_p || !demo_start)
+		return;
+	if (!(demoflags & DF_GHOST))
+		return; // No ghost data to use.
+
+	testmo = players[0].mo;
+
+	// Grab ghost data.
+	ziptic = READUINT8(demo_p);
+	if (ziptic & GZT_XYZ)
+	{
+		oldghost.x = READFIXED(demo_p);
+		oldghost.y = READFIXED(demo_p);
+		oldghost.z = READFIXED(demo_p);
+	}
+	else
+	{
+		if (ziptic & GZT_MOMXY)
+		{
+			oldghost.momx = READINT16(demo_p)<<8;
+			oldghost.momy = READINT16(demo_p)<<8;
+		}
+		if (ziptic & GZT_MOMZ)
+			oldghost.momz = READINT16(demo_p)<<8;
+		oldghost.x += oldghost.momx;
+		oldghost.y += oldghost.momy;
+		oldghost.z += oldghost.momz;
+	}
+	if (ziptic & GZT_ANGLE)
+		demo_p++;
+	if (ziptic & GZT_FRAME)
+		demo_p++;
+	if (ziptic & GZT_SPR2)
+		demo_p++;
+
+	if (ziptic & GZT_EXTRA)
+	{ // But wait, there's more!
+		UINT8 xziptic = READUINT8(demo_p);
+		if (xziptic & EZT_COLOR)
+			demo_p++;
+		if (xziptic & EZT_SCALE)
+			demo_p += sizeof(fixed_t);
+		if (xziptic & EZT_HIT)
+		{ // Resync mob damage.
+			UINT16 i, count = READUINT16(demo_p);
+			thinker_t *th;
+			mobj_t *mobj;
+
+			UINT32 type;
+			UINT16 health;
+			fixed_t x;
+			fixed_t y;
+			fixed_t z;
+
+			for (i = 0; i < count; i++)
+			{
+				//demo_p += 4; // reserved.
+				type = READUINT32(demo_p);
+				health = READUINT16(demo_p);
+				x = READFIXED(demo_p);
+				y = READFIXED(demo_p);
+				z = READFIXED(demo_p);
+				demo_p += sizeof(angle_t); // angle, unnecessary for cons.
+
+				mobj = NULL;
+				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+				{
+					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;
+				}
+				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"));
+					demosynced = false;
+					P_DamageMobj(mobj, players[0].mo, players[0].mo, 1, 0);
+				}
+			}
+		}
+		if (xziptic & EZT_SPRITE)
+			demo_p += sizeof(UINT16);
+		if (xziptic & EZT_HEIGHT)
+			demo_p += sizeof(INT16);
+	}
+
+	if (ziptic & GZT_FOLLOW)
+	{ // Even more...
+		UINT8 followtic = READUINT8(demo_p);
+		if (followtic & FZT_SPAWNED)
+		{
+			demo_p += sizeof(INT16);
+			if (followtic & FZT_SKIN)
+				demo_p++;
+		}
+		if (followtic & FZT_SCALE)
+			demo_p += sizeof(fixed_t);
+		demo_p += sizeof(INT16);
+		demo_p += sizeof(INT16);
+		demo_p += sizeof(INT16);
+		if (followtic & FZT_SKIN)
+			demo_p++;
+		demo_p += sizeof(UINT16);
+		demo_p++;
+		demo_p++;
+	}
+
+	// Re-synchronise
+	px = testmo->x>>FRACBITS;
+	py = testmo->y>>FRACBITS;
+	pz = testmo->z>>FRACBITS;
+	gx = oldghost.x>>FRACBITS;
+	gy = oldghost.y>>FRACBITS;
+	gz = oldghost.z>>FRACBITS;
+
+	if (px != gx || py != gy || pz != gz)
+	{
+		if (demosynced)
+			CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n"));
+		demosynced = false;
+
+		P_UnsetThingPosition(testmo);
+		testmo->x = oldghost.x;
+		testmo->y = oldghost.y;
+		P_SetThingPosition(testmo);
+		testmo->z = oldghost.z;
+	}
+
+	if (*demo_p == DEMOMARKER)
+	{
+		// end of demo data stream
+		G_CheckDemoStatus();
+		return;
+	}
+}
+
+void G_GhostTicker(void)
+{
+	demoghost *g,*p;
+	for(g = ghosts, p = NULL; g; g = g->next)
+	{
+		// Skip normal demo data.
+		UINT8 ziptic = READUINT8(g->p);
+		UINT8 xziptic = 0;
+		if (ziptic & ZT_FWD)
+			g->p++;
+		if (ziptic & ZT_SIDE)
+			g->p++;
+		if (ziptic & ZT_ANGLE)
+			g->p += 2;
+		if (ziptic & ZT_BUTTONS)
+			g->p += 2;
+		if (ziptic & ZT_AIMING)
+			g->p += 2;
+
+		// Grab ghost data.
+		ziptic = READUINT8(g->p);
+		if (ziptic & GZT_XYZ)
+		{
+			g->oldmo.x = READFIXED(g->p);
+			g->oldmo.y = READFIXED(g->p);
+			g->oldmo.z = READFIXED(g->p);
+		}
+		else
+		{
+			if (ziptic & GZT_MOMXY)
+			{
+				g->oldmo.momx = READINT16(g->p)<<8;
+				g->oldmo.momy = READINT16(g->p)<<8;
+			}
+			if (ziptic & GZT_MOMZ)
+				g->oldmo.momz = READINT16(g->p)<<8;
+			g->oldmo.x += g->oldmo.momx;
+			g->oldmo.y += g->oldmo.momy;
+			g->oldmo.z += g->oldmo.momz;
+		}
+		if (ziptic & GZT_ANGLE)
+			g->mo->angle = READUINT8(g->p)<<24;
+		if (ziptic & GZT_FRAME)
+			g->oldmo.frame = READUINT8(g->p);
+		if (ziptic & GZT_SPR2)
+			g->oldmo.sprite2 = READUINT8(g->p);
+
+		// Update ghost
+		P_UnsetThingPosition(g->mo);
+		g->mo->x = g->oldmo.x;
+		g->mo->y = g->oldmo.y;
+		g->mo->z = g->oldmo.z;
+		P_SetThingPosition(g->mo);
+		g->mo->frame = g->oldmo.frame | tr_trans30<<FF_TRANSSHIFT;
+		if (g->fadein)
+		{
+			g->mo->frame += (((--g->fadein)/6)<<FF_TRANSSHIFT); // this calc never exceeds 9 unless g->fadein is bad, and it's only set once, so...
+			g->mo->flags2 &= ~MF2_DONTDRAW;
+		}
+		g->mo->sprite2 = g->oldmo.sprite2;
+
+		if (ziptic & GZT_EXTRA)
+		{ // But wait, there's more!
+			xziptic = READUINT8(g->p);
+			if (xziptic & EZT_COLOR)
+			{
+				g->color = READUINT8(g->p);
+				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;
+				// Handled below
+				case GHC_SUPER:
+				case GHC_INVINCIBLE:
+					break;
+				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)
+				g->mo->eflags ^= MFE_VERTICALFLIP;
+			if (xziptic & EZT_SCALE)
+			{
+				g->mo->destscale = READFIXED(g->p);
+				if (g->mo->destscale != g->mo->scale)
+					P_SetScale(g->mo, g->mo->destscale);
+			}
+			if (xziptic & EZT_THOKMASK)
+			{ // Let's only spawn ONE of these per frame, thanks.
+				mobj_t *mobj;
+				INT32 type = -1;
+				if (g->mo->skin)
+				{
+					skin_t *skin = (skin_t *)g->mo->skin;
+					switch (xziptic & EZT_THOKMASK)
+					{
+					case EZT_THOK:
+						type = skin->thokitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].painchance : (UINT32)skin->thokitem;
+						break;
+					case EZT_SPIN:
+						type = skin->spinitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].damage : (UINT32)skin->spinitem;
+						break;
+					case EZT_REV:
+						type = skin->revitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].raisestate : (UINT32)skin->revitem;
+						break;
+					}
+				}
+				if (type != MT_NULL)
+				{
+					if (type == MT_GHOST)
+					{
+						mobj = P_SpawnGhostMobj(g->mo); // does a large portion of the work for us
+						mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<<FF_TRANSSHIFT; // P_SpawnGhostMobj sets trans50, we want trans60
+					}
+					else
+					{
+						mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, -FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK);
+						mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
+						mobj->frame = (states[mobjinfo[type].spawnstate].frame & FF_FRAMEMASK) | tr_trans60<<FF_TRANSSHIFT;
+						mobj->color = g->mo->color;
+						mobj->skin = g->mo->skin;
+						P_SetScale(mobj, (mobj->destscale = g->mo->scale));
+
+						if (type == MT_THOK) // spintrail-specific modification for MT_THOK
+						{
+							mobj->frame = FF_TRANS80;
+							mobj->fuse = mobj->tics;
+						}
+						mobj->tics = -1; // nope.
+					}
+					mobj->floorz = mobj->z;
+					mobj->ceilingz = mobj->z+mobj->height;
+					P_UnsetThingPosition(mobj);
+					mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
+					P_SetThingPosition(mobj);
+					if (!mobj->fuse)
+						mobj->fuse = 8;
+					P_SetTarget(&mobj->target, g->mo);
+				}
+			}
+			if (xziptic & EZT_HIT)
+			{ // Spawn hit poofs for killing things!
+				UINT16 i, count = READUINT16(g->p), health;
+				UINT32 type;
+				fixed_t x,y,z;
+				angle_t angle;
+				mobj_t *poof;
+				for (i = 0; i < count; i++)
+				{
+					//g->p += 4; // reserved
+					type = READUINT32(g->p);
+					health = READUINT16(g->p);
+					x = READFIXED(g->p);
+					y = READFIXED(g->p);
+					z = READFIXED(g->p);
+					angle = READANGLE(g->p);
+					if (!(mobjinfo[type].flags & MF_SHOOTABLE)
+					|| !(mobjinfo[type].flags & (MF_ENEMY|MF_MONITOR))
+					|| health != 0 || i >= 4) // only spawn for the first 4 hits per frame, to prevent ghosts from splode-spamming too bad.
+						continue;
+					poof = P_SpawnMobj(x, y, z, MT_GHOST);
+					poof->angle = angle;
+					poof->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
+					poof->health = 0;
+					P_SetMobjStateNF(poof, S_XPLD1);
+				}
+			}
+			if (xziptic & EZT_SPRITE)
+				g->mo->sprite = READUINT16(g->p);
+			if (xziptic & EZT_HEIGHT)
+			{
+				fixed_t temp = READINT16(g->p)<<FRACBITS;
+				g->mo->height = FixedMul(temp, g->mo->scale);
+			}
+		}
+
+		// Tick ghost colors (Super and Mario Invincibility flashing)
+		switch(g->color)
+		{
+		case GHC_SUPER: // Super (P_DoSuperStuff)
+			if (g->mo->skin)
+			{
+				skin_t *skin = (skin_t *)g->mo->skin;
+				g->mo->color = skin->supercolor;
+			}
+			else
+				g->mo->color = SKINCOLOR_SUPERGOLD1;
+			g->mo->color += abs( ( (signed)( (unsigned)leveltime >> 1 ) % 9) - 4);
+			break;
+		case GHC_INVINCIBLE: // Mario invincibility (P_CheckInvincibilityTimer)
+			g->mo->color = (UINT8)(SKINCOLOR_RUBY + (leveltime % (MAXSKINCOLORS - SKINCOLOR_RUBY))); // Passes through all saturated colours
+			break;
+		default:
+			break;
+		}
+
+#define follow g->mo->tracer
+		if (ziptic & GZT_FOLLOW)
+		{ // Even more...
+			UINT8 followtic = READUINT8(g->p);
+			fixed_t temp;
+			if (followtic & FZT_SPAWNED)
+			{
+				if (follow)
+					P_RemoveMobj(follow);
+				P_SetTarget(&follow, P_SpawnMobjFromMobj(g->mo, 0, 0, 0, MT_GHOST));
+				P_SetTarget(&follow->tracer, g->mo);
+				follow->tics = -1;
+				temp = READINT16(g->p)<<FRACBITS;
+				follow->height = FixedMul(follow->scale, temp);
+
+				if (followtic & FZT_LINKDRAW)
+					follow->flags2 |= MF2_LINKDRAW;
+
+				if (followtic & FZT_COLORIZED)
+					follow->colorized = true;
+
+				if (followtic & FZT_SKIN)
+					follow->skin = &skins[READUINT8(g->p)];
+			}
+			if (follow)
+			{
+				if (followtic & FZT_SCALE)
+					follow->destscale = READFIXED(g->p);
+				else
+					follow->destscale = g->mo->destscale;
+				if (follow->destscale != follow->scale)
+					P_SetScale(follow, follow->destscale);
+
+				P_UnsetThingPosition(follow);
+				temp = READINT16(g->p)<<8;
+				follow->x = g->mo->x + temp;
+				temp = READINT16(g->p)<<8;
+				follow->y = g->mo->y + temp;
+				temp = READINT16(g->p)<<8;
+				follow->z = g->mo->z + temp;
+				P_SetThingPosition(follow);
+				if (followtic & FZT_SKIN)
+					follow->sprite2 = READUINT8(g->p);
+				else
+					follow->sprite2 = 0;
+				follow->sprite = READUINT16(g->p);
+				follow->frame = (READUINT8(g->p)) | (g->mo->frame & FF_TRANSMASK);
+				follow->angle = g->mo->angle;
+				follow->color = READUINT8(g->p);
+
+				if (!(followtic & FZT_SPAWNED))
+				{
+					if (xziptic & EZT_FLIP)
+					{
+						follow->flags2 ^= MF2_OBJECTFLIP;
+						follow->eflags ^= MFE_VERTICALFLIP;
+					}
+				}
+			}
+		}
+		else if (follow)
+		{
+			P_RemoveMobj(follow);
+			P_SetTarget(&follow, NULL);
+		}
+		// Demo ends after ghost data.
+		if (*g->p == DEMOMARKER)
+		{
+			g->mo->momx = g->mo->momy = g->mo->momz = 0;
+#if 1 // freeze frame (maybe more useful for time attackers)
+			g->mo->colorized = true;
+			if (follow)
+				follow->colorized = true;
+#else // dissapearing act
+			g->mo->fuse = TICRATE;
+			if (follow)
+				follow->fuse = TICRATE;
+#endif
+			if (p)
+				p->next = g->next;
+			else
+				ghosts = g->next;
+			Z_Free(g);
+			continue;
+		}
+		p = g;
+#undef follow
+	}
+}
+
+void G_ReadMetalTic(mobj_t *metal)
+{
+	UINT8 ziptic;
+	UINT8 xziptic = 0;
+
+	if (!metal_p)
+		return;
+
+	if (!metal->health)
+	{
+		G_StopMetalDemo();
+		return;
+	}
+
+	switch (*metal_p)
+	{
+		case METALSNICE:
+			break;
+		case METALDEATH:
+			if (metal->tracer)
+				P_RemoveMobj(metal->tracer);
+			P_KillMobj(metal, NULL, NULL, 0);
+			/* FALLTHRU */
+		case DEMOMARKER:
+		default:
+			// end of demo data stream
+			G_StopMetalDemo();
+			return;
+	}
+	metal_p++;
+
+	ziptic = READUINT8(metal_p);
+
+	// Read changes from the tic
+	if (ziptic & GZT_XYZ)
+	{
+		P_TeleportMove(metal, READFIXED(metal_p), READFIXED(metal_p), READFIXED(metal_p));
+		oldmetal.x = metal->x;
+		oldmetal.y = metal->y;
+		oldmetal.z = metal->z;
+	}
+	else
+	{
+		if (ziptic & GZT_MOMXY)
+		{
+			oldmetal.momx = READINT16(metal_p)<<8;
+			oldmetal.momy = READINT16(metal_p)<<8;
+		}
+		if (ziptic & GZT_MOMZ)
+			oldmetal.momz = READINT16(metal_p)<<8;
+		oldmetal.x += oldmetal.momx;
+		oldmetal.y += oldmetal.momy;
+		oldmetal.z += oldmetal.momz;
+	}
+	if (ziptic & GZT_ANGLE)
+		metal->angle = READUINT8(metal_p)<<24;
+	if (ziptic & GZT_FRAME)
+		oldmetal.frame = READUINT32(metal_p);
+	if (ziptic & GZT_SPR2)
+		oldmetal.sprite2 = READUINT8(metal_p);
+
+	// Set movement, position, and angle
+	// oldmetal contains where you're supposed to be.
+	metal->momx = oldmetal.momx;
+	metal->momy = oldmetal.momy;
+	metal->momz = oldmetal.momz;
+	P_UnsetThingPosition(metal);
+	metal->x = oldmetal.x;
+	metal->y = oldmetal.y;
+	metal->z = oldmetal.z;
+	P_SetThingPosition(metal);
+	metal->frame = oldmetal.frame;
+	metal->sprite2 = oldmetal.sprite2;
+
+	if (ziptic & GZT_EXTRA)
+	{ // But wait, there's more!
+		xziptic = READUINT8(metal_p);
+		if (xziptic & EZT_FLIP)
+		{
+			metal->eflags ^= MFE_VERTICALFLIP;
+			metal->flags2 ^= MF2_OBJECTFLIP;
+		}
+		if (xziptic & EZT_SCALE)
+		{
+			metal->destscale = READFIXED(metal_p);
+			if (metal->destscale != metal->scale)
+				P_SetScale(metal, metal->destscale);
+		}
+		if (xziptic & EZT_THOKMASK)
+		{ // Let's only spawn ONE of these per frame, thanks.
+			mobj_t *mobj;
+			INT32 type = -1;
+			if (metal->skin)
+			{
+				skin_t *skin = (skin_t *)metal->skin;
+				switch (xziptic & EZT_THOKMASK)
+				{
+				case EZT_THOK:
+					type = skin->thokitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].painchance : (UINT32)skin->thokitem;
+					break;
+				case EZT_SPIN:
+					type = skin->spinitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].damage : (UINT32)skin->spinitem;
+					break;
+				case EZT_REV:
+					type = skin->revitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].raisestate : (UINT32)skin->revitem;
+					break;
+				}
+			}
+			if (type != MT_NULL)
+			{
+				if (type == MT_GHOST)
+				{
+					mobj = P_SpawnGhostMobj(metal); // does a large portion of the work for us
+				}
+				else
+				{
+					mobj = P_SpawnMobjFromMobj(metal, 0, 0, -FixedDiv(FixedMul(metal->info->height, metal->scale) - metal->height,3*FRACUNIT), MT_THOK);
+					mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
+					mobj->frame = states[mobjinfo[type].spawnstate].frame;
+					mobj->angle = metal->angle;
+					mobj->color = metal->color;
+					mobj->skin = metal->skin;
+					P_SetScale(mobj, (mobj->destscale = metal->scale));
+
+					if (type == MT_THOK) // spintrail-specific modification for MT_THOK
+					{
+						mobj->frame = FF_TRANS70;
+						mobj->fuse = mobj->tics;
+					}
+					mobj->tics = -1; // nope.
+				}
+				mobj->floorz = mobj->z;
+				mobj->ceilingz = mobj->z+mobj->height;
+				P_UnsetThingPosition(mobj);
+				mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
+				P_SetThingPosition(mobj);
+				if (!mobj->fuse)
+					mobj->fuse = 8;
+				P_SetTarget(&mobj->target, metal);
+			}
+		}
+		if (xziptic & EZT_SPRITE)
+			metal->sprite = READUINT16(metal_p);
+		if (xziptic & EZT_HEIGHT)
+		{
+			fixed_t temp = READINT16(metal_p)<<FRACBITS;
+			metal->height = FixedMul(temp, metal->scale);
+		}
+	}
+
+#define follow metal->tracer
+		if (ziptic & GZT_FOLLOW)
+		{ // Even more...
+			UINT8 followtic = READUINT8(metal_p);
+			fixed_t temp;
+			if (followtic & FZT_SPAWNED)
+			{
+				if (follow)
+					P_RemoveMobj(follow);
+				P_SetTarget(&follow, P_SpawnMobjFromMobj(metal, 0, 0, 0, MT_GHOST));
+				P_SetTarget(&follow->tracer, metal);
+				follow->tics = -1;
+				temp = READINT16(metal_p)<<FRACBITS;
+				follow->height = FixedMul(follow->scale, temp);
+
+				if (followtic & FZT_LINKDRAW)
+					follow->flags2 |= MF2_LINKDRAW;
+
+				if (followtic & FZT_COLORIZED)
+					follow->colorized = true;
+
+				if (followtic & FZT_SKIN)
+					follow->skin = &skins[READUINT8(metal_p)];
+			}
+			if (follow)
+			{
+				if (followtic & FZT_SCALE)
+					follow->destscale = READFIXED(metal_p);
+				else
+					follow->destscale = metal->destscale;
+				if (follow->destscale != follow->scale)
+					P_SetScale(follow, follow->destscale);
+
+				P_UnsetThingPosition(follow);
+				temp = READINT16(metal_p)<<8;
+				follow->x = metal->x + temp;
+				temp = READINT16(metal_p)<<8;
+				follow->y = metal->y + temp;
+				temp = READINT16(metal_p)<<8;
+				follow->z = metal->z + temp;
+				P_SetThingPosition(follow);
+				if (followtic & FZT_SKIN)
+					follow->sprite2 = READUINT8(metal_p);
+				else
+					follow->sprite2 = 0;
+				follow->sprite = READUINT16(metal_p);
+				follow->frame = READUINT32(metal_p); // NOT & FF_FRAMEMASK here, so 32 bits
+				follow->angle = metal->angle;
+				follow->color = READUINT8(metal_p);
+
+				if (!(followtic & FZT_SPAWNED))
+				{
+					if (xziptic & EZT_FLIP)
+					{
+						follow->flags2 ^= MF2_OBJECTFLIP;
+						follow->eflags ^= MFE_VERTICALFLIP;
+					}
+				}
+			}
+		}
+		else if (follow)
+		{
+			P_RemoveMobj(follow);
+			P_SetTarget(&follow, NULL);
+		}
+#undef follow
+}
+
+void G_WriteMetalTic(mobj_t *metal)
+{
+	UINT8 ziptic = 0;
+	UINT8 *ziptic_p;
+	fixed_t height;
+
+	if (!demo_p) // demo_p will be NULL until the race start linedef executor is activated!
+		return;
+
+	WRITEUINT8(demo_p, METALSNICE);
+	ziptic_p = demo_p++; // the ziptic, written at the end of this function
+
+	#define MAXMOM (0xFFFF<<8)
+
+	// GZT_XYZ is only useful if you've moved 256 FRACUNITS or more in a single tic.
+	if (abs(metal->x-oldmetal.x) > MAXMOM
+	|| abs(metal->y-oldmetal.y) > MAXMOM
+	|| abs(metal->z-oldmetal.z) > MAXMOM)
+	{
+		oldmetal.x = metal->x;
+		oldmetal.y = metal->y;
+		oldmetal.z = metal->z;
+		ziptic |= GZT_XYZ;
+		WRITEFIXED(demo_p,oldmetal.x);
+		WRITEFIXED(demo_p,oldmetal.y);
+		WRITEFIXED(demo_p,oldmetal.z);
+	}
+	else
+	{
+		// For moving normally:
+		// Store one full byte of movement, plus one byte of fractional movement.
+		INT16 momx = (INT16)((metal->x-oldmetal.x)>>8);
+		INT16 momy = (INT16)((metal->y-oldmetal.y)>>8);
+		if (momx != oldmetal.momx
+		|| momy != oldmetal.momy)
+		{
+			oldmetal.momx = momx;
+			oldmetal.momy = momy;
+			ziptic |= GZT_MOMXY;
+			WRITEINT16(demo_p,momx);
+			WRITEINT16(demo_p,momy);
+		}
+		momx = (INT16)((metal->z-oldmetal.z)>>8);
+		if (momx != oldmetal.momz)
+		{
+			oldmetal.momz = momx;
+			ziptic |= GZT_MOMZ;
+			WRITEINT16(demo_p,momx);
+		}
+
+		// This SHOULD set oldmetal.x/y/z to match metal->x/y/z
+		// but it keeps the fractional loss of one byte,
+		// so it will hopefully be made up for in future tics.
+		oldmetal.x += oldmetal.momx<<8;
+		oldmetal.y += oldmetal.momy<<8;
+		oldmetal.z += oldmetal.momz<<8;
+	}
+
+	#undef MAXMOM
+
+	// Only store the 8 most relevant bits of angle
+	// because exact values aren't too easy to discern to begin with when only 8 angles have different sprites
+	// and it does not affect movement at all anyway.
+	if (metal->player && metal->player->drawangle>>24 != oldmetal.angle)
+	{
+		oldmetal.angle = metal->player->drawangle>>24;
+		ziptic |= GZT_ANGLE;
+		WRITEUINT8(demo_p,oldmetal.angle);
+	}
+
+	// Store the sprite frame.
+	if ((metal->frame & FF_FRAMEMASK) != oldmetal.frame)
+	{
+		oldmetal.frame = metal->frame; // NOT & FF_FRAMEMASK here, so 32 bits
+		ziptic |= GZT_FRAME;
+		WRITEUINT32(demo_p,oldmetal.frame);
+	}
+
+	if (metal->sprite == SPR_PLAY
+	&& metal->sprite2 != oldmetal.sprite2)
+	{
+		oldmetal.sprite2 = metal->sprite2;
+		ziptic |= GZT_SPR2;
+		WRITEUINT8(demo_p,oldmetal.sprite2);
+	}
+
+	// Check for sprite set changes
+	if (metal->sprite != oldmetal.sprite)
+	{
+		oldmetal.sprite = metal->sprite;
+		ghostext.flags |= EZT_SPRITE;
+	}
+
+	if ((height = FixedDiv(metal->height, metal->scale)) != oldmetal.height)
+	{
+		oldmetal.height = height;
+		ghostext.flags |= EZT_HEIGHT;
+	}
+
+	if (ghostext.flags & ~(EZT_COLOR|EZT_HIT)) // these two aren't handled by metal ever
+	{
+		ziptic |= GZT_EXTRA;
+
+		if (ghostext.scale == ghostext.lastscale)
+			ghostext.flags &= ~EZT_SCALE;
+
+		WRITEUINT8(demo_p,ghostext.flags);
+		if (ghostext.flags & EZT_SCALE)
+		{
+			WRITEFIXED(demo_p,ghostext.scale);
+			ghostext.lastscale = ghostext.scale;
+		}
+		if (ghostext.flags & EZT_SPRITE)
+			WRITEUINT16(demo_p,oldmetal.sprite);
+		if (ghostext.flags & EZT_HEIGHT)
+		{
+			height >>= FRACBITS;
+			WRITEINT16(demo_p, height);
+		}
+		ghostext.flags = 0;
+	}
+
+	if (metal->player && metal->player->followmobj && !(metal->player->followmobj->sprite == SPR_NULL || (metal->player->followmobj->flags2 & MF2_DONTDRAW)))
+	{
+		INT16 temp;
+		UINT8 *followtic_p = demo_p++;
+		UINT8 followtic = 0;
+
+		ziptic |= GZT_FOLLOW;
+
+		if (metal->player->followmobj->skin)
+			followtic |= FZT_SKIN;
+
+		if (!(oldmetal.flags2 & MF2_AMBUSH))
+		{
+			followtic |= FZT_SPAWNED;
+			WRITEINT16(demo_p,metal->player->followmobj->info->height>>FRACBITS);
+			if (metal->player->followmobj->flags2 & MF2_LINKDRAW)
+				followtic |= FZT_LINKDRAW;
+			if (metal->player->followmobj->colorized)
+				followtic |= FZT_COLORIZED;
+			if (followtic & FZT_SKIN)
+				WRITEUINT8(demo_p,(UINT8)(((skin_t *)(metal->player->followmobj->skin))-skins));
+			oldmetal.flags2 |= MF2_AMBUSH;
+		}
+
+		if (metal->player->followmobj->scale != metal->scale)
+		{
+			followtic |= FZT_SCALE;
+			WRITEFIXED(demo_p,metal->player->followmobj->scale);
+		}
+
+		temp = (INT16)((metal->player->followmobj->x-metal->x)>>8);
+		WRITEINT16(demo_p,temp);
+		temp = (INT16)((metal->player->followmobj->y-metal->y)>>8);
+		WRITEINT16(demo_p,temp);
+		temp = (INT16)((metal->player->followmobj->z-metal->z)>>8);
+		WRITEINT16(demo_p,temp);
+		if (followtic & FZT_SKIN)
+			WRITEUINT8(demo_p,metal->player->followmobj->sprite2);
+		WRITEUINT16(demo_p,metal->player->followmobj->sprite);
+		WRITEUINT32(demo_p,metal->player->followmobj->frame); // NOT & FF_FRAMEMASK here, so 32 bits
+		WRITEUINT8(demo_p,metal->player->followmobj->color);
+
+		*followtic_p = followtic;
+	}
+	else
+		oldmetal.flags2 &= ~MF2_AMBUSH;
+
+	*ziptic_p = ziptic;
+
+	// attention here for the ticcmd size!
+	// latest demos with mouse aiming byte in ticcmd
+	if (demo_p >= demoend - 32)
+	{
+		G_StopMetalRecording(false); // no more space
+		return;
+	}
+}
+
+//
+// G_RecordDemo
+//
+void G_RecordDemo(const char *name)
+{
+	INT32 maxsize;
+
+	strcpy(demoname, name);
+	strcat(demoname, ".lmp");
+	maxsize = 1024*1024;
+	if (M_CheckParm("-maxdemo") && M_IsNextParm())
+		maxsize = atoi(M_GetNextParm()) * 1024;
+//	if (demobuffer)
+//		free(demobuffer);
+	demo_p = NULL;
+	demobuffer = malloc(maxsize);
+	demoend = demobuffer + maxsize;
+
+	demorecording = true;
+}
+
+void G_RecordMetal(void)
+{
+	INT32 maxsize;
+	maxsize = 1024*1024;
+	if (M_CheckParm("-maxdemo") && M_IsNextParm())
+		maxsize = atoi(M_GetNextParm()) * 1024;
+	demo_p = NULL;
+	demobuffer = malloc(maxsize);
+	demoend = demobuffer + maxsize;
+	metalrecording = true;
+}
+
+void G_BeginRecording(void)
+{
+	UINT8 i;
+	char name[16];
+	player_t *player = &players[consoleplayer];
+
+	if (demo_p)
+		return;
+	memset(name,0,sizeof(name));
+
+	demo_p = demobuffer;
+	demoflags = DF_GHOST|(modeattacking<<DF_ATTACKSHIFT);
+
+	// Setup header.
+	M_Memcpy(demo_p, DEMOHEADER, 12); demo_p += 12;
+	WRITEUINT8(demo_p,VERSION);
+	WRITEUINT8(demo_p,SUBVERSION);
+	WRITEUINT16(demo_p,DEMOVERSION);
+
+	// demo checksum
+	demo_p += 16;
+
+	// game data
+	M_Memcpy(demo_p, "PLAY", 4); demo_p += 4;
+	WRITEINT16(demo_p,gamemap);
+	M_Memcpy(demo_p, mapmd5, 16); demo_p += 16;
+
+	WRITEUINT8(demo_p,demoflags);
+	switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT)
+	{
+	case ATTACKING_NONE: // 0
+		break;
+	case ATTACKING_RECORD: // 1
+		demotime_p = demo_p;
+		WRITEUINT32(demo_p,UINT32_MAX); // time
+		WRITEUINT32(demo_p,0); // score
+		WRITEUINT16(demo_p,0); // rings
+		break;
+	case ATTACKING_NIGHTS: // 2
+		demotime_p = demo_p;
+		WRITEUINT32(demo_p,UINT32_MAX); // time
+		WRITEUINT32(demo_p,0); // score
+		break;
+	default: // 3
+		break;
+	}
+
+	WRITEUINT32(demo_p,P_GetInitSeed());
+
+	// Name
+	for (i = 0; i < 16 && cv_playername.string[i]; i++)
+		name[i] = cv_playername.string[i];
+	for (; i < 16; i++)
+		name[i] = '\0';
+	M_Memcpy(demo_p,name,16);
+	demo_p += 16;
+
+	// Skin
+	for (i = 0; i < 16 && cv_skin.string[i]; i++)
+		name[i] = cv_skin.string[i];
+	for (; i < 16; i++)
+		name[i] = '\0';
+	M_Memcpy(demo_p,name,16);
+	demo_p += 16;
+
+	// Color
+	for (i = 0; i < 16 && cv_playercolor.string[i]; i++)
+		name[i] = cv_playercolor.string[i];
+	for (; i < 16; i++)
+		name[i] = '\0';
+	M_Memcpy(demo_p,name,16);
+	demo_p += 16;
+
+	// Stats
+	WRITEUINT8(demo_p,player->charability);
+	WRITEUINT8(demo_p,player->charability2);
+	WRITEUINT8(demo_p,player->actionspd>>FRACBITS);
+	WRITEUINT8(demo_p,player->mindash>>FRACBITS);
+	WRITEUINT8(demo_p,player->maxdash>>FRACBITS);
+	WRITEUINT8(demo_p,player->normalspeed>>FRACBITS);
+	WRITEUINT8(demo_p,player->runspeed>>FRACBITS);
+	WRITEUINT8(demo_p,player->thrustfactor);
+	WRITEUINT8(demo_p,player->accelstart);
+	WRITEUINT8(demo_p,player->acceleration);
+	WRITEUINT8(demo_p,player->height>>FRACBITS);
+	WRITEUINT8(demo_p,player->spinheight>>FRACBITS);
+	WRITEUINT8(demo_p,player->camerascale>>FRACBITS);
+	WRITEUINT8(demo_p,player->shieldscale>>FRACBITS);
+
+	// Trying to convert it back to % causes demo desync due to precision loss.
+	// Don't do it.
+	WRITEFIXED(demo_p, player->jumpfactor);
+
+	// And mobjtype_t is best with UINT32 too...
+	WRITEUINT32(demo_p, player->followitem);
+
+	// Save pflag data - see SendWeaponPref()
+	{
+		UINT8 buf = 0;
+		pflags_t pflags = 0;
+		if (cv_flipcam.value)
+		{
+			buf |= 0x01;
+			pflags |= PF_FLIPCAM;
+		}
+		if (cv_analog[0].value)
+		{
+			buf |= 0x02;
+			pflags |= PF_ANALOGMODE;
+		}
+		if (cv_directionchar[0].value)
+		{
+			buf |= 0x04;
+			pflags |= PF_DIRECTIONCHAR;
+		}
+		if (cv_autobrake.value)
+		{
+			buf |= 0x08;
+			pflags |= PF_AUTOBRAKE;
+		}
+		if (cv_usejoystick.value)
+			buf |= 0x10;
+		CV_SetValue(&cv_showinputjoy, !!(cv_usejoystick.value));
+
+		WRITEUINT8(demo_p,buf);
+		player->pflags = pflags;
+	}
+
+	// Save netvar data
+	CV_SaveNetVars(&demo_p);
+
+	memset(&oldcmd,0,sizeof(oldcmd));
+	memset(&oldghost,0,sizeof(oldghost));
+	memset(&ghostext,0,sizeof(ghostext));
+	ghostext.lastcolor = ghostext.color = GHC_NORMAL;
+	ghostext.lastscale = ghostext.scale = FRACUNIT;
+
+	if (player->mo)
+	{
+		oldghost.x = player->mo->x;
+		oldghost.y = player->mo->y;
+		oldghost.z = player->mo->z;
+		oldghost.angle = player->mo->angle>>24;
+
+		// preticker started us gravity flipped
+		if (player->mo->eflags & MFE_VERTICALFLIP)
+			ghostext.flags |= EZT_FLIP;
+	}
+}
+
+void G_BeginMetal(void)
+{
+	mobj_t *mo = players[consoleplayer].mo;
+
+#if 0
+	if (demo_p)
+		return;
+#endif
+
+	demo_p = demobuffer;
+
+	// Write header.
+	M_Memcpy(demo_p, DEMOHEADER, 12); demo_p += 12;
+	WRITEUINT8(demo_p,VERSION);
+	WRITEUINT8(demo_p,SUBVERSION);
+	WRITEUINT16(demo_p,DEMOVERSION);
+
+	// demo checksum
+	demo_p += 16;
+
+	M_Memcpy(demo_p, "METL", 4); demo_p += 4;
+
+	memset(&ghostext,0,sizeof(ghostext));
+	ghostext.lastscale = ghostext.scale = FRACUNIT;
+
+	// Set up our memory.
+	memset(&oldmetal,0,sizeof(oldmetal));
+	oldmetal.x = mo->x;
+	oldmetal.y = mo->y;
+	oldmetal.z = mo->z;
+	oldmetal.angle = mo->angle>>24;
+}
+
+void G_SetDemoTime(UINT32 ptime, UINT32 pscore, UINT16 prings)
+{
+	if (!demorecording || !demotime_p)
+		return;
+	if (demoflags & DF_RECORDATTACK)
+	{
+		WRITEUINT32(demotime_p, ptime);
+		WRITEUINT32(demotime_p, pscore);
+		WRITEUINT16(demotime_p, prings);
+		demotime_p = NULL;
+	}
+	else if (demoflags & DF_NIGHTSATTACK)
+	{
+		WRITEUINT32(demotime_p, ptime);
+		WRITEUINT32(demotime_p, pscore);
+		demotime_p = NULL;
+	}
+}
+
+// Returns bitfield:
+// 1 == new demo has lower time
+// 2 == new demo has higher score
+// 4 == new demo has higher rings
+UINT8 G_CmpDemoTime(char *oldname, char *newname)
+{
+	UINT8 *buffer,*p;
+	UINT8 flags;
+	UINT32 oldtime, newtime, oldscore, newscore;
+	UINT16 oldrings, newrings, oldversion;
+	size_t bufsize ATTRUNUSED;
+	UINT8 c;
+	UINT16 s ATTRUNUSED;
+	UINT8 aflags = 0;
+
+	// load the new file
+	FIL_DefaultExtension(newname, ".lmp");
+	bufsize = FIL_ReadFile(newname, &buffer);
+	I_Assert(bufsize != 0);
+	p = buffer;
+
+	// read demo header
+	I_Assert(!memcmp(p, DEMOHEADER, 12));
+	p += 12; // DEMOHEADER
+	c = READUINT8(p); // VERSION
+	I_Assert(c == VERSION);
+	c = READUINT8(p); // SUBVERSION
+	I_Assert(c == SUBVERSION);
+	s = READUINT16(p);
+	I_Assert(s == DEMOVERSION);
+	p += 16; // demo checksum
+	I_Assert(!memcmp(p, "PLAY", 4));
+	p += 4; // PLAY
+	p += 2; // gamemap
+	p += 16; // map md5
+	flags = READUINT8(p); // demoflags
+
+	aflags = flags & (DF_RECORDATTACK|DF_NIGHTSATTACK);
+	I_Assert(aflags);
+	if (flags & DF_RECORDATTACK)
+	{
+		newtime = READUINT32(p);
+		newscore = READUINT32(p);
+		newrings = READUINT16(p);
+	}
+	else if (flags & DF_NIGHTSATTACK)
+	{
+		newtime = READUINT32(p);
+		newscore = READUINT32(p);
+		newrings = 0;
+	}
+	else // appease compiler
+		return 0;
+
+	Z_Free(buffer);
+
+	// load old file
+	FIL_DefaultExtension(oldname, ".lmp");
+	if (!FIL_ReadFile(oldname, &buffer))
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Failed to read file '%s'.\n"), oldname);
+		return UINT8_MAX;
+	}
+	p = buffer;
+
+	// read demo header
+	if (memcmp(p, DEMOHEADER, 12))
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' invalid format. It will be overwritten.\n"), oldname);
+		Z_Free(buffer);
+		return UINT8_MAX;
+	} p += 12; // DEMOHEADER
+	p++; // VERSION
+	p++; // SUBVERSION
+	oldversion = READUINT16(p);
+	switch(oldversion) // demoversion
+	{
+	case DEMOVERSION: // latest always supported
+		break;
+	// too old, cannot support.
+	default:
+		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' invalid format. It will be overwritten.\n"), oldname);
+		Z_Free(buffer);
+		return UINT8_MAX;
+	}
+	p += 16; // demo checksum
+	if (memcmp(p, "PLAY", 4))
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' invalid format. It will be overwritten.\n"), oldname);
+		Z_Free(buffer);
+		return UINT8_MAX;
+	} p += 4; // "PLAY"
+	if (oldversion <= 0x0008)
+		p++; // gamemap
+	else
+		p += 2; // gamemap
+	p += 16; // mapmd5
+	flags = READUINT8(p);
+	if (!(flags & aflags))
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' not from same game mode. It will be overwritten.\n"), oldname);
+		Z_Free(buffer);
+		return UINT8_MAX;
+	}
+	if (flags & DF_RECORDATTACK)
+	{
+		oldtime = READUINT32(p);
+		oldscore = READUINT32(p);
+		oldrings = READUINT16(p);
+	}
+	else if (flags & DF_NIGHTSATTACK)
+	{
+		oldtime = READUINT32(p);
+		oldscore = READUINT32(p);
+		oldrings = 0;
+	}
+	else // appease compiler
+		return UINT8_MAX;
+
+	Z_Free(buffer);
+
+	c = 0;
+	if (newtime < oldtime
+	|| (newtime == oldtime && (newscore > oldscore || newrings > oldrings)))
+		c |= 1; // Better time
+	if (newscore > oldscore
+	|| (newscore == oldscore && newtime < oldtime))
+		c |= 1<<1; // Better score
+	if (newrings > oldrings
+	|| (newrings == oldrings && newtime < oldtime))
+		c |= 1<<2; // Better rings
+	return c;
+}
+
+//
+// G_PlayDemo
+//
+void G_DeferedPlayDemo(const char *name)
+{
+	COM_BufAddText("playdemo \"");
+	COM_BufAddText(name);
+	COM_BufAddText("\"\n");
+}
+
+//
+// Start a demo from a .LMP file or from a wad resource
+//
+void G_DoPlayDemo(char *defdemoname)
+{
+	UINT8 i;
+	lumpnum_t l;
+	char skin[17],color[17],*n,*pdemoname;
+	UINT8 version,subversion,charability,charability2,thrustfactor,accelstart,acceleration;
+	pflags_t pflags;
+	UINT32 randseed, followitem;
+	fixed_t camerascale,shieldscale,actionspd,mindash,maxdash,normalspeed,runspeed,jumpfactor,height,spinheight;
+	char msg[1024];
+
+	skin[16] = '\0';
+	color[16] = '\0';
+
+	n = defdemoname+strlen(defdemoname);
+	while (*n != '/' && *n != '\\' && n != defdemoname)
+		n--;
+	if (n != defdemoname)
+		n++;
+	pdemoname = ZZ_Alloc(strlen(n)+1);
+	strcpy(pdemoname,n);
+
+	// Internal if no extension, external if one exists
+	if (FIL_CheckExtension(defdemoname))
+	{
+		//FIL_DefaultExtension(defdemoname, ".lmp");
+		if (!FIL_ReadFile(defdemoname, &demobuffer))
+		{
+			snprintf(msg, 1024, M_GetText("Failed to read file '%s'.\n"), defdemoname);
+			CONS_Alert(CONS_ERROR, "%s", msg);
+			gameaction = ga_nothing;
+			M_StartMessage(msg, NULL, MM_NOTHING);
+			return;
+		}
+		demo_p = demobuffer;
+	}
+	// load demo resource from WAD
+	else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR)
+	{
+		snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname);
+		CONS_Alert(CONS_ERROR, "%s", msg);
+		gameaction = ga_nothing;
+		M_StartMessage(msg, NULL, MM_NOTHING);
+		return;
+	}
+	else // it's an internal demo
+		demobuffer = demo_p = W_CacheLumpNum(l, PU_STATIC);
+
+	// read demo header
+	gameaction = ga_nothing;
+	demoplayback = true;
+	if (memcmp(demo_p, DEMOHEADER, 12))
+	{
+		snprintf(msg, 1024, M_GetText("%s is not a SRB2 replay file.\n"), pdemoname);
+		CONS_Alert(CONS_ERROR, "%s", msg);
+		M_StartMessage(msg, NULL, MM_NOTHING);
+		Z_Free(pdemoname);
+		Z_Free(demobuffer);
+		demoplayback = false;
+		titledemo = false;
+		return;
+	}
+	demo_p += 12; // DEMOHEADER
+
+	version = READUINT8(demo_p);
+	subversion = READUINT8(demo_p);
+	demoversion = READUINT16(demo_p);
+	switch(demoversion)
+	{
+	case DEMOVERSION: // latest always supported
+		break;
+	// too old, cannot support.
+	default:
+		snprintf(msg, 1024, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemoname);
+		CONS_Alert(CONS_ERROR, "%s", msg);
+		M_StartMessage(msg, NULL, MM_NOTHING);
+		Z_Free(pdemoname);
+		Z_Free(demobuffer);
+		demoplayback = false;
+		titledemo = false;
+		return;
+	}
+	demo_p += 16; // demo checksum
+	if (memcmp(demo_p, "PLAY", 4))
+	{
+		snprintf(msg, 1024, M_GetText("%s is the wrong type of recording and cannot be played.\n"), pdemoname);
+		CONS_Alert(CONS_ERROR, "%s", msg);
+		M_StartMessage(msg, NULL, MM_NOTHING);
+		Z_Free(pdemoname);
+		Z_Free(demobuffer);
+		demoplayback = false;
+		titledemo = false;
+		return;
+	}
+	demo_p += 4; // "PLAY"
+	gamemap = READINT16(demo_p);
+	demo_p += 16; // mapmd5
+
+	demoflags = READUINT8(demo_p);
+	modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT;
+	CON_ToggleOff();
+
+	hu_demoscore = 0;
+	hu_demotime = UINT32_MAX;
+	hu_demorings = 0;
+
+	switch (modeattacking)
+	{
+	case ATTACKING_NONE: // 0
+		break;
+	case ATTACKING_RECORD: // 1
+		hu_demotime  = READUINT32(demo_p);
+		hu_demoscore = READUINT32(demo_p);
+		hu_demorings = READUINT16(demo_p);
+		break;
+	case ATTACKING_NIGHTS: // 2
+		hu_demotime  = READUINT32(demo_p);
+		hu_demoscore = READUINT32(demo_p);
+		break;
+	default: // 3
+		modeattacking = ATTACKING_NONE;
+		break;
+	}
+
+	// Random seed
+	randseed = READUINT32(demo_p);
+
+	// Player name
+	M_Memcpy(player_names[0],demo_p,16);
+	demo_p += 16;
+
+	// Skin
+	M_Memcpy(skin,demo_p,16);
+	demo_p += 16;
+
+	// Color
+	M_Memcpy(color,demo_p,16);
+	demo_p += 16;
+
+	charability = READUINT8(demo_p);
+	charability2 = READUINT8(demo_p);
+	actionspd = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	mindash = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	maxdash = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	normalspeed = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	runspeed = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	thrustfactor = READUINT8(demo_p);
+	accelstart = READUINT8(demo_p);
+	acceleration = READUINT8(demo_p);
+	height = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	spinheight = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	camerascale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	shieldscale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	jumpfactor = READFIXED(demo_p);
+	followitem = READUINT32(demo_p);
+
+	// pflag data
+	{
+		UINT8 buf = READUINT8(demo_p);
+		pflags = 0;
+		if (buf & 0x01)
+			pflags |= PF_FLIPCAM;
+		if (buf & 0x02)
+			pflags |= PF_ANALOGMODE;
+		if (buf & 0x04)
+			pflags |= PF_DIRECTIONCHAR;
+		if (buf & 0x08)
+			pflags |= PF_AUTOBRAKE;
+		CV_SetValue(&cv_showinputjoy, !!(buf & 0x10));
+	}
+
+	// net var data
+	CV_LoadNetVars(&demo_p);
+
+	// Sigh ... it's an empty demo.
+	if (*demo_p == DEMOMARKER)
+	{
+		snprintf(msg, 1024, M_GetText("%s contains no data to be played.\n"), pdemoname);
+		CONS_Alert(CONS_ERROR, "%s", msg);
+		M_StartMessage(msg, NULL, MM_NOTHING);
+		Z_Free(pdemoname);
+		Z_Free(demobuffer);
+		demoplayback = false;
+		titledemo = false;
+		return;
+	}
+
+	Z_Free(pdemoname);
+
+	memset(&oldcmd,0,sizeof(oldcmd));
+	memset(&oldghost,0,sizeof(oldghost));
+
+	if (VERSION != version || SUBVERSION != subversion)
+		CONS_Alert(CONS_WARNING, M_GetText("Demo version does not match game version. Desyncs may occur.\n"));
+
+	// didn't start recording right away.
+	demo_start = false;
+
+	// Set skin
+	SetPlayerSkin(0, skin);
+
+	LUAh_MapChange(gamemap);
+	displayplayer = consoleplayer = 0;
+	memset(playeringame,0,sizeof(playeringame));
+	playeringame[0] = true;
+	P_SetRandSeed(randseed);
+	G_InitNew(false, G_BuildMapName(gamemap), true, true, false);
+
+	// Set color
+	for (i = 0; i < MAXSKINCOLORS; i++)
+		if (!stricmp(Color_Names[i],color))
+		{
+			players[0].skincolor = i;
+			break;
+		}
+	CV_StealthSetValue(&cv_playercolor, players[0].skincolor);
+	if (players[0].mo)
+	{
+		players[0].mo->color = players[0].skincolor;
+		oldghost.x = players[0].mo->x;
+		oldghost.y = players[0].mo->y;
+		oldghost.z = players[0].mo->z;
+	}
+
+	// Set saved attribute values
+	// No cheat checking here, because even if they ARE wrong...
+	// it would only break the replay if we clipped them.
+	players[0].camerascale = camerascale;
+	players[0].shieldscale = shieldscale;
+	players[0].charability = charability;
+	players[0].charability2 = charability2;
+	players[0].actionspd = actionspd;
+	players[0].mindash = mindash;
+	players[0].maxdash = maxdash;
+	players[0].normalspeed = normalspeed;
+	players[0].runspeed = runspeed;
+	players[0].thrustfactor = thrustfactor;
+	players[0].accelstart = accelstart;
+	players[0].acceleration = acceleration;
+	players[0].height = height;
+	players[0].spinheight = spinheight;
+	players[0].jumpfactor = jumpfactor;
+	players[0].followitem = followitem;
+	players[0].pflags = pflags;
+
+	demo_start = true;
+}
+
+void G_AddGhost(char *defdemoname)
+{
+	INT32 i;
+	lumpnum_t l;
+	char name[17],skin[17],color[17],*n,*pdemoname,md5[16];
+	demoghost *gh;
+	UINT8 flags;
+	UINT8 *buffer,*p;
+	mapthing_t *mthing;
+	UINT16 count, ghostversion;
+
+	name[16] = '\0';
+	skin[16] = '\0';
+	color[16] = '\0';
+
+	n = defdemoname+strlen(defdemoname);
+	while (*n != '/' && *n != '\\' && n != defdemoname)
+		n--;
+	if (n != defdemoname)
+		n++;
+	pdemoname = ZZ_Alloc(strlen(n)+1);
+	strcpy(pdemoname,n);
+
+	// Internal if no extension, external if one exists
+	if (FIL_CheckExtension(defdemoname))
+	{
+		//FIL_DefaultExtension(defdemoname, ".lmp");
+		if (!FIL_ReadFileTag(defdemoname, &buffer, PU_LEVEL))
+		{
+			CONS_Alert(CONS_ERROR, M_GetText("Failed to read file '%s'.\n"), defdemoname);
+			Z_Free(pdemoname);
+			return;
+		}
+		p = buffer;
+	}
+	// load demo resource from WAD
+	else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Failed to read lump '%s'.\n"), defdemoname);
+		Z_Free(pdemoname);
+		return;
+	}
+	else // it's an internal demo
+		buffer = p = W_CacheLumpNum(l, PU_LEVEL);
+
+	// read demo header
+	if (memcmp(p, DEMOHEADER, 12))
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Not a SRB2 replay.\n"), pdemoname);
+		Z_Free(pdemoname);
+		Z_Free(buffer);
+		return;
+	} p += 12; // DEMOHEADER
+	p++; // VERSION
+	p++; // SUBVERSION
+	ghostversion = READUINT16(p);
+	switch(ghostversion)
+	{
+	case DEMOVERSION: // latest always supported
+		break;
+	// too old, cannot support.
+	default:
+		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Demo version incompatible.\n"), pdemoname);
+		Z_Free(pdemoname);
+		Z_Free(buffer);
+		return;
+	}
+	M_Memcpy(md5, p, 16); p += 16; // demo checksum
+	for (gh = ghosts; gh; gh = gh->next)
+		if (!memcmp(md5, gh->checksum, 16)) // another ghost in the game already has this checksum?
+		{ // Don't add another one, then!
+			CONS_Debug(DBG_SETUP, "Rejecting duplicate ghost %s (MD5 was matched)\n", pdemoname);
+			Z_Free(pdemoname);
+			Z_Free(buffer);
+			return;
+		}
+	if (memcmp(p, "PLAY", 4))
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Demo format unacceptable.\n"), pdemoname);
+		Z_Free(pdemoname);
+		Z_Free(buffer);
+		return;
+	} p += 4; // "PLAY"
+	if (ghostversion <= 0x0008)
+		p++; // gamemap
+	else
+		p += 2; // gamemap
+	p += 16; // mapmd5 (possibly check for consistency?)
+	flags = READUINT8(p);
+	if (!(flags & DF_GHOST))
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: No ghost data in this demo.\n"), pdemoname);
+		Z_Free(pdemoname);
+		Z_Free(buffer);
+		return;
+	}
+	switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT)
+	{
+	case ATTACKING_NONE: // 0
+		break;
+	case ATTACKING_RECORD: // 1
+		p += 10; // demo time, score, and rings
+		break;
+	case ATTACKING_NIGHTS: // 2
+		p += 8; // demo time left, score
+		break;
+	default: // 3
+		break;
+	}
+
+	p += 4; // random seed
+
+	// Player name (TODO: Display this somehow if it doesn't match cv_playername!)
+	M_Memcpy(name, p,16);
+	p += 16;
+
+	// Skin
+	M_Memcpy(skin, p,16);
+	p += 16;
+
+	// Color
+	M_Memcpy(color, p,16);
+	p += 16;
+
+	// Ghosts do not have a player structure to put this in.
+	p++; // charability
+	p++; // charability2
+	p++; // actionspd
+	p++; // mindash
+	p++; // maxdash
+	p++; // normalspeed
+	p++; // runspeed
+	p++; // thrustfactor
+	p++; // accelstart
+	p++; // acceleration
+	p++; // height
+	p++; // spinheight
+	p++; // camerascale
+	p++; // shieldscale
+	p += 4; // jumpfactor
+	p += 4; // followitem
+
+	p++; // pflag data
+
+	// net var data
+	count = READUINT16(p);
+	while (count--)
+	{
+		p += 2;
+		SKIPSTRING(p);
+		p++;
+	}
+
+	if (*p == DEMOMARKER)
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Replay is empty.\n"), pdemoname);
+		Z_Free(pdemoname);
+		Z_Free(buffer);
+		return;
+	}
+
+	gh = Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL);
+	gh->next = ghosts;
+	gh->buffer = buffer;
+	M_Memcpy(gh->checksum, md5, 16);
+	gh->p = p;
+
+	ghosts = gh;
+
+	gh->version = ghostversion;
+	mthing = playerstarts[0];
+	I_Assert(mthing);
+	{ // A bit more complex than P_SpawnPlayer because ghosts aren't solid and won't just push themselves out of the ceiling.
+		fixed_t z,f,c;
+		fixed_t offset = mthing->z << FRACBITS;
+		gh->mo = P_SpawnMobj(mthing->x << FRACBITS, mthing->y << FRACBITS, 0, MT_GHOST);
+		gh->mo->angle = FixedAngle(mthing->angle << FRACBITS);
+		f = gh->mo->floorz;
+		c = gh->mo->ceilingz - mobjinfo[MT_PLAYER].height;
+		if (!!(mthing->options & MTF_AMBUSH) ^ !!(mthing->options & MTF_OBJECTFLIP))
+		{
+			z = c - offset;
+			if (z < f)
+				z = f;
+		}
+		else
+		{
+			z = f + offset;
+			if (z > c)
+				z = c;
+		}
+		gh->mo->z = z;
+	}
+
+	gh->oldmo.x = gh->mo->x;
+	gh->oldmo.y = gh->mo->y;
+	gh->oldmo.z = gh->mo->z;
+
+	// Set skin
+	gh->mo->skin = &skins[0];
+	for (i = 0; i < numskins; i++)
+		if (!stricmp(skins[i].name,skin))
+		{
+			gh->mo->skin = &skins[i];
+			break;
+		}
+	gh->oldmo.skin = gh->mo->skin;
+
+	// Set color
+	gh->mo->color = ((skin_t*)gh->mo->skin)->prefcolor;
+	for (i = 0; i < MAXSKINCOLORS; i++)
+		if (!stricmp(Color_Names[i],color))
+		{
+			gh->mo->color = (UINT8)i;
+			break;
+		}
+	gh->oldmo.color = gh->mo->color;
+
+	gh->mo->state = states+S_PLAY_STND;
+	gh->mo->sprite = gh->mo->state->sprite;
+	gh->mo->sprite2 = (gh->mo->state->frame & FF_FRAMEMASK);
+	//gh->mo->frame = tr_trans30<<FF_TRANSSHIFT;
+	gh->mo->flags2 |= MF2_DONTDRAW;
+	gh->fadein = (9-3)*6; // fade from invisible to trans30 over as close to 35 tics as possible
+	gh->mo->tics = -1;
+
+	CONS_Printf(M_GetText("Added ghost %s from %s\n"), name, pdemoname);
+	Z_Free(pdemoname);
+}
+
+// Clean up all ghosts
+void G_FreeGhosts(void)
+{
+	while (ghosts)
+	{
+		demoghost *next = ghosts->next;
+		Z_Free(ghosts);
+		ghosts = next;
+	}
+	ghosts = NULL;
+}
+
+//
+// G_TimeDemo
+// NOTE: name is a full filename for external demos
+//
+static INT32 restorecv_vidwait;
+
+void G_TimeDemo(const char *name)
+{
+	nodrawers = M_CheckParm("-nodraw");
+	noblit = M_CheckParm("-noblit");
+	restorecv_vidwait = cv_vidwait.value;
+	if (cv_vidwait.value)
+		CV_Set(&cv_vidwait, "0");
+	timingdemo = true;
+	singletics = true;
+	framecount = 0;
+	demostarttime = I_GetTime();
+	G_DeferedPlayDemo(name);
+}
+
+void G_DoPlayMetal(void)
+{
+	lumpnum_t l;
+	mobj_t *mo = NULL;
+	thinker_t *th;
+
+	// it's an internal demo
+	if ((l = W_CheckNumForName(va("%sMS",G_BuildMapName(gamemap)))) == LUMPERROR)
+	{
+		CONS_Alert(CONS_WARNING, M_GetText("No bot recording for this map.\n"));
+		return;
+	}
+	else
+		metalbuffer = metal_p = W_CacheLumpNum(l, PU_STATIC);
+
+	// find metal sonic
+	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	{
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		mo = (mobj_t *)th;
+		if (mo->type != MT_METALSONIC_RACE)
+			continue;
+
+		break;
+	}
+	if (th == &thlist[THINK_MOBJ])
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Failed to find bot entity.\n"));
+		Z_Free(metalbuffer);
+		return;
+	}
+
+	// read demo header
+    metal_p += 12; // DEMOHEADER
+	metal_p++; // VERSION
+	metal_p++; // SUBVERSION
+	metalversion = READUINT16(metal_p);
+	switch(metalversion)
+	{
+	case DEMOVERSION: // latest always supported
+		break;
+	// too old, cannot support.
+	default:
+		CONS_Alert(CONS_WARNING, M_GetText("Failed to load bot recording for this map, format version incompatible.\n"));
+		Z_Free(metalbuffer);
+		return;
+	}
+	metal_p += 16; // demo checksum
+	if (memcmp(metal_p, "METL", 4))
+	{
+		CONS_Alert(CONS_WARNING, M_GetText("Failed to load bot recording for this map, wasn't recorded in Metal format.\n"));
+		Z_Free(metalbuffer);
+		return;
+	} metal_p += 4; // "METL"
+
+	// read initial tic
+	memset(&oldmetal,0,sizeof(oldmetal));
+	oldmetal.x = mo->x;
+	oldmetal.y = mo->y;
+	oldmetal.z = mo->z;
+	metalplayback = mo;
+}
+
+void G_DoneLevelLoad(void)
+{
+	CONS_Printf(M_GetText("Loaded level in %f sec\n"), (double)(I_GetTime() - demostarttime) / TICRATE);
+	framecount = 0;
+	demostarttime = I_GetTime();
+}
+
+/*
+===================
+=
+= G_CheckDemoStatus
+=
+= Called after a death or level completion to allow demos to be cleaned up
+= Returns true if a new demo loop action will take place
+===================
+*/
+
+// Writes the demo's checksum, or just random garbage if you can't do that for some reason.
+static void WriteDemoChecksum(void)
+{
+	UINT8 *p = demobuffer+16; // checksum position
+#ifdef NOMD5
+	UINT8 i;
+	for (i = 0; i < 16; i++, p++)
+		*p = P_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct.
+#else
+	md5_buffer((char *)p+16, demo_p - (p+16), p); // make a checksum of everything after the checksum in the file.
+#endif
+}
+
+// Stops recording a demo.
+static void G_StopDemoRecording(void)
+{
+	boolean saved = false;
+	WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
+	WriteDemoChecksum();
+	saved = FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer); // finally output the file.
+	free(demobuffer);
+	demorecording = false;
+
+	if (modeattacking != ATTACKING_RECORD)
+	{
+		if (saved)
+			CONS_Printf(M_GetText("Demo %s recorded\n"), demoname);
+		else
+			CONS_Alert(CONS_WARNING, M_GetText("Demo %s not saved\n"), demoname);
+	}
+}
+
+// Stops metal sonic's demo. Separate from other functions because metal + replays can coexist
+void G_StopMetalDemo(void)
+{
+
+	// Metal Sonic finishing doesn't end the game, dammit.
+	Z_Free(metalbuffer);
+	metalbuffer = NULL;
+	metalplayback = NULL;
+	metal_p = NULL;
+}
+
+// Stops metal sonic recording.
+ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill)
+{
+	boolean saved = false;
+	if (demo_p)
+	{
+		WRITEUINT8(demo_p, (kill) ? METALDEATH : DEMOMARKER); // add the demo end (or metal death) marker
+		WriteDemoChecksum();
+		saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuffer, demo_p - demobuffer); // finally output the file.
+	}
+	free(demobuffer);
+	metalrecording = false;
+	if (saved)
+		I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap));
+	I_Error("Failed to save demo!");
+}
+
+// Stops timing a demo.
+static void G_StopTimingDemo(void)
+{
+	INT32 demotime;
+	double f1, f2;
+	demotime = I_GetTime() - demostarttime;
+	if (!demotime)
+		return;
+	G_StopDemo();
+	timingdemo = false;
+	f1 = (double)demotime;
+	f2 = (double)framecount*TICRATE;
+
+	CONS_Printf(M_GetText("timed %u gametics in %d realtics - %u frames\n%f seconds, %f avg fps\n"),
+		leveltime,demotime,(UINT32)framecount,f1/TICRATE,f2/f1);
+
+	// CSV-readable timedemo results, for external parsing
+	if (timedemo_csv)
+	{
+		FILE *f;
+		const char *csvpath = va("%s"PATHSEP"%s", srb2home, "timedemo.csv");
+		const char *header = "id,demoname,seconds,avgfps,leveltime,demotime,framecount,ticrate,rendermode,vidmode,vidwidth,vidheight,procbits\n";
+		const char *rowformat = "\"%s\",\"%s\",%f,%f,%u,%d,%u,%u,%u,%u,%u,%u,%u\n";
+		boolean headerrow = !FIL_FileExists(csvpath);
+		UINT8 procbits = 0;
+
+		// Bitness
+		if (sizeof(void*) == 4)
+			procbits = 32;
+		else if (sizeof(void*) == 8)
+			procbits = 64;
+
+		f = fopen(csvpath, "a+");
+
+		if (f)
+		{
+			if (headerrow)
+				fputs(header, f);
+			fprintf(f, rowformat,
+				timedemo_csv_id,timedemo_name,f1/TICRATE,f2/f1,leveltime,demotime,(UINT32)framecount,TICRATE,rendermode,vid.modenum,vid.width,vid.height,procbits);
+			fclose(f);
+			CONS_Printf("Timedemo results saved to '%s'\n", csvpath);
+		}
+		else
+		{
+			// Just print the CSV output to console
+			CON_LogMessage(header);
+			CONS_Printf(rowformat,
+				timedemo_csv_id,timedemo_name,f1/TICRATE,f2/f1,leveltime,demotime,(UINT32)framecount,TICRATE,rendermode,vid.modenum,vid.width,vid.height,procbits);
+		}
+	}
+
+	if (restorecv_vidwait != cv_vidwait.value)
+		CV_SetValue(&cv_vidwait, restorecv_vidwait);
+	D_AdvanceDemo();
+}
+
+// reset engine variable set for the demos
+// called from stopdemo command, map command, and g_checkdemoStatus.
+void G_StopDemo(void)
+{
+	Z_Free(demobuffer);
+	demobuffer = NULL;
+	demoplayback = false;
+	titledemo = false;
+	timingdemo = false;
+	singletics = false;
+
+	if (gamestate == GS_INTERMISSION)
+		Y_EndIntermission(); // cleanup
+
+	G_SetGamestate(GS_NULL);
+	wipegamestate = GS_NULL;
+	SV_StopServer();
+	SV_ResetServer();
+}
+
+boolean G_CheckDemoStatus(void)
+{
+	G_FreeGhosts();
+
+	// DO NOT end metal sonic demos here
+
+	if (timingdemo)
+	{
+		G_StopTimingDemo();
+		return true;
+	}
+
+	if (demoplayback)
+	{
+		if (singledemo)
+			I_Quit();
+		G_StopDemo();
+
+		if (modeattacking)
+			M_EndModeAttackRun();
+		else
+			D_AdvanceDemo();
+
+		return true;
+	}
+
+	if (demorecording)
+	{
+		G_StopDemoRecording();
+		return true;
+	}
+
+	return false;
+}
diff --git a/src/g_demo.h b/src/g_demo.h
new file mode 100644
index 0000000000000000000000000000000000000000..df25042c48030402622a71a611c8a7f2a53649e7
--- /dev/null
+++ b/src/g_demo.h
@@ -0,0 +1,86 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  g_demo.h
+/// \brief Demo recording and playback
+
+#ifndef __G_DEMO__
+#define __G_DEMO__
+
+#include "doomdef.h"
+#include "doomstat.h"
+#include "d_event.h"
+
+// ======================================
+// DEMO playback/recording related stuff.
+// ======================================
+
+// demoplaying back and demo recording
+extern boolean demoplayback, titledemo, demorecording, timingdemo;
+extern tic_t demostarttime;
+
+// Quit after playing a demo from cmdline.
+extern boolean singledemo;
+extern boolean demo_start;
+extern boolean demosynced;
+
+extern mobj_t *metalplayback;
+
+// Only called by startup code.
+void G_RecordDemo(const char *name);
+void G_RecordMetal(void);
+void G_BeginRecording(void);
+void G_BeginMetal(void);
+
+// Only called by shutdown code.
+void G_SetDemoTime(UINT32 ptime, UINT32 pscore, UINT16 prings);
+UINT8 G_CmpDemoTime(char *oldname, char *newname);
+
+typedef enum
+{
+	GHC_NORMAL = 0,
+	GHC_SUPER,
+	GHC_FIREFLOWER,
+	GHC_INVINCIBLE,
+	GHC_NIGHTSSKIN, // not actually a colour
+	GHC_RETURNSKIN // ditto
+} ghostcolor_t;
+
+// Record/playback tics
+void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
+void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
+void G_GhostAddThok(void);
+void G_GhostAddSpin(void);
+void G_GhostAddRev(void);
+void G_GhostAddColor(ghostcolor_t color);
+void G_GhostAddFlip(void);
+void G_GhostAddScale(fixed_t scale);
+void G_GhostAddHit(mobj_t *victim);
+void G_WriteGhostTic(mobj_t *ghost);
+void G_ConsGhostTic(void);
+void G_GhostTicker(void);
+void G_ReadMetalTic(mobj_t *metal);
+void G_WriteMetalTic(mobj_t *metal);
+void G_SaveMetal(UINT8 **buffer);
+void G_LoadMetal(UINT8 **buffer);
+
+void G_DeferedPlayDemo(const char *demo);
+void G_DoPlayDemo(char *defdemoname);
+void G_TimeDemo(const char *name);
+void G_AddGhost(char *defdemoname);
+void G_FreeGhosts(void);
+void G_DoPlayMetal(void);
+void G_DoneLevelLoad(void);
+void G_StopMetalDemo(void);
+ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill);
+void G_StopDemo(void);
+boolean G_CheckDemoStatus(void);
+
+#endif // __G_DEMO__
diff --git a/src/g_game.c b/src/g_game.c
index bf73f6ce24cde224f797a294b36bde86b356f509..5bcf9f580ad2612fa64619499c0e634e8ad630ba 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -27,6 +27,7 @@
 #include "r_main.h"
 #include "s_sound.h"
 #include "g_game.h"
+#include "g_demo.h"
 #include "m_cheat.h"
 #include "m_misc.h"
 #include "m_menu.h"
@@ -41,11 +42,9 @@
 #include "r_skins.h"
 #include "y_inter.h"
 #include "v_video.h"
-#include "dehacked.h" // get_number (for ghost thok)
 #include "lua_hook.h"
 #include "b_bot.h"
 #include "m_cond.h" // condition sets
-#include "md5.h" // demo checksums
 
 #include "lua_hud.h"
 
@@ -103,11 +102,6 @@ UINT8  numDemos      = 0;
 UINT32 demoDelayTime = 15*TICRATE;
 UINT32 demoIdleTime  = 3*TICRATE;
 
-boolean timingdemo; // if true, exit with report on completion
-boolean nodrawers; // for comparative timing purposes
-boolean noblit; // for comparative timing purposes
-tic_t demostarttime; // for comparative timing purposes
-
 boolean netgame; // only true if packets are broadcast
 boolean multiplayer;
 boolean playeringame[MAXPLAYERS];
@@ -225,6 +219,7 @@ UINT8 ammoremovaltics = 2*TICRATE;
 
 UINT8 use1upSound = 0;
 UINT8 maxXtraLife = 2; // Max extra lives from rings
+UINT8 useContinues = 0; // Set to 1 to enable continues outside of no-save scenarioes
 
 UINT8 introtoplay;
 UINT8 creditscutscene;
@@ -254,57 +249,12 @@ UINT32 timesBeaten;
 UINT32 timesBeatenWithEmeralds;
 UINT32 timesBeatenUltimate;
 
-static char demoname[64];
-boolean demorecording;
-boolean demoplayback;
-boolean titledemo; // Title Screen demo can be cancelled by any key
-static UINT8 *demobuffer = NULL;
-static UINT8 *demo_p, *demotime_p;
-static UINT8 *demoend;
-static UINT8 demoflags;
-static UINT16 demoversion;
-boolean singledemo; // quit after playing a demo from cmdline
-boolean demo_start; // don't start playing demo right away
-boolean demosynced = true; // console warning message
-
-boolean metalrecording; // recording as metal sonic
-mobj_t *metalplayback;
-static UINT8 *metalbuffer = NULL;
-static UINT8 *metal_p;
-static UINT16 metalversion;
-
 typedef struct joystickvector2_s
 {
 	INT32 xaxis;
 	INT32 yaxis;
 } joystickvector2_t;
 
-// extra data stuff (events registered this frame while recording)
-static struct {
-	UINT8 flags; // EZT flags
-
-	// EZT_COLOR
-	UINT8 color, lastcolor;
-
-	// EZT_SCALE
-	fixed_t scale, lastscale;
-
-	// EZT_HIT
-	UINT16 hits;
-	mobj_t **hitlist;
-} ghostext;
-
-// Your naming conventions are stupid and useless.
-// There is no conflict here.
-typedef struct demoghost {
-	UINT8 checksum[16];
-	UINT8 *buffer, *p, color, fadein;
-	UINT16 version;
-	mobj_t oldmo, *mo;
-	struct demoghost *next;
-} demoghost;
-demoghost *ghosts = NULL;
-
 boolean precache = true; // if true, load all graphics at start
 
 INT16 prevmap, nextmap;
@@ -1100,8 +1050,6 @@ static void G_HandleAxisDeadZone(UINT8 splitnum, joystickvector2_t *joystickvect
 	}
 }
 
-
-
 //
 // G_BuildTiccmd
 // Builds a ticcmd from all of the available inputs
@@ -1719,6 +1667,25 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	}
 }
 
+ticcmd_t *G_CopyTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n)
+{
+	return M_Memcpy(dest, src, n*sizeof(*src));
+}
+
+ticcmd_t *G_MoveTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n)
+{
+	size_t i;
+	for (i = 0; i < n; i++)
+	{
+		dest[i].forwardmove = src[i].forwardmove;
+		dest[i].sidemove = src[i].sidemove;
+		dest[i].angleturn = SHORT(src[i].angleturn);
+		dest[i].aiming = (INT16)SHORT(src[i].aiming);
+		dest[i].buttons = (UINT16)SHORT(src[i].buttons);
+	}
+	return dest;
+}
+
 // User has designated that they want
 // analog ON, so tell the game to stop
 // fudging with it.
@@ -3893,7 +3860,8 @@ static void G_DoContinued(void)
 	I_Assert(!netgame && !multiplayer);
 	I_Assert(pl->continues > 0);
 
-	pl->continues--;
+	if (pl->continues)
+		pl->continues--;
 
 	// Reset score
 	pl->score = 0;
@@ -4512,7 +4480,7 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, b
 
 	if (demoplayback)
 		COM_BufAddText("stopdemo\n");
-	ghosts = NULL;
+	G_FreeGhosts(); // TODO: do we actually need to do this?
 
 	// this leave the actual game if needed
 	SV_StartSinglePlayerServer();
@@ -4661,7 +4629,7 @@ char *G_BuildMapTitle(INT32 mapnum)
 	{
 		size_t len = 1;
 		const char *zonetext = NULL;
-		const INT32 actnum = mapheaderinfo[mapnum-1]->actnum;
+		const UINT8 actnum = mapheaderinfo[mapnum-1]->actnum;
 
 		len += strlen(mapheaderinfo[mapnum-1]->lvlttl);
 		if (!(mapheaderinfo[mapnum-1]->levelflags & LF_NOZONE))
@@ -4921,2430 +4889,6 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep)
 	return newmapnum;
 }
 
-//
-// DEMO RECORDING
-//
-
-#define DEMOVERSION 0x000c
-#define DEMOHEADER  "\xF0" "SRB2Replay" "\x0F"
-
-#define DF_GHOST        0x01 // This demo contains ghost data too!
-#define DF_RECORDATTACK 0x02 // This demo is from record attack and contains its final completion time, score, and rings!
-#define DF_NIGHTSATTACK 0x04 // This demo is from NiGHTS attack and contains its time left, score, and mares!
-#define DF_ATTACKMASK   0x06 // This demo is from ??? attack and contains ???
-#define DF_ATTACKSHIFT  1
-
-// For demos
-#define ZT_FWD     0x01
-#define ZT_SIDE    0x02
-#define ZT_ANGLE   0x04
-#define ZT_BUTTONS 0x08
-#define ZT_AIMING  0x10
-#define DEMOMARKER 0x80 // demoend
-#define METALDEATH 0x44
-#define METALSNICE 0x69
-
-static ticcmd_t oldcmd;
-
-// For Metal Sonic and time attack ghosts
-#define GZT_XYZ    0x01
-#define GZT_MOMXY  0x02
-#define GZT_MOMZ   0x04
-#define GZT_ANGLE  0x08
-#define GZT_FRAME  0x10 // Animation frame
-#define GZT_SPR2   0x20 // Player animations
-#define GZT_EXTRA  0x40
-#define GZT_FOLLOW 0x80 // Followmobj
-
-// GZT_EXTRA flags
-#define EZT_THOK   0x01 // Spawned a thok object
-#define EZT_SPIN   0x02 // Because one type of thok object apparently wasn't enough
-#define EZT_REV    0x03 // And two types wasn't enough either yet
-#define EZT_THOKMASK 0x03
-#define EZT_COLOR  0x04 // Changed color (Super transformation, Mario fireflowers/invulnerability, etc.)
-#define EZT_FLIP   0x08 // Reversed gravity
-#define EZT_SCALE  0x10 // Changed size
-#define EZT_HIT    0x20 // Damaged a mobj
-#define EZT_SPRITE 0x40 // Changed sprite set completely out of PLAY (NiGHTS, SOCs, whatever)
-#define EZT_HEIGHT 0x80 // Changed height
-
-// GZT_FOLLOW flags
-#define FZT_SPAWNED 0x01 // just been spawned
-#define FZT_SKIN 0x02 // has skin
-#define FZT_LINKDRAW 0x04 // has linkdraw (combine with spawned only)
-#define FZT_COLORIZED 0x08 // colorized (ditto)
-#define FZT_SCALE 0x10 // different scale to object
-// spare FZT slots 0x20 to 0x80
-
-static mobj_t oldmetal, oldghost;
-
-void G_SaveMetal(UINT8 **buffer)
-{
-	I_Assert(buffer != NULL && *buffer != NULL);
-
-	WRITEUINT32(*buffer, metal_p - metalbuffer);
-}
-
-void G_LoadMetal(UINT8 **buffer)
-{
-	I_Assert(buffer != NULL && *buffer != NULL);
-
-	G_DoPlayMetal();
-	metal_p = metalbuffer + READUINT32(*buffer);
-}
-
-ticcmd_t *G_CopyTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n)
-{
-	return M_Memcpy(dest, src, n*sizeof(*src));
-}
-
-ticcmd_t *G_MoveTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n)
-{
-	size_t i;
-	for (i = 0; i < n; i++)
-	{
-		dest[i].forwardmove = src[i].forwardmove;
-		dest[i].sidemove = src[i].sidemove;
-		dest[i].angleturn = SHORT(src[i].angleturn);
-		dest[i].aiming = (INT16)SHORT(src[i].aiming);
-		dest[i].buttons = (UINT16)SHORT(src[i].buttons);
-	}
-	return dest;
-}
-
-void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum)
-{
-	UINT8 ziptic;
-	(void)playernum;
-
-	if (!demo_p || !demo_start)
-		return;
-	ziptic = READUINT8(demo_p);
-
-	if (ziptic & ZT_FWD)
-		oldcmd.forwardmove = READSINT8(demo_p);
-	if (ziptic & ZT_SIDE)
-		oldcmd.sidemove = READSINT8(demo_p);
-	if (ziptic & ZT_ANGLE)
-		oldcmd.angleturn = READINT16(demo_p);
-	if (ziptic & ZT_BUTTONS)
-		oldcmd.buttons = (oldcmd.buttons & (BT_CAMLEFT|BT_CAMRIGHT)) | (READUINT16(demo_p) & ~(BT_CAMLEFT|BT_CAMRIGHT));
-	if (ziptic & ZT_AIMING)
-		oldcmd.aiming = READINT16(demo_p);
-
-	G_CopyTiccmd(cmd, &oldcmd, 1);
-
-	if (!(demoflags & DF_GHOST) && *demo_p == DEMOMARKER)
-	{
-		// end of demo data stream
-		G_CheckDemoStatus();
-		return;
-	}
-}
-
-void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum)
-{
-	char ziptic = 0;
-	UINT8 *ziptic_p;
-	(void)playernum;
-
-	if (!demo_p)
-		return;
-	ziptic_p = demo_p++; // the ziptic, written at the end of this function
-
-	if (cmd->forwardmove != oldcmd.forwardmove)
-	{
-		WRITEUINT8(demo_p,cmd->forwardmove);
-		oldcmd.forwardmove = cmd->forwardmove;
-		ziptic |= ZT_FWD;
-	}
-
-	if (cmd->sidemove != oldcmd.sidemove)
-	{
-		WRITEUINT8(demo_p,cmd->sidemove);
-		oldcmd.sidemove = cmd->sidemove;
-		ziptic |= ZT_SIDE;
-	}
-
-	if (cmd->angleturn != oldcmd.angleturn)
-	{
-		WRITEINT16(demo_p,cmd->angleturn);
-		oldcmd.angleturn = cmd->angleturn;
-		ziptic |= ZT_ANGLE;
-	}
-
-	if (cmd->buttons != oldcmd.buttons)
-	{
-		WRITEUINT16(demo_p,cmd->buttons);
-		oldcmd.buttons = cmd->buttons;
-		ziptic |= ZT_BUTTONS;
-	}
-
-	if (cmd->aiming != oldcmd.aiming)
-	{
-		WRITEINT16(demo_p,cmd->aiming);
-		oldcmd.aiming = cmd->aiming;
-		ziptic |= ZT_AIMING;
-	}
-
-	*ziptic_p = ziptic;
-
-	// attention here for the ticcmd size!
-	// latest demos with mouse aiming byte in ticcmd
-	if (!(demoflags & DF_GHOST) && ziptic_p > demoend - 9)
-	{
-		G_CheckDemoStatus(); // no more space
-		return;
-	}
-}
-
-void G_GhostAddThok(void)
-{
-	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
-		return;
-	ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_THOK;
-}
-
-void G_GhostAddSpin(void)
-{
-	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
-		return;
-	ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_SPIN;
-}
-
-void G_GhostAddRev(void)
-{
-	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
-		return;
-	ghostext.flags = (ghostext.flags & ~EZT_THOKMASK) | EZT_REV;
-}
-
-void G_GhostAddFlip(void)
-{
-	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
-		return;
-	ghostext.flags |= EZT_FLIP;
-}
-
-void G_GhostAddColor(ghostcolor_t color)
-{
-	if (!demorecording || !(demoflags & DF_GHOST))
-		return;
-	if (ghostext.lastcolor == (UINT8)color)
-	{
-		ghostext.flags &= ~EZT_COLOR;
-		return;
-	}
-	ghostext.flags |= EZT_COLOR;
-	ghostext.color = (UINT8)color;
-}
-
-void G_GhostAddScale(fixed_t scale)
-{
-	if (!metalrecording && (!demorecording || !(demoflags & DF_GHOST)))
-		return;
-	if (ghostext.lastscale == scale)
-	{
-		ghostext.flags &= ~EZT_SCALE;
-		return;
-	}
-	ghostext.flags |= EZT_SCALE;
-	ghostext.scale = scale;
-}
-
-void G_GhostAddHit(mobj_t *victim)
-{
-	if (!demorecording || !(demoflags & DF_GHOST))
-		return;
-	ghostext.flags |= EZT_HIT;
-	ghostext.hits++;
-	ghostext.hitlist = Z_Realloc(ghostext.hitlist, ghostext.hits * sizeof(mobj_t *), PU_LEVEL, NULL);
-	ghostext.hitlist[ghostext.hits-1] = victim;
-}
-
-void G_WriteGhostTic(mobj_t *ghost)
-{
-	char ziptic = 0;
-	UINT8 *ziptic_p;
-	UINT32 i;
-	fixed_t height;
-
-	if (!demo_p)
-		return;
-	if (!(demoflags & DF_GHOST))
-		return; // No ghost data to write.
-
-	ziptic_p = demo_p++; // the ziptic, written at the end of this function
-
-	#define MAXMOM (0xFFFF<<8)
-
-	// GZT_XYZ is only useful if you've moved 256 FRACUNITS or more in a single tic.
-	if (abs(ghost->x-oldghost.x) > MAXMOM
-	|| abs(ghost->y-oldghost.y) > MAXMOM
-	|| abs(ghost->z-oldghost.z) > MAXMOM)
-	{
-		oldghost.x = ghost->x;
-		oldghost.y = ghost->y;
-		oldghost.z = ghost->z;
-		ziptic |= GZT_XYZ;
-		WRITEFIXED(demo_p,oldghost.x);
-		WRITEFIXED(demo_p,oldghost.y);
-		WRITEFIXED(demo_p,oldghost.z);
-	}
-	else
-	{
-		// For moving normally:
-		// Store one full byte of movement, plus one byte of fractional movement.
-		INT16 momx = (INT16)((ghost->x-oldghost.x)>>8);
-		INT16 momy = (INT16)((ghost->y-oldghost.y)>>8);
-		if (momx != oldghost.momx
-		|| momy != oldghost.momy)
-		{
-			oldghost.momx = momx;
-			oldghost.momy = momy;
-			ziptic |= GZT_MOMXY;
-			WRITEINT16(demo_p,momx);
-			WRITEINT16(demo_p,momy);
-		}
-		momx = (INT16)((ghost->z-oldghost.z)>>8);
-		if (momx != oldghost.momz)
-		{
-			oldghost.momz = momx;
-			ziptic |= GZT_MOMZ;
-			WRITEINT16(demo_p,momx);
-		}
-
-		// This SHOULD set oldghost.x/y/z to match ghost->x/y/z
-		// but it keeps the fractional loss of one byte,
-		// so it will hopefully be made up for in future tics.
-		oldghost.x += oldghost.momx<<8;
-		oldghost.y += oldghost.momy<<8;
-		oldghost.z += oldghost.momz<<8;
-	}
-
-	#undef MAXMOM
-
-	// Only store the 8 most relevant bits of angle
-	// because exact values aren't too easy to discern to begin with when only 8 angles have different sprites
-	// and it does not affect this mode of movement at all anyway.
-	if (ghost->player && ghost->player->drawangle>>24 != oldghost.angle)
-	{
-		oldghost.angle = ghost->player->drawangle>>24;
-		ziptic |= GZT_ANGLE;
-		WRITEUINT8(demo_p,oldghost.angle);
-	}
-
-	// Store the sprite frame.
-	if ((ghost->frame & FF_FRAMEMASK) != oldghost.frame)
-	{
-		oldghost.frame = (ghost->frame & FF_FRAMEMASK);
-		ziptic |= GZT_FRAME;
-		WRITEUINT8(demo_p,oldghost.frame);
-	}
-
-	if (ghost->sprite == SPR_PLAY
-	&& ghost->sprite2 != oldghost.sprite2)
-	{
-		oldghost.sprite2 = ghost->sprite2;
-		ziptic |= GZT_SPR2;
-		WRITEUINT8(demo_p,oldghost.sprite2);
-	}
-
-	// Check for sprite set changes
-	if (ghost->sprite != oldghost.sprite)
-	{
-		oldghost.sprite = ghost->sprite;
-		ghostext.flags |= EZT_SPRITE;
-	}
-
-	if ((height = FixedDiv(ghost->height, ghost->scale)) != oldghost.height)
-	{
-		oldghost.height = height;
-		ghostext.flags |= EZT_HEIGHT;
-	}
-
-	if (ghostext.flags)
-	{
-		ziptic |= GZT_EXTRA;
-
-		if (ghostext.color == ghostext.lastcolor)
-			ghostext.flags &= ~EZT_COLOR;
-		if (ghostext.scale == ghostext.lastscale)
-			ghostext.flags &= ~EZT_SCALE;
-
-		WRITEUINT8(demo_p,ghostext.flags);
-		if (ghostext.flags & EZT_COLOR)
-		{
-			WRITEUINT8(demo_p,ghostext.color);
-			ghostext.lastcolor = ghostext.color;
-		}
-		if (ghostext.flags & EZT_SCALE)
-		{
-			WRITEFIXED(demo_p,ghostext.scale);
-			ghostext.lastscale = ghostext.scale;
-		}
-		if (ghostext.flags & EZT_HIT)
-		{
-			WRITEUINT16(demo_p,ghostext.hits);
-			for (i = 0; i < ghostext.hits; i++)
-			{
-				mobj_t *mo = ghostext.hitlist[i];
-				//WRITEUINT32(demo_p,UINT32_MAX); // reserved for some method of determining exactly which mobj this is. (mobjnum doesn't work here.)
-				WRITEUINT32(demo_p,mo->type);
-				WRITEUINT16(demo_p,(UINT16)mo->health);
-				WRITEFIXED(demo_p,mo->x);
-				WRITEFIXED(demo_p,mo->y);
-				WRITEFIXED(demo_p,mo->z);
-				WRITEANGLE(demo_p,mo->angle);
-			}
-			Z_Free(ghostext.hitlist);
-			ghostext.hits = 0;
-			ghostext.hitlist = NULL;
-		}
-		if (ghostext.flags & EZT_SPRITE)
-			WRITEUINT16(demo_p,oldghost.sprite);
-		if (ghostext.flags & EZT_HEIGHT)
-		{
-			height >>= FRACBITS;
-			WRITEINT16(demo_p, height);
-		}
-		ghostext.flags = 0;
-	}
-
-	if (ghost->player && ghost->player->followmobj && !(ghost->player->followmobj->sprite == SPR_NULL || (ghost->player->followmobj->flags2 & MF2_DONTDRAW))) // bloats tails runs but what can ya do
-	{
-		INT16 temp;
-		UINT8 *followtic_p = demo_p++;
-		UINT8 followtic = 0;
-
-		ziptic |= GZT_FOLLOW;
-
-		if (ghost->player->followmobj->skin)
-			followtic |= FZT_SKIN;
-
-		if (!(oldghost.flags2 & MF2_AMBUSH))
-		{
-			followtic |= FZT_SPAWNED;
-			WRITEINT16(demo_p,ghost->player->followmobj->info->height>>FRACBITS);
-			if (ghost->player->followmobj->flags2 & MF2_LINKDRAW)
-				followtic |= FZT_LINKDRAW;
-			if (ghost->player->followmobj->colorized)
-				followtic |= FZT_COLORIZED;
-			if (followtic & FZT_SKIN)
-				WRITEUINT8(demo_p,(UINT8)(((skin_t *)(ghost->player->followmobj->skin))-skins));
-			oldghost.flags2 |= MF2_AMBUSH;
-		}
-
-		if (ghost->player->followmobj->scale != ghost->scale)
-		{
-			followtic |= FZT_SCALE;
-			WRITEFIXED(demo_p,ghost->player->followmobj->scale);
-		}
-
-		temp = (INT16)((ghost->player->followmobj->x-ghost->x)>>8);
-		WRITEINT16(demo_p,temp);
-		temp = (INT16)((ghost->player->followmobj->y-ghost->y)>>8);
-		WRITEINT16(demo_p,temp);
-		temp = (INT16)((ghost->player->followmobj->z-ghost->z)>>8);
-		WRITEINT16(demo_p,temp);
-		if (followtic & FZT_SKIN)
-			WRITEUINT8(demo_p,ghost->player->followmobj->sprite2);
-		WRITEUINT16(demo_p,ghost->player->followmobj->sprite);
-		WRITEUINT8(demo_p,(ghost->player->followmobj->frame & FF_FRAMEMASK));
-		WRITEUINT8(demo_p,ghost->player->followmobj->color);
-
-		*followtic_p = followtic;
-	}
-	else
-		oldghost.flags2 &= ~MF2_AMBUSH;
-
-	*ziptic_p = ziptic;
-
-	// attention here for the ticcmd size!
-	// latest demos with mouse aiming byte in ticcmd
-	if (demo_p >= demoend - (13 + 9 + 9))
-	{
-		G_CheckDemoStatus(); // no more space
-		return;
-	}
-}
-
-// Uses ghost data to do consistency checks on your position.
-// This fixes desynchronising demos when fighting eggman.
-void G_ConsGhostTic(void)
-{
-	UINT8 ziptic;
-	UINT16 px,py,pz,gx,gy,gz;
-	mobj_t *testmo;
-
-	if (!demo_p || !demo_start)
-		return;
-	if (!(demoflags & DF_GHOST))
-		return; // No ghost data to use.
-
-	testmo = players[0].mo;
-
-	// Grab ghost data.
-	ziptic = READUINT8(demo_p);
-	if (ziptic & GZT_XYZ)
-	{
-		oldghost.x = READFIXED(demo_p);
-		oldghost.y = READFIXED(demo_p);
-		oldghost.z = READFIXED(demo_p);
-	}
-	else
-	{
-		if (ziptic & GZT_MOMXY)
-		{
-			oldghost.momx = READINT16(demo_p)<<8;
-			oldghost.momy = READINT16(demo_p)<<8;
-		}
-		if (ziptic & GZT_MOMZ)
-			oldghost.momz = READINT16(demo_p)<<8;
-		oldghost.x += oldghost.momx;
-		oldghost.y += oldghost.momy;
-		oldghost.z += oldghost.momz;
-	}
-	if (ziptic & GZT_ANGLE)
-		demo_p++;
-	if (ziptic & GZT_FRAME)
-		demo_p++;
-	if (ziptic & GZT_SPR2)
-		demo_p++;
-
-	if (ziptic & GZT_EXTRA)
-	{ // But wait, there's more!
-		UINT8 xziptic = READUINT8(demo_p);
-		if (xziptic & EZT_COLOR)
-			demo_p++;
-		if (xziptic & EZT_SCALE)
-			demo_p += sizeof(fixed_t);
-		if (xziptic & EZT_HIT)
-		{ // Resync mob damage.
-			UINT16 i, count = READUINT16(demo_p);
-			thinker_t *th;
-			mobj_t *mobj;
-
-			UINT32 type;
-			UINT16 health;
-			fixed_t x;
-			fixed_t y;
-			fixed_t z;
-
-			for (i = 0; i < count; i++)
-			{
-				//demo_p += 4; // reserved.
-				type = READUINT32(demo_p);
-				health = READUINT16(demo_p);
-				x = READFIXED(demo_p);
-				y = READFIXED(demo_p);
-				z = READFIXED(demo_p);
-				demo_p += sizeof(angle_t); // angle, unnecessary for cons.
-
-				mobj = NULL;
-				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
-				{
-					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;
-				}
-				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"));
-					demosynced = false;
-					P_DamageMobj(mobj, players[0].mo, players[0].mo, 1, 0);
-				}
-			}
-		}
-		if (xziptic & EZT_SPRITE)
-			demo_p += sizeof(UINT16);
-		if (xziptic & EZT_HEIGHT)
-			demo_p += sizeof(INT16);
-	}
-
-	if (ziptic & GZT_FOLLOW)
-	{ // Even more...
-		UINT8 followtic = READUINT8(demo_p);
-		if (followtic & FZT_SPAWNED)
-		{
-			demo_p += sizeof(INT16);
-			if (followtic & FZT_SKIN)
-				demo_p++;
-		}
-		if (followtic & FZT_SCALE)
-			demo_p += sizeof(fixed_t);
-		demo_p += sizeof(INT16);
-		demo_p += sizeof(INT16);
-		demo_p += sizeof(INT16);
-		if (followtic & FZT_SKIN)
-			demo_p++;
-		demo_p += sizeof(UINT16);
-		demo_p++;
-		demo_p++;
-	}
-
-	// Re-synchronise
-	px = testmo->x>>FRACBITS;
-	py = testmo->y>>FRACBITS;
-	pz = testmo->z>>FRACBITS;
-	gx = oldghost.x>>FRACBITS;
-	gy = oldghost.y>>FRACBITS;
-	gz = oldghost.z>>FRACBITS;
-
-	if (px != gx || py != gy || pz != gz)
-	{
-		if (demosynced)
-			CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced!\n"));
-		demosynced = false;
-
-		P_UnsetThingPosition(testmo);
-		testmo->x = oldghost.x;
-		testmo->y = oldghost.y;
-		P_SetThingPosition(testmo);
-		testmo->z = oldghost.z;
-	}
-
-	if (*demo_p == DEMOMARKER)
-	{
-		// end of demo data stream
-		G_CheckDemoStatus();
-		return;
-	}
-}
-
-void G_GhostTicker(void)
-{
-	demoghost *g,*p;
-	for(g = ghosts, p = NULL; g; g = g->next)
-	{
-		// Skip normal demo data.
-		UINT8 ziptic = READUINT8(g->p);
-		UINT8 xziptic = 0;
-		if (ziptic & ZT_FWD)
-			g->p++;
-		if (ziptic & ZT_SIDE)
-			g->p++;
-		if (ziptic & ZT_ANGLE)
-			g->p += 2;
-		if (ziptic & ZT_BUTTONS)
-			g->p += 2;
-		if (ziptic & ZT_AIMING)
-			g->p += 2;
-
-		// Grab ghost data.
-		ziptic = READUINT8(g->p);
-		if (ziptic & GZT_XYZ)
-		{
-			g->oldmo.x = READFIXED(g->p);
-			g->oldmo.y = READFIXED(g->p);
-			g->oldmo.z = READFIXED(g->p);
-		}
-		else
-		{
-			if (ziptic & GZT_MOMXY)
-			{
-				g->oldmo.momx = READINT16(g->p)<<8;
-				g->oldmo.momy = READINT16(g->p)<<8;
-			}
-			if (ziptic & GZT_MOMZ)
-				g->oldmo.momz = READINT16(g->p)<<8;
-			g->oldmo.x += g->oldmo.momx;
-			g->oldmo.y += g->oldmo.momy;
-			g->oldmo.z += g->oldmo.momz;
-		}
-		if (ziptic & GZT_ANGLE)
-			g->mo->angle = READUINT8(g->p)<<24;
-		if (ziptic & GZT_FRAME)
-			g->oldmo.frame = READUINT8(g->p);
-		if (ziptic & GZT_SPR2)
-			g->oldmo.sprite2 = READUINT8(g->p);
-
-		// Update ghost
-		P_UnsetThingPosition(g->mo);
-		g->mo->x = g->oldmo.x;
-		g->mo->y = g->oldmo.y;
-		g->mo->z = g->oldmo.z;
-		P_SetThingPosition(g->mo);
-		g->mo->frame = g->oldmo.frame | tr_trans30<<FF_TRANSSHIFT;
-		if (g->fadein)
-		{
-			g->mo->frame += (((--g->fadein)/6)<<FF_TRANSSHIFT); // this calc never exceeds 9 unless g->fadein is bad, and it's only set once, so...
-			g->mo->flags2 &= ~MF2_DONTDRAW;
-		}
-		g->mo->sprite2 = g->oldmo.sprite2;
-
-		if (ziptic & GZT_EXTRA)
-		{ // But wait, there's more!
-			xziptic = READUINT8(g->p);
-			if (xziptic & EZT_COLOR)
-			{
-				g->color = READUINT8(g->p);
-				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;
-				// Handled below
-				case GHC_SUPER:
-				case GHC_INVINCIBLE:
-					break;
-				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)
-				g->mo->eflags ^= MFE_VERTICALFLIP;
-			if (xziptic & EZT_SCALE)
-			{
-				g->mo->destscale = READFIXED(g->p);
-				if (g->mo->destscale != g->mo->scale)
-					P_SetScale(g->mo, g->mo->destscale);
-			}
-			if (xziptic & EZT_THOKMASK)
-			{ // Let's only spawn ONE of these per frame, thanks.
-				mobj_t *mobj;
-				INT32 type = -1;
-				if (g->mo->skin)
-				{
-					skin_t *skin = (skin_t *)g->mo->skin;
-					switch (xziptic & EZT_THOKMASK)
-					{
-					case EZT_THOK:
-						type = skin->thokitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].painchance : (UINT32)skin->thokitem;
-						break;
-					case EZT_SPIN:
-						type = skin->spinitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].damage : (UINT32)skin->spinitem;
-						break;
-					case EZT_REV:
-						type = skin->revitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].raisestate : (UINT32)skin->revitem;
-						break;
-					}
-				}
-				if (type != MT_NULL)
-				{
-					if (type == MT_GHOST)
-					{
-						mobj = P_SpawnGhostMobj(g->mo); // does a large portion of the work for us
-						mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<<FF_TRANSSHIFT; // P_SpawnGhostMobj sets trans50, we want trans60
-					}
-					else
-					{
-						mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, -FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK);
-						mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
-						mobj->frame = (states[mobjinfo[type].spawnstate].frame & FF_FRAMEMASK) | tr_trans60<<FF_TRANSSHIFT;
-						mobj->color = g->mo->color;
-						mobj->skin = g->mo->skin;
-						P_SetScale(mobj, (mobj->destscale = g->mo->scale));
-
-						if (type == MT_THOK) // spintrail-specific modification for MT_THOK
-						{
-							mobj->frame = FF_TRANS80;
-							mobj->fuse = mobj->tics;
-						}
-						mobj->tics = -1; // nope.
-					}
-					mobj->floorz = mobj->z;
-					mobj->ceilingz = mobj->z+mobj->height;
-					P_UnsetThingPosition(mobj);
-					mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
-					P_SetThingPosition(mobj);
-					if (!mobj->fuse)
-						mobj->fuse = 8;
-					P_SetTarget(&mobj->target, g->mo);
-				}
-			}
-			if (xziptic & EZT_HIT)
-			{ // Spawn hit poofs for killing things!
-				UINT16 i, count = READUINT16(g->p), health;
-				UINT32 type;
-				fixed_t x,y,z;
-				angle_t angle;
-				mobj_t *poof;
-				for (i = 0; i < count; i++)
-				{
-					//g->p += 4; // reserved
-					type = READUINT32(g->p);
-					health = READUINT16(g->p);
-					x = READFIXED(g->p);
-					y = READFIXED(g->p);
-					z = READFIXED(g->p);
-					angle = READANGLE(g->p);
-					if (!(mobjinfo[type].flags & MF_SHOOTABLE)
-					|| !(mobjinfo[type].flags & (MF_ENEMY|MF_MONITOR))
-					|| health != 0 || i >= 4) // only spawn for the first 4 hits per frame, to prevent ghosts from splode-spamming too bad.
-						continue;
-					poof = P_SpawnMobj(x, y, z, MT_GHOST);
-					poof->angle = angle;
-					poof->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
-					poof->health = 0;
-					P_SetMobjStateNF(poof, S_XPLD1);
-				}
-			}
-			if (xziptic & EZT_SPRITE)
-				g->mo->sprite = READUINT16(g->p);
-			if (xziptic & EZT_HEIGHT)
-			{
-				fixed_t temp = READINT16(g->p)<<FRACBITS;
-				g->mo->height = FixedMul(temp, g->mo->scale);
-			}
-		}
-
-		// Tick ghost colors (Super and Mario Invincibility flashing)
-		switch(g->color)
-		{
-		case GHC_SUPER: // Super (P_DoSuperStuff)
-			if (g->mo->skin)
-			{
-				skin_t *skin = (skin_t *)g->mo->skin;
-				g->mo->color = skin->supercolor;
-			}
-			else
-				g->mo->color = SKINCOLOR_SUPERGOLD1;
-			g->mo->color += abs( ( (signed)( (unsigned)leveltime >> 1 ) % 9) - 4);
-			break;
-		case GHC_INVINCIBLE: // Mario invincibility (P_CheckInvincibilityTimer)
-			g->mo->color = (UINT8)(SKINCOLOR_RUBY + (leveltime % (MAXSKINCOLORS - SKINCOLOR_RUBY))); // Passes through all saturated colours
-			break;
-		default:
-			break;
-		}
-
-#define follow g->mo->tracer
-		if (ziptic & GZT_FOLLOW)
-		{ // Even more...
-			UINT8 followtic = READUINT8(g->p);
-			fixed_t temp;
-			if (followtic & FZT_SPAWNED)
-			{
-				if (follow)
-					P_RemoveMobj(follow);
-				P_SetTarget(&follow, P_SpawnMobjFromMobj(g->mo, 0, 0, 0, MT_GHOST));
-				P_SetTarget(&follow->tracer, g->mo);
-				follow->tics = -1;
-				temp = READINT16(g->p)<<FRACBITS;
-				follow->height = FixedMul(follow->scale, temp);
-
-				if (followtic & FZT_LINKDRAW)
-					follow->flags2 |= MF2_LINKDRAW;
-
-				if (followtic & FZT_COLORIZED)
-					follow->colorized = true;
-
-				if (followtic & FZT_SKIN)
-					follow->skin = &skins[READUINT8(g->p)];
-			}
-			if (follow)
-			{
-				if (followtic & FZT_SCALE)
-					follow->destscale = READFIXED(g->p);
-				else
-					follow->destscale = g->mo->destscale;
-				if (follow->destscale != follow->scale)
-					P_SetScale(follow, follow->destscale);
-
-				P_UnsetThingPosition(follow);
-				temp = READINT16(g->p)<<8;
-				follow->x = g->mo->x + temp;
-				temp = READINT16(g->p)<<8;
-				follow->y = g->mo->y + temp;
-				temp = READINT16(g->p)<<8;
-				follow->z = g->mo->z + temp;
-				P_SetThingPosition(follow);
-				if (followtic & FZT_SKIN)
-					follow->sprite2 = READUINT8(g->p);
-				else
-					follow->sprite2 = 0;
-				follow->sprite = READUINT16(g->p);
-				follow->frame = (READUINT8(g->p)) | (g->mo->frame & FF_TRANSMASK);
-				follow->angle = g->mo->angle;
-				follow->color = READUINT8(g->p);
-
-				if (!(followtic & FZT_SPAWNED))
-				{
-					if (xziptic & EZT_FLIP)
-					{
-						follow->flags2 ^= MF2_OBJECTFLIP;
-						follow->eflags ^= MFE_VERTICALFLIP;
-					}
-				}
-			}
-		}
-		else if (follow)
-		{
-			P_RemoveMobj(follow);
-			P_SetTarget(&follow, NULL);
-		}
-		// Demo ends after ghost data.
-		if (*g->p == DEMOMARKER)
-		{
-			g->mo->momx = g->mo->momy = g->mo->momz = 0;
-#if 1 // freeze frame (maybe more useful for time attackers)
-			g->mo->colorized = true;
-			if (follow)
-				follow->colorized = true;
-#else // dissapearing act
-			g->mo->fuse = TICRATE;
-			if (follow)
-				follow->fuse = TICRATE;
-#endif
-			if (p)
-				p->next = g->next;
-			else
-				ghosts = g->next;
-			Z_Free(g);
-			continue;
-		}
-		p = g;
-#undef follow
-	}
-}
-
-void G_ReadMetalTic(mobj_t *metal)
-{
-	UINT8 ziptic;
-	UINT8 xziptic = 0;
-
-	if (!metal_p)
-		return;
-
-	if (!metal->health)
-	{
-		G_StopMetalDemo();
-		return;
-	}
-
-	switch (*metal_p)
-	{
-		case METALSNICE:
-			break;
-		case METALDEATH:
-			if (metal->tracer)
-				P_RemoveMobj(metal->tracer);
-			P_KillMobj(metal, NULL, NULL, 0);
-			/* FALLTHRU */
-		case DEMOMARKER:
-		default:
-			// end of demo data stream
-			G_StopMetalDemo();
-			return;
-	}
-	metal_p++;
-
-	ziptic = READUINT8(metal_p);
-
-	// Read changes from the tic
-	if (ziptic & GZT_XYZ)
-	{
-		P_TeleportMove(metal, READFIXED(metal_p), READFIXED(metal_p), READFIXED(metal_p));
-		oldmetal.x = metal->x;
-		oldmetal.y = metal->y;
-		oldmetal.z = metal->z;
-	}
-	else
-	{
-		if (ziptic & GZT_MOMXY)
-		{
-			oldmetal.momx = READINT16(metal_p)<<8;
-			oldmetal.momy = READINT16(metal_p)<<8;
-		}
-		if (ziptic & GZT_MOMZ)
-			oldmetal.momz = READINT16(metal_p)<<8;
-		oldmetal.x += oldmetal.momx;
-		oldmetal.y += oldmetal.momy;
-		oldmetal.z += oldmetal.momz;
-	}
-	if (ziptic & GZT_ANGLE)
-		metal->angle = READUINT8(metal_p)<<24;
-	if (ziptic & GZT_FRAME)
-		oldmetal.frame = READUINT32(metal_p);
-	if (ziptic & GZT_SPR2)
-		oldmetal.sprite2 = READUINT8(metal_p);
-
-	// Set movement, position, and angle
-	// oldmetal contains where you're supposed to be.
-	metal->momx = oldmetal.momx;
-	metal->momy = oldmetal.momy;
-	metal->momz = oldmetal.momz;
-	P_UnsetThingPosition(metal);
-	metal->x = oldmetal.x;
-	metal->y = oldmetal.y;
-	metal->z = oldmetal.z;
-	P_SetThingPosition(metal);
-	metal->frame = oldmetal.frame;
-	metal->sprite2 = oldmetal.sprite2;
-
-	if (ziptic & GZT_EXTRA)
-	{ // But wait, there's more!
-		xziptic = READUINT8(metal_p);
-		if (xziptic & EZT_FLIP)
-		{
-			metal->eflags ^= MFE_VERTICALFLIP;
-			metal->flags2 ^= MF2_OBJECTFLIP;
-		}
-		if (xziptic & EZT_SCALE)
-		{
-			metal->destscale = READFIXED(metal_p);
-			if (metal->destscale != metal->scale)
-				P_SetScale(metal, metal->destscale);
-		}
-		if (xziptic & EZT_THOKMASK)
-		{ // Let's only spawn ONE of these per frame, thanks.
-			mobj_t *mobj;
-			INT32 type = -1;
-			if (metal->skin)
-			{
-				skin_t *skin = (skin_t *)metal->skin;
-				switch (xziptic & EZT_THOKMASK)
-				{
-				case EZT_THOK:
-					type = skin->thokitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].painchance : (UINT32)skin->thokitem;
-					break;
-				case EZT_SPIN:
-					type = skin->spinitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].damage : (UINT32)skin->spinitem;
-					break;
-				case EZT_REV:
-					type = skin->revitem < 0 ? (UINT32)mobjinfo[MT_PLAYER].raisestate : (UINT32)skin->revitem;
-					break;
-				}
-			}
-			if (type != MT_NULL)
-			{
-				if (type == MT_GHOST)
-				{
-					mobj = P_SpawnGhostMobj(metal); // does a large portion of the work for us
-				}
-				else
-				{
-					mobj = P_SpawnMobjFromMobj(metal, 0, 0, -FixedDiv(FixedMul(metal->info->height, metal->scale) - metal->height,3*FRACUNIT), MT_THOK);
-					mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
-					mobj->frame = states[mobjinfo[type].spawnstate].frame;
-					mobj->angle = metal->angle;
-					mobj->color = metal->color;
-					mobj->skin = metal->skin;
-					P_SetScale(mobj, (mobj->destscale = metal->scale));
-
-					if (type == MT_THOK) // spintrail-specific modification for MT_THOK
-					{
-						mobj->frame = FF_TRANS70;
-						mobj->fuse = mobj->tics;
-					}
-					mobj->tics = -1; // nope.
-				}
-				mobj->floorz = mobj->z;
-				mobj->ceilingz = mobj->z+mobj->height;
-				P_UnsetThingPosition(mobj);
-				mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
-				P_SetThingPosition(mobj);
-				if (!mobj->fuse)
-					mobj->fuse = 8;
-				P_SetTarget(&mobj->target, metal);
-			}
-		}
-		if (xziptic & EZT_SPRITE)
-			metal->sprite = READUINT16(metal_p);
-		if (xziptic & EZT_HEIGHT)
-		{
-			fixed_t temp = READINT16(metal_p)<<FRACBITS;
-			metal->height = FixedMul(temp, metal->scale);
-		}
-	}
-
-#define follow metal->tracer
-		if (ziptic & GZT_FOLLOW)
-		{ // Even more...
-			UINT8 followtic = READUINT8(metal_p);
-			fixed_t temp;
-			if (followtic & FZT_SPAWNED)
-			{
-				if (follow)
-					P_RemoveMobj(follow);
-				P_SetTarget(&follow, P_SpawnMobjFromMobj(metal, 0, 0, 0, MT_GHOST));
-				P_SetTarget(&follow->tracer, metal);
-				follow->tics = -1;
-				temp = READINT16(metal_p)<<FRACBITS;
-				follow->height = FixedMul(follow->scale, temp);
-
-				if (followtic & FZT_LINKDRAW)
-					follow->flags2 |= MF2_LINKDRAW;
-
-				if (followtic & FZT_COLORIZED)
-					follow->colorized = true;
-
-				if (followtic & FZT_SKIN)
-					follow->skin = &skins[READUINT8(metal_p)];
-			}
-			if (follow)
-			{
-				if (followtic & FZT_SCALE)
-					follow->destscale = READFIXED(metal_p);
-				else
-					follow->destscale = metal->destscale;
-				if (follow->destscale != follow->scale)
-					P_SetScale(follow, follow->destscale);
-
-				P_UnsetThingPosition(follow);
-				temp = READINT16(metal_p)<<8;
-				follow->x = metal->x + temp;
-				temp = READINT16(metal_p)<<8;
-				follow->y = metal->y + temp;
-				temp = READINT16(metal_p)<<8;
-				follow->z = metal->z + temp;
-				P_SetThingPosition(follow);
-				if (followtic & FZT_SKIN)
-					follow->sprite2 = READUINT8(metal_p);
-				else
-					follow->sprite2 = 0;
-				follow->sprite = READUINT16(metal_p);
-				follow->frame = READUINT32(metal_p); // NOT & FF_FRAMEMASK here, so 32 bits
-				follow->angle = metal->angle;
-				follow->color = READUINT8(metal_p);
-
-				if (!(followtic & FZT_SPAWNED))
-				{
-					if (xziptic & EZT_FLIP)
-					{
-						follow->flags2 ^= MF2_OBJECTFLIP;
-						follow->eflags ^= MFE_VERTICALFLIP;
-					}
-				}
-			}
-		}
-		else if (follow)
-		{
-			P_RemoveMobj(follow);
-			P_SetTarget(&follow, NULL);
-		}
-#undef follow
-}
-
-void G_WriteMetalTic(mobj_t *metal)
-{
-	UINT8 ziptic = 0;
-	UINT8 *ziptic_p;
-	fixed_t height;
-
-	if (!demo_p) // demo_p will be NULL until the race start linedef executor is activated!
-		return;
-
-	WRITEUINT8(demo_p, METALSNICE);
-	ziptic_p = demo_p++; // the ziptic, written at the end of this function
-
-	#define MAXMOM (0xFFFF<<8)
-
-	// GZT_XYZ is only useful if you've moved 256 FRACUNITS or more in a single tic.
-	if (abs(metal->x-oldmetal.x) > MAXMOM
-	|| abs(metal->y-oldmetal.y) > MAXMOM
-	|| abs(metal->z-oldmetal.z) > MAXMOM)
-	{
-		oldmetal.x = metal->x;
-		oldmetal.y = metal->y;
-		oldmetal.z = metal->z;
-		ziptic |= GZT_XYZ;
-		WRITEFIXED(demo_p,oldmetal.x);
-		WRITEFIXED(demo_p,oldmetal.y);
-		WRITEFIXED(demo_p,oldmetal.z);
-	}
-	else
-	{
-		// For moving normally:
-		// Store one full byte of movement, plus one byte of fractional movement.
-		INT16 momx = (INT16)((metal->x-oldmetal.x)>>8);
-		INT16 momy = (INT16)((metal->y-oldmetal.y)>>8);
-		if (momx != oldmetal.momx
-		|| momy != oldmetal.momy)
-		{
-			oldmetal.momx = momx;
-			oldmetal.momy = momy;
-			ziptic |= GZT_MOMXY;
-			WRITEINT16(demo_p,momx);
-			WRITEINT16(demo_p,momy);
-		}
-		momx = (INT16)((metal->z-oldmetal.z)>>8);
-		if (momx != oldmetal.momz)
-		{
-			oldmetal.momz = momx;
-			ziptic |= GZT_MOMZ;
-			WRITEINT16(demo_p,momx);
-		}
-
-		// This SHOULD set oldmetal.x/y/z to match metal->x/y/z
-		// but it keeps the fractional loss of one byte,
-		// so it will hopefully be made up for in future tics.
-		oldmetal.x += oldmetal.momx<<8;
-		oldmetal.y += oldmetal.momy<<8;
-		oldmetal.z += oldmetal.momz<<8;
-	}
-
-	#undef MAXMOM
-
-	// Only store the 8 most relevant bits of angle
-	// because exact values aren't too easy to discern to begin with when only 8 angles have different sprites
-	// and it does not affect movement at all anyway.
-	if (metal->player && metal->player->drawangle>>24 != oldmetal.angle)
-	{
-		oldmetal.angle = metal->player->drawangle>>24;
-		ziptic |= GZT_ANGLE;
-		WRITEUINT8(demo_p,oldmetal.angle);
-	}
-
-	// Store the sprite frame.
-	if ((metal->frame & FF_FRAMEMASK) != oldmetal.frame)
-	{
-		oldmetal.frame = metal->frame; // NOT & FF_FRAMEMASK here, so 32 bits
-		ziptic |= GZT_FRAME;
-		WRITEUINT32(demo_p,oldmetal.frame);
-	}
-
-	if (metal->sprite == SPR_PLAY
-	&& metal->sprite2 != oldmetal.sprite2)
-	{
-		oldmetal.sprite2 = metal->sprite2;
-		ziptic |= GZT_SPR2;
-		WRITEUINT8(demo_p,oldmetal.sprite2);
-	}
-
-	// Check for sprite set changes
-	if (metal->sprite != oldmetal.sprite)
-	{
-		oldmetal.sprite = metal->sprite;
-		ghostext.flags |= EZT_SPRITE;
-	}
-
-	if ((height = FixedDiv(metal->height, metal->scale)) != oldmetal.height)
-	{
-		oldmetal.height = height;
-		ghostext.flags |= EZT_HEIGHT;
-	}
-
-	if (ghostext.flags & ~(EZT_COLOR|EZT_HIT)) // these two aren't handled by metal ever
-	{
-		ziptic |= GZT_EXTRA;
-
-		if (ghostext.scale == ghostext.lastscale)
-			ghostext.flags &= ~EZT_SCALE;
-
-		WRITEUINT8(demo_p,ghostext.flags);
-		if (ghostext.flags & EZT_SCALE)
-		{
-			WRITEFIXED(demo_p,ghostext.scale);
-			ghostext.lastscale = ghostext.scale;
-		}
-		if (ghostext.flags & EZT_SPRITE)
-			WRITEUINT16(demo_p,oldmetal.sprite);
-		if (ghostext.flags & EZT_HEIGHT)
-		{
-			height >>= FRACBITS;
-			WRITEINT16(demo_p, height);
-		}
-		ghostext.flags = 0;
-	}
-
-	if (metal->player && metal->player->followmobj && !(metal->player->followmobj->sprite == SPR_NULL || (metal->player->followmobj->flags2 & MF2_DONTDRAW)))
-	{
-		INT16 temp;
-		UINT8 *followtic_p = demo_p++;
-		UINT8 followtic = 0;
-
-		ziptic |= GZT_FOLLOW;
-
-		if (metal->player->followmobj->skin)
-			followtic |= FZT_SKIN;
-
-		if (!(oldmetal.flags2 & MF2_AMBUSH))
-		{
-			followtic |= FZT_SPAWNED;
-			WRITEINT16(demo_p,metal->player->followmobj->info->height>>FRACBITS);
-			if (metal->player->followmobj->flags2 & MF2_LINKDRAW)
-				followtic |= FZT_LINKDRAW;
-			if (metal->player->followmobj->colorized)
-				followtic |= FZT_COLORIZED;
-			if (followtic & FZT_SKIN)
-				WRITEUINT8(demo_p,(UINT8)(((skin_t *)(metal->player->followmobj->skin))-skins));
-			oldmetal.flags2 |= MF2_AMBUSH;
-		}
-
-		if (metal->player->followmobj->scale != metal->scale)
-		{
-			followtic |= FZT_SCALE;
-			WRITEFIXED(demo_p,metal->player->followmobj->scale);
-		}
-
-		temp = (INT16)((metal->player->followmobj->x-metal->x)>>8);
-		WRITEINT16(demo_p,temp);
-		temp = (INT16)((metal->player->followmobj->y-metal->y)>>8);
-		WRITEINT16(demo_p,temp);
-		temp = (INT16)((metal->player->followmobj->z-metal->z)>>8);
-		WRITEINT16(demo_p,temp);
-		if (followtic & FZT_SKIN)
-			WRITEUINT8(demo_p,metal->player->followmobj->sprite2);
-		WRITEUINT16(demo_p,metal->player->followmobj->sprite);
-		WRITEUINT32(demo_p,metal->player->followmobj->frame); // NOT & FF_FRAMEMASK here, so 32 bits
-		WRITEUINT8(demo_p,metal->player->followmobj->color);
-
-		*followtic_p = followtic;
-	}
-	else
-		oldmetal.flags2 &= ~MF2_AMBUSH;
-
-	*ziptic_p = ziptic;
-
-	// attention here for the ticcmd size!
-	// latest demos with mouse aiming byte in ticcmd
-	if (demo_p >= demoend - 32)
-	{
-		G_StopMetalRecording(false); // no more space
-		return;
-	}
-}
-
-//
-// G_RecordDemo
-//
-void G_RecordDemo(const char *name)
-{
-	INT32 maxsize;
-
-	strcpy(demoname, name);
-	strcat(demoname, ".lmp");
-	maxsize = 1024*1024;
-	if (M_CheckParm("-maxdemo") && M_IsNextParm())
-		maxsize = atoi(M_GetNextParm()) * 1024;
-//	if (demobuffer)
-//		free(demobuffer);
-	demo_p = NULL;
-	demobuffer = malloc(maxsize);
-	demoend = demobuffer + maxsize;
-
-	demorecording = true;
-}
-
-void G_RecordMetal(void)
-{
-	INT32 maxsize;
-	maxsize = 1024*1024;
-	if (M_CheckParm("-maxdemo") && M_IsNextParm())
-		maxsize = atoi(M_GetNextParm()) * 1024;
-	demo_p = NULL;
-	demobuffer = malloc(maxsize);
-	demoend = demobuffer + maxsize;
-	metalrecording = true;
-}
-
-void G_BeginRecording(void)
-{
-	UINT8 i;
-	char name[16];
-	player_t *player = &players[consoleplayer];
-
-	if (demo_p)
-		return;
-	memset(name,0,sizeof(name));
-
-	demo_p = demobuffer;
-	demoflags = DF_GHOST|(modeattacking<<DF_ATTACKSHIFT);
-
-	// Setup header.
-	M_Memcpy(demo_p, DEMOHEADER, 12); demo_p += 12;
-	WRITEUINT8(demo_p,VERSION);
-	WRITEUINT8(demo_p,SUBVERSION);
-	WRITEUINT16(demo_p,DEMOVERSION);
-
-	// demo checksum
-	demo_p += 16;
-
-	// game data
-	M_Memcpy(demo_p, "PLAY", 4); demo_p += 4;
-	WRITEINT16(demo_p,gamemap);
-	M_Memcpy(demo_p, mapmd5, 16); demo_p += 16;
-
-	WRITEUINT8(demo_p,demoflags);
-	switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT)
-	{
-	case ATTACKING_NONE: // 0
-		break;
-	case ATTACKING_RECORD: // 1
-		demotime_p = demo_p;
-		WRITEUINT32(demo_p,UINT32_MAX); // time
-		WRITEUINT32(demo_p,0); // score
-		WRITEUINT16(demo_p,0); // rings
-		break;
-	case ATTACKING_NIGHTS: // 2
-		demotime_p = demo_p;
-		WRITEUINT32(demo_p,UINT32_MAX); // time
-		WRITEUINT32(demo_p,0); // score
-		break;
-	default: // 3
-		break;
-	}
-
-	WRITEUINT32(demo_p,P_GetInitSeed());
-
-	// Name
-	for (i = 0; i < 16 && cv_playername.string[i]; i++)
-		name[i] = cv_playername.string[i];
-	for (; i < 16; i++)
-		name[i] = '\0';
-	M_Memcpy(demo_p,name,16);
-	demo_p += 16;
-
-	// Skin
-	for (i = 0; i < 16 && cv_skin.string[i]; i++)
-		name[i] = cv_skin.string[i];
-	for (; i < 16; i++)
-		name[i] = '\0';
-	M_Memcpy(demo_p,name,16);
-	demo_p += 16;
-
-	// Color
-	for (i = 0; i < 16 && cv_playercolor.string[i]; i++)
-		name[i] = cv_playercolor.string[i];
-	for (; i < 16; i++)
-		name[i] = '\0';
-	M_Memcpy(demo_p,name,16);
-	demo_p += 16;
-
-	// Stats
-	WRITEUINT8(demo_p,player->charability);
-	WRITEUINT8(demo_p,player->charability2);
-	WRITEUINT8(demo_p,player->actionspd>>FRACBITS);
-	WRITEUINT8(demo_p,player->mindash>>FRACBITS);
-	WRITEUINT8(demo_p,player->maxdash>>FRACBITS);
-	WRITEUINT8(demo_p,player->normalspeed>>FRACBITS);
-	WRITEUINT8(demo_p,player->runspeed>>FRACBITS);
-	WRITEUINT8(demo_p,player->thrustfactor);
-	WRITEUINT8(demo_p,player->accelstart);
-	WRITEUINT8(demo_p,player->acceleration);
-	WRITEUINT8(demo_p,player->height>>FRACBITS);
-	WRITEUINT8(demo_p,player->spinheight>>FRACBITS);
-	WRITEUINT8(demo_p,player->camerascale>>FRACBITS);
-	WRITEUINT8(demo_p,player->shieldscale>>FRACBITS);
-
-	// Trying to convert it back to % causes demo desync due to precision loss.
-	// Don't do it.
-	WRITEFIXED(demo_p, player->jumpfactor);
-
-	// And mobjtype_t is best with UINT32 too...
-	WRITEUINT32(demo_p, player->followitem);
-
-	// Save pflag data - see SendWeaponPref()
-	{
-		UINT8 buf = 0;
-		pflags_t pflags = 0;
-		if (cv_flipcam.value)
-		{
-			buf |= 0x01;
-			pflags |= PF_FLIPCAM;
-		}
-		if (cv_analog[0].value)
-		{
-			buf |= 0x02;
-			pflags |= PF_ANALOGMODE;
-		}
-		if (cv_directionchar[0].value)
-		{
-			buf |= 0x04;
-			pflags |= PF_DIRECTIONCHAR;
-		}
-		if (cv_autobrake.value)
-		{
-			buf |= 0x08;
-			pflags |= PF_AUTOBRAKE;
-		}
-		if (cv_usejoystick.value)
-			buf |= 0x10;
-		CV_SetValue(&cv_showinputjoy, !!(cv_usejoystick.value));
-
-		WRITEUINT8(demo_p,buf);
-		player->pflags = pflags;
-	}
-
-	// Save netvar data
-	CV_SaveNetVars(&demo_p);
-
-	memset(&oldcmd,0,sizeof(oldcmd));
-	memset(&oldghost,0,sizeof(oldghost));
-	memset(&ghostext,0,sizeof(ghostext));
-	ghostext.lastcolor = ghostext.color = GHC_NORMAL;
-	ghostext.lastscale = ghostext.scale = FRACUNIT;
-
-	if (player->mo)
-	{
-		oldghost.x = player->mo->x;
-		oldghost.y = player->mo->y;
-		oldghost.z = player->mo->z;
-		oldghost.angle = player->mo->angle>>24;
-
-		// preticker started us gravity flipped
-		if (player->mo->eflags & MFE_VERTICALFLIP)
-			ghostext.flags |= EZT_FLIP;
-	}
-}
-
-void G_BeginMetal(void)
-{
-	mobj_t *mo = players[consoleplayer].mo;
-
-#if 0
-	if (demo_p)
-		return;
-#endif
-
-	demo_p = demobuffer;
-
-	// Write header.
-	M_Memcpy(demo_p, DEMOHEADER, 12); demo_p += 12;
-	WRITEUINT8(demo_p,VERSION);
-	WRITEUINT8(demo_p,SUBVERSION);
-	WRITEUINT16(demo_p,DEMOVERSION);
-
-	// demo checksum
-	demo_p += 16;
-
-	M_Memcpy(demo_p, "METL", 4); demo_p += 4;
-
-	memset(&ghostext,0,sizeof(ghostext));
-	ghostext.lastscale = ghostext.scale = FRACUNIT;
-
-	// Set up our memory.
-	memset(&oldmetal,0,sizeof(oldmetal));
-	oldmetal.x = mo->x;
-	oldmetal.y = mo->y;
-	oldmetal.z = mo->z;
-	oldmetal.angle = mo->angle>>24;
-}
-
-void G_SetDemoTime(UINT32 ptime, UINT32 pscore, UINT16 prings)
-{
-	if (!demorecording || !demotime_p)
-		return;
-	if (demoflags & DF_RECORDATTACK)
-	{
-		WRITEUINT32(demotime_p, ptime);
-		WRITEUINT32(demotime_p, pscore);
-		WRITEUINT16(demotime_p, prings);
-		demotime_p = NULL;
-	}
-	else if (demoflags & DF_NIGHTSATTACK)
-	{
-		WRITEUINT32(demotime_p, ptime);
-		WRITEUINT32(demotime_p, pscore);
-		demotime_p = NULL;
-	}
-}
-
-// Returns bitfield:
-// 1 == new demo has lower time
-// 2 == new demo has higher score
-// 4 == new demo has higher rings
-UINT8 G_CmpDemoTime(char *oldname, char *newname)
-{
-	UINT8 *buffer,*p;
-	UINT8 flags;
-	UINT32 oldtime, newtime, oldscore, newscore;
-	UINT16 oldrings, newrings, oldversion;
-	size_t bufsize ATTRUNUSED;
-	UINT8 c;
-	UINT16 s ATTRUNUSED;
-	UINT8 aflags = 0;
-
-	// load the new file
-	FIL_DefaultExtension(newname, ".lmp");
-	bufsize = FIL_ReadFile(newname, &buffer);
-	I_Assert(bufsize != 0);
-	p = buffer;
-
-	// read demo header
-	I_Assert(!memcmp(p, DEMOHEADER, 12));
-	p += 12; // DEMOHEADER
-	c = READUINT8(p); // VERSION
-	I_Assert(c == VERSION);
-	c = READUINT8(p); // SUBVERSION
-	I_Assert(c == SUBVERSION);
-	s = READUINT16(p);
-	I_Assert(s == DEMOVERSION);
-	p += 16; // demo checksum
-	I_Assert(!memcmp(p, "PLAY", 4));
-	p += 4; // PLAY
-	p += 2; // gamemap
-	p += 16; // map md5
-	flags = READUINT8(p); // demoflags
-
-	aflags = flags & (DF_RECORDATTACK|DF_NIGHTSATTACK);
-	I_Assert(aflags);
-	if (flags & DF_RECORDATTACK)
-	{
-		newtime = READUINT32(p);
-		newscore = READUINT32(p);
-		newrings = READUINT16(p);
-	}
-	else if (flags & DF_NIGHTSATTACK)
-	{
-		newtime = READUINT32(p);
-		newscore = READUINT32(p);
-		newrings = 0;
-	}
-	else // appease compiler
-		return 0;
-
-	Z_Free(buffer);
-
-	// load old file
-	FIL_DefaultExtension(oldname, ".lmp");
-	if (!FIL_ReadFile(oldname, &buffer))
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Failed to read file '%s'.\n"), oldname);
-		return UINT8_MAX;
-	}
-	p = buffer;
-
-	// read demo header
-	if (memcmp(p, DEMOHEADER, 12))
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' invalid format. It will be overwritten.\n"), oldname);
-		Z_Free(buffer);
-		return UINT8_MAX;
-	} p += 12; // DEMOHEADER
-	p++; // VERSION
-	p++; // SUBVERSION
-	oldversion = READUINT16(p);
-	switch(oldversion) // demoversion
-	{
-	case DEMOVERSION: // latest always supported
-		break;
-	// too old, cannot support.
-	default:
-		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' invalid format. It will be overwritten.\n"), oldname);
-		Z_Free(buffer);
-		return UINT8_MAX;
-	}
-	p += 16; // demo checksum
-	if (memcmp(p, "PLAY", 4))
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' invalid format. It will be overwritten.\n"), oldname);
-		Z_Free(buffer);
-		return UINT8_MAX;
-	} p += 4; // "PLAY"
-	if (oldversion <= 0x0008)
-		p++; // gamemap
-	else
-		p += 2; // gamemap
-	p += 16; // mapmd5
-	flags = READUINT8(p);
-	if (!(flags & aflags))
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("File '%s' not from same game mode. It will be overwritten.\n"), oldname);
-		Z_Free(buffer);
-		return UINT8_MAX;
-	}
-	if (flags & DF_RECORDATTACK)
-	{
-		oldtime = READUINT32(p);
-		oldscore = READUINT32(p);
-		oldrings = READUINT16(p);
-	}
-	else if (flags & DF_NIGHTSATTACK)
-	{
-		oldtime = READUINT32(p);
-		oldscore = READUINT32(p);
-		oldrings = 0;
-	}
-	else // appease compiler
-		return UINT8_MAX;
-
-	Z_Free(buffer);
-
-	c = 0;
-	if (newtime < oldtime
-	|| (newtime == oldtime && (newscore > oldscore || newrings > oldrings)))
-		c |= 1; // Better time
-	if (newscore > oldscore
-	|| (newscore == oldscore && newtime < oldtime))
-		c |= 1<<1; // Better score
-	if (newrings > oldrings
-	|| (newrings == oldrings && newtime < oldtime))
-		c |= 1<<2; // Better rings
-	return c;
-}
-
-//
-// G_PlayDemo
-//
-void G_DeferedPlayDemo(const char *name)
-{
-	COM_BufAddText("playdemo \"");
-	COM_BufAddText(name);
-	COM_BufAddText("\"\n");
-}
-
-//
-// Start a demo from a .LMP file or from a wad resource
-//
-void G_DoPlayDemo(char *defdemoname)
-{
-	UINT8 i;
-	lumpnum_t l;
-	char skin[17],color[17],*n,*pdemoname;
-	UINT8 version,subversion,charability,charability2,thrustfactor,accelstart,acceleration;
-	pflags_t pflags;
-	UINT32 randseed, followitem;
-	fixed_t camerascale,shieldscale,actionspd,mindash,maxdash,normalspeed,runspeed,jumpfactor,height,spinheight;
-	char msg[1024];
-
-	skin[16] = '\0';
-	color[16] = '\0';
-
-	n = defdemoname+strlen(defdemoname);
-	while (*n != '/' && *n != '\\' && n != defdemoname)
-		n--;
-	if (n != defdemoname)
-		n++;
-	pdemoname = ZZ_Alloc(strlen(n)+1);
-	strcpy(pdemoname,n);
-
-	// Internal if no extension, external if one exists
-	if (FIL_CheckExtension(defdemoname))
-	{
-		//FIL_DefaultExtension(defdemoname, ".lmp");
-		if (!FIL_ReadFile(defdemoname, &demobuffer))
-		{
-			snprintf(msg, 1024, M_GetText("Failed to read file '%s'.\n"), defdemoname);
-			CONS_Alert(CONS_ERROR, "%s", msg);
-			gameaction = ga_nothing;
-			M_StartMessage(msg, NULL, MM_NOTHING);
-			return;
-		}
-		demo_p = demobuffer;
-	}
-	// load demo resource from WAD
-	else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR)
-	{
-		snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname);
-		CONS_Alert(CONS_ERROR, "%s", msg);
-		gameaction = ga_nothing;
-		M_StartMessage(msg, NULL, MM_NOTHING);
-		return;
-	}
-	else // it's an internal demo
-		demobuffer = demo_p = W_CacheLumpNum(l, PU_STATIC);
-
-	// read demo header
-	gameaction = ga_nothing;
-	demoplayback = true;
-	if (memcmp(demo_p, DEMOHEADER, 12))
-	{
-		snprintf(msg, 1024, M_GetText("%s is not a SRB2 replay file.\n"), pdemoname);
-		CONS_Alert(CONS_ERROR, "%s", msg);
-		M_StartMessage(msg, NULL, MM_NOTHING);
-		Z_Free(pdemoname);
-		Z_Free(demobuffer);
-		demoplayback = false;
-		titledemo = false;
-		return;
-	}
-	demo_p += 12; // DEMOHEADER
-
-	version = READUINT8(demo_p);
-	subversion = READUINT8(demo_p);
-	demoversion = READUINT16(demo_p);
-	switch(demoversion)
-	{
-	case DEMOVERSION: // latest always supported
-		break;
-	// too old, cannot support.
-	default:
-		snprintf(msg, 1024, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemoname);
-		CONS_Alert(CONS_ERROR, "%s", msg);
-		M_StartMessage(msg, NULL, MM_NOTHING);
-		Z_Free(pdemoname);
-		Z_Free(demobuffer);
-		demoplayback = false;
-		titledemo = false;
-		return;
-	}
-	demo_p += 16; // demo checksum
-	if (memcmp(demo_p, "PLAY", 4))
-	{
-		snprintf(msg, 1024, M_GetText("%s is the wrong type of recording and cannot be played.\n"), pdemoname);
-		CONS_Alert(CONS_ERROR, "%s", msg);
-		M_StartMessage(msg, NULL, MM_NOTHING);
-		Z_Free(pdemoname);
-		Z_Free(demobuffer);
-		demoplayback = false;
-		titledemo = false;
-		return;
-	}
-	demo_p += 4; // "PLAY"
-	gamemap = READINT16(demo_p);
-	demo_p += 16; // mapmd5
-
-	demoflags = READUINT8(demo_p);
-	modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT;
-	CON_ToggleOff();
-
-	hu_demoscore = 0;
-	hu_demotime = UINT32_MAX;
-	hu_demorings = 0;
-
-	switch (modeattacking)
-	{
-	case ATTACKING_NONE: // 0
-		break;
-	case ATTACKING_RECORD: // 1
-		hu_demotime  = READUINT32(demo_p);
-		hu_demoscore = READUINT32(demo_p);
-		hu_demorings = READUINT16(demo_p);
-		break;
-	case ATTACKING_NIGHTS: // 2
-		hu_demotime  = READUINT32(demo_p);
-		hu_demoscore = READUINT32(demo_p);
-		break;
-	default: // 3
-		modeattacking = ATTACKING_NONE;
-		break;
-	}
-
-	// Random seed
-	randseed = READUINT32(demo_p);
-
-	// Player name
-	M_Memcpy(player_names[0],demo_p,16);
-	demo_p += 16;
-
-	// Skin
-	M_Memcpy(skin,demo_p,16);
-	demo_p += 16;
-
-	// Color
-	M_Memcpy(color,demo_p,16);
-	demo_p += 16;
-
-	charability = READUINT8(demo_p);
-	charability2 = READUINT8(demo_p);
-	actionspd = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	mindash = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	maxdash = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	normalspeed = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	runspeed = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	thrustfactor = READUINT8(demo_p);
-	accelstart = READUINT8(demo_p);
-	acceleration = READUINT8(demo_p);
-	height = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	spinheight = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	camerascale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	shieldscale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	jumpfactor = READFIXED(demo_p);
-	followitem = READUINT32(demo_p);
-
-	// pflag data
-	{
-		UINT8 buf = READUINT8(demo_p);
-		pflags = 0;
-		if (buf & 0x01)
-			pflags |= PF_FLIPCAM;
-		if (buf & 0x02)
-			pflags |= PF_ANALOGMODE;
-		if (buf & 0x04)
-			pflags |= PF_DIRECTIONCHAR;
-		if (buf & 0x08)
-			pflags |= PF_AUTOBRAKE;
-		CV_SetValue(&cv_showinputjoy, !!(buf & 0x10));
-	}
-
-	// net var data
-	CV_LoadNetVars(&demo_p);
-
-	// Sigh ... it's an empty demo.
-	if (*demo_p == DEMOMARKER)
-	{
-		snprintf(msg, 1024, M_GetText("%s contains no data to be played.\n"), pdemoname);
-		CONS_Alert(CONS_ERROR, "%s", msg);
-		M_StartMessage(msg, NULL, MM_NOTHING);
-		Z_Free(pdemoname);
-		Z_Free(demobuffer);
-		demoplayback = false;
-		titledemo = false;
-		return;
-	}
-
-	Z_Free(pdemoname);
-
-	memset(&oldcmd,0,sizeof(oldcmd));
-	memset(&oldghost,0,sizeof(oldghost));
-
-	if (VERSION != version || SUBVERSION != subversion)
-		CONS_Alert(CONS_WARNING, M_GetText("Demo version does not match game version. Desyncs may occur.\n"));
-
-	// didn't start recording right away.
-	demo_start = false;
-
-	// Set skin
-	SetPlayerSkin(0, skin);
-
-	LUAh_MapChange(gamemap);
-	displayplayer = consoleplayer = 0;
-	memset(playeringame,0,sizeof(playeringame));
-	playeringame[0] = true;
-	P_SetRandSeed(randseed);
-	G_InitNew(false, G_BuildMapName(gamemap), true, true, false);
-
-	// Set color
-	for (i = 0; i < MAXSKINCOLORS; i++)
-		if (!stricmp(Color_Names[i],color))
-		{
-			players[0].skincolor = i;
-			break;
-		}
-	CV_StealthSetValue(&cv_playercolor, players[0].skincolor);
-	if (players[0].mo)
-	{
-		players[0].mo->color = players[0].skincolor;
-		oldghost.x = players[0].mo->x;
-		oldghost.y = players[0].mo->y;
-		oldghost.z = players[0].mo->z;
-	}
-
-	// Set saved attribute values
-	// No cheat checking here, because even if they ARE wrong...
-	// it would only break the replay if we clipped them.
-	players[0].camerascale = camerascale;
-	players[0].shieldscale = shieldscale;
-	players[0].charability = charability;
-	players[0].charability2 = charability2;
-	players[0].actionspd = actionspd;
-	players[0].mindash = mindash;
-	players[0].maxdash = maxdash;
-	players[0].normalspeed = normalspeed;
-	players[0].runspeed = runspeed;
-	players[0].thrustfactor = thrustfactor;
-	players[0].accelstart = accelstart;
-	players[0].acceleration = acceleration;
-	players[0].height = height;
-	players[0].spinheight = spinheight;
-	players[0].jumpfactor = jumpfactor;
-	players[0].followitem = followitem;
-	players[0].pflags = pflags;
-
-	demo_start = true;
-}
-
-void G_AddGhost(char *defdemoname)
-{
-	INT32 i;
-	lumpnum_t l;
-	char name[17],skin[17],color[17],*n,*pdemoname,md5[16];
-	demoghost *gh;
-	UINT8 flags;
-	UINT8 *buffer,*p;
-	mapthing_t *mthing;
-	UINT16 count, ghostversion;
-
-	name[16] = '\0';
-	skin[16] = '\0';
-	color[16] = '\0';
-
-	n = defdemoname+strlen(defdemoname);
-	while (*n != '/' && *n != '\\' && n != defdemoname)
-		n--;
-	if (n != defdemoname)
-		n++;
-	pdemoname = ZZ_Alloc(strlen(n)+1);
-	strcpy(pdemoname,n);
-
-	// Internal if no extension, external if one exists
-	if (FIL_CheckExtension(defdemoname))
-	{
-		//FIL_DefaultExtension(defdemoname, ".lmp");
-		if (!FIL_ReadFileTag(defdemoname, &buffer, PU_LEVEL))
-		{
-			CONS_Alert(CONS_ERROR, M_GetText("Failed to read file '%s'.\n"), defdemoname);
-			Z_Free(pdemoname);
-			return;
-		}
-		p = buffer;
-	}
-	// load demo resource from WAD
-	else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR)
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Failed to read lump '%s'.\n"), defdemoname);
-		Z_Free(pdemoname);
-		return;
-	}
-	else // it's an internal demo
-		buffer = p = W_CacheLumpNum(l, PU_LEVEL);
-
-	// read demo header
-	if (memcmp(p, DEMOHEADER, 12))
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Not a SRB2 replay.\n"), pdemoname);
-		Z_Free(pdemoname);
-		Z_Free(buffer);
-		return;
-	} p += 12; // DEMOHEADER
-	p++; // VERSION
-	p++; // SUBVERSION
-	ghostversion = READUINT16(p);
-	switch(ghostversion)
-	{
-	case DEMOVERSION: // latest always supported
-		break;
-	// too old, cannot support.
-	default:
-		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Demo version incompatible.\n"), pdemoname);
-		Z_Free(pdemoname);
-		Z_Free(buffer);
-		return;
-	}
-	M_Memcpy(md5, p, 16); p += 16; // demo checksum
-	for (gh = ghosts; gh; gh = gh->next)
-		if (!memcmp(md5, gh->checksum, 16)) // another ghost in the game already has this checksum?
-		{ // Don't add another one, then!
-			CONS_Debug(DBG_SETUP, "Rejecting duplicate ghost %s (MD5 was matched)\n", pdemoname);
-			Z_Free(pdemoname);
-			Z_Free(buffer);
-			return;
-		}
-	if (memcmp(p, "PLAY", 4))
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Demo format unacceptable.\n"), pdemoname);
-		Z_Free(pdemoname);
-		Z_Free(buffer);
-		return;
-	} p += 4; // "PLAY"
-	if (ghostversion <= 0x0008)
-		p++; // gamemap
-	else
-		p += 2; // gamemap
-	p += 16; // mapmd5 (possibly check for consistency?)
-	flags = READUINT8(p);
-	if (!(flags & DF_GHOST))
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: No ghost data in this demo.\n"), pdemoname);
-		Z_Free(pdemoname);
-		Z_Free(buffer);
-		return;
-	}
-	switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT)
-	{
-	case ATTACKING_NONE: // 0
-		break;
-	case ATTACKING_RECORD: // 1
-		p += 10; // demo time, score, and rings
-		break;
-	case ATTACKING_NIGHTS: // 2
-		p += 8; // demo time left, score
-		break;
-	default: // 3
-		break;
-	}
-
-	p += 4; // random seed
-
-	// Player name (TODO: Display this somehow if it doesn't match cv_playername!)
-	M_Memcpy(name, p,16);
-	p += 16;
-
-	// Skin
-	M_Memcpy(skin, p,16);
-	p += 16;
-
-	// Color
-	M_Memcpy(color, p,16);
-	p += 16;
-
-	// Ghosts do not have a player structure to put this in.
-	p++; // charability
-	p++; // charability2
-	p++; // actionspd
-	p++; // mindash
-	p++; // maxdash
-	p++; // normalspeed
-	p++; // runspeed
-	p++; // thrustfactor
-	p++; // accelstart
-	p++; // acceleration
-	p++; // height
-	p++; // spinheight
-	p++; // camerascale
-	p++; // shieldscale
-	p += 4; // jumpfactor
-	p += 4; // followitem
-
-	p++; // pflag data
-
-	// net var data
-	count = READUINT16(p);
-	while (count--)
-	{
-		p += 2;
-		SKIPSTRING(p);
-		p++;
-	}
-
-	if (*p == DEMOMARKER)
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Replay is empty.\n"), pdemoname);
-		Z_Free(pdemoname);
-		Z_Free(buffer);
-		return;
-	}
-
-	gh = Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL);
-	gh->next = ghosts;
-	gh->buffer = buffer;
-	M_Memcpy(gh->checksum, md5, 16);
-	gh->p = p;
-
-	ghosts = gh;
-
-	gh->version = ghostversion;
-	mthing = playerstarts[0];
-	I_Assert(mthing);
-	{ // A bit more complex than P_SpawnPlayer because ghosts aren't solid and won't just push themselves out of the ceiling.
-		fixed_t z,f,c;
-		fixed_t offset = mthing->z << FRACBITS;
-		gh->mo = P_SpawnMobj(mthing->x << FRACBITS, mthing->y << FRACBITS, 0, MT_GHOST);
-		gh->mo->angle = FixedAngle(mthing->angle << FRACBITS);
-		f = gh->mo->floorz;
-		c = gh->mo->ceilingz - mobjinfo[MT_PLAYER].height;
-		if (!!(mthing->options & MTF_AMBUSH) ^ !!(mthing->options & MTF_OBJECTFLIP))
-		{
-			z = c - offset;
-			if (z < f)
-				z = f;
-		}
-		else
-		{
-			z = f + offset;
-			if (z > c)
-				z = c;
-		}
-		gh->mo->z = z;
-	}
-
-	gh->oldmo.x = gh->mo->x;
-	gh->oldmo.y = gh->mo->y;
-	gh->oldmo.z = gh->mo->z;
-
-	// Set skin
-	gh->mo->skin = &skins[0];
-	for (i = 0; i < numskins; i++)
-		if (!stricmp(skins[i].name,skin))
-		{
-			gh->mo->skin = &skins[i];
-			break;
-		}
-	gh->oldmo.skin = gh->mo->skin;
-
-	// Set color
-	gh->mo->color = ((skin_t*)gh->mo->skin)->prefcolor;
-	for (i = 0; i < MAXSKINCOLORS; i++)
-		if (!stricmp(Color_Names[i],color))
-		{
-			gh->mo->color = (UINT8)i;
-			break;
-		}
-	gh->oldmo.color = gh->mo->color;
-
-	gh->mo->state = states+S_PLAY_STND;
-	gh->mo->sprite = gh->mo->state->sprite;
-	gh->mo->sprite2 = (gh->mo->state->frame & FF_FRAMEMASK);
-	//gh->mo->frame = tr_trans30<<FF_TRANSSHIFT;
-	gh->mo->flags2 |= MF2_DONTDRAW;
-	gh->fadein = (9-3)*6; // fade from invisible to trans30 over as close to 35 tics as possible
-	gh->mo->tics = -1;
-
-	CONS_Printf(M_GetText("Added ghost %s from %s\n"), name, pdemoname);
-	Z_Free(pdemoname);
-}
-
-//
-// G_TimeDemo
-// NOTE: name is a full filename for external demos
-//
-static INT32 restorecv_vidwait;
-
-void G_TimeDemo(const char *name)
-{
-	nodrawers = M_CheckParm("-nodraw");
-	noblit = M_CheckParm("-noblit");
-	restorecv_vidwait = cv_vidwait.value;
-	if (cv_vidwait.value)
-		CV_Set(&cv_vidwait, "0");
-	timingdemo = true;
-	singletics = true;
-	framecount = 0;
-	demostarttime = I_GetTime();
-	G_DeferedPlayDemo(name);
-}
-
-void G_DoPlayMetal(void)
-{
-	lumpnum_t l;
-	mobj_t *mo = NULL;
-	thinker_t *th;
-
-	// it's an internal demo
-	if ((l = W_CheckNumForName(va("%sMS",G_BuildMapName(gamemap)))) == LUMPERROR)
-	{
-		CONS_Alert(CONS_WARNING, M_GetText("No bot recording for this map.\n"));
-		return;
-	}
-	else
-		metalbuffer = metal_p = W_CacheLumpNum(l, PU_STATIC);
-
-	// find metal sonic
-	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
-	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-			continue;
-
-		mo = (mobj_t *)th;
-		if (mo->type != MT_METALSONIC_RACE)
-			continue;
-
-		break;
-	}
-	if (th == &thlist[THINK_MOBJ])
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Failed to find bot entity.\n"));
-		Z_Free(metalbuffer);
-		return;
-	}
-
-	// read demo header
-    metal_p += 12; // DEMOHEADER
-	metal_p++; // VERSION
-	metal_p++; // SUBVERSION
-	metalversion = READUINT16(metal_p);
-	switch(metalversion)
-	{
-	case DEMOVERSION: // latest always supported
-		break;
-	// too old, cannot support.
-	default:
-		CONS_Alert(CONS_WARNING, M_GetText("Failed to load bot recording for this map, format version incompatible.\n"));
-		Z_Free(metalbuffer);
-		return;
-	}
-	metal_p += 16; // demo checksum
-	if (memcmp(metal_p, "METL", 4))
-	{
-		CONS_Alert(CONS_WARNING, M_GetText("Failed to load bot recording for this map, wasn't recorded in Metal format.\n"));
-		Z_Free(metalbuffer);
-		return;
-	} metal_p += 4; // "METL"
-
-	// read initial tic
-	memset(&oldmetal,0,sizeof(oldmetal));
-	oldmetal.x = mo->x;
-	oldmetal.y = mo->y;
-	oldmetal.z = mo->z;
-	metalplayback = mo;
-}
-
-void G_DoneLevelLoad(void)
-{
-	CONS_Printf(M_GetText("Loaded level in %f sec\n"), (double)(I_GetTime() - demostarttime) / TICRATE);
-	framecount = 0;
-	demostarttime = I_GetTime();
-}
-
-/*
-===================
-=
-= G_CheckDemoStatus
-=
-= Called after a death or level completion to allow demos to be cleaned up
-= Returns true if a new demo loop action will take place
-===================
-*/
-
-// Stops metal sonic's demo. Separate from other functions because metal + replays can coexist
-void G_StopMetalDemo(void)
-{
-
-	// Metal Sonic finishing doesn't end the game, dammit.
-	Z_Free(metalbuffer);
-	metalbuffer = NULL;
-	metalplayback = NULL;
-	metal_p = NULL;
-}
-
-// Stops metal sonic recording.
-ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill)
-{
-	boolean saved = false;
-	if (demo_p)
-	{
-		UINT8 *p = demobuffer+16; // checksum position
-		if (kill)
-			WRITEUINT8(demo_p, METALDEATH); // add the metal death marker
-		else
-			WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
-#ifdef NOMD5
-		{
-			UINT8 i;
-			for (i = 0; i < 16; i++, p++)
-				*p = P_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct.
-		}
-#else
-		md5_buffer((char *)p+16, demo_p - (p+16), (void *)p); // make a checksum of everything after the checksum in the file.
-#endif
-		saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuffer, demo_p - demobuffer); // finally output the file.
-	}
-	free(demobuffer);
-	metalrecording = false;
-	if (saved)
-		I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap));
-	I_Error("Failed to save demo!");
-}
-
-// reset engine variable set for the demos
-// called from stopdemo command, map command, and g_checkdemoStatus.
-void G_StopDemo(void)
-{
-	Z_Free(demobuffer);
-	demobuffer = NULL;
-	demoplayback = false;
-	titledemo = false;
-	timingdemo = false;
-	singletics = false;
-
-	if (gamestate == GS_INTERMISSION)
-		Y_EndIntermission(); // cleanup
-
-	G_SetGamestate(GS_NULL);
-	wipegamestate = GS_NULL;
-	SV_StopServer();
-	SV_ResetServer();
-}
-
-boolean G_CheckDemoStatus(void)
-{
-	boolean saved;
-
-	while (ghosts)
-	{
-		demoghost *next = ghosts->next;
-		Z_Free(ghosts);
-		ghosts = next;
-	}
-	ghosts = NULL;
-
-
-	// DO NOT end metal sonic demos here
-
-	if (timingdemo)
-	{
-		INT32 demotime;
-		double f1, f2;
-		demotime = I_GetTime() - demostarttime;
-		if (!demotime)
-			return true;
-		G_StopDemo();
-		timingdemo = false;
-		f1 = (double)demotime;
-		f2 = (double)framecount*TICRATE;
-
-		CONS_Printf(M_GetText("timed %u gametics in %d realtics - %u frames\n%f seconds, %f avg fps\n"),
-			leveltime,demotime,(UINT32)framecount,f1/TICRATE,f2/f1);
-
-		// CSV-readable timedemo results, for external parsing
-		if (timedemo_csv)
-		{
-			FILE *f;
-			const char *csvpath = va("%s"PATHSEP"%s", srb2home, "timedemo.csv");
-			const char *header = "id,demoname,seconds,avgfps,leveltime,demotime,framecount,ticrate,rendermode,vidmode,vidwidth,vidheight,procbits\n";
-			const char *rowformat = "\"%s\",\"%s\",%f,%f,%u,%d,%u,%u,%u,%u,%u,%u,%u\n";
-			boolean headerrow = !FIL_FileExists(csvpath);
-			UINT8 procbits = 0;
-
-			// Bitness
-			if (sizeof(void*) == 4)
-				procbits = 32;
-			else if (sizeof(void*) == 8)
-				procbits = 64;
-
-			f = fopen(csvpath, "a+");
-
-			if (f)
-			{
-				if (headerrow)
-					fputs(header, f);
-				fprintf(f, rowformat,
-					timedemo_csv_id,timedemo_name,f1/TICRATE,f2/f1,leveltime,demotime,(UINT32)framecount,TICRATE,rendermode,vid.modenum,vid.width,vid.height,procbits);
-				fclose(f);
-				CONS_Printf("Timedemo results saved to '%s'\n", csvpath);
-			}
-			else
-			{
-				// Just print the CSV output to console
-				CON_LogMessage(header);
-				CONS_Printf(rowformat,
-					timedemo_csv_id,timedemo_name,f1/TICRATE,f2/f1,leveltime,demotime,(UINT32)framecount,TICRATE,rendermode,vid.modenum,vid.width,vid.height,procbits);
-			}
-		}
-
-		if (restorecv_vidwait != cv_vidwait.value)
-			CV_SetValue(&cv_vidwait, restorecv_vidwait);
-		D_AdvanceDemo();
-		return true;
-	}
-
-	if (demoplayback)
-	{
-		if (singledemo)
-			I_Quit();
-		G_StopDemo();
-
-		if (modeattacking)
-			M_EndModeAttackRun();
-		else
-			D_AdvanceDemo();
-
-		return true;
-	}
-
-	if (demorecording)
-	{
-		UINT8 *p = demobuffer+16; // checksum position
-#ifdef NOMD5
-		UINT8 i;
-		WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
-		for (i = 0; i < 16; i++, p++)
-			*p = P_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct.
-#else
-		WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
-		md5_buffer((char *)p+16, demo_p - (p+16), p); // make a checksum of everything after the checksum in the file.
-#endif
-		saved = FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer); // finally output the file.
-		free(demobuffer);
-		demorecording = false;
-
-		if (modeattacking != ATTACKING_RECORD)
-		{
-			if (saved)
-				CONS_Printf(M_GetText("Demo %s recorded\n"), demoname);
-			else
-				CONS_Alert(CONS_WARNING, M_GetText("Demo %s not saved\n"), demoname);
-		}
-		return true;
-	}
-
-	return false;
-}
-
 //
 // G_SetGamestate
 //
diff --git a/src/g_game.h b/src/g_game.h
index 8a2c5b0aedce18feb0a6f4514e465294be65db94..df0c9392e69c87cf3b139e29f97f34d950b28af2 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -17,6 +17,7 @@
 #include "doomdef.h"
 #include "doomstat.h"
 #include "d_event.h"
+#include "g_demo.h"
 
 extern char gamedatafilename[64];
 extern char timeattackfolder[64];
@@ -31,21 +32,6 @@ extern char player_names[MAXPLAYERS][MAXPLAYERNAME+1];
 extern player_t players[MAXPLAYERS];
 extern boolean playeringame[MAXPLAYERS];
 
-// ======================================
-// DEMO playback/recording related stuff.
-// ======================================
-
-// demoplaying back and demo recording
-extern boolean demoplayback, titledemo, demorecording, timingdemo;
-extern tic_t demostarttime;
-
-// Quit after playing a demo from cmdline.
-extern boolean singledemo;
-extern boolean demo_start;
-extern boolean demosynced;
-
-extern mobj_t *metalplayback;
-
 // gametic at level start
 extern tic_t levelstarttic;
 
@@ -173,7 +159,6 @@ void G_DoLoadLevel(boolean resetplayer);
 void G_StartTitleCard(void);
 void G_PreLevelTitleCard(void);
 boolean G_IsTitleCardAvailable(void);
-void G_DeferedPlayDemo(const char *demo);
 
 // Can be called by the startup code or M_Responder, calls P_SetupLevel.
 void G_LoadGame(UINT32 slot, INT16 mapoverride);
@@ -184,54 +169,6 @@ void G_SaveGame(UINT32 slot);
 
 void G_SaveGameOver(UINT32 slot, boolean modifylives);
 
-// Only called by startup code.
-void G_RecordDemo(const char *name);
-void G_RecordMetal(void);
-void G_BeginRecording(void);
-void G_BeginMetal(void);
-
-// Only called by shutdown code.
-void G_SetDemoTime(UINT32 ptime, UINT32 pscore, UINT16 prings);
-UINT8 G_CmpDemoTime(char *oldname, char *newname);
-
-typedef enum
-{
-	GHC_NORMAL = 0,
-	GHC_SUPER,
-	GHC_FIREFLOWER,
-	GHC_INVINCIBLE,
-	GHC_NIGHTSSKIN, // not actually a colour
-	GHC_RETURNSKIN // ditto
-} ghostcolor_t;
-
-// Record/playback tics
-void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
-void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum);
-void G_GhostAddThok(void);
-void G_GhostAddSpin(void);
-void G_GhostAddRev(void);
-void G_GhostAddColor(ghostcolor_t color);
-void G_GhostAddFlip(void);
-void G_GhostAddScale(fixed_t scale);
-void G_GhostAddHit(mobj_t *victim);
-void G_WriteGhostTic(mobj_t *ghost);
-void G_ConsGhostTic(void);
-void G_GhostTicker(void);
-void G_ReadMetalTic(mobj_t *metal);
-void G_WriteMetalTic(mobj_t *metal);
-void G_SaveMetal(UINT8 **buffer);
-void G_LoadMetal(UINT8 **buffer);
-
-void G_DoPlayDemo(char *defdemoname);
-void G_TimeDemo(const char *name);
-void G_AddGhost(char *defdemoname);
-void G_DoPlayMetal(void);
-void G_DoneLevelLoad(void);
-void G_StopMetalDemo(void);
-ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill);
-void G_StopDemo(void);
-boolean G_CheckDemoStatus(void);
-
 extern UINT32 gametypedefaultrules[NUMGAMETYPES];
 extern UINT32 gametypetol[NUMGAMETYPES];
 extern INT16 gametyperankings[NUMGAMETYPES];
diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c
index 6f3dd9fbd5290ead1959d17a70d0429582e6d963..ebb74f653ad0b5157f86a497e3d12df2696f3f4f 100644
--- a/src/hardware/hw_bsp.c
+++ b/src/hardware/hw_bsp.c
@@ -887,12 +887,10 @@ static void AdjustSegs(void)
 			float distv1,distv2,tmp;
 			nearv1 = nearv2 = MYMAX;
 
-#ifdef POLYOBJECTS
 			// Don't touch polyobject segs. We'll compensate
 			// for this when we go about drawing them.
 			if (lseg->polyseg)
 				continue;
-#endif
 
 			if (p) {
 				for (j = 0; j < p->numpts; j++)
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 598a635aa47c18b4b79697efdc131e601ab97607..d0133176589e9a03f6681fc393e9d3cf0c19526a 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -291,7 +291,7 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
 			{
 				// 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);
+				patch_t *realpatch = W_CacheSoftwarePatchNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
 				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
 				if (!column->topdelta)
 				{
@@ -450,7 +450,7 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
 			{
 				// 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);
+				patch_t *realpatch = W_CacheSoftwarePatchNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
 				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
 				if (!column->topdelta)
 				{
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index c5af8d6d336a8c8b29d9ce492402c323bb964b13..3d1316a2f199508e866dc983265ff0da9de27f99 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -298,6 +298,8 @@ light_t *t_lspr[NUMSPRITES] =
 
 	// Projectiles
 	&lspr[NOLIGHT],     // SPR_MISL
+	&lspr[SMALLREDBALL_L], // SPR_LASR
+	&lspr[REDSHINE_L],  // SPR_LASF
 	&lspr[NOLIGHT],     // SPR_TORP
 	&lspr[NOLIGHT],     // SPR_ENRG
 	&lspr[NOLIGHT],     // SPR_MINE
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 26013f7791df6af5e9072eb4654b963b94f23def..e266ee06c32a7003f2a876ce369ba42e1c59eb63 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -467,7 +467,7 @@ static UINT8 HWR_FogBlockAlpha(INT32 light, UINT32 color) // Let's see if this c
 // -----------------+
 // HWR_RenderPlane  : Render a floor or ceiling convex polygon
 // -----------------+
-static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight,
+static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight,
                            FBITFIELD PolyFlags, INT32 lightlevel, levelflat_t *levelflat, sector_t *FOFsector, UINT8 alpha, boolean fogplane, extracolormap_t *planecolormap)
 {
 	polyvertex_t *  pv;
@@ -489,8 +489,6 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	static FOutVector *planeVerts = NULL;
 	static UINT16 numAllocedPlaneVerts = 0;
 
-	(void)sector; ///@TODO remove shitty unused variable
-
 	// no convex poly were generated for this subsector
 	if (!xsub->planepoly)
 		return;
@@ -587,8 +585,6 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	flatyref = (float)(((fixed_t)pv->y & (~flatflag)) / fflatheight);
 
 	// transform
-	v3d = planeVerts;
-
 	if (FOFsector != NULL)
 	{
 		if (!isceiling) // it's a floor
@@ -631,44 +627,39 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 		flatyref = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINESINE(angle)) + FixedMul(tempytow, FINECOSINE(angle))));
 	}
 
-	for (i = 0; i < nrPlaneVerts; i++,v3d++,pv++)
-	{
-		// Hurdler: add scrolling texture on floor/ceiling
-		if (texflat)
-		{
-			v3d->sow = (float)(pv->x / fflatwidth) + scrollx;
-			v3d->tow = -(float)(pv->y / fflatheight) + scrolly;
-		}
-		else
-		{
-			v3d->sow = (float)((pv->x / fflatwidth) - flatxref + scrollx);
-			v3d->tow = (float)(flatyref - (pv->y / fflatheight) + scrolly);
-		}
-
-		// Need to rotate before translate
-		if (angle) // Only needs to be done if there's an altered angle
-		{
-			tempxsow = FLOAT_TO_FIXED(v3d->sow);
-			tempytow = FLOAT_TO_FIXED(v3d->tow);
-			if (texflat)
-				tempytow = -tempytow;
-			v3d->sow = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINECOSINE(angle)) - FixedMul(tempytow, FINESINE(angle))));
-			v3d->tow = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINESINE(angle)) + FixedMul(tempytow, FINECOSINE(angle))));
-		}
-
-		//v3d->sow = (float)(v3d->sow - flatxref + scrollx);
-		//v3d->tow = (float)(flatyref - v3d->tow + scrolly);
-
-		v3d->x = pv->x;
-		v3d->y = height;
-		v3d->z = pv->y;
+#define SETUP3DVERT(vert, vx, vy) {\
+		/* Hurdler: add scrolling texture on floor/ceiling */\
+		if (texflat)\
+		{\
+			vert->sow = (float)((vx) / fflatwidth) + scrollx;\
+			vert->tow = -(float)((vy) / fflatheight) + scrolly;\
+		}\
+		else\
+		{\
+			vert->sow = (float)(((vx) / fflatwidth) - flatxref + scrollx);\
+			vert->tow = (float)(flatyref - ((vy) / fflatheight) + scrolly);\
+		}\
+\
+		/* Need to rotate before translate */\
+		if (angle) /* Only needs to be done if there's an altered angle */\
+		{\
+			tempxsow = FLOAT_TO_FIXED(vert->sow);\
+			tempytow = FLOAT_TO_FIXED(vert->tow);\
+			if (texflat)\
+				tempytow = -tempytow;\
+			vert->sow = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINECOSINE(angle)) - FixedMul(tempytow, FINESINE(angle))));\
+			vert->tow = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINESINE(angle)) + FixedMul(tempytow, FINECOSINE(angle))));\
+		}\
+\
+		vert->x = (vx);\
+		vert->z = (vy);\
+\
+		fixedheight = P_GetZAt(slope, FLOAT_TO_FIXED((vx)), FLOAT_TO_FIXED((vy)), height);\
+		vert->y = FIXED_TO_FLOAT(fixedheight);\
+}
 
-		if (slope)
-		{
-			fixedheight = P_GetSlopeZAt(slope, FLOAT_TO_FIXED(pv->x), FLOAT_TO_FIXED(pv->y));
-			v3d->y = FIXED_TO_FLOAT(fixedheight);
-		}
-	}
+	for (i = 0, v3d = planeVerts; i < nrPlaneVerts; i++,v3d++,pv++)
+		SETUP3DVERT(v3d, pv->x, pv->y);
 
 	// only useful for flat coloured triangles
 	//Surf.FlatColor = 0xff804020;
@@ -679,36 +670,6 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	Surf.FlatColor.s.red = Surf.FlatColor.s.green =
 	Surf.FlatColor.s.blue = LightLevelToLum(lightlevel); //  Don't take from the frontsector, or the game will crash
 
-#if 0 // no colormap test
-	// colormap test
-	if (gr_frontsector)
-	{
-		sector_t *psector = gr_frontsector;
-
-		if (slope)
-			fixedheight = P_GetSlopeZAt(slope, psector->soundorg.x, psector->soundorg.y);
-
-		if (psector->ffloors)
-		{
-			ffloor_t *caster = psector->lightlist[R_GetPlaneLight(psector, fixedheight, false)].caster;
-			psector = caster ? &sectors[caster->secnum] : psector;
-
-			if (caster)
-			{
-				lightlevel = psector->lightlevel;
-				Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = LightLevelToLum(lightlevel);
-			}
-		}
-		if (psector->extra_colormap)
-			Surf.FlatColor.rgba = HWR_Lighting(lightlevel,psector->extra_colormap->rgba,psector->extra_colormap->fadergba, false, true);
-		else
-			Surf.FlatColor.rgba = HWR_Lighting(lightlevel,NORMALFOG,FADEFOG, false, true);
-	}
-	else
-		Surf.FlatColor.rgba = HWR_Lighting(lightlevel,NORMALFOG,FADEFOG, false, true);
-
-#endif // NOPE
-
 	if (planecolormap)
 	{
 		if (fogplane)
@@ -734,6 +695,79 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 
 	HWD.pfnDrawPolygon(&Surf, planeVerts, nrPlaneVerts, PolyFlags);
 
+	if (subsector)
+	{
+		// Horizon lines
+		FOutVector horizonpts[6];
+		float dist, vx, vy;
+		float x1, y1, xd, yd;
+		UINT8 numplanes, j;
+		vertex_t v; // For determining the closest distance from the line to the camera, to split render planes for minimum distortion;
+
+		const float renderdist = 27000.0f; // How far out to properly render the plane
+		const float farrenderdist = 32768.0f; // From here, raise plane to horizon level to fill in the line with some texture distortion
+
+		seg_t *line = &segs[subsector->firstline];
+
+		for (i = 0; i < subsector->numlines; i++, line++)
+		{
+			if (!line->glseg && line->linedef->special == HORIZONSPECIAL && R_PointOnSegSide(dup_viewx, dup_viewy, line) == 0)
+			{
+				P_ClosestPointOnLine(viewx, viewy, line->linedef, &v);
+				dist = FIXED_TO_FLOAT(R_PointToDist(v.x, v.y));
+
+				x1 = ((polyvertex_t *)line->pv1)->x;
+				y1 = ((polyvertex_t *)line->pv1)->y;
+				xd = ((polyvertex_t *)line->pv2)->x - x1;
+				yd = ((polyvertex_t *)line->pv2)->y - y1;
+
+				// Based on the seg length and the distance from the line, split horizon into multiple poly sets to reduce distortion
+				dist = sqrtf((xd*xd) + (yd*yd)) / dist / 16.0f;
+				if (dist > 100.0f)
+					numplanes = 100;
+				else
+					numplanes = (UINT8)dist + 1;
+
+				for (j = 0; j < numplanes; j++)
+				{
+					// Left side
+					vx = x1 + xd * j / numplanes;
+					vy = y1 + yd * j / numplanes;
+					SETUP3DVERT((&horizonpts[1]), vx, vy);
+
+					dist = sqrtf(powf(vx - gr_viewx, 2) + powf(vy - gr_viewy, 2));
+					vx = (vx - gr_viewx) * renderdist / dist + gr_viewx;
+					vy = (vy - gr_viewy) * renderdist / dist + gr_viewy;
+					SETUP3DVERT((&horizonpts[0]), vx, vy);
+
+					// Right side
+					vx = x1 + xd * (j+1) / numplanes;
+					vy = y1 + yd * (j+1) / numplanes;
+					SETUP3DVERT((&horizonpts[2]), vx, vy);
+
+					dist = sqrtf(powf(vx - gr_viewx, 2) + powf(vy - gr_viewy, 2));
+					vx = (vx - gr_viewx) * renderdist / dist + gr_viewx;
+					vy = (vy - gr_viewy) * renderdist / dist + gr_viewy;
+					SETUP3DVERT((&horizonpts[3]), vx, vy);
+
+					// Horizon fills
+					vx = (horizonpts[0].x - gr_viewx) * farrenderdist / renderdist + gr_viewx;
+					vy = (horizonpts[0].z - gr_viewy) * farrenderdist / renderdist + gr_viewy;
+					SETUP3DVERT((&horizonpts[5]), vx, vy);
+					horizonpts[5].y = gr_viewz;
+
+					vx = (horizonpts[3].x - gr_viewx) * farrenderdist / renderdist + gr_viewx;
+					vy = (horizonpts[3].z - gr_viewy) * farrenderdist / renderdist + gr_viewy;
+					SETUP3DVERT((&horizonpts[4]), vx, vy);
+					horizonpts[4].y = gr_viewz;
+
+					// Draw
+					HWD.pfnDrawPolygon(&Surf, horizonpts, 6, PolyFlags);
+				}
+			}
+		}
+	}
+
 #ifdef ALAM_LIGHTING
 	// add here code for dynamic lighting on planes
 	HWR_PlaneLighting(planeVerts, nrPlaneVerts);
@@ -1546,7 +1580,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 			// heights of the polygon, and h & l, are the final (clipped)
 			// poly coords.
 
-#ifdef POLYOBJECTS
 			// NOTE: With polyobjects, whenever you need to check the properties of the polyobject sector it belongs to,
 			// you must use the linedef's backsector to be correct
 			// From CB
@@ -1556,7 +1589,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				popenbottom = back->floorheight;
 			}
 			else
-#endif
             {
 				popentop = min(worldtop, worldhigh);
 				popenbottom = max(worldbottom, worldlow);
@@ -1586,7 +1618,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				polybottom = polytop - textureheight[gr_midtexture]*repeats;
 			}
 			// CB
-#ifdef POLYOBJECTS
 			// NOTE: With polyobjects, whenever you need to check the properties of the polyobject sector it belongs to,
 			// you must use the linedef's backsector to be correct
 			if (gr_curline->polyseg)
@@ -1594,7 +1625,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				lowcut = polybottom;
 				highcut = polytop;
 			}
-#endif
 			else
 			{
 				// The cut-off values of a linedef can always be constant, since every line has an absoulute front and or back sector
@@ -1724,7 +1754,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 					break;
 			}
 
-#ifdef POLYOBJECTS
 			if (gr_curline->polyseg && gr_curline->polyseg->translucency > 0)
 			{
 				if (gr_curline->polyseg->translucency >= NUMTRANSMAPS) // wall not drawn
@@ -1735,7 +1764,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				else
 					blendmode = HWR_TranstableToAlpha(gr_curline->polyseg->translucency, &Surf);
 			}
-#endif
 
 			if (gr_frontsector->numlights)
 			{
@@ -1884,7 +1912,9 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 		{
 			for (rover = gr_backsector->ffloors; rover; rover = rover->next)
 			{
-				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES) || (rover->flags & FF_INVERTSIDES))
+				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES))
+					continue;
+				if (!(rover->flags & FF_ALLSIDES) && rover->flags & FF_INVERTSIDES)
 					continue;
 				if (*rover->topheight < lowcut || *rover->bottomheight > highcut)
 					continue;
@@ -2024,7 +2054,9 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 		{
 			for (rover = gr_frontsector->ffloors; rover; rover = rover->next)
 			{
-				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES) || !(rover->flags & FF_ALLSIDES))
+				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES))
+					continue;
+				if (!(rover->flags & FF_ALLSIDES || rover->flags & FF_INVERTSIDES))
 					continue;
 				if (*rover->topheight < lowcut || *rover->bottomheight > highcut)
 					continue;
@@ -2524,10 +2556,8 @@ static void HWR_AddLine(seg_t * line)
 	static sector_t tempsec;
 
 	fixed_t v1x, v1y, v2x, v2y; // the seg's vertexes as fixed_t
-#ifdef POLYOBJECTS
 	if (line->polyseg && !(line->polyseg->flags & POF_RENDERSIDES))
 		return;
-#endif
 
 	gr_curline = line;
 
@@ -2654,13 +2684,10 @@ static void HWR_AddLine(seg_t * line)
 
 		if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then
 		{
-			if (
-#ifdef POLYOBJECTS
-			!line->polyseg &&
-#endif
-			!line->sidedef->midtexture
-			&& ((!gr_frontsector->ffloors && !gr_backsector->ffloors)
-			|| (gr_frontsector->tag == gr_backsector->tag)))
+			if (!line->polyseg &&
+				!line->sidedef->midtexture
+				&& ((!gr_frontsector->ffloors && !gr_backsector->ffloors)
+					|| (gr_frontsector->tag == gr_backsector->tag)))
 				return; // line is empty, don't even bother
 			// treat like wide open window instead
 			HWR_ProcessSeg(); // Doesn't need arguments because they're defined globally :D
@@ -2696,13 +2723,10 @@ static void HWR_AddLine(seg_t * line)
 
 	if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then
 	{
-		if (
-#ifdef POLYOBJECTS
-		!line->polyseg &&
-#endif
-		!line->sidedef->midtexture
-		&& ((!gr_frontsector->ffloors && !gr_backsector->ffloors)
-		|| (gr_frontsector->tag == gr_backsector->tag)))
+		if (!line->polyseg &&
+			!line->sidedef->midtexture
+			&& ((!gr_frontsector->ffloors && !gr_backsector->ffloors)
+				|| (gr_frontsector->tag == gr_backsector->tag)))
 			return; // line is empty, don't even bother
 
 		goto clippass; // treat like wide open window instead
@@ -2891,8 +2915,6 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 #endif
 }
 
-#ifdef POLYOBJECTS
-
 //
 // HWR_AddPolyObjectSegs
 //
@@ -2935,7 +2957,6 @@ static inline void HWR_AddPolyObjectSegs(void)
 	Z_Free(gr_fakeline);
 }
 
-#ifdef POLYOBJECTS_PLANES
 static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
 									FBITFIELD blendmode, UINT8 lightlevel, levelflat_t *levelflat, sector_t *FOFsector,
 									UINT8 alpha, extracolormap_t *planecolormap)
@@ -3190,8 +3211,6 @@ static void HWR_AddPolyObjectPlanes(void)
 		}
 	}
 }
-#endif
-#endif
 
 // -----------------+
 // HWR_Subsector    : Determine floor/ceiling planes.
@@ -3324,7 +3343,7 @@ static void HWR_Subsector(size_t num)
 			if (sub->validcount != validcount)
 			{
 				HWR_GetLevelFlat(&levelflats[gr_frontsector->floorpic]);
-				HWR_RenderPlane(gr_frontsector, &extrasubsectors[num], false,
+				HWR_RenderPlane(sub, &extrasubsectors[num], false,
 					// Hack to make things continue to work around slopes.
 					locFloorHeight == cullFloorHeight ? locFloorHeight : gr_frontsector->floorheight,
 					// We now return you to your regularly scheduled rendering.
@@ -3346,7 +3365,7 @@ static void HWR_Subsector(size_t num)
 			if (sub->validcount != validcount)
 			{
 				HWR_GetLevelFlat(&levelflats[gr_frontsector->ceilingpic]);
-				HWR_RenderPlane(NULL, &extrasubsectors[num], true,
+				HWR_RenderPlane(sub, &extrasubsectors[num], true,
 					// Hack to make things continue to work around slopes.
 					locCeilingHeight == cullCeilingHeight ? locCeilingHeight : gr_frontsector->ceilingheight,
 					// We now return you to your regularly scheduled rendering.
@@ -3388,7 +3407,7 @@ static void HWR_Subsector(size_t num)
 
 			if (centerHeight <= locCeilingHeight &&
 			    centerHeight >= locFloorHeight &&
-			    ((dup_viewz < cullHeight && !(rover->flags & FF_INVERTPLANES)) ||
+			    ((dup_viewz < cullHeight && (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES))) ||
 			     (dup_viewz > cullHeight && (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES))))
 			{
 				if (rover->flags & FF_FOG)
@@ -3433,7 +3452,7 @@ static void HWR_Subsector(size_t num)
 				{
 					HWR_GetLevelFlat(&levelflats[*rover->bottompic]);
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(NULL, &extrasubsectors[num], false, *rover->bottomheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic],
+					HWR_RenderPlane(sub, &extrasubsectors[num], false, *rover->bottomheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic],
 					                rover->master->frontsector, 255, false, *gr_frontsector->lightlist[light].extra_colormap);
 				}
 			}
@@ -3444,7 +3463,7 @@ static void HWR_Subsector(size_t num)
 
 			if (centerHeight >= locFloorHeight &&
 			    centerHeight <= locCeilingHeight &&
-			    ((dup_viewz > cullHeight && !(rover->flags & FF_INVERTPLANES)) ||
+			    ((dup_viewz > cullHeight && (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES))) ||
 			     (dup_viewz < cullHeight && (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES))))
 			{
 				if (rover->flags & FF_FOG)
@@ -3490,7 +3509,7 @@ static void HWR_Subsector(size_t num)
 				{
 					HWR_GetLevelFlat(&levelflats[*rover->toppic]);
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(NULL, &extrasubsectors[num], true, *rover->topheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic],
+					HWR_RenderPlane(sub, &extrasubsectors[num], true, *rover->topheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic],
 					                  rover->master->frontsector, 255, false, *gr_frontsector->lightlist[light].extra_colormap);
 				}
 			}
@@ -3499,7 +3518,6 @@ static void HWR_Subsector(size_t num)
 #endif
 #endif //doplanes
 
-#ifdef POLYOBJECTS
 	// Draw all the polyobjects in this subsector
 	if (sub->polyList)
 	{
@@ -3520,15 +3538,12 @@ static void HWR_Subsector(size_t num)
 		// Draw polyobject lines.
 		HWR_AddPolyObjectSegs();
 
-#ifdef POLYOBJECTS_PLANES
 		if (sub->validcount != validcount) // This validcount situation seems to let us know that the floors have already been drawn.
 		{
 			// Draw polyobject planes
 			HWR_AddPolyObjectPlanes();
 		}
-#endif
 	}
-#endif
 
 // Hurder ici se passe les choses INT32�essantes!
 // on vient de tracer le sol et le plafond
@@ -3551,14 +3566,8 @@ static void HWR_Subsector(size_t num)
 		while (count--)
 		{
 
-			if (!line->glseg
-#ifdef POLYOBJECTS
-			    && !line->polyseg // ignore segs that belong to polyobjects
-#endif
-			)
-			{
+			if (!line->glseg && !line->polyseg) // ignore segs that belong to polyobjects
 				HWR_AddLine(line);
-			}
 			line++;
 		}
 	}
@@ -3873,7 +3882,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, gr_vissprite_t *spr, fixed_t scale
 	HWR_GetPatch(gpatch);
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
-	scalemul = FixedMul(scalemul, (thing->radius*2) / gpatch->height);
+	scalemul = FixedMul(scalemul, (thing->radius*2) / SHORT(gpatch->height));
 
 	fscale = FIXED_TO_FLOAT(scalemul);
 	fx = FIXED_TO_FLOAT(thing->x);
@@ -3885,9 +3894,9 @@ static void HWR_DrawDropShadow(mobj_t *thing, gr_vissprite_t *spr, fixed_t scale
 	//  0--1
 
 	if (thing && fabsf(fscale - 1.0f) > 1.0E-36f)
-		offset = (gpatch->height/2) * fscale;
+		offset = (SHORT(gpatch->height)/2) * fscale;
 	else
-		offset = (float)(gpatch->height/2);
+		offset = (float)(SHORT(gpatch->height)/2);
 
 	shadowVerts[0].x = shadowVerts[3].x = fx - offset;
 	shadowVerts[2].x = shadowVerts[1].x = fx + offset;
@@ -3935,7 +3944,10 @@ static void HWR_DrawDropShadow(mobj_t *thing, gr_vissprite_t *spr, fixed_t scale
 	{
 		light = R_GetPlaneLight(thing->subsector->sector, floorz, false); // Always use the light at the top instead of whatever I was doing before
 
-		lightlevel = *thing->subsector->sector->lightlist[light].lightlevel;
+		if (*thing->subsector->sector->lightlist[light].lightlevel > 255)
+			lightlevel = 255;
+		else
+			lightlevel = *thing->subsector->sector->lightlist[light].lightlevel;
 
 		if (*thing->subsector->sector->lightlist[light].extra_colormap)
 			colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
@@ -3959,7 +3971,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, gr_vissprite_t *spr, fixed_t scale
 }
 
 // This is expecting a pointer to an array containing 4 wallVerts for a sprite
-static void HWR_RotateSpritePolyToAim(gr_vissprite_t *spr, FOutVector *wallVerts)
+static void HWR_RotateSpritePolyToAim(gr_vissprite_t *spr, FOutVector *wallVerts, const boolean precip)
 {
 	if (cv_grspritebillboarding.value
 		&& spr && spr->mobj && !(spr->mobj->frame & FF_PAPERSPRITE)
@@ -3967,7 +3979,7 @@ static void HWR_RotateSpritePolyToAim(gr_vissprite_t *spr, FOutVector *wallVerts
 	{
 		float basey = FIXED_TO_FLOAT(spr->mobj->z);
 		float lowy = wallVerts[0].y;
-		if (P_MobjFlip(spr->mobj) == -1)
+		if (!precip && P_MobjFlip(spr->mobj) == -1) // precip doesn't have eflags so they can't flip
 		{
 			basey = FIXED_TO_FLOAT(spr->mobj->z + spr->mobj->height);
 		}
@@ -4080,7 +4092,7 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 	}
 
 	// Let dispoffset work first since this adjust each vertex
-	HWR_RotateSpritePolyToAim(spr, baseWallVerts);
+	HWR_RotateSpritePolyToAim(spr, baseWallVerts, false);
 
 	realtop = top = baseWallVerts[3].y;
 	realbot = bot = baseWallVerts[0].y;
@@ -4135,7 +4147,7 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 		if (h <= temp)
 		{
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = *list[i-1].lightlevel;
+				lightlevel = *list[i-1].lightlevel > 255 ? 255 : *list[i-1].lightlevel;
 			colormap = *list[i-1].extra_colormap;
 			break;
 		}
@@ -4150,7 +4162,7 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 		if (!(list[i].flags & FF_NOSHADE) && (list[i].flags & FF_CUTSPRITES))
 		{
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = *list[i].lightlevel;
+				lightlevel = *list[i].lightlevel > 255 ? 255 : *list[i].lightlevel;
 			colormap = *list[i].extra_colormap;
 		}
 
@@ -4353,7 +4365,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	}
 
 	// Let dispoffset work first since this adjust each vertex
-	HWR_RotateSpritePolyToAim(spr, wallVerts);
+	HWR_RotateSpritePolyToAim(spr, wallVerts, false);
 
 	// This needs to be AFTER the shadows so that the regular sprites aren't drawn completely black.
 	// sprite lighting by modulating the RGB components
@@ -4366,7 +4378,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 		extracolormap_t *colormap = sector->extra_colormap;
 
 		if (!(spr->mobj->frame & FF_FULLBRIGHT))
-			lightlevel = sector->lightlevel;
+			lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel;
 
 		if (colormap)
 			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, colormap->rgba, colormap->fadergba, false, false);
@@ -4437,7 +4449,7 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 	wallVerts[1].z = wallVerts[2].z = spr->z2;
 
 	// Let dispoffset work first since this adjust each vertex
-	HWR_RotateSpritePolyToAim(spr, wallVerts);
+	HWR_RotateSpritePolyToAim(spr, wallVerts, true);
 
 	wallVerts[0].sow = wallVerts[3].sow = 0;
 	wallVerts[2].sow = wallVerts[1].sow = gpatch->max_s;
@@ -4463,7 +4475,7 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 			light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before
 
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = *sector->lightlist[light].lightlevel;
+				lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel;
 
 			if (*sector->lightlist[light].extra_colormap)
 				colormap = *sector->lightlist[light].extra_colormap;
@@ -4471,7 +4483,7 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 		else
 		{
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = sector->lightlevel;
+				lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel;
 
 			if (sector->extra_colormap)
 				colormap = sector->extra_colormap;
@@ -5187,10 +5199,10 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
 		if (rotsprite != NULL)
 		{
-			spr_width = rotsprite->width << FRACBITS;
-			spr_height = rotsprite->height << FRACBITS;
-			spr_offset = rotsprite->leftoffset << FRACBITS;
-			spr_topoffset = rotsprite->topoffset << FRACBITS;
+			spr_width = SHORT(rotsprite->width) << FRACBITS;
+			spr_height = SHORT(rotsprite->height) << FRACBITS;
+			spr_offset = SHORT(rotsprite->leftoffset) << FRACBITS;
+			spr_topoffset = SHORT(rotsprite->topoffset) << FRACBITS;
 			// flip -> rotate, not rotate -> flip
 			flip = 0;
 		}
@@ -6292,7 +6304,6 @@ void HWR_Shutdown(void)
 	CONS_Printf("HWR_Shutdown()\n");
 	HWR_FreeExtraSubsectors();
 	HWR_FreePolyPool();
-	HWR_FreeMipmapCache();
 	HWR_FreeTextureCache();
 	HWD.pfnFlushScreenTextures();
 }
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 5c3cd40a6c02cedacfa55988b94675654a9dd558..b47ac09bfeb321ccc67e870bc2b5c8885aee4de7 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1221,7 +1221,7 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 			light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before
 
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = *sector->lightlist[light].lightlevel;
+				lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel;
 
 			if (*sector->lightlist[light].extra_colormap)
 				colormap = *sector->lightlist[light].extra_colormap;
@@ -1229,7 +1229,7 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 		else
 		{
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
-				lightlevel = sector->lightlevel;
+				lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel;
 
 			if (sector->extra_colormap)
 				colormap = sector->extra_colormap;
@@ -1478,7 +1478,7 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 
 			// rotation pivot
 			p.centerx = FIXED_TO_FLOAT(spr->mobj->radius/2);
-			p.centery = FIXED_TO_FLOAT(spr->mobj->height/2);
+			p.centery = FIXED_TO_FLOAT(spr->mobj->height/(flip ? -2 : 2));
 
 			// rotation axis
 			if (sprinfo->available)
@@ -1490,6 +1490,9 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 				p.rollflip = 1;
 			else if ((sprframe->rotate & SRF_LEFT) && (ang >= ANGLE_180)) // See from left
 				p.rollflip = -1;
+
+			if (flip)
+				p.rollflip *= -1;
 		}
 
 		p.anglex = 0.0f;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 6aa5a451000a429ab351065c3c97b2d9a7fb72f4..3ff9db2b66a39737bcbdc04260c3842e1bc6cf1d 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -68,7 +68,7 @@ patch_t *nightsnum[10]; // 0-9
 // Level title and credits fonts
 patch_t *lt_font[LT_FONTSIZE];
 patch_t *cred_font[CRED_FONTSIZE];
-patch_t *ttlnum[20]; // act numbers (0-19)
+patch_t *ttlnum[10]; // act numbers (0-9)
 
 // Name tag fonts
 patch_t *ntb_font[NT_FONTSIZE];
@@ -243,7 +243,7 @@ void HU_LoadGraphics(void)
 	tallinfin = (patch_t *)W_CachePatchName("STTINFIN", PU_HUDGFX);
 
 	// cache act numbers for level titles
-	for (i = 0; i < 20; i++)
+	for (i = 0; i < 10; i++)
 	{
 		sprintf(buffer, "TTL%.2d", i);
 		ttlnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index 9e3c667479906955a1063d478062fd06aa71cfd5..63d85f1b81a7637579a1b510a2d979f79368281c 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -85,7 +85,7 @@ extern patch_t *lt_font[LT_FONTSIZE];
 extern patch_t *cred_font[CRED_FONTSIZE];
 extern patch_t *ntb_font[NT_FONTSIZE];
 extern patch_t *nto_font[NT_FONTSIZE];
-extern patch_t *ttlnum[20];
+extern patch_t *ttlnum[10];
 extern patch_t *emeraldpics[3][8];
 extern patch_t *rflagico;
 extern patch_t *bflagico;
diff --git a/src/i_tcp.c b/src/i_tcp.c
index 34cad1765df8225cb86fb93d1b781191cd506f2f..373ea1bd03836514cd219030fd72fb1431791126 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -1423,6 +1423,7 @@ static void SOCK_ClearBans(void)
 boolean I_InitTcpNetwork(void)
 {
 	char serverhostname[255];
+	const char *urlparam = NULL;
 	boolean ret = false;
 	// initilize the OS's TCP/IP stack
 	if (!I_InitTcpDriver())
@@ -1476,10 +1477,12 @@ boolean I_InitTcpNetwork(void)
 
 		ret = true;
 	}
-	else if (M_CheckParm("-connect"))
+	else if ((urlparam = M_GetUrlProtocolArg()) != NULL || M_CheckParm("-connect"))
 	{
-		if (M_IsNextParm())
-			strcpy(serverhostname, M_GetNextParm());
+		if (urlparam != NULL)
+			strlcpy(serverhostname, urlparam, sizeof(serverhostname));
+		else if (M_IsNextParm())
+			strlcpy(serverhostname, M_GetNextParm(), sizeof(serverhostname));
 		else
 			serverhostname[0] = 0; // assuming server in the LAN, use broadcast to detect it
 
diff --git a/src/i_video.h b/src/i_video.h
index bdc10c9c5d2aab8a2eaa8eceff5a1b2db7411cd5..98ed7f38a18822d8038ec0973ef3c680572c2d89 100644
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -36,10 +36,10 @@ typedef enum
 */
 extern rendermode_t rendermode;
 
-/**	\brief hardware renderer loaded
+/**	\brief OpenGL state
 	0 = never loaded, 1 = loaded successfully, -1 = failed loading
 */
-extern INT32 hwrenderloaded;
+extern INT32 vid_opengl_state;
 
 /**	\brief use highcolor modes if true
 */
@@ -49,11 +49,7 @@ extern boolean highcolor;
 */
 void I_StartupGraphics(void);
 
-/**	\brief setup hardware mode
-*/
-void I_StartupHardwareGraphics(void);
-
-/**	\brief restore old video mode
+/**	\brief shutdown video mode
 */
 void I_ShutdownGraphics(void);
 
@@ -97,6 +93,14 @@ INT32 VID_SetMode(INT32 modenum);
 */
 void VID_CheckRenderer(void);
 
+/**	\brief Load OpenGL mode
+*/
+void VID_StartupOpenGL(void);
+
+/**	\brief Checks if OpenGL loaded
+*/
+void VID_CheckGLLoaded(rendermode_t oldrender);
+
 /**	\brief	The VID_GetModeName function
 
 	\param	modenum	video mode number
diff --git a/src/info.c b/src/info.c
index bd6ccb527a8ace37c59397091a8d0c868c668905..d443e035d004c8437401d89d31a1e6a1186284c5 100644
--- a/src/info.c
+++ b/src/info.c
@@ -187,6 +187,8 @@ char sprnames[NUMSPRITES + 1][5] =
 
 	// Projectiles
 	"MISL",
+	"LASR", // GFZ3 laser
+	"LASF", // GFZ3 laser flames
 	"TORP", // Torpedo
 	"ENRG", // Energy ball
 	"MINE", // Skim mine
@@ -2058,7 +2060,15 @@ state_t states[NUMSTATES] =
 
 	{SPR_MISL, FF_FULLBRIGHT, 1, {A_SmokeTrailer}, MT_SMOKE, 0, S_ROCKET}, // S_ROCKET
 
-	{SPR_MISL, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_LASER
+	{SPR_LASR, FF_FULLBRIGHT|0, 2, {NULL}, 0, 0, S_NULL}, // S_LASER
+	{SPR_LASR, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_NULL}, // S_LASER2
+	{SPR_LASR, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_NULL}, // S_LASERFLASH
+
+	{SPR_LASF,                           FF_FULLBRIGHT|0,       2,           {NULL}, 0, 0, S_LASERFLAME2}, // S_LASERFLAME1
+	{SPR_LASF,                           FF_FULLBRIGHT|1,       1, {A_ChangeHeight}, 156*FRACUNIT, 3, S_LASERFLAME3}, // S_LASERFLAME2
+	{SPR_LASF,                           FF_FULLBRIGHT|2,       0, {A_ChangeHeight}, 32*FRACUNIT, 3, S_LASERFLAME4}, // S_LASERFLAME3
+	{SPR_LASF, FF_ANIMATE|FF_PAPERSPRITE|FF_FULLBRIGHT|2,       4,           {NULL}, 1, 2, S_LASERFLAME5}, // S_LASERFLAME4
+	{SPR_LASF, FF_ANIMATE|FF_PAPERSPRITE|FF_FULLBRIGHT|4,      28,           {NULL}, 2, 2, S_NULL}, // S_LASERFLAME5
 
 	{SPR_TORP, 0, 1, {A_SmokeTrailer}, MT_SMOKE, 0, S_TORPEDO}, // S_TORPEDO
 
@@ -5665,28 +5675,28 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_EGGMOBILE_FIRE
 		-1,             // doomednum
-		S_SPINFIRE1,    // spawnstate
+		S_LASERFLAME1,  // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
-		sfx_None,       // seesound
+		sfx_s3kc2s,     // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
-		sfx_None,       // painsound
+		sfx_s3k8d,      // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		14*FRACUNIT,    // height
+		24*FRACUNIT,    // radius
+		84*FRACUNIT,    // height
 		0,              // display offset
 		DMG_FIRE,       // mass
 		1,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY|MF_FIRE, // flags
+		MF_NOGRAVITY|MF_FIRE|MF_PAIN, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9637,8 +9647,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
+		S_LASERFLASH,   // meleestate
+		S_LASER2,       // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
@@ -9649,7 +9659,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // mass
 		20,             // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY, // flags
+		MF_MISSILE|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
diff --git a/src/info.h b/src/info.h
index f8a7136740486df8cbed9af6a76bbc8b4996b378..79af9bbbb8ae444fa3e9aee46cc17ca922bf9d68 100644
--- a/src/info.h
+++ b/src/info.h
@@ -165,6 +165,7 @@ void A_SetTics();
 void A_SetRandomTics();
 void A_ChangeColorRelative();
 void A_ChangeColorAbsolute();
+void A_Dye();
 void A_MoveRelative();
 void A_MoveAbsolute();
 void A_Thrust();
@@ -283,6 +284,7 @@ void A_RolloutRock();
 void A_DragonbomberSpawn();
 void A_DragonWing();
 void A_DragonSegment();
+void A_ChangeHeight();
 
 // ratio of states to sprites to mobj types is roughly 6 : 1 : 1
 #define NUMMOBJFREESLOTS 512
@@ -450,6 +452,8 @@ typedef enum sprite
 
 	// Projectiles
 	SPR_MISL,
+	SPR_LASR, // GFZ3 laser
+	SPR_LASF, // GFZ3 laser flames
 	SPR_TORP, // Torpedo
 	SPR_ENRG, // Energy ball
 	SPR_MINE, // Skim mine
@@ -2219,6 +2223,14 @@ typedef enum state
 	S_ROCKET,
 
 	S_LASER,
+	S_LASER2,
+	S_LASERFLASH,
+
+	S_LASERFLAME1,
+	S_LASERFLAME2,
+	S_LASERFLAME3,
+	S_LASERFLAME4,
+	S_LASERFLAME5,
 
 	S_TORPEDO,
 
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index b25915e6b4dcd53904517e0e8783928c0b22f304..519801f2f6d174bdad220c99afd5fd01af0287db 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -218,10 +218,16 @@ static const char *GetUserdataUType(lua_State *L)
 //   or players[0].powers -> "player_t.powers"
 static int lib_userdataType(lua_State *L)
 {
+	int type;
 	lua_settop(L, 1); // pop everything except arg 1 (in case somebody decided to add more)
-	luaL_checktype(L, 1, LUA_TUSERDATA);
-	lua_pushstring(L, GetUserdataUType(L));
-	return 1;
+	type = lua_type(L, 1);
+	if (type == LUA_TLIGHTUSERDATA || type == LUA_TUSERDATA)
+	{
+		lua_pushstring(L, GetUserdataUType(L));
+		return 1;
+	}
+	else
+		return luaL_typerror(L, 1, "userdata");
 }
 
 static int lib_isPlayerAdmin(lua_State *L)
@@ -2456,6 +2462,20 @@ static int lib_sStopSound(lua_State *L)
 	return 0;
 }
 
+static int lib_sStopSoundByID(lua_State *L)
+{
+	void *origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	sfxenum_t sound_id = luaL_checkinteger(L, 2);
+	//NOHUD
+	if (!origin)
+		return LUA_ErrInvalid(L, "mobj_t");
+	if (sound_id >= NUMSFX)
+		return luaL_error(L, "sfx %d out of range (0 - %d)", sound_id, NUMSFX-1);
+
+	S_StopSoundByID(origin, sound_id);
+	return 0;
+}
+
 static int lib_sChangeMusic(lua_State *L)
 {
 #ifdef MUSICSLOT_COMPATIBILITY
@@ -3251,6 +3271,7 @@ static luaL_Reg lib[] = {
 	{"S_StartSound",lib_sStartSound},
 	{"S_StartSoundAtVolume",lib_sStartSoundAtVolume},
 	{"S_StopSound",lib_sStopSound},
+	{"S_StopSoundByID",lib_sStopSoundByID},
 	{"S_ChangeMusic",lib_sChangeMusic},
 	{"S_SpeedMusic",lib_sSpeedMusic},
 	{"S_StopMusic",lib_sStopMusic},
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
index bc8d20e8eb2ba9113d027f0316cbaa3e99685ead..5aae73284d985f34c16d11498aa35105fdba92ba 100644
--- a/src/lua_blockmaplib.c
+++ b/src/lua_blockmaplib.c
@@ -80,9 +80,7 @@ static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *th
 {
 	INT32 offset;
 	const INT32 *list; // Big blockmap
-#ifdef POLYOBJECTS
 	polymaplink_t *plink; // haleyjd 02/22/06
-#endif
 	line_t *ld;
 
 	if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
@@ -90,7 +88,6 @@ static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *th
 
 	offset = y*bmapwidth + x;
 
-#ifdef POLYOBJECTS
 	// haleyjd 02/22/06: consider polyobject lines
 	plink = polyblocklinks[offset];
 
@@ -133,7 +130,6 @@ static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *th
 		}
 		plink = (polymaplink_t *)(plink->link.next);
 	}
-#endif
 
 	offset = *(blockmap + offset); // offset = blockmap[y*bmapwidth+x];
 
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index a9fbad65f5585631490a8f7647288f0682685922..4fe234deeecc88f6643d04bd0663a070b3fa85be 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -430,22 +430,8 @@ static int lib_cvRegisterVar(lua_State *L)
 
 static int lib_cvFindVar(lua_State *L)
 {
-	consvar_t *cv;
-	if (( cv = CV_FindVar(luaL_checkstring(L,1)) ))
-	{
-		lua_settop(L,1);/* We only want one argument in the stack. */
-		lua_pushlightuserdata(L, cv);/* Now the second value on stack. */
-		luaL_getmetatable(L, META_CVAR);
-		/*
-		The metatable is the last value on the stack, so this
-		applies it to the second value, which is the cvar.
-		*/
-		lua_setmetatable(L,2);
-		lua_pushvalue(L,2);
-		return 1;
-	}
-	else
-		return 0;
+	LUA_PushLightUserdata(L, CV_FindVar(luaL_checkstring(L,1)), META_CVAR);
+	return 1;
 }
 
 // CONS_Printf for a single player
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 244b1bbb8748c16d0322ddd13f3b87838ce2d980..315c35cdf3e3b00c2c96759056a1db201b220a46 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -40,6 +40,7 @@ enum hook {
 	hook_JumpSpinSpecial,
 	hook_BotTiccmd,
 	hook_BotAI,
+	hook_BotRespawn,
 	hook_LinedefExecute,
 	hook_PlayerMsg,
 	hook_HurtMsg,
@@ -91,6 +92,7 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8
 #define LUAh_JumpSpinSpecial(player) LUAh_PlayerHook(player, hook_JumpSpinSpecial) // Hook for P_DoJumpStuff (Spin button effect (mid-air))
 boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd); // Hook for B_BuildTiccmd
 boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd); // Hook for B_BuildTailsTiccmd by skin name
+boolean LUAh_BotRespawn(mobj_t *sonic, mobj_t *tails); // Hook for B_CheckRespawn
 boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector); // Hook for linedef executors
 boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg); // Hook for chat messages
 boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype); // Hook for hurt messages
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 522be8c32f1f917f443557fa281fbadbd7048d9d..d4fe72682dfe62702e9fb16a2950f672e9cd5290 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -52,6 +52,7 @@ const char *const hookNames[hook_MAX+1] = {
 	"JumpSpinSpecial",
 	"BotTiccmd",
 	"BotAI",
+	"BotRespawn",
 	"LinedefExecute",
 	"PlayerMsg",
 	"HurtMsg",
@@ -80,9 +81,7 @@ struct hook_s
 	UINT16 id;
 	union {
 		mobjtype_t mt;
-		char *skinname;
-		char *musname;
-		char *funcname;
+		char *str;
 	} s;
 	boolean error;
 };
@@ -151,28 +150,16 @@ static int lib_addHook(lua_State *L)
 		break;
 	case hook_BotAI:
 	case hook_ShouldJingleContinue:
-		hook.s.skinname = NULL;
+		hook.s.str = NULL;
 		if (lua_isstring(L, 2))
 		{ // lowercase copy
-			const char *s = lua_tostring(L, 2);
-			char *p = hook.s.skinname = ZZ_Alloc(strlen(s)+1);
-			do {
-				*p = tolower(*s);
-				++p;
-			} while(*(++s));
-			*p = 0;
+			hook.s.str = Z_StrDup(lua_tostring(L, 2));
+			strlwr(hook.s.str);
 		}
 		break;
 	case hook_LinedefExecute: // Linedef executor functions
-		{ // uppercase copy
-			const char *s = luaL_checkstring(L, 2);
-			char *p = hook.s.funcname = ZZ_Alloc(strlen(s)+1);
-			do {
-				*p = toupper(*s);
-				++p;
-			} while(*(++s));
-			*p = 0;
-		}
+		hook.s.str = Z_StrDup(luaL_checkstring(L, 2));
+		strupr(hook.s.str);
 		break;
 	default:
 		break;
@@ -1075,7 +1062,7 @@ boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 	for (hookp = roothook; hookp; hookp = hookp->next)
 	{
 		if (hookp->type != hook_BotAI
-		|| (hookp->s.skinname && strcmp(hookp->s.skinname, ((skin_t*)tails->skin)->name)))
+		|| (hookp->s.str && strcmp(hookp->s.str, ((skin_t*)tails->skin)->name)))
 			continue;
 
 		if (lua_gettop(gL) == 0)
@@ -1125,6 +1112,51 @@ boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 	return hooked;
 }
 
+// Hook for B_CheckRespawn
+boolean LUAh_BotRespawn(mobj_t *sonic, mobj_t *tails)
+{
+	hook_p hookp;
+	UINT8 shouldRespawn = 0; // 0 = default, 1 = force yes, 2 = force no.
+	if (!gL || !(hooksAvailable[hook_BotRespawn/8] & (1<<(hook_BotRespawn%8))))
+		return false;
+
+	lua_settop(gL, 0);
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_BotRespawn)
+			continue;
+
+		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, 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))
+				shouldRespawn = 1; // Force yes
+			else
+				shouldRespawn = 2; // Force no
+		}
+		lua_pop(gL, 1);
+	}
+
+	lua_settop(gL, 0);
+	return shouldRespawn;
+}
+
 // Hook for linedef executors
 boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
 {
@@ -1137,7 +1169,7 @@ boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
 
 	for (hookp = linedefexecutorhooks; hookp; hookp = hookp->next)
 	{
-		if (strcmp(hookp->s.funcname, line->text))
+		if (strcmp(hookp->s.str, line->text))
 			continue;
 
 		if (lua_gettop(gL) == 0)
@@ -1675,7 +1707,7 @@ boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname)
 	for (hookp = roothook; hookp; hookp = hookp->next)
 	{
 		if (hookp->type != hook_ShouldJingleContinue
-			|| (hookp->s.musname && strcmp(hookp->s.musname, musname)))
+			|| (hookp->s.str && strcmp(hookp->s.str, musname)))
 			continue;
 
 		if (lua_gettop(gL) == 0)
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 818e760c9d9b6b39fb1aff73394784ebf794c427..703b924bb908fe7b2adfeb2fe16b453fd5b05bd0 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -412,9 +412,9 @@ static int libd_cachePatch(lua_State *L)
 	HUDONLY
 
 	luapat = patchinfohead;
-	lumpnum = W_CheckNumForName(luaL_checkstring(L, 1));
+	lumpnum = W_CheckNumForLongName(luaL_checkstring(L, 1));
 	if (lumpnum == LUMPERROR)
-		lumpnum = W_GetNumForName("MISSING");
+		lumpnum = W_GetNumForLongName("MISSING");
 
 	for (i = 0; i < numluapatches; i++)
 	{
@@ -454,7 +454,7 @@ static int libd_cachePatch(lua_State *L)
 	numluapatches++;
 #else
 	HUDONLY
-	LUA_PushUserdata(L, W_CachePatchName(luaL_checkstring(L, 1), PU_PATCH), META_PATCH);
+	LUA_PushUserdata(L, W_CachePatchLongName(luaL_checkstring(L, 1), PU_PATCH), META_PATCH);
 #endif
 	return 1;
 }
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 25870d0b1cfced651926f7cbc40eeb4880c001da..a82403097835cf38cb9c8c92b801c26e8724eec8 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -388,10 +388,7 @@ static int lib_setSpriteInfo(lua_State *L)
 		lua_Integer i = 0;
 		const char *str = NULL;
 		if (lua_isnumber(L, 2))
-		{
 			i = lua_tointeger(L, 2);
-			i++; // shift index in case of missing rotsprite support
-		}
 		else
 			str = luaL_checkstring(L, 2);
 
@@ -1634,7 +1631,6 @@ int LUA_InfoLib(lua_State *L)
 			lua_pushcfunction(L, lib_spriteinfolen);
 			lua_setfield(L, -2, "__len");
 		lua_setmetatable(L, -2);
-	lua_pushvalue(L, -1);
 	lua_setglobal(L, "spriteinfo");
 
 	luaL_newmetatable(L, META_LUABANKS);
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index d851c820e5a4c9ee280e2b180ba82415470fc494..ece42b8d3dec950b8c6a904eb6370715963f2cd7 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -2082,6 +2082,12 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushinteger(L, header->menuflags);
 	else if (fastcmp(field,"startrings"))
 		lua_pushinteger(L, header->startrings);
+	else if (fastcmp(field, "sstimer"))
+		lua_pushinteger(L, header->sstimer);
+	else if (fastcmp(field, "ssspheres"))
+		lua_pushinteger(L, header->ssspheres);
+	else if (fastcmp(field, "gravity"))
+		lua_pushfixed(L, header->gravity);
 	// TODO add support for reading numGradedMares and grades
 	else {
 		// Read custom vars now
diff --git a/src/lua_script.c b/src/lua_script.c
index 8c0cd5351dc43d705a23a1c14b5bcb681541939f..d2069e8a72f1f370c36b086482c2c68d8c529705 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -441,9 +441,9 @@ void LUA_LoadLump(UINT16 wad, UINT16 lump)
 	else // If it's not a .lua file, copy the lump name in too.
 	{
 		lumpinfo_t *lump_p = &wadfiles[wad]->lumpinfo[lump];
-		len += 1 + strlen(lump_p->name2); // length of file name, '|', and lump name
+		len += 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name
 		name = malloc(len+1);
-		sprintf(name, "%s|%s", wadfiles[wad]->filename, lump_p->name2);
+		sprintf(name, "%s|%s", wadfiles[wad]->filename, lump_p->fullname);
 		name[len] = '\0';
 	}
 
@@ -568,6 +568,27 @@ fixed_t LUA_EvalMath(const char *word)
 	return res;
 }
 
+/*
+LUA_PushUserdata but no userdata is created.
+You can't invalidate it therefore.
+*/
+
+void LUA_PushLightUserdata (lua_State *L, void *data, const char *meta)
+{
+	if (data)
+	{
+		lua_pushlightuserdata(L, data);
+		luaL_getmetatable(L, meta);
+		/*
+		The metatable is the last value on the stack, so this
+		applies it to the second value, which is the userdata.
+		*/
+		lua_setmetatable(L, -2);
+	}
+	else
+		lua_pushnil(L);
+}
+
 // Takes a pointer, any pointer, and a metatable name
 // Creates a userdata for that pointer with the given metatable
 // Pushes it to the stack and stores it in the registry.
@@ -709,9 +730,13 @@ void LUA_InvalidatePlayer(player_t *player)
 enum
 {
 	ARCH_NULL=0,
-	ARCH_BOOLEAN,
-	ARCH_SIGNED,
-	ARCH_STRING,
+	ARCH_TRUE,
+	ARCH_FALSE,
+	ARCH_INT8,
+	ARCH_INT16,
+	ARCH_INT32,
+	ARCH_SMALLSTRING,
+	ARCH_LARGESTRING,
 	ARCH_TABLE,
 
 	ARCH_MOBJINFO,
@@ -796,22 +821,33 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 		WRITEUINT8(save_p, ARCH_NULL);
 		return 2;
 	case LUA_TBOOLEAN:
-		WRITEUINT8(save_p, ARCH_BOOLEAN);
-		WRITEUINT8(save_p, lua_toboolean(gL, myindex));
+		WRITEUINT8(save_p, lua_toboolean(gL, myindex) ? ARCH_TRUE : ARCH_FALSE);
 		break;
 	case LUA_TNUMBER:
 	{
 		lua_Integer number = lua_tointeger(gL, myindex);
-        WRITEUINT8(save_p, ARCH_SIGNED);
-        WRITEFIXED(save_p, number);
+		if (number >= INT8_MIN && number <= INT8_MAX)
+		{
+			WRITEUINT8(save_p, ARCH_INT8);
+			WRITESINT8(save_p, number);
+		}
+		else if (number >= INT16_MIN && number <= INT16_MAX)
+		{
+			WRITEUINT8(save_p, ARCH_INT16);
+			WRITEINT16(save_p, number);
+		}
+		else
+		{
+			WRITEUINT8(save_p, ARCH_INT32);
+			WRITEFIXED(save_p, number);
+		}
 		break;
 	}
 	case LUA_TSTRING:
 	{
-		UINT16 len = (UINT16)lua_objlen(gL, myindex); // get length of string, including embedded zeros
+		UINT32 len = (UINT32)lua_objlen(gL, myindex); // get length of string, including embedded zeros
 		const char *s = lua_tostring(gL, myindex);
-		UINT16 i = 0;
-		WRITEUINT8(save_p, ARCH_STRING);
+		UINT32 i = 0;
 		// if you're wondering why we're writing a string to save_p this way,
 		// it turns out that Lua can have embedded zeros ('\0') in the strings,
 		// so we can't use WRITESTRING as that cuts off when it finds a '\0'.
@@ -819,7 +855,16 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 		// fixing the awful crashes previously encountered for reading strings longer than 1024
 		// (yes I know that's kind of a stupid thing to care about, but it'd be evil to trim or ignore them?)
 		// -- Monster Iestyn 05/08/18
-		WRITEUINT16(save_p, len); // save size of string
+		if (len < 255)
+		{
+			WRITEUINT8(save_p, ARCH_SMALLSTRING);
+			WRITEUINT8(save_p, len); // save size of string
+		}
+		else
+		{
+			WRITEUINT8(save_p, ARCH_LARGESTRING);
+			WRITEUINT32(save_p, len); // save size of string
+		}
 		while (i < len)
 			WRITECHAR(save_p, s[i++]); // write chars individually, including the embedded zeros
 		break;
@@ -989,16 +1034,8 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			if (!rover)
 				WRITEUINT8(save_p, ARCH_NULL);
 			else {
-				ffloor_t *r2;
-				UINT16 i = 0;
-				// search for id
-				for (r2 = rover->target->ffloors; r2; r2 = r2->next)
-				{
-					if (r2 == rover)
-						break;
-					i++;
-				}
-				if (!r2)
+				UINT16 i = P_GetFFloorID(rover);
+				if (i == UINT16_MAX) // invalid ID
 					WRITEUINT8(save_p, ARCH_NULL);
 				else
 				{
@@ -1157,21 +1194,36 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 	case ARCH_NULL:
 		lua_pushnil(gL);
 		break;
-	case ARCH_BOOLEAN:
-		lua_pushboolean(gL, READUINT8(save_p));
+	case ARCH_TRUE:
+		lua_pushboolean(gL, true);
+		break;
+	case ARCH_FALSE:
+		lua_pushboolean(gL, false);
+		break;
+	case ARCH_INT8:
+		lua_pushinteger(gL, READSINT8(save_p));
 		break;
-	case ARCH_SIGNED:
+	case ARCH_INT16:
+		lua_pushinteger(gL, READINT16(save_p));
+		break;
+	case ARCH_INT32:
 		lua_pushinteger(gL, READFIXED(save_p));
 		break;
-	case ARCH_STRING:
+	case ARCH_SMALLSTRING:
+	case ARCH_LARGESTRING:
 	{
-		UINT16 len = READUINT16(save_p); // length of string, including embedded zeros
+		UINT32 len;
 		char *value;
-		UINT16 i = 0;
+		UINT32 i = 0;
+
 		// See my comments in the ArchiveValue function;
 		// it's much the same for reading strings as writing them!
 		// (i.e. we can't use READSTRING either)
 		// -- Monster Iestyn 05/08/18
+		if (type == ARCH_SMALLSTRING)
+			len = READUINT8(save_p); // length of string, including embedded zeros
+		else
+			len = READUINT32(save_p); // length of string, including embedded zeros
 		value = malloc(len); // make temp buffer of size len
 		// now read the actual string
 		while (i < len)
diff --git a/src/lua_script.h b/src/lua_script.h
index 7bdf7685b39dc3d5daf74a3a110222e71dc43984..3166fdfc7ce03b21fc0f6f4b6a0fd081850faad7 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -44,6 +44,7 @@ void LUA_LoadLump(UINT16 wad, UINT16 lump);
 void LUA_DumpFile(const char *filename);
 #endif
 fixed_t LUA_EvalMath(const char *word);
+void LUA_PushLightUserdata(lua_State *L, void *data, const char *meta);
 void LUA_PushUserdata(lua_State *L, void *data, const char *meta);
 void LUA_InvalidateUserdata(void *data);
 void LUA_InvalidateLevel(void);
diff --git a/src/m_anigif.c b/src/m_anigif.c
index ce2ca20b9677302f08e140f65abc6c638fd7ce89..1b71a09bce278b59741db664f334faa5075e2aec 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -490,29 +490,28 @@ const UINT8 gifframe_gchead[4] = {0x21,0xF9,0x04,0x04}; // GCE, bytes, packed by
 static UINT8 *gifframe_data = NULL;
 static size_t gifframe_size = 8192;
 
+//
+// GIF_rgbconvert
+// converts an RGB frame to a frame with a palette.
+//
 #ifdef HWRENDER
-static void hwrconvert(void)
+static void GIF_rgbconvert(UINT8 *linear, UINT8 *scr)
 {
-	UINT8 *linear = HWR_GetScreenshot();
-	UINT8 *dest = screens[2];
 	UINT8 r, g, b;
-	INT32 x, y;
-	size_t i = 0;
+	size_t src = 0, dest = 0;
+	size_t size = (vid.width * vid.height * 3);
 
 	InitColorLUT(gif_framepalette);
 
-	for (y = 0; y < vid.height; y++)
+	while (src < size)
 	{
-		for (x = 0; x < vid.width; x++, i += 3)
-		{
-			r = (UINT8)linear[i];
-			g = (UINT8)linear[i + 1];
-			b = (UINT8)linear[i + 2];
-			dest[(y * vid.width) + x] = colorlookup[r >> SHIFTCOLORBITS][g >> SHIFTCOLORBITS][b >> SHIFTCOLORBITS];
-		}
+		r = (UINT8)linear[src];
+		g = (UINT8)linear[src + 1];
+		b = (UINT8)linear[src + 2];
+		scr[dest] = colorlookup[r >> SHIFTCOLORBITS][g >> SHIFTCOLORBITS][b >> SHIFTCOLORBITS];
+		src += (3 * scrbuf_downscaleamt);
+		dest += scrbuf_downscaleamt;
 	}
-
-	free(linear);
 }
 #endif
 
@@ -556,7 +555,11 @@ static void GIF_framewrite(void)
 			I_ReadScreen(movie_screen);
 #ifdef HWRENDER
 		else if (rendermode == render_opengl)
-			hwrconvert();
+		{
+			UINT8 *linear = HWR_GetScreenshot();
+			GIF_rgbconvert(linear, movie_screen);
+			free(linear);
+		}
 #endif
 	}
 	else
@@ -565,18 +568,20 @@ static void GIF_framewrite(void)
 		blitw = vid.width;
 		blith = vid.height;
 
-		if (gif_frames == 0)
-		{
-			if (rendermode == render_soft)
-				I_ReadScreen(movie_screen);
 #ifdef HWRENDER
-			else if (rendermode == render_opengl)
-			{
-				hwrconvert();
-				VID_BlitLinearScreen(screens[2], screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
-			}
-#endif
+		// Copy the current OpenGL frame into the base screen
+		if (rendermode == render_opengl)
+		{
+			UINT8 *linear = HWR_GetScreenshot();
+			GIF_rgbconvert(linear, screens[0]);
+			free(linear);
 		}
+#endif
+
+		// Copy the first frame into the movie screen
+		// OpenGL already does the same above.
+		if (gif_frames == 0 && rendermode == render_soft)
+			I_ReadScreen(movie_screen);
 
 		movie_screen = screens[0];
 	}
diff --git a/src/m_argv.c b/src/m_argv.c
index acb74cff415d2d84058ce5cc81e74a548c0ee936..7d43d96bc62f0c20c2b1f1485809defab2bdda0a 100644
--- a/src/m_argv.c
+++ b/src/m_argv.c
@@ -34,6 +34,25 @@ boolean myargmalloc = false;
 */
 static INT32 found;
 
+/**	\brief Parses a server URL (such as srb2://127.0.0.1) as may be passed to the game via a web browser, etc.
+
+	\return the contents of the URL after the protocol (a server to join), or NULL if not found
+*/
+const char *M_GetUrlProtocolArg(void)
+{
+	INT32 i;
+	const size_t len = strlen(SERVER_URL_PROTOCOL);
+
+	for (i = 1; i < myargc; i++)
+	{
+		if (strlen(myargv[i]) > len && !strnicmp(myargv[i], SERVER_URL_PROTOCOL, len))
+		{
+			return &myargv[i][len];
+		}
+	}
+
+	return NULL;
+}
 
 /**	\brief	The M_CheckParm function
 
diff --git a/src/m_argv.h b/src/m_argv.h
index ca97d9b126106e1e2a6a2d70271acc7ee40ab380..92770f4e9e9b0c58b3a32ad8b752e62cca65b236 100644
--- a/src/m_argv.h
+++ b/src/m_argv.h
@@ -21,6 +21,9 @@ extern INT32 myargc;
 extern char **myargv;
 extern boolean myargmalloc;
 
+// Looks for an srb2:// (or similar) URL passed in as an argument and returns the IP to connect to if found.
+const char *M_GetUrlProtocolArg(void);
+
 // Returns the position of the given parameter in the arg list (0 if not found).
 INT32 M_CheckParm(const char *check);
 
diff --git a/src/m_cheat.c b/src/m_cheat.c
index e705f26d8a1a258fca8835f22a49019ced308089..156c5db16bbc7d92a4a89151d1c8b05344ef27f8 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -934,6 +934,12 @@ void Command_Setcontinues_f(void)
 	REQUIRE_NOULTIMATE;
 	REQUIRE_PANDORA;
 
+	if (!continuesInSession)
+	{
+		CONS_Printf(M_GetText("This session does not use continues.\n"));
+		return;
+	}
+
 	if (COM_Argc() > 1)
 	{
 		INT32 numcontinues = atoi(COM_Argv(1));
diff --git a/src/m_fixed.h b/src/m_fixed.h
index 7fdb9ad0a014e05828c289d67bb0342039208d19..cc54c1aea257d5d0ef8180fe3ef15d25b392afb6 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -38,8 +38,20 @@ typedef INT32 fixed_t;
 /*!
   \brief convert fixed_t into floating number
 */
-#define FIXED_TO_FLOAT(x) (((float)(x)) / ((float)FRACUNIT))
-#define FLOAT_TO_FIXED(f) (fixed_t)((f) * ((float)FRACUNIT))
+
+FUNCMATH FUNCINLINE static ATTRINLINE float FixedToFloat(fixed_t x)
+{
+	return x / (float)FRACUNIT;
+}
+
+FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FloatToFixed(float f)
+{
+	return (fixed_t)(f * FRACUNIT);
+}
+
+// for backwards compat
+#define FIXED_TO_FLOAT(x) FixedToFloat(x) // (((float)(x)) / ((float)FRACUNIT))
+#define FLOAT_TO_FIXED(f) FloatToFixed(f) // (fixed_t)((f) * ((float)FRACUNIT))
 
 
 #if defined (__WATCOMC__) && FRACBITS == 16
diff --git a/src/m_menu.c b/src/m_menu.c
index 84ee898d6a789ee39fceb7be18921c01bf6704f1..175dc1c2229f1cbddda69a1795e43a16a9840bb4 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -522,6 +522,8 @@ static menuitem_t MISC_AddonsMenu[] =
 // ---------------------------------
 static menuitem_t MAPauseMenu[] =
 {
+	{IT_CALL | IT_STRING,    NULL, "Emblem Hints...",      M_EmblemHints,         32},
+
 	{IT_CALL | IT_STRING,    NULL, "Continue",             M_SelectableClearMenus,48},
 	{IT_CALL | IT_STRING,    NULL, "Retry",                M_ModeAttackRetry,     56},
 	{IT_CALL | IT_STRING,    NULL, "Abort",                M_ModeAttackEndGame,   64},
@@ -529,6 +531,7 @@ static menuitem_t MAPauseMenu[] =
 
 typedef enum
 {
+	mapause_hints,
 	mapause_continue,
 	mapause_retry,
 	mapause_abort
@@ -730,9 +733,9 @@ static menuitem_t SR_SoundTestMenu[] =
 
 static menuitem_t SR_EmblemHintMenu[] =
 {
-	{IT_STRING | IT_ARROWS,       NULL, "Page", M_HandleEmblemHints, 10},
-	{IT_STRING|IT_CVAR,         NULL, "Emblem Radar", &cv_itemfinder, 20},
-	{IT_WHITESTRING|IT_SUBMENU, NULL, "Back",         &SPauseDef,     30}
+	{IT_STRING | IT_ARROWS,  NULL, "Page",    M_HandleEmblemHints, 10},
+	{IT_STRING|IT_CVAR,      NULL, "Emblem Radar", &cv_itemfinder, 20},
+	{IT_WHITESTRING|IT_CALL, NULL, "Back",         M_GoBack,       30}
 };
 
 // --------------------------------
@@ -879,7 +882,8 @@ static menuitem_t SP_NightsAttackLevelSelectMenu[] =
 static menuitem_t SP_NightsAttackMenu[] =
 {
 	{IT_STRING|IT_KEYHANDLER,        NULL, "Level Select...",  &M_HandleTimeAttackLevelSelect,  52},
-	{IT_STRING|IT_CVAR,        NULL, "Show Records For", &cv_dummymares,              62},
+	{IT_STRING|IT_CVAR,        NULL, "Character",       &cv_chooseskin,             62},
+	{IT_STRING|IT_CVAR,        NULL, "Show Records For", &cv_dummymares,              72},
 
 	{IT_DISABLED,              NULL, "Guest Option...",  &SP_NightsGuestReplayDef,    100},
 	{IT_DISABLED,              NULL, "Replay...",        &SP_NightsReplayDef,         110},
@@ -890,6 +894,7 @@ static menuitem_t SP_NightsAttackMenu[] =
 enum
 {
 	nalevel,
+	nachar,
 	narecords,
 
 	naguest,
@@ -1580,7 +1585,7 @@ static menuitem_t OP_ServerOptionsMenu[] =
 	{IT_HEADER, NULL, "General", NULL, 0},
 #ifndef NONET
 	{IT_STRING | IT_CVAR | IT_CV_STRING,
-	                         NULL, "Server name",                      &cv_servername,          7},
+	                         NULL, "Server name",                      &cv_servername,           7},
 	{IT_STRING | IT_CVAR,    NULL, "Max Players",                      &cv_maxplayers,          21},
 	{IT_STRING | IT_CVAR,    NULL, "Allow Add-on Downloading",         &cv_downloading,         26},
 	{IT_STRING | IT_CVAR,    NULL, "Allow players to join",            &cv_allownewplayer,      31},
@@ -1625,8 +1630,9 @@ static menuitem_t OP_ServerOptionsMenu[] =
 
 #ifndef NONET
 	{IT_HEADER, NULL, "Advanced", NULL, 225},
-	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server",        &cv_masterserver,        231},
-	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",        &cv_resynchattempts,     245},
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server",        &cv_masterserver,       231},
+	{IT_STRING | IT_CVAR,    NULL, "Join delay",                       &cv_joindelay,          246},
+	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",        &cv_resynchattempts,    251},
 #endif
 };
 
@@ -1698,7 +1704,7 @@ static INT32 highlightflags, recommendedflags, warningflags;
 // Sky Room
 menu_t SR_PandoraDef =
 {
-	MN_SR_MAIN + (MN_SR_PANDORA << 6),
+	MTREE2(MN_SR_MAIN, MN_SR_PANDORA),
 	"M_PANDRA",
 	sizeof (SR_PandorasBox)/sizeof (menuitem_t),
 	&SPauseDef,
@@ -1712,12 +1718,12 @@ menu_t SR_PandoraDef =
 menu_t SR_MainDef = DEFAULTMENUSTYLE(MN_SR_MAIN, "M_SECRET", SR_MainMenu, &MainDef, 60, 40);
 
 menu_t SR_LevelSelectDef = MAPPLATTERMENUSTYLE(
-	MN_SR_MAIN + (MN_SR_LEVELSELECT << 6),
+	MTREE2(MN_SR_MAIN, MN_SR_LEVELSELECT),
 	NULL, SR_LevelSelectMenu);
 
 menu_t SR_UnlockChecklistDef =
 {
-	MN_SR_MAIN + (MN_SR_UNLOCKCHECKLIST << 6),
+	MTREE2(MN_SR_MAIN, MN_SR_UNLOCKCHECKLIST),
 	"M_SECRET",
 	1,
 	&SR_MainDef,
@@ -1730,7 +1736,7 @@ menu_t SR_UnlockChecklistDef =
 
 menu_t SR_SoundTestDef =
 {
-	MN_SR_MAIN + (MN_SR_SOUNDTEST << 6),
+	MTREE2(MN_SR_MAIN, MN_SR_SOUNDTEST),
 	NULL,
 	sizeof (SR_SoundTestMenu)/sizeof (menuitem_t),
 	&SR_MainDef,
@@ -1743,7 +1749,7 @@ menu_t SR_SoundTestDef =
 
 menu_t SR_EmblemHintDef =
 {
-	MN_SR_MAIN + (MN_SR_EMBLEMHINT << 6),
+	MTREE2(MN_SR_MAIN, MN_SR_EMBLEMHINT),
 	NULL,
 	sizeof (SR_EmblemHintMenu)/sizeof (menuitem_t),
 	&SPauseDef,
@@ -1770,7 +1776,7 @@ menu_t SP_MainDef = //CENTERMENUSTYLE(NULL, SP_MainMenu, &MainDef, 72);
 
 menu_t SP_LoadDef =
 {
-	MN_SP_MAIN + (MN_SP_LOAD << 6),
+	MTREE2(MN_SP_MAIN, MN_SP_LOAD),
 	"M_PICKG",
 	1,
 	&SP_MainDef,
@@ -1782,12 +1788,12 @@ menu_t SP_LoadDef =
 };
 
 menu_t SP_LevelSelectDef = MAPPLATTERMENUSTYLE(
-	MN_SP_MAIN + (MN_SP_LOAD << 6) + (MN_SP_PLAYER << 12) + (MN_SP_LEVELSELECT << 18),
+	MTREE4(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER, MN_SP_LEVELSELECT),
 	NULL, SP_LevelSelectMenu);
 
 menu_t SP_LevelStatsDef =
 {
-	MN_SP_MAIN + (MN_SP_LEVELSTATS << 6),
+	MTREE2(MN_SP_MAIN, MN_SP_LEVELSTATS),
 	"M_STATS",
 	1,
 	&SP_MainDef,
@@ -1799,12 +1805,12 @@ menu_t SP_LevelStatsDef =
 };
 
 menu_t SP_TimeAttackLevelSelectDef = MAPPLATTERMENUSTYLE(
-	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6) + (MN_SP_TIMEATTACK_LEVELSELECT << 12),
+	MTREE3(MN_SP_MAIN, MN_SP_TIMEATTACK, MN_SP_TIMEATTACK_LEVELSELECT),
 	"M_ATTACK", SP_TimeAttackLevelSelectMenu);
 
 static menu_t SP_TimeAttackDef =
 {
-	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6),
+	MTREE2(MN_SP_MAIN, MN_SP_TIMEATTACK),
 	"M_ATTACK",
 	sizeof (SP_TimeAttackMenu)/sizeof (menuitem_t),
 	&MainDef,  // Doesn't matter.
@@ -1816,7 +1822,7 @@ static menu_t SP_TimeAttackDef =
 };
 static menu_t SP_ReplayDef =
 {
-	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6) + (MN_SP_REPLAY << 12),
+	MTREE3(MN_SP_MAIN, MN_SP_TIMEATTACK, MN_SP_REPLAY),
 	"M_ATTACK",
 	sizeof(SP_ReplayMenu)/sizeof(menuitem_t),
 	&SP_TimeAttackDef,
@@ -1828,7 +1834,7 @@ static menu_t SP_ReplayDef =
 };
 static menu_t SP_GuestReplayDef =
 {
-	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6) + (MN_SP_GUESTREPLAY << 12),
+	MTREE3(MN_SP_MAIN, MN_SP_TIMEATTACK, MN_SP_GUESTREPLAY),
 	"M_ATTACK",
 	sizeof(SP_GuestReplayMenu)/sizeof(menuitem_t),
 	&SP_TimeAttackDef,
@@ -1840,7 +1846,7 @@ static menu_t SP_GuestReplayDef =
 };
 static menu_t SP_GhostDef =
 {
-	MN_SP_MAIN + (MN_SP_TIMEATTACK << 6) + (MN_SP_GHOST << 12),
+	MTREE3(MN_SP_MAIN, MN_SP_TIMEATTACK, MN_SP_GHOST),
 	"M_ATTACK",
 	sizeof(SP_GhostMenu)/sizeof(menuitem_t),
 	&SP_TimeAttackDef,
@@ -1852,12 +1858,12 @@ static menu_t SP_GhostDef =
 };
 
 menu_t SP_NightsAttackLevelSelectDef = MAPPLATTERMENUSTYLE(
-	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6) + (MN_SP_NIGHTS_LEVELSELECT << 12),
+	MTREE3(MN_SP_MAIN, MN_SP_NIGHTSATTACK, MN_SP_NIGHTS_LEVELSELECT),
 	 "M_NIGHTS", SP_NightsAttackLevelSelectMenu);
 
 static menu_t SP_NightsAttackDef =
 {
-	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6),
+	MTREE2(MN_SP_MAIN, MN_SP_NIGHTSATTACK),
 	"M_NIGHTS",
 	sizeof (SP_NightsAttackMenu)/sizeof (menuitem_t),
 	&MainDef,  // Doesn't matter.
@@ -1869,7 +1875,7 @@ static menu_t SP_NightsAttackDef =
 };
 static menu_t SP_NightsReplayDef =
 {
-	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6) + (MN_SP_NIGHTS_REPLAY << 12),
+	MTREE3(MN_SP_MAIN, MN_SP_NIGHTSATTACK, MN_SP_NIGHTS_REPLAY),
 	"M_NIGHTS",
 	sizeof(SP_NightsReplayMenu)/sizeof(menuitem_t),
 	&SP_NightsAttackDef,
@@ -1881,7 +1887,7 @@ static menu_t SP_NightsReplayDef =
 };
 static menu_t SP_NightsGuestReplayDef =
 {
-	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6) + (MN_SP_NIGHTS_GUESTREPLAY << 12),
+	MTREE3(MN_SP_MAIN, MN_SP_NIGHTSATTACK, MN_SP_NIGHTS_GUESTREPLAY),
 	"M_NIGHTS",
 	sizeof(SP_NightsGuestReplayMenu)/sizeof(menuitem_t),
 	&SP_NightsAttackDef,
@@ -1893,7 +1899,7 @@ static menu_t SP_NightsGuestReplayDef =
 };
 static menu_t SP_NightsGhostDef =
 {
-	MN_SP_MAIN + (MN_SP_NIGHTSATTACK << 6) + (MN_SP_NIGHTS_GHOST << 12),
+	MTREE3(MN_SP_MAIN, MN_SP_NIGHTSATTACK, MN_SP_NIGHTS_GHOST),
 	"M_NIGHTS",
 	sizeof(SP_NightsGhostMenu)/sizeof(menuitem_t),
 	&SP_NightsAttackDef,
@@ -1907,7 +1913,7 @@ static menu_t SP_NightsGhostDef =
 
 menu_t SP_PlayerDef =
 {
-	MN_SP_MAIN + (MN_SP_LOAD << 6) + (MN_SP_PLAYER << 12),
+	MTREE3(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER),
 	"M_PICKP",
 	sizeof (SP_PlayerMenu)/sizeof (menuitem_t),
 	&SP_MainDef,
@@ -1922,7 +1928,7 @@ menu_t SP_PlayerDef =
 
 menu_t MP_SplitServerDef =
 {
-	MN_MP_MAIN + (MN_MP_SPLITSCREEN << 6),
+	MTREE2(MN_MP_MAIN, MN_MP_SPLITSCREEN),
 	"M_MULTI",
 	sizeof (MP_SplitServerMenu)/sizeof (menuitem_t),
 #ifndef NONET
@@ -1954,7 +1960,7 @@ menu_t MP_MainDef =
 
 menu_t MP_ServerDef =
 {
-	MN_MP_MAIN + (MN_MP_SERVER << 6),
+	MTREE2(MN_MP_MAIN, MN_MP_SERVER),
 	"M_MULTI",
 	sizeof (MP_ServerMenu)/sizeof (menuitem_t),
 	&MP_MainDef,
@@ -1967,7 +1973,7 @@ menu_t MP_ServerDef =
 
 menu_t MP_ConnectDef =
 {
-	MN_MP_MAIN + (MN_MP_CONNECT << 6),
+	MTREE2(MN_MP_MAIN, MN_MP_CONNECT),
 	"M_MULTI",
 	sizeof (MP_ConnectMenu)/sizeof (menuitem_t),
 	&MP_MainDef,
@@ -1980,7 +1986,7 @@ menu_t MP_ConnectDef =
 
 menu_t MP_RoomDef =
 {
-	MN_MP_MAIN + (MN_MP_ROOM << 6),
+	MTREE2(MN_MP_MAIN, MN_MP_ROOM),
 	"M_MULTI",
 	sizeof (MP_RoomMenu)/sizeof (menuitem_t),
 	&MP_ConnectDef,
@@ -1995,9 +2001,9 @@ menu_t MP_RoomDef =
 menu_t MP_PlayerSetupDef =
 {
 #ifdef NONET
-	MN_MP_MAIN + (MN_MP_PLAYERSETUP << 6),
+	MTREE2(MN_MP_MAIN, MN_MP_PLAYERSETUP),
 #else
-	MN_MP_MAIN + (MN_MP_SPLITSCREEN << 6) + (MN_MP_PLAYERSETUP << 12),
+	MTREE3(MN_MP_MAIN, MN_MP_SPLITSCREEN, MN_MP_PLAYERSETUP),
 #endif
 	"M_SPLAYR",
 	sizeof (MP_PlayerSetupMenu)/sizeof (menuitem_t),
@@ -2013,12 +2019,13 @@ menu_t MP_PlayerSetupDef =
 menu_t OP_MainDef = DEFAULTMENUSTYLE(
 	MN_OP_MAIN,
 	"M_OPTTTL", OP_MainMenu, &MainDef, 50, 30);
+
 menu_t OP_ChangeControlsDef = CONTROLMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_CHANGECONTROLS << 12), // second level (<<6) set on runtime
+	MTREE3(MN_OP_MAIN, 0, MN_OP_CHANGECONTROLS), // second level set on runtime
 	OP_ChangeControlsMenu, &OP_MainDef);
 
 menu_t OP_P1ControlsDef = {
-	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6),
+	MTREE2(MN_OP_MAIN, MN_OP_P1CONTROLS),
 	"M_CONTRO",
 	sizeof(OP_P1ControlsMenu)/sizeof(menuitem_t),
 	&OP_MainDef,
@@ -2026,7 +2033,7 @@ menu_t OP_P1ControlsDef = {
 	M_DrawControlsDefMenu,
 	50, 30, 0, NULL};
 menu_t OP_P2ControlsDef = {
-	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6),
+	MTREE2(MN_OP_MAIN, MN_OP_P2CONTROLS),
 	"M_CONTRO",
 	sizeof(OP_P2ControlsMenu)/sizeof(menuitem_t),
 	&OP_MainDef,
@@ -2035,20 +2042,22 @@ menu_t OP_P2ControlsDef = {
 	50, 30, 0, NULL};
 
 menu_t OP_MouseOptionsDef = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_P1MOUSE << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_P1CONTROLS, MN_OP_P1MOUSE),
 	"M_CONTRO", OP_MouseOptionsMenu, &OP_P1ControlsDef, 35, 30);
 menu_t OP_Mouse2OptionsDef = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6) + (MN_OP_P2MOUSE << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_P2CONTROLS, MN_OP_P2MOUSE),
 	"M_CONTRO", OP_Mouse2OptionsMenu, &OP_P2ControlsDef, 35, 30);
+
 menu_t OP_Joystick1Def = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_P1JOYSTICK << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_P1CONTROLS, MN_OP_P1JOYSTICK),
 	"M_CONTRO", OP_Joystick1Menu, &OP_P1ControlsDef, 50, 30);
 menu_t OP_Joystick2Def = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6) + (MN_OP_P2JOYSTICK << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_P2CONTROLS, MN_OP_P2JOYSTICK),
 	"M_CONTRO", OP_Joystick2Menu, &OP_P2ControlsDef, 50, 30);
+
 menu_t OP_JoystickSetDef =
 {
-	MN_OP_MAIN + (MN_OP_JOYSTICKSET << MENUBITS*3),  // second (<<6) and third level (<<12) set on runtime
+	MTREE4(MN_OP_MAIN, 0, 0, MN_OP_JOYSTICKSET), // second and third level set on runtime
 	"M_CONTRO",
 	sizeof (OP_JoystickSetMenu)/sizeof (menuitem_t),
 	&OP_Joystick1Def,
@@ -2060,7 +2069,7 @@ menu_t OP_JoystickSetDef =
 };
 
 menu_t OP_CameraOptionsDef = {
-	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_P1CAMERA << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_P1CONTROLS, MN_OP_P1CAMERA),
 	"M_CONTRO",
 	sizeof (OP_CameraOptionsMenu)/sizeof (menuitem_t),
 	&OP_P1ControlsDef,
@@ -2071,7 +2080,7 @@ menu_t OP_CameraOptionsDef = {
 	NULL
 };
 menu_t OP_Camera2OptionsDef = {
-	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6) + (MN_OP_P2CAMERA << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_P2CONTROLS, MN_OP_P2CAMERA),
 	"M_CONTRO",
 	sizeof (OP_Camera2OptionsMenu)/sizeof (menuitem_t),
 	&OP_P2ControlsDef,
@@ -2085,7 +2094,7 @@ menu_t OP_Camera2OptionsDef = {
 static menuitem_t OP_PlaystyleMenu[] = {{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandlePlaystyleMenu, 0}};
 
 menu_t OP_PlaystyleDef = {
-	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_PLAYSTYLE << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_P1CONTROLS, MN_OP_PLAYSTYLE), ///@TODO the second level should be set in runtime
 	NULL,
 	1,
 	&OP_P1ControlsDef,
@@ -2098,7 +2107,7 @@ static void M_VideoOptions(INT32 choice)
 {
 	(void)choice;
 #ifdef HWRENDER
-	if (hwrenderloaded == -1)
+	if (vid_opengl_state == -1)
 	{
 		OP_VideoOptionsMenu[op_video_renderer].status = (IT_TRANSTEXT | IT_PAIR);
 		OP_VideoOptionsMenu[op_video_renderer].patch = "Renderer";
@@ -2111,7 +2120,7 @@ static void M_VideoOptions(INT32 choice)
 
 menu_t OP_VideoOptionsDef =
 {
-	MN_OP_MAIN + (MN_OP_VIDEO << 6),
+	MTREE2(MN_OP_MAIN, MN_OP_VIDEO),
 	"M_VIDEO",
 	sizeof (OP_VideoOptionsMenu)/sizeof (menuitem_t),
 	&OP_MainDef,
@@ -2123,7 +2132,7 @@ menu_t OP_VideoOptionsDef =
 };
 menu_t OP_VideoModeDef =
 {
-	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_VIDEOMODE << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_VIDEO, MN_OP_VIDEOMODE),
 	"M_VIDEO",
 	1,
 	&OP_VideoOptionsDef,
@@ -2135,7 +2144,7 @@ menu_t OP_VideoModeDef =
 };
 menu_t OP_ColorOptionsDef =
 {
-	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_COLOR << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_VIDEO, MN_OP_COLOR),
 	"M_VIDEO",
 	sizeof (OP_ColorOptionsMenu)/sizeof (menuitem_t),
 	&OP_VideoOptionsDef,
@@ -2146,17 +2155,19 @@ menu_t OP_ColorOptionsDef =
 	NULL
 };
 menu_t OP_SoundOptionsDef = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_SOUND << 6),
+	MTREE2(MN_OP_MAIN, MN_OP_SOUND),
 	"M_SOUND", OP_SoundOptionsMenu, &OP_MainDef, 30, 30);
-menu_t OP_SoundAdvancedDef = DEFAULTMENUSTYLE(MN_OP_MAIN + (MN_OP_SOUND << 6), "M_SOUND", OP_SoundAdvancedMenu, &OP_SoundOptionsDef, 30, 30);
+menu_t OP_SoundAdvancedDef = DEFAULTMENUSTYLE(
+	MTREE2(MN_OP_MAIN, MN_OP_SOUND),
+	"M_SOUND", OP_SoundAdvancedMenu, &OP_SoundOptionsDef, 30, 30);
 
 menu_t OP_ServerOptionsDef = DEFAULTSCROLLMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_SERVER << 6),
+	MTREE2(MN_OP_MAIN, MN_OP_SERVER),
 	"M_SERVER", OP_ServerOptionsMenu, &OP_MainDef, 30, 30);
 
 menu_t OP_MonitorToggleDef =
 {
-	MN_OP_MAIN + (MN_OP_SERVER << 6) + (MN_OP_MONITORTOGGLE << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_SOUND, MN_OP_MONITORTOGGLE),
 	"M_SERVER",
 	sizeof (OP_MonitorToggleMenu)/sizeof (menuitem_t),
 	&OP_ServerOptionsDef,
@@ -2177,16 +2188,16 @@ static void M_OpenGLOptionsMenu(void)
 }
 
 menu_t OP_OpenGLOptionsDef = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_OPENGL << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_VIDEO, MN_OP_OPENGL),
 	"M_VIDEO", OP_OpenGLOptionsMenu, &OP_VideoOptionsDef, 30, 30);
 #ifdef ALAM_LIGHTING
 menu_t OP_OpenGLLightingDef = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_OPENGL << 12) + (MN_OP_OPENGL_LIGHTING << 18),
+	MTREE4(MN_OP_MAIN, MN_OP_VIDEO, MN_OP_OPENGL, MN_OP_OPENGL_LIGHTING),
 	"M_VIDEO", OP_OpenGLLightingMenu, &OP_OpenGLOptionsDef, 60, 40);
 #endif
 menu_t OP_OpenGLFogDef =
 {
-	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_OPENGL << 12) + (MN_OP_OPENGL_FOG << 18),
+	MTREE4(MN_OP_MAIN, MN_OP_VIDEO, MN_OP_OPENGL, MN_OP_OPENGL_FOG),
 	"M_VIDEO",
 	sizeof (OP_OpenGLFogMenu)/sizeof (menuitem_t),
 	&OP_OpenGLOptionsDef,
@@ -2198,12 +2209,12 @@ menu_t OP_OpenGLFogDef =
 };
 #endif
 menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_DATA << 6),
+	MTREE2(MN_OP_MAIN, MN_OP_DATA),
 	"M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30);
 
 menu_t OP_ScreenshotOptionsDef =
 {
-	MN_OP_MAIN + (MN_OP_DATA << 6) + (MN_OP_SCREENSHOTS << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_DATA, MN_OP_SCREENSHOTS),
 	"M_SCREEN",
 	sizeof (OP_ScreenshotOptionsMenu)/sizeof (menuitem_t),
 	&OP_DataOptionsDef,
@@ -2215,11 +2226,11 @@ menu_t OP_ScreenshotOptionsDef =
 };
 
 menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_DATA << 6) + (MN_OP_ADDONS << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_DATA, MN_OP_ADDONS),
 	"M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30);
 
 menu_t OP_EraseDataDef = DEFAULTMENUSTYLE(
-	MN_OP_MAIN + (MN_OP_DATA << 6) + (MN_OP_ERASEDATA << 12),
+	MTREE3(MN_OP_MAIN, MN_OP_DATA, MN_OP_ERASEDATA),
 	"M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 60, 30);
 
 // ==========================================================================
@@ -3634,6 +3645,7 @@ void M_StartControlPanel(void)
 	else if (modeattacking)
 	{
 		currentMenu = &MAPauseDef;
+		MAPauseMenu[mapause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 		itemOn = mapause_continue;
 	}
 	else if (!(netgame || multiplayer)) // Single Player
@@ -5074,6 +5086,17 @@ static boolean M_SetNextMapOnPlatter(void)
 }
 #endif
 
+static boolean M_GametypeHasLevels(INT32 gt)
+{
+	INT32 mapnum;
+
+	for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
+		if (M_CanShowLevelOnPlatter(mapnum, gt))
+			return true;
+
+	return false;
+}
+
 static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 {
 	INT32 mapnum = 0, prevmapnum = 0, col = 0, rows = 0;
@@ -5165,7 +5188,7 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 	{
 		if (M_CanShowLevelOnPlatter(mapnum, gt))
 		{
-			const INT32 actnum = mapheaderinfo[mapnum]->actnum;
+			const UINT8 actnum = mapheaderinfo[mapnum]->actnum;
 			const boolean headingisname = (fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[mapnum]->lvlttl));
 			const boolean wide = (mapheaderinfo[mapnum]->menuflags & LF2_WIDEICON);
 
@@ -5391,7 +5414,10 @@ static void M_HandleLevelPlatter(INT32 choice)
 		case KEY_RIGHTARROW:
 			if (levellistmode == LLM_CREATESERVER && !lsrow)
 			{
-				CV_AddValue(&cv_newgametype, 1);
+				INT32 startinggametype = cv_newgametype.value;
+				do
+					CV_AddValue(&cv_newgametype, 1);
+				while (cv_newgametype.value != startinggametype && !M_GametypeHasLevels(cv_newgametype.value));
 				S_StartSound(NULL,sfx_menu1);
 				lscol = 0;
 
@@ -5420,7 +5446,10 @@ static void M_HandleLevelPlatter(INT32 choice)
 		case KEY_LEFTARROW:
 			if (levellistmode == LLM_CREATESERVER && !lsrow)
 			{
-				CV_AddValue(&cv_newgametype, -1);
+				INT32 startinggametype = cv_newgametype.value;
+				do
+					CV_AddValue(&cv_newgametype, -1);
+				while (cv_newgametype.value != startinggametype && !M_GametypeHasLevels(cv_newgametype.value));
 				S_StartSound(NULL,sfx_menu1);
 				lscol = 0;
 
@@ -6707,6 +6736,7 @@ static void M_PandorasBox(INT32 choice)
 	else
 		CV_StealthSetValue(&cv_dummylives, max(players[consoleplayer].lives, 1));
 	CV_StealthSetValue(&cv_dummycontinues, players[consoleplayer].continues);
+	SR_PandorasBox[3].status = (continuesInSession) ? (IT_STRING | IT_CVAR) : (IT_GRAYEDOUT);
 	SR_PandorasBox[6].status = (players[consoleplayer].charflags & SF_SUPER) ? (IT_GRAYEDOUT) : (IT_STRING | IT_CALL);
 	SR_PandorasBox[7].status = (emeralds == ((EMERALD7)*2)-1) ? (IT_GRAYEDOUT) : (IT_STRING | IT_CALL);
 	M_SetupNextMenu(&SR_PandoraDef);
@@ -6723,7 +6753,7 @@ static boolean M_ExitPandorasBox(void)
 	}
 	if (cv_dummylives.value != players[consoleplayer].lives)
 		COM_ImmedExecute(va("setlives %d", cv_dummylives.value));
-	if (cv_dummycontinues.value != players[consoleplayer].continues)
+	if (continuesInSession && cv_dummycontinues.value != players[consoleplayer].continues)
 		COM_ImmedExecute(va("setcontinues %d", cv_dummycontinues.value));
 	return true;
 }
@@ -7287,6 +7317,7 @@ static void M_EmblemHints(INT32 choice)
 	SR_EmblemHintMenu[0].status = (local > NUMHINTS*2) ? (IT_STRING | IT_ARROWS) : (IT_DISABLED);
 	SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER)) ? (IT_CVAR|IT_STRING) : (IT_SECRET);
 	hintpage = 1;
+	SR_EmblemHintDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&SR_EmblemHintDef);
 	itemOn = 2; // always start on back.
 }
@@ -7967,12 +7998,20 @@ static void M_CustomLevelSelect(INT32 choice)
 static void M_SinglePlayerMenu(INT32 choice)
 {
 	(void)choice;
-	SP_MainMenu[sptutorial].status =
-		tutorialmap ? IT_CALL|IT_STRING : IT_NOTHING|IT_DISABLED;
-	SP_MainMenu[sprecordattack].status =
-		(M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING : IT_SECRET;
-	SP_MainMenu[spnightsmode].status =
-		(M_SecretUnlocked(SECRET_NIGHTSMODE)) ? IT_CALL|IT_STRING : IT_SECRET;
+
+	levellistmode = LLM_RECORDATTACK;
+	if (M_GametypeHasLevels(-1))
+		SP_MainMenu[sprecordattack].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING : IT_SECRET;
+	else
+		SP_MainMenu[sprecordattack].status = IT_NOTHING|IT_DISABLED;
+
+	levellistmode = LLM_NIGHTSATTACK;
+	if (M_GametypeHasLevels(-1))
+		SP_MainMenu[spnightsmode].status = (M_SecretUnlocked(SECRET_NIGHTSMODE)) ? IT_CALL|IT_STRING : IT_SECRET;
+	else
+		SP_MainMenu[spnightsmode].status = IT_NOTHING|IT_DISABLED;
+
+	SP_MainMenu[sptutorial].status = tutorialmap ? IT_CALL|IT_STRING : IT_NOTHING|IT_DISABLED;
 
 	M_SetupNextMenu(&SP_MainDef);
 }
@@ -8234,9 +8273,19 @@ static void M_DrawLoadGameData(void)
 				V_DrawRightAlignedThinString(x + 79, y, V_YELLOWMAP, savegameinfo[savetodraw].levelname);
 		}
 
-		if ((savegameinfo[savetodraw].lives == -42)
-		|| (savegameinfo[savetodraw].lives == -666))
+		if (savegameinfo[savetodraw].lives == -42)
+		{
+			if (!useContinues)
+				V_DrawRightAlignedThinString(x + 80, y+1+60+16, V_GRAYMAP, "00000000");
+			continue;
+		}
+
+		if (savegameinfo[savetodraw].lives == -666)
+		{
+			if (!useContinues)
+				V_DrawRightAlignedThinString(x + 80, y+1+60+16, V_REDMAP, "????????");
 			continue;
+		}
 
 		y += 64;
 
@@ -8253,7 +8302,7 @@ static void M_DrawLoadGameData(void)
 
 		y -= 4;
 
-		// character heads, lives, and continues
+		// character heads, lives, and continues/score
 		{
 			spritedef_t *sprdef;
 			spriteframe_t *sprframe;
@@ -8304,10 +8353,14 @@ skipbot:
 skipsign:
 			y += 16;
 
-			tempx = x + 10;
-			if (savegameinfo[savetodraw].lives != INFLIVES
-			&& savegameinfo[savetodraw].lives > 9)
-				tempx -= 4;
+			tempx = x;
+			if (useContinues)
+			{
+				tempx += 10;
+				if (savegameinfo[savetodraw].lives != INFLIVES
+				&& savegameinfo[savetodraw].lives > 9)
+					tempx -= 4;
+			}
 
 			if (!charskin) // shut up compiler
 				goto skiplife;
@@ -8337,22 +8390,45 @@ skiplife:
 			else
 				V_DrawString(tempx, y, 0, va("%d", savegameinfo[savetodraw].lives));
 
-			tempx = x + 47;
-			if (savegameinfo[savetodraw].continues > 9)
-				tempx -= 4;
-
-			// continues
-			if (savegameinfo[savetodraw].continues > 0)
+			if (!useContinues)
 			{
-				V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTSAVE", PU_PATCH));
-				V_DrawScaledPatch(tempx + 9, y + 2, 0, patch);
-				V_DrawString(tempx + 16, y, 0, va("%d", savegameinfo[savetodraw].continues));
+				INT32 workingscorenum = savegameinfo[savetodraw].continuescore;
+				char workingscorestr[11] = " 000000000\0";
+				SINT8 j = 9;
+				// Change the above two lines if MAXSCORE ever changes from 8 digits long.
+				workingscorestr[0] = '\x86'; // done here instead of in initialiser 'cuz compiler complains
+				if (!workingscorenum)
+					j--; // just so ONE digit is not greyed out
+				else
+				{
+					while (workingscorenum)
+					{
+						workingscorestr[j--] = '0' + (workingscorenum % 10);
+						workingscorenum /= 10;
+					}
+				}
+				workingscorestr[j] = (savegameinfo[savetodraw].continuescore == MAXSCORE) ? '\x83' : '\x80';
+				V_DrawRightAlignedThinString(x + 80, y+1, 0, workingscorestr);
 			}
 			else
 			{
-				V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTNONE", PU_PATCH));
-				V_DrawScaledPatch(tempx + 9, y + 2, 0, W_CachePatchName("STNONEX", PU_PATCH));
-				V_DrawString(tempx + 16, y, V_GRAYMAP, "0");
+				tempx = x + 47;
+				if (savegameinfo[savetodraw].continuescore > 9)
+					tempx -= 4;
+
+				// continues
+				if (savegameinfo[savetodraw].continuescore > 0)
+				{
+					V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTSAVE", PU_PATCH));
+					V_DrawScaledPatch(tempx + 9, y + 2, 0, patch);
+					V_DrawString(tempx + 16, y, 0, va("%d", savegameinfo[savetodraw].continuescore));
+				}
+				else
+				{
+					V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTNONE", PU_PATCH));
+					V_DrawScaledPatch(tempx + 9, y + 2, 0, W_CachePatchName("STNONEX", PU_PATCH));
+					V_DrawString(tempx + 16, y, V_GRAYMAP, "0");
+				}
 			}
 		}
 	}
@@ -8485,9 +8561,11 @@ static void M_ReadSavegameInfo(UINT32 slot)
 	CHECKPOS
 	savegameinfo[slot].lives = READSINT8(save_p); // lives
 	CHECKPOS
-	(void)READINT32(save_p); // Score
+	savegameinfo[slot].continuescore = READINT32(save_p); // score
 	CHECKPOS
-	savegameinfo[slot].continues = READINT32(save_p); // continues
+	fake = READINT32(save_p); // continues
+	if (useContinues)
+		savegameinfo[slot].continuescore = fake;
 
 	// File end marker check
 	CHECKPOS
@@ -8869,16 +8947,11 @@ static void M_SetupChoosePlayer(INT32 choice)
 	/* the menus suck -James */
 	if (currentMenu == &SP_LoadDef)/* from save states */
 	{
-		SP_PlayerDef.menuid =
-			MN_SP_MAIN +
-			( MN_SP_LOAD   <<  6 ) +
-			( MN_SP_PLAYER << 12 );
+		SP_PlayerDef.menuid = MTREE3(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER);
 	}
 	else/* from Secret level select */
 	{
-		SP_PlayerDef.menuid =
-			MN_SR_MAIN +
-			( MN_SR_PLAYER <<  6 );
+		SP_PlayerDef.menuid = MTREE2(MN_SR_MAIN, MN_SR_PLAYER);
 	}
 
 	SP_PlayerDef.prevMenu = currentMenu;
@@ -9024,7 +9097,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		col = Color_Opposite[charskin->prefcolor - 1][0];
 
 	// Make the translation colormap
-	colormap = R_GetTranslationColormap(TC_DEFAULT, col, 0);
+	colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_CACHE);
 
 	// Don't render the title map
 	hidetitlemap = true;
@@ -9100,8 +9173,8 @@ static void M_DrawSetupChoosePlayerMenu(void)
 			{
 				V_DrawNameTag(
 					x, y, V_CENTERNAMETAG, FRACUNIT,
-					R_GetTranslationColormap(TC_DEFAULT, curtextcolor, 0),
-					R_GetTranslationColormap(TC_DEFAULT, curoutlinecolor, 0),
+					R_GetTranslationColormap(TC_DEFAULT, curtextcolor, GTC_CACHE),
+					R_GetTranslationColormap(TC_DEFAULT, curoutlinecolor, GTC_CACHE),
 					curtext
 				);
 			}
@@ -9133,8 +9206,8 @@ static void M_DrawSetupChoosePlayerMenu(void)
 				{
 					V_DrawNameTag(
 						x, y, V_CENTERNAMETAG, FRACUNIT,
-						R_GetTranslationColormap(TC_DEFAULT, prevtextcolor, 0),
-						R_GetTranslationColormap(TC_DEFAULT, prevoutlinecolor, 0),
+						R_GetTranslationColormap(TC_DEFAULT, prevtextcolor, GTC_CACHE),
+						R_GetTranslationColormap(TC_DEFAULT, prevoutlinecolor, GTC_CACHE),
 						prevtext
 					);
 				}
@@ -9163,8 +9236,8 @@ static void M_DrawSetupChoosePlayerMenu(void)
 				{
 					V_DrawNameTag(
 						x, y, V_CENTERNAMETAG, FRACUNIT,
-						R_GetTranslationColormap(TC_DEFAULT, nexttextcolor, 0),
-						R_GetTranslationColormap(TC_DEFAULT, nextoutlinecolor, 0),
+						R_GetTranslationColormap(TC_DEFAULT, nexttextcolor, GTC_CACHE),
+						R_GetTranslationColormap(TC_DEFAULT, nextoutlinecolor, GTC_CACHE),
 						nexttext
 					);
 				}
@@ -10752,7 +10825,8 @@ static void M_ServerOptions(INT32 choice)
 		OP_ServerOptionsMenu[ 3].status = IT_GRAYEDOUT; // Allow add-on downloading
 		OP_ServerOptionsMenu[ 4].status = IT_GRAYEDOUT; // Allow players to join
 		OP_ServerOptionsMenu[35].status = IT_GRAYEDOUT; // Master server
-		OP_ServerOptionsMenu[36].status = IT_GRAYEDOUT; // Attempts to resynchronise
+		OP_ServerOptionsMenu[36].status = IT_GRAYEDOUT; // Minimum delay between joins
+		OP_ServerOptionsMenu[37].status = IT_GRAYEDOUT; // Attempts to resynchronise
 	}
 	else
 	{
@@ -10764,14 +10838,15 @@ static void M_ServerOptions(INT32 choice)
 			? IT_GRAYEDOUT
 			: (IT_STRING | IT_CVAR | IT_CV_STRING));
 		OP_ServerOptionsMenu[36].status = IT_STRING | IT_CVAR;
+		OP_ServerOptionsMenu[37].status = IT_STRING | IT_CVAR;
 	}
 #endif
 
 	/* Disable fading because of different menu head. */
 	if (currentMenu == &OP_MainDef)/* from Options menu */
-		OP_ServerOptionsDef.menuid = MN_OP_MAIN + ( MN_OP_SERVER << 6 );
+		OP_ServerOptionsDef.menuid = MTREE2(MN_OP_MAIN, MN_OP_SERVER);
 	else/* from Multiplayer menu */
-		OP_ServerOptionsDef.menuid = MN_MP_MAIN + ( MN_MP_SERVER_OPTIONS << 6 );
+		OP_ServerOptionsDef.menuid = MTREE2(MN_MP_MAIN, MN_MP_SERVER_OPTIONS);
 
 	OP_ServerOptionsDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&OP_ServerOptionsDef);
@@ -11688,8 +11763,8 @@ static void M_Setup1PControlsMenu(INT32 choice)
 	OP_ChangeControlsMenu[27+3].status = IT_CALL|IT_STRING2;
 
 	OP_ChangeControlsDef.prevMenu = &OP_P1ControlsDef;
-	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove first level (<< 6)
-	OP_ChangeControlsDef.menuid |= MN_OP_P1CONTROLS << MENUBITS; // combine first level (<< 6)
+	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove second level
+	OP_ChangeControlsDef.menuid |= MN_OP_P1CONTROLS << MENUBITS; // combine second level
 	M_SetupNextMenu(&OP_ChangeControlsDef);
 }
 
@@ -11719,8 +11794,8 @@ static void M_Setup2PControlsMenu(INT32 choice)
 	OP_ChangeControlsMenu[27+3].status = IT_GRAYEDOUT2;
 
 	OP_ChangeControlsDef.prevMenu = &OP_P2ControlsDef;
-	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove first level (<< 6)
-	OP_ChangeControlsDef.menuid |= MN_OP_P2CONTROLS << MENUBITS; // combine first level (<< 6)
+	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove second level
+	OP_ChangeControlsDef.menuid |= MN_OP_P2CONTROLS << MENUBITS; // combine second level
 	M_SetupNextMenu(&OP_ChangeControlsDef);
 }
 
diff --git a/src/m_menu.h b/src/m_menu.h
index e7270380d503a84c1beb8cfe6e3f4fd69fc5bdb1..565b98945718e9612a47da82e1d306fa64a54845 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -31,6 +31,9 @@
 #define MENUBITS 6
 
 // Menu IDs sectioned by numeric places to signify hierarchy
+/**
+ * IF YOU MODIFY THIS, MODIFY MENUTYPES_LIST[] IN dehacked.c TO MATCH.
+ */
 typedef enum
 {
 	MN_NONE,
@@ -129,6 +132,9 @@ typedef enum
 	MN_SPECIAL,
 	NUMMENUTYPES,
 } menutype_t; // up to 63; MN_SPECIAL = 53
+#define MTREE2(a,b) (a | (b<<MENUBITS))
+#define MTREE3(a,b,c) MTREE2(a, MTREE2(b,c))
+#define MTREE4(a,b,c,d) MTREE2(a, MTREE3(b,c,d))
 
 typedef struct
 {
@@ -397,7 +403,7 @@ typedef struct
 	UINT8 numemeralds;
 	UINT8 numgameovers;
 	INT32 lives;
-	INT32 continues;
+	INT32 continuescore;
 	INT32 gamemap;
 } saveinfo_t;
 
diff --git a/src/mserv.h b/src/mserv.h
index 6d91da64395be09b8b7eda1d87e9718767a49fdd..5f9b8da5f430f5cbc384ba3497de3845fcb5c8b6 100644
--- a/src/mserv.h
+++ b/src/mserv.h
@@ -68,7 +68,7 @@ extern consvar_t cv_masterserver, cv_servername;
 // < 0 to not connect (usually -1) (offline mode)
 // == 0 to show all rooms, not a valid hosting room
 // anything else is whatever room the MS assigns to that number (online mode)
-INT16 ms_RoomId;
+extern INT16 ms_RoomId;
 
 const char *GetMasterServerPort(void);
 const char *GetMasterServerIP(void);
diff --git a/src/p_ceilng.c b/src/p_ceilng.c
index c65156b6f6b599b3966b6c85e6770a9f9a38cd57..3c3c507cd168e46a50ccdce3a503c8570c028797 100644
--- a/src/p_ceilng.c
+++ b/src/p_ceilng.c
@@ -47,8 +47,7 @@ void T_MoveCeiling(ceiling_t *ceiling)
 		case 0: // IN STASIS
 			break;
 		case 1: // UP
-			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->topheight, false,
-				1, ceiling->direction);
+			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->topheight, false, true, ceiling->direction);
 
 			if (ceiling->type == bounceCeiling)
 			{
@@ -159,8 +158,7 @@ void T_MoveCeiling(ceiling_t *ceiling)
 			break;
 
 		case -1: // DOWN
-			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight,
-				ceiling->crush, 1, ceiling->direction);
+			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight, ceiling->crush, true, ceiling->direction);
 
 			if (ceiling->type == bounceCeiling)
 			{
@@ -314,11 +312,10 @@ void T_CrushCeiling(ceiling_t *ceiling)
 			if (ceiling->type == crushBothOnce)
 			{
 				// Move the floor
-				T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight-(ceiling->topheight-ceiling->bottomheight), false, 0, -ceiling->direction);
+				T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight-(ceiling->topheight-ceiling->bottomheight), false, false, -ceiling->direction);
 			}
 
-			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->topheight,
-				false, 1, ceiling->direction);
+			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->topheight, false, true, ceiling->direction);
 
 			if (res == pastdest)
 			{
@@ -357,11 +354,10 @@ void T_CrushCeiling(ceiling_t *ceiling)
 			if (ceiling->type == crushBothOnce)
 			{
 				// Move the floor
-				T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight, ceiling->crush, 0, -ceiling->direction);
+				T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight, ceiling->crush, false, -ceiling->direction);
 			}
 
-			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight,
-				ceiling->crush, 1, ceiling->direction);
+			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight, ceiling->crush, true, ceiling->direction);
 
 			if (res == pastdest)
 			{
@@ -399,7 +395,7 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 	sector_t *sec;
 	ceiling_t *ceiling;
 
-	while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -619,7 +615,7 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 	sector_t *sec;
 	ceiling_t *ceiling;
 
-	while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 09d33c5370da8c35c8b7ba84342a2c5890f3a481..8fbf5baa6a8a9cf7479c4195fa7af0824fd73956 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -189,6 +189,7 @@ void A_SetTics(mobj_t *actor);
 void A_SetRandomTics(mobj_t *actor);
 void A_ChangeColorRelative(mobj_t *actor);
 void A_ChangeColorAbsolute(mobj_t *actor);
+void A_Dye(mobj_t *actor);
 void A_MoveRelative(mobj_t *actor);
 void A_MoveAbsolute(mobj_t *actor);
 void A_Thrust(mobj_t *actor);
@@ -311,6 +312,7 @@ void A_RolloutRock(mobj_t *actor);
 void A_DragonbomberSpawn(mobj_t *actor);
 void A_DragonWing(mobj_t *actor);
 void A_DragonSegment(mobj_t *actor);
+void A_ChangeHeight(mobj_t *actor);
 
 //for p_enemy.c
 
@@ -2989,6 +2991,19 @@ void A_Boss1Laser(mobj_t *actor)
 	angle_t angle;
 	mobj_t *point;
 	tic_t dur;
+	static const UINT8 LASERCOLORS[] =
+	{
+		SKINCOLOR_SUPERRED3,
+		SKINCOLOR_SUPERRED4,
+		SKINCOLOR_SUPERRED5,
+		SKINCOLOR_FLAME,
+		SKINCOLOR_RED,
+		SKINCOLOR_RED,
+		SKINCOLOR_FLAME,
+		SKINCOLOR_SUPERRED5,
+		SKINCOLOR_SUPERRED4,
+		SKINCOLOR_SUPERRED3,
+	};
 
 	if (LUA_CallAction("A_Boss1Laser", actor))
 		return;
@@ -3063,7 +3078,7 @@ void A_Boss1Laser(mobj_t *actor)
 	point = P_SpawnMobj(x, y, z, locvar1);
 	P_SetTarget(&point->target, actor);
 	point->angle = actor->angle;
-	speed = point->radius*2;
+	speed = point->radius;
 	point->momz = FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT), speed);
 	point->momx = FixedMul(FINESINE(angle>>ANGLETOFINESHIFT), FixedMul(FINECOSINE(point->angle>>ANGLETOFINESHIFT), speed));
 	point->momy = FixedMul(FINESINE(angle>>ANGLETOFINESHIFT), FixedMul(FINESINE(point->angle>>ANGLETOFINESHIFT), speed));
@@ -3072,23 +3087,69 @@ void A_Boss1Laser(mobj_t *actor)
 	{
 		mobj_t *mo = P_SpawnMobj(point->x, point->y, point->z, point->type);
 		mo->angle = point->angle;
+		mo->color = LASERCOLORS[((UINT8)(i + 3*dur) >> 2) % sizeof(LASERCOLORS)]; // codeing
 		P_UnsetThingPosition(mo);
-		mo->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY;
+		mo->flags = MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY;
 		P_SetThingPosition(mo);
 
+		if (dur & 1 && mo->info->missilestate)
+		{
+			P_SetMobjState(mo, mo->info->missilestate);
+			if (mo->info->meleestate)
+			{
+				mobj_t *mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_PARTICLE);
+				mo2->flags2 |= MF2_LINKDRAW;
+				P_SetTarget(&mo2->tracer, actor);
+				P_SetMobjState(mo2, mo->info->meleestate);
+			}
+		}
+
+		if (dur == 1)
+			P_SpawnGhostMobj(mo);
+
 		x = point->x, y = point->y, z = point->z;
 		if (P_RailThinker(point))
 			break;
 	}
 
+	x += point->momx;
+	y += point->momy;
 	floorz = P_FloorzAtPos(x, y, z, mobjinfo[MT_EGGMOBILE_FIRE].height);
-	if (z - floorz < mobjinfo[MT_EGGMOBILE_FIRE].height>>1)
+	if (z - floorz < mobjinfo[MT_EGGMOBILE_FIRE].height>>1 && dur & 1)
 	{
-		point = P_SpawnMobj(x, y, floorz+1, MT_EGGMOBILE_FIRE);
+		point = P_SpawnMobj(x, y, floorz, MT_EGGMOBILE_FIRE);
+		point->angle = actor->angle;
+		point->destscale = actor->scale;
+		P_SetScale(point, point->destscale);
 		P_SetTarget(&point->target, actor);
-		point->destscale = 3*FRACUNIT;
-		point->scalespeed = FRACUNIT>>2;
-		point->fuse = TICRATE;
+		P_MobjCheckWater(point);
+		if (point->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER))
+		{
+			for (i = 0; i < 2; i++)
+			{
+				UINT8 size = 3;
+				mobj_t *steam = P_SpawnMobj(x, y, point->watertop - size*mobjinfo[MT_DUST].height, MT_DUST);
+				P_SetScale(steam, size*actor->scale);
+				P_SetObjectMomZ(steam, FRACUNIT + 2*P_RandomFixed(), true);
+				P_InstaThrust(steam, FixedAngle(P_RandomKey(360)*FRACUNIT), 2*P_RandomFixed());
+				if (point->info->painsound)
+					S_StartSound(steam, point->info->painsound);
+			}
+		}
+		else
+		{
+			fixed_t distx = P_ReturnThrustX(point, point->angle, point->radius);
+			fixed_t disty = P_ReturnThrustY(point, point->angle, point->radius);
+			if (P_TryMove(point, point->x + distx, point->y + disty, false) // prevents the sprite from clipping into the wall or dangling off ledges
+				&& P_TryMove(point, point->x - 2*distx, point->y - 2*disty, false)
+				&& P_TryMove(point, point->x + distx, point->y + disty, false))
+			{
+				if (point->info->seesound)
+					S_StartSound(point, point->info->seesound);
+			}
+			else
+				P_RemoveMobj(point);
+		}
 	}
 
 	if (dur > 1)
@@ -8773,6 +8834,40 @@ void A_ChangeColorAbsolute(mobj_t *actor)
 		actor->color = (UINT8)locvar2;
 }
 
+// Function: A_Dye
+//
+// Description: Colorizes an object.
+//
+// var1 = if (var1 != 0), dye your target instead of yourself
+// var2 = color value to dye
+//
+void A_Dye(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+	mobj_t *target = ((locvar1 && actor->target) ? actor->target : actor);
+	UINT8 color = (UINT8)locvar2;
+	if (LUA_CallAction("A_Dye", actor))
+		return;
+	if (color >= MAXTRANSLATIONS)
+		return;
+	
+	if (!color)
+		target->colorized = false;
+	else
+		target->colorized = true;
+		
+	// What if it's a player?
+	if (target->player)
+	{
+		target->player->powers[pw_dye] = color;
+		return;
+	}
+	
+	target->color = color;
+}
+
 // Function: A_MoveRelative
 //
 // Description: Moves an object (wrapper for P_Thrust)
@@ -13274,8 +13369,9 @@ static boolean PIT_DustDevilLaunch(mobj_t *thing)
 				P_ResetPlayer(player);
 				A_PlayActiveSound(dustdevil);
 			}
+			player->powers[pw_carry] = CR_DUSTDEVIL;
 			player->powers[pw_nocontrol] = 2;
-			player->drawangle += ANG20;
+			P_SetTarget(&thing->tracer, dustdevil);
 			P_SetPlayerMobjState(thing, S_PLAY_PAIN);
 
 			if (dist > dragamount)
@@ -13295,7 +13391,9 @@ static boolean PIT_DustDevilLaunch(mobj_t *thing)
 			P_ResetPlayer(player);
 			thing->z = dustdevil->z + dustdevil->height;
 			thrust = 20 * FRACUNIT;
+			player->powers[pw_carry] = CR_NONE;
 			player->powers[pw_nocontrol] = 0;
+			P_SetTarget(&thing->tracer, NULL);
 			S_StartSound(thing, sfx_wdjump);
 			P_SetPlayerMobjState(thing, S_PLAY_FALL);
 		}
@@ -14372,3 +14470,43 @@ void A_DragonSegment(mobj_t *actor)
 	actor->angle = hangle;
 	P_TeleportMove(actor, target->x + xdist, target->y + ydist, target->z + zdist);
 }
+
+// Function: A_ChangeHeight
+//
+// Description: Changes the actor's height by var1
+//
+// var1 = height
+// var2 =
+//     &1: height is absolute
+//     &2: scale with actor's scale
+//
+void A_ChangeHeight(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	fixed_t height = locvar1;
+	boolean reverse;
+
+	if (LUA_CallAction("A_ChangeHeight", actor))
+		return;
+
+	reverse = (actor->eflags & MFE_VERTICALFLIP) || (actor->flags2 & MF2_OBJECTFLIP);
+
+	if (locvar2 & 2)
+		height = FixedMul(height, actor->scale);
+
+	P_UnsetThingPosition(actor);
+	if (locvar2 & 1)
+	{
+		if (reverse)
+			actor->z += actor->height - locvar1;
+		actor->height = locvar1;
+	}
+	else
+	{
+		if (reverse)
+			actor->z -= locvar1;
+		actor->height += locvar1;
+	}
+	P_SetThingPosition(actor);
+}
diff --git a/src/p_floor.c b/src/p_floor.c
index 5da0a1833f512edbcecbc7848775cc43a48c8c58..ee673cb0478cd977b7d26c6ff94be444f8bd512c 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -26,246 +26,129 @@
 //                              FLOORS
 // ==========================================================================
 
-//
-// Mini-P_IsObjectOnGroundIn for T_MovePlane hack
-//
-static inline boolean P_MobjReadyToMove(mobj_t *mo, sector_t *sec, boolean sectorisffloor, boolean sectorisquicksand)
-{
-	if (sectorisquicksand)
-		return (mo->z > sec->floorheight && mo->z < sec->ceilingheight);
-	else if (!!(mo->flags & MF_SPAWNCEILING) ^ !!(mo->eflags & MFE_VERTICALFLIP))
-		return ((sectorisffloor) ? (mo->z+mo->height != sec->floorheight) : (mo->z+mo->height != sec->ceilingheight));
-	else
-		return ((sectorisffloor) ? (mo->z != sec->ceilingheight) : (mo->z != sec->floorheight));
-}
-
 //
 // Move a plane (floor or ceiling) and check for crushing
 //
 result_e T_MovePlane(sector_t *sector, fixed_t speed, fixed_t dest, boolean crush,
-	INT32 floorOrCeiling, INT32 direction)
+	boolean ceiling, INT32 direction)
 {
-	boolean flag;
 	fixed_t lastpos;
 	fixed_t destheight; // used to keep floors/ceilings from moving through each other
-	// Stuff used for mobj hacks.
-	INT32 secnum = -1;
-	mobj_t *mo = NULL;
-	sector_t *sec = NULL;
-	ffloor_t *rover = NULL;
-	boolean sectorisffloor = false;
-	boolean sectorisquicksand = false;
-
 	sector->moved = true;
 
-	switch (floorOrCeiling)
+	if (ceiling)
 	{
-		case 0:
-			// moving a floor
-			switch (direction)
-			{
-				case -1:
-					// Moving a floor down
-					if (sector->floorheight - speed < dest)
-					{
-						lastpos = sector->floorheight;
-						sector->floorheight = dest;
-						flag = P_CheckSector(sector, crush);
-						if (flag && sector->numattached)
-						{
-							sector->floorheight = lastpos;
-							P_CheckSector(sector, crush);
-						}
-						return pastdest;
-					}
-					else
-					{
-						lastpos = sector->floorheight;
-						sector->floorheight -= speed;
-						flag = P_CheckSector(sector, crush);
-						if (flag && sector->numattached)
-						{
-							sector->floorheight = lastpos;
-							P_CheckSector(sector, crush);
-							return crushed;
-						}
-					}
-					break;
-
-				case 1:
-					// Moving a floor up
-					// keep floor from moving through ceilings
-					destheight = (dest < sector->ceilingheight) ? dest : sector->ceilingheight;
-					if (sector->floorheight + speed > destheight)
+		lastpos = sector->ceilingheight;
+		// moving a ceiling
+		switch (direction)
+		{
+			case -1:
+				// moving a ceiling down
+				// keep ceiling from moving through floors
+				destheight = (dest > sector->floorheight) ? dest : sector->floorheight;
+				if (sector->ceilingheight - speed < destheight)
+				{
+					sector->ceilingheight = destheight;
+					if (P_CheckSector(sector, crush))
 					{
-						lastpos = sector->floorheight;
-						sector->floorheight = destheight;
-						flag = P_CheckSector(sector, crush);
-						if (flag)
-						{
-							sector->floorheight = lastpos;
-							P_CheckSector(sector, crush);
-						}
-						return pastdest;
+						sector->ceilingheight = lastpos;
+						P_CheckSector(sector, crush);
 					}
-					else
+					return pastdest;
+				}
+				else
+				{
+					// crushing is possible
+					sector->ceilingheight -= speed;
+					if (P_CheckSector(sector, crush))
 					{
-						// crushing is possible
-						lastpos = sector->floorheight;
-						sector->floorheight += speed;
-						flag = P_CheckSector(sector, crush);
-						if (flag)
-						{
-							sector->floorheight = lastpos;
-							P_CheckSector(sector, crush);
-							return crushed;
-						}
+						sector->ceilingheight = lastpos;
+						P_CheckSector(sector, crush);
+						return crushed;
 					}
-					break;
-			}
-			break;
+				}
+				break;
 
-		case 1:
-			// moving a ceiling
-			switch (direction)
-			{
-				case -1:
-					// moving a ceiling down
-					// keep ceiling from moving through floors
-					destheight = (dest > sector->floorheight) ? dest : sector->floorheight;
-					if (sector->ceilingheight - speed < destheight)
+			case 1:
+				// moving a ceiling up
+				if (sector->ceilingheight + speed > dest)
+				{
+					sector->ceilingheight = dest;
+					if (P_CheckSector(sector, crush) && sector->numattached)
 					{
-						lastpos = sector->ceilingheight;
-						sector->ceilingheight = destheight;
-						flag = P_CheckSector(sector, crush);
-
-						if (flag)
-						{
-							sector->ceilingheight = lastpos;
-							P_CheckSector(sector, crush);
-						}
-						return pastdest;
+						sector->ceilingheight = lastpos;
+						P_CheckSector(sector, crush);
 					}
-					else
+					return pastdest;
+				}
+				else
+				{
+					sector->ceilingheight += speed;
+					if (P_CheckSector(sector, crush) && sector->numattached)
 					{
-						// crushing is possible
-						lastpos = sector->ceilingheight;
-						sector->ceilingheight -= speed;
-						flag = P_CheckSector(sector, crush);
-
-						if (flag)
-						{
-							sector->ceilingheight = lastpos;
-							P_CheckSector(sector, crush);
-							return crushed;
-						}
+						sector->ceilingheight = lastpos;
+						P_CheckSector(sector, crush);
+						return crushed;
 					}
-					break;
-
-				case 1:
-					// moving a ceiling up
-					if (sector->ceilingheight + speed > dest)
+				}
+				break;
+		}
+	}
+	else
+	{
+		lastpos = sector->floorheight;
+		// moving a floor
+		switch (direction)
+		{
+			case -1:
+				// Moving a floor down
+				if (sector->floorheight - speed < dest)
+				{
+					sector->floorheight = dest;
+					if (P_CheckSector(sector, crush) && sector->numattached)
 					{
-						lastpos = sector->ceilingheight;
-						sector->ceilingheight = dest;
-						flag = P_CheckSector(sector, crush);
-						if (flag && sector->numattached)
-						{
-							sector->ceilingheight = lastpos;
-							P_CheckSector(sector, crush);
-						}
-						return pastdest;
+						sector->floorheight = lastpos;
+						P_CheckSector(sector, crush);
 					}
-					else
+					return pastdest;
+				}
+				else
+				{
+					sector->floorheight -= speed;
+					if (P_CheckSector(sector, crush) && sector->numattached)
 					{
-						lastpos = sector->ceilingheight;
-						sector->ceilingheight += speed;
-						flag = P_CheckSector(sector, crush);
-						if (flag && sector->numattached)
-						{
-							sector->ceilingheight = lastpos;
-							P_CheckSector(sector, crush);
-							return crushed;
-						}
+						sector->floorheight = lastpos;
+						P_CheckSector(sector, crush);
+						return crushed;
 					}
-					break;
-			}
-			break;
-	}
-
-	// Hack for buggy mobjs to move by gravity with moving planes.
-	if (sector->tagline)
-		sectorisffloor = true;
-
-	// Optimization condition. If the sector is not an FOF, declare sec as the main sector outside of the loop.
-	if (!sectorisffloor)
-		sec = sector;
-
-	// Optimization condition. Only run the logic if there is any Things in the sector.
-	if (sectorisffloor || sec->thinglist)
-	{
-		// If this is an FOF being checked, check all the affected sectors for moving mobjs.
-		while ((sectorisffloor ? (secnum = P_FindSectorFromLineTag(sector->tagline, secnum)) : (secnum = 1)) >= 0)
-		{
-			if (sectorisffloor)
-			{
-				// Get actual sector from the list of sectors.
-				sec = &sectors[secnum];
+				}
+				break;
 
-				// Can't use P_InQuicksand because it will return the incorrect result
-				// because of checking for heights.
-				for (rover = sec->ffloors; rover; rover = rover->next)
+			case 1:
+				// Moving a floor up
+				// keep floor from moving through ceilings
+				destheight = (dest < sector->ceilingheight) ? dest : sector->ceilingheight;
+				if (sector->floorheight + speed > destheight)
 				{
-					if (rover->target == sec && (rover->flags & FF_QUICKSAND))
+					sector->floorheight = destheight;
+					if (P_CheckSector(sector, crush))
 					{
-						sectorisquicksand = true;
-						break;
+						sector->floorheight = lastpos;
+						P_CheckSector(sector, crush);
 					}
+					return pastdest;
 				}
-			}
-
-			for (mo = sec->thinglist; mo; mo = mo->snext)
-			{
-				// The object should be ready to move as defined by this function.
-				if (!P_MobjReadyToMove(mo, sec, sectorisffloor, sectorisquicksand))
-					continue;
-
-				// The object should not be moving at all.
-				if (mo->momx || mo->momy || mo->momz)
-					continue;
-
-				// These objects will be affected by this condition.
-				switch (mo->type)
+				else
 				{
-					case MT_GOOP: // Egg Slimer's goop objects
-					case MT_SPINFIRE: // Elemental Shield flame balls
-					case MT_SPIKE: // Floor Spike
-						// Is the object hang from the ceiling?
-						// In that case, swap the planes used.
-						// verticalflip inverts
-						if (!!(mo->flags & MF_SPAWNCEILING) ^ !!(mo->eflags & MFE_VERTICALFLIP))
-						{
-							if (sectorisffloor && !sectorisquicksand)
-								mo->z = mo->ceilingz - mo->height;
-							else
-								mo->z = mo->ceilingz = mo->subsector->sector->ceilingheight - mo->height;
-						}
-						else
-						{
-							if (sectorisffloor && !sectorisquicksand)
-								mo->z = mo->floorz;
-							else
-								mo->z = mo->floorz = mo->subsector->sector->floorheight;
-						}
-						break;
-					// Kill warnings...
-					default:
-						break;
+					// crushing is possible
+					sector->floorheight += speed;
+					if (P_CheckSector(sector, crush))
+					{
+						sector->floorheight = lastpos;
+						P_CheckSector(sector, crush);
+						return crushed;
+					}
 				}
-			}
-
-			// Break from loop if there is no FOFs to check.
-			if (!sectorisffloor)
 				break;
 		}
 	}
@@ -290,7 +173,7 @@ void T_MoveFloor(floormove_t *movefloor)
 	res = T_MovePlane(movefloor->sector,
 	                  movefloor->speed,
 	                  movefloor->floordestheight,
-	                  movefloor->crush, 0, movefloor->direction);
+	                  movefloor->crush, false, movefloor->direction);
 
 	if (movefloor->type == bounceFloor)
 	{
@@ -483,7 +366,7 @@ void T_MoveElevator(elevator_t *elevator)
 			elevator->speed,
 			elevator->ceilingdestheight,
 			elevator->distance,
-			1,                          // move floor
+			true,                          // move ceiling
 			elevator->direction
 		);
 
@@ -493,7 +376,7 @@ void T_MoveElevator(elevator_t *elevator)
 			elevator->speed,
 			elevator->floordestheight,
 			elevator->distance,
-			0,                        // move ceiling
+			false,                        // move floor
 			elevator->direction
 		);
 
@@ -545,7 +428,7 @@ void T_MoveElevator(elevator_t *elevator)
 			elevator->speed,
 			elevator->floordestheight,
 			elevator->distance,
-			0,                          // move ceiling
+			false,                          // move floor
 			elevator->direction
 		);
 
@@ -557,7 +440,7 @@ void T_MoveElevator(elevator_t *elevator)
 				elevator->speed,
 				elevator->ceilingdestheight,
 				elevator->distance,
-				1,                        // move floor
+				true,                        // move ceiling
 				elevator->direction
 			);
 		}
@@ -678,43 +561,18 @@ void T_MoveElevator(elevator_t *elevator)
 //
 // Useful for things like intermittent falling lava.
 //
-void T_ContinuousFalling(levelspecthink_t *faller)
+void T_ContinuousFalling(continuousfall_t *faller)
 {
-#define speed vars[0]
-#define direction vars[1]
-#define floorwasheight vars[2]
-#define ceilingwasheight vars[3]
-#define floordestheight vars[4]
-#define ceilingdestheight vars[5]
-
-	if (faller->direction == -1)
-	{
-		faller->sector->ceilingheight -= faller->speed;
-		faller->sector->floorheight -= faller->speed;
-	}
-	else
-	{
-		faller->sector->ceilingheight += faller->speed;
-		faller->sector->floorheight += faller->speed;
-	}
+	faller->sector->ceilingheight += faller->speed*faller->direction;
+	faller->sector->floorheight += faller->speed*faller->direction;
 
 	P_CheckSector(faller->sector, false);
 
-	if (faller->direction == -1) // Down
+	if ((faller->direction == -1 && faller->sector->ceilingheight <= faller->destheight)
+		|| (faller->direction == 1 && faller->sector->floorheight >= faller->destheight))
 	{
-		if (faller->sector->ceilingheight <= faller->ceilingdestheight)            // if destination height acheived
-		{
-			faller->sector->ceilingheight = faller->ceilingwasheight;
-			faller->sector->floorheight = faller->floorwasheight;
-		}
-	}
-	else // Up
-	{
-		if (faller->sector->floorheight >= faller->floordestheight)            // if destination height acheived
-		{
-			faller->sector->ceilingheight = faller->ceilingwasheight;
-			faller->sector->floorheight = faller->floorwasheight;
-		}
+		faller->sector->ceilingheight = faller->ceilingstartheight;
+		faller->sector->floorheight = faller->floorstartheight;
 	}
 
 	P_CheckSector(faller->sector, false); // you might think this is irrelevant. you would be wrong
@@ -722,12 +580,6 @@ void T_ContinuousFalling(levelspecthink_t *faller)
 	faller->sector->floorspeed = faller->speed*faller->direction;
 	faller->sector->ceilspeed = 42;
 	faller->sector->moved = true;
-#undef speed
-#undef direction
-#undef floorwasheight
-#undef ceilingwasheight
-#undef floordestheight
-#undef ceilingdestheight
 }
 
 //
@@ -773,23 +625,20 @@ static fixed_t P_SectorCheckWater(sector_t *analyzesector,
 //////////////////////////////////////////////////
 // Bounces a floating cheese
 
-void T_BounceCheese(levelspecthink_t *bouncer)
+void T_BounceCheese(bouncecheese_t *bouncer)
 {
-#define speed vars[0]
-#define distance vars[1]
-#define low vars[2]
-#define ceilingwasheight vars[3]
-#define floorwasheight vars[4]
+	fixed_t sectorheight;
 	fixed_t halfheight;
 	fixed_t waterheight;
 	fixed_t floorheight;
 	sector_t *actionsector;
 	INT32 i;
+	boolean remove;
 
-	if (bouncer->sector->crumblestate == 4 || bouncer->sector->crumblestate == 1
-		|| bouncer->sector->crumblestate == 2) // Oops! Crumbler says to remove yourself!
+	if (bouncer->sector->crumblestate == CRUMBLE_RESTORE || bouncer->sector->crumblestate == CRUMBLE_WAIT
+		|| bouncer->sector->crumblestate == CRUMBLE_ACTIVATED) // Oops! Crumbler says to remove yourself!
 	{
-		bouncer->sector->crumblestate = 1;
+		bouncer->sector->crumblestate = CRUMBLE_WAIT;
 		bouncer->sector->ceilingdata = NULL;
 		bouncer->sector->ceilspeed = 0;
 		bouncer->sector->floordata = NULL;
@@ -804,35 +653,39 @@ void T_BounceCheese(levelspecthink_t *bouncer)
 		actionsector = &sectors[i];
 		actionsector->moved = true;
 
-		halfheight = abs(bouncer->sector->ceilingheight - bouncer->sector->floorheight) >> 1;
+		sectorheight = abs(bouncer->sector->ceilingheight - bouncer->sector->floorheight);
+		halfheight = sectorheight/2;
 
 		waterheight = P_SectorCheckWater(actionsector, bouncer->sector); // sorts itself out if there's no suitable water in the sector
 
-		floorheight = P_FloorzAtPos(actionsector->soundorg.x, actionsector->soundorg.y, bouncer->sector->floorheight, halfheight << 1);
+		floorheight = P_FloorzAtPos(actionsector->soundorg.x, actionsector->soundorg.y, bouncer->sector->floorheight, sectorheight);
+
+		remove = false;
 
 		// Water level is up to the ceiling.
 		if (waterheight > bouncer->sector->ceilingheight - halfheight && bouncer->sector->ceilingheight >= actionsector->ceilingheight) // Tails 01-08-2004
 		{
 			bouncer->sector->ceilingheight = actionsector->ceilingheight;
-			bouncer->sector->floorheight = bouncer->sector->ceilingheight - (halfheight*2);
-			T_MovePlane(bouncer->sector, 0, bouncer->sector->ceilingheight, 0, 1, -1); // update things on ceiling
-			T_MovePlane(bouncer->sector, 0, bouncer->sector->floorheight, 0, 0, -1); // update things on floor
-			P_RecalcPrecipInSector(actionsector);
-			bouncer->sector->ceilingdata = NULL;
-			bouncer->sector->floordata = NULL;
-			bouncer->sector->floorspeed = 0;
-			bouncer->sector->ceilspeed = 0;
-			bouncer->sector->moved = true;
-			P_RemoveThinker(&bouncer->thinker); // remove bouncer from actives
-			return;
+			bouncer->sector->floorheight = actionsector->ceilingheight - sectorheight;
+			remove = true;
 		}
 		// Water level is too shallow.
 		else if (waterheight < bouncer->sector->floorheight + halfheight && bouncer->sector->floorheight <= floorheight)
 		{
-			bouncer->sector->ceilingheight = floorheight + (halfheight << 1);
+			bouncer->sector->ceilingheight = floorheight + sectorheight;
 			bouncer->sector->floorheight = floorheight;
-			T_MovePlane(bouncer->sector, 0, bouncer->sector->ceilingheight, 0, 1, -1); // update things on ceiling
-			T_MovePlane(bouncer->sector, 0, bouncer->sector->floorheight, 0, 0, -1); // update things on floor
+			remove = true;
+		}
+		else
+		{
+			bouncer->ceilingwasheight = waterheight + halfheight;
+			bouncer->floorwasheight = waterheight - halfheight;
+		}
+
+		if (remove)
+		{
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->ceilingheight, false, true, -1); // update things on ceiling
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->floorheight, false, false, -1); // update things on floor
 			P_RecalcPrecipInSector(actionsector);
 			bouncer->sector->ceilingdata = NULL;
 			bouncer->sector->floordata = NULL;
@@ -842,43 +695,24 @@ void T_BounceCheese(levelspecthink_t *bouncer)
 			P_RemoveThinker(&bouncer->thinker); // remove bouncer from actives
 			return;
 		}
-		else
-		{
-			bouncer->ceilingwasheight = waterheight + halfheight;
-			bouncer->floorwasheight = waterheight - halfheight;
-		}
 
 		T_MovePlane(bouncer->sector, bouncer->speed/2, bouncer->sector->ceilingheight -
-			70*FRACUNIT, 0, 1, -1); // move ceiling
+			70*FRACUNIT, false, true, -1); // move ceiling
 		T_MovePlane(bouncer->sector, bouncer->speed/2, bouncer->sector->floorheight - 70*FRACUNIT,
-			0, 0, -1); // move floor
+			false, false, -1); // move floor
 
 		bouncer->sector->floorspeed = -bouncer->speed/2;
 		bouncer->sector->ceilspeed = 42;
 
-		if (bouncer->sector->ceilingheight < bouncer->ceilingwasheight && bouncer->low == 0) // Down
-		{
-			if (abs(bouncer->speed) < 6*FRACUNIT)
-				bouncer->speed -= bouncer->speed/3;
-			else
-				bouncer->speed -= bouncer->speed/2;
-
-			bouncer->low = 1;
-			if (abs(bouncer->speed) > 6*FRACUNIT)
-			{
-				mobj_t *mp = (void *)&actionsector->soundorg;
-				actionsector->soundorg.z = bouncer->sector->floorheight;
-				S_StartSound(mp, sfx_splash);
-			}
-		}
-		else if (bouncer->sector->ceilingheight > bouncer->ceilingwasheight && bouncer->low) // Up
+		if ((bouncer->sector->ceilingheight < bouncer->ceilingwasheight && !bouncer->low) // Down
+			|| (bouncer->sector->ceilingheight > bouncer->ceilingwasheight && bouncer->low)) // Up
 		{
 			if (abs(bouncer->speed) < 6*FRACUNIT)
 				bouncer->speed -= bouncer->speed/3;
 			else
 				bouncer->speed -= bouncer->speed/2;
 
-			bouncer->low = 0;
+			bouncer->low = !bouncer->low;
 			if (abs(bouncer->speed) > 6*FRACUNIT)
 			{
 				mobj_t *mp = (void *)&actionsector->soundorg;
@@ -901,8 +735,8 @@ void T_BounceCheese(levelspecthink_t *bouncer)
 		{
 			bouncer->sector->floorheight = bouncer->floorwasheight;
 			bouncer->sector->ceilingheight = bouncer->ceilingwasheight;
-			T_MovePlane(bouncer->sector, 0, bouncer->sector->ceilingheight, 0, 1, -1); // update things on ceiling
-			T_MovePlane(bouncer->sector, 0, bouncer->sector->floorheight, 0, 0, -1); // update things on floor
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->ceilingheight, false, true, -1); // update things on ceiling
+			T_MovePlane(bouncer->sector, 0, bouncer->sector->floorheight, false, false, -1); // update things on floor
 			bouncer->sector->ceilingdata = NULL;
 			bouncer->sector->floordata = NULL;
 			bouncer->sector->floorspeed = 0;
@@ -917,27 +751,13 @@ void T_BounceCheese(levelspecthink_t *bouncer)
 		if (actionsector)
 			P_RecalcPrecipInSector(actionsector);
 	}
-#undef speed
-#undef distance
-#undef low
-#undef ceilingwasheight
-#undef floorwasheight
 }
 
 //////////////////////////////////////////////////
 // T_StartCrumble ////////////////////////////////
 //////////////////////////////////////////////////
 // Crumbling platform Tails 03-11-2002
-//
-// DEFINITION OF THE 'CRUMBLESTATE'S:
-//
-// 0 - No crumble thinker
-// 1 - Don't float on water because this is supposed to wait for a crumble
-// 2 - Crumble thinker activated, but hasn't fallen yet
-// 3 - Crumble thinker is falling
-// 4 - Crumble thinker is about to restore to original position
-//
-void T_StartCrumble(elevator_t *elevator)
+void T_StartCrumble(crumble_t *crumble)
 {
 	ffloor_t *rover;
 	sector_t *sector;
@@ -945,84 +765,96 @@ void T_StartCrumble(elevator_t *elevator)
 
 	// Once done, the no-return thinker just sits there,
 	// constantly 'returning'... kind of an oxymoron, isn't it?
-	if (((elevator->floordestheight == 1 && elevator->direction == -1)
-		|| (elevator->floordestheight == 0 && elevator->direction == 1))
-		&& elevator->type == elevateContinuous) // No return crumbler
+	if ((((crumble->flags & CF_REVERSE) && crumble->direction == -1)
+		|| (!(crumble->flags & CF_REVERSE) && crumble->direction == 1))
+		&& !(crumble->flags & CF_RETURN))
 	{
-		elevator->sector->ceilspeed = 0;
-		elevator->sector->floorspeed = 0;
+		crumble->sector->ceilspeed = 0;
+		crumble->sector->floorspeed = 0;
 		return;
 	}
 
-	if (elevator->distance != 0)
+	if (crumble->timer != 0)
 	{
-		if (elevator->distance > 0) // Count down the timer
+		if (crumble->timer > 0) // Count down the timer
 		{
-			elevator->distance--;
-			if (elevator->distance <= 0)
-				elevator->distance = -15*TICRATE; // Timer until platform returns to original position.
+			if (--crumble->timer <= 0)
+				crumble->timer = -15*TICRATE; // Timer until platform returns to original position.
 			else
 			{
 				// Timer isn't up yet, so just keep waiting.
-				elevator->sector->ceilspeed = 0;
-				elevator->sector->floorspeed = 0;
+				crumble->sector->ceilspeed = 0;
+				crumble->sector->floorspeed = 0;
 				return;
 			}
 		}
-		else if (++elevator->distance == 0) // Reposition back to original spot
+		else if (++crumble->timer == 0) // Reposition back to original spot
 		{
-			for (i = -1; (i = P_FindSectorFromTag(elevator->sourceline->tag, i)) >= 0 ;)
+			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
 			{
 				sector = &sectors[i];
 
 				for (rover = sector->ffloors; rover; rover = rover->next)
 				{
-					if (rover->flags & FF_CRUMBLE && rover->flags & FF_FLOATBOB
-						&& rover->master == elevator->sourceline)
-					{
-						rover->alpha = elevator->origspeed;
+					if (!(rover->flags & FF_CRUMBLE))
+						continue;
 
-						if (rover->alpha == 0xff)
-							rover->flags &= ~FF_TRANSLUCENT;
-					}
+					if (!(rover->flags & FF_FLOATBOB))
+						continue;
+
+					if (rover->master != crumble->sourceline)
+						continue;
+
+					rover->alpha = crumble->origalpha;
+
+					if (rover->alpha == 0xff)
+						rover->flags &= ~FF_TRANSLUCENT;
 				}
 			}
 
 			// Up!
-			if (elevator->floordestheight == 1)
-				elevator->direction = -1;
+			if (crumble->flags & CF_REVERSE)
+				crumble->direction = -1;
 			else
-				elevator->direction = 1;
+				crumble->direction = 1;
 
-			elevator->sector->ceilspeed = 0;
-			elevator->sector->floorspeed = 0;
+			crumble->sector->ceilspeed = 0;
+			crumble->sector->floorspeed = 0;
 			return;
 		}
 
 		// Flash to indicate that the platform is about to return.
-		if (elevator->distance > -224 && (leveltime % ((abs(elevator->distance)/8) + 1) == 0))
+		if (crumble->timer > -224 && (leveltime % ((abs(crumble->timer)/8) + 1) == 0))
 		{
-			for (i = -1; (i = P_FindSectorFromTag(elevator->sourceline->tag, i)) >= 0 ;)
+			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
 			{
 				sector = &sectors[i];
 
 				for (rover = sector->ffloors; rover; rover = rover->next)
 				{
-					if (!(rover->flags & FF_NORETURN) && rover->flags & FF_CRUMBLE && rover->flags & FF_FLOATBOB
-						&& rover->master == elevator->sourceline)
+					if (rover->flags & FF_NORETURN)
+						continue;
+
+					if (!(rover->flags & FF_CRUMBLE))
+						continue;
+
+					if (!(rover->flags & FF_FLOATBOB))
+						continue;
+
+					if (rover->master != crumble->sourceline)
+						continue;
+
+					if (rover->alpha == crumble->origalpha)
+					{
+						rover->flags |= FF_TRANSLUCENT;
+						rover->alpha = 0x00;
+					}
+					else
 					{
-						if (rover->alpha == elevator->origspeed)
-						{
-							rover->flags |= FF_TRANSLUCENT;
-							rover->alpha = 0x00;
-						}
-						else
-						{
-							if (elevator->origspeed == 0xff)
-								rover->flags &= ~FF_TRANSLUCENT;
-
-							rover->alpha = elevator->origspeed;
-						}
+						rover->alpha = crumble->origalpha;
+
+						if (rover->alpha == 0xff)
+							rover->flags &= ~FF_TRANSLUCENT;
 					}
 				}
 			}
@@ -1031,74 +863,62 @@ void T_StartCrumble(elevator_t *elevator)
 		// We're about to go back to the original position,
 		// so set this to let other thinkers know what is
 		// about to happen.
-		if (elevator->distance < 0 && elevator->distance > -3)
-			elevator->sector->crumblestate = 4; // makes T_BounceCheese remove itself
+		if (crumble->timer < 0 && crumble->timer > -3)
+			crumble->sector->crumblestate = CRUMBLE_RESTORE; // makes T_BounceCheese remove itself
 	}
 
-	if ((elevator->floordestheight == 0 && elevator->direction == -1)
-		|| (elevator->floordestheight == 1 && elevator->direction == 1)) // Down
+	if ((!(crumble->flags & CF_REVERSE) && crumble->direction == -1)
+		|| ((crumble->flags & CF_REVERSE) && crumble->direction == 1)) // Down
 	{
-		elevator->sector->crumblestate = 3; // Allow floating now.
+		crumble->sector->crumblestate = CRUMBLE_FALL; // Allow floating now.
 
 		// Only fall like this if it isn't meant to float on water
-		if (elevator->high != 42)
+		if (!(crumble->flags & CF_FLOATBOB))
 		{
-			elevator->speed += gravity; // Gain more and more speed
+			crumble->speed += gravity; // Gain more and more speed
 
-			if ((elevator->floordestheight == 0 && !(elevator->sector->ceilingheight < -16384*FRACUNIT))
-				|| (elevator->floordestheight == 1 && !(elevator->sector->ceilingheight > 16384*FRACUNIT)))
+			if ((!(crumble->flags & CF_REVERSE) && crumble->sector->ceilingheight >= -16384*FRACUNIT)
+				|| ((crumble->flags & CF_REVERSE) && crumble->sector->ceilingheight <= 16384*FRACUNIT))
 			{
-				fixed_t dest;
-
-				if (elevator->floordestheight == 1)
-					dest = elevator->sector->ceilingheight + (elevator->speed*2);
-				else
-					dest = elevator->sector->ceilingheight - (elevator->speed*2);
-
 				T_MovePlane             //jff 4/7/98 reverse order of ceiling/floor
 				(
-				  elevator->sector,
-				  elevator->speed,
-				  dest,
-				  0,
-				  1, // move floor
-				  elevator->direction
+				  crumble->sector,
+				  crumble->speed,
+				  crumble->sector->ceilingheight + crumble->direction*crumble->speed*2,
+				  false,
+				  true, // move ceiling
+				  crumble->direction
 				);
 
-				if (elevator->floordestheight == 1)
-					dest = elevator->sector->floorheight + (elevator->speed*2);
-				else
-					dest = elevator->sector->floorheight - (elevator->speed*2);
-
-				  T_MovePlane
-				  (
-					elevator->sector,
-					elevator->speed,
-					dest,
-					0,
-					0,                        // move ceiling
-					elevator->direction
+				T_MovePlane
+				(
+				  crumble->sector,
+				  crumble->speed,
+				  crumble->sector->floorheight + crumble->direction*crumble->speed*2,
+				  false,
+				  false, // move floor
+				  crumble->direction
 				);
 
-				elevator->sector->ceilspeed = 42;
-				elevator->sector->floorspeed = elevator->speed*elevator->direction;
+				crumble->sector->ceilspeed = 42;
+				crumble->sector->floorspeed = crumble->speed*crumble->direction;
 			}
 		}
 	}
 	else // Up (restore to original position)
 	{
-		elevator->sector->crumblestate = 1;
-		elevator->sector->ceilingheight = elevator->ceilingwasheight;
-		elevator->sector->floorheight = elevator->floorwasheight;
-		elevator->sector->floordata = NULL;
-		elevator->sector->ceilingdata = NULL;
-		elevator->sector->ceilspeed = 0;
-		elevator->sector->floorspeed = 0;
-		elevator->sector->moved = true;
-		P_RemoveThinker(&elevator->thinker);
+		crumble->sector->crumblestate = CRUMBLE_WAIT;
+		crumble->sector->ceilingheight = crumble->ceilingwasheight;
+		crumble->sector->floorheight = crumble->floorwasheight;
+		crumble->sector->floordata = NULL;
+		crumble->sector->ceilingdata = NULL;
+		crumble->sector->ceilspeed = 0;
+		crumble->sector->floorspeed = 0;
+		crumble->sector->moved = true;
+		P_RemoveThinker(&crumble->thinker);
 	}
 
-	for (i = -1; (i = P_FindSectorFromTag(elevator->sourceline->tag, i)) >= 0 ;)
+	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
 	{
 		sector = &sectors[i];
 		sector->moved = true;
@@ -1111,24 +931,17 @@ void T_StartCrumble(elevator_t *elevator)
 //////////////////////////////////////////////////
 // Mario hits a block!
 //
-void T_MarioBlock(levelspecthink_t *block)
+void T_MarioBlock(mariothink_t *block)
 {
 	INT32 i;
 
-#define speed vars[1]
-#define direction vars[2]
-#define floorwasheight vars[3]
-#define ceilingwasheight vars[4]
-#define distance vars[5]
-#define low vars[6]
-
 	T_MovePlane
 	(
 	  block->sector,
 	  block->speed,
 	  block->sector->ceilingheight + 70*FRACUNIT * block->direction,
-	  0,
-	  1, // move floor
+	  false,
+	  true, // move ceiling
 	  block->direction
 	);
 
@@ -1137,17 +950,17 @@ void T_MarioBlock(levelspecthink_t *block)
 	  block->sector,
 	  block->speed,
 	  block->sector->floorheight + 70*FRACUNIT * block->direction,
-	  0,
-	  0, // move ceiling
+	  false,
+	  false, // move floor
 	  block->direction
 	);
 
-	if (block->sector->ceilingheight >= block->ceilingwasheight + 32*FRACUNIT) // Go back down now..
-		block->direction = -block->direction;
-	else if (block->sector->ceilingheight <= block->ceilingwasheight)
+	if (block->sector->ceilingheight >= block->ceilingstartheight + 32*FRACUNIT) // Go back down now..
+		block->direction *= -1;
+	else if (block->sector->ceilingheight <= block->ceilingstartheight)
 	{
-		block->sector->ceilingheight = block->ceilingwasheight;
-		block->sector->floorheight = block->floorwasheight;
+		block->sector->ceilingheight = block->ceilingstartheight;
+		block->sector->floorheight = block->floorstartheight;
 		P_RemoveThinker(&block->thinker);
 		block->sector->floordata = NULL;
 		block->sector->ceilingdata = NULL;
@@ -1156,595 +969,41 @@ void T_MarioBlock(levelspecthink_t *block)
 		block->direction = 0;
 	}
 
-	for (i = -1; (i = P_FindSectorFromTag((INT16)block->vars[0], i)) >= 0 ;)
+	for (i = -1; (i = P_FindSectorFromTag(block->tag, i)) >= 0 ;)
 		P_RecalcPrecipInSector(&sectors[i]);
-
-#undef speed
-#undef direction
-#undef floorwasheight
-#undef ceilingwasheight
-#undef distance
-#undef low
-}
-
-void T_SpikeSector(levelspecthink_t *spikes)
-{
-	mobj_t *thing;
-	msecnode_t *node;
-	boolean dothepain;
-	sector_t *affectsec;
-
-	node = spikes->sector->touching_thinglist; // things touching this sector
-
-	for (; node; node = node->m_thinglist_next)
-	{
-		thing = node->m_thing;
-		if (!thing->player)
-			continue;
-
-		dothepain = false;
-		affectsec = &sectors[spikes->vars[0]];
-
-		if (affectsec == spikes->sector) // Applied to an actual sector
-		{
-			fixed_t affectfloor = P_GetSpecialBottomZ(thing, affectsec, affectsec);
-			fixed_t affectceil = P_GetSpecialTopZ(thing, affectsec, affectsec);
-
-			if (affectsec->flags & SF_FLIPSPECIAL_FLOOR)
-			{
-				if (!(thing->eflags & MFE_VERTICALFLIP) && thing->momz > 0)
-					continue;
-
-				if (thing->z == affectfloor)
-					dothepain = true;
-			}
-
-			if (affectsec->flags & SF_FLIPSPECIAL_CEILING)
-			{
-				if ((thing->eflags & MFE_VERTICALFLIP) && thing->momz < 0)
-					continue;
-
-				if (thing->z + thing->height == affectceil)
-					dothepain = true;
-			}
-		}
-		else
-		{
-			fixed_t affectfloor = P_GetSpecialBottomZ(thing, affectsec, spikes->sector);
-			fixed_t affectceil = P_GetSpecialTopZ(thing, affectsec, spikes->sector);
-			if (affectsec->flags & SF_FLIPSPECIAL_FLOOR)
-			{
-				if (!(thing->eflags & MFE_VERTICALFLIP) && thing->momz > 0)
-					continue;
-
-				if (thing->z == affectceil)
-					dothepain = true;
-			}
-
-			if (affectsec->flags & SF_FLIPSPECIAL_CEILING)
-			{
-				if ((thing->eflags & MFE_VERTICALFLIP) && thing->momz < 0)
-					continue;
-
-				if (thing->z + thing->height == affectfloor)
-					dothepain = true;
-			}
-		}
-
-		if (dothepain)
-		{
-			P_DamageMobj(thing, NULL, NULL, 1, DMG_SPIKE);
-			break;
-		}
-	}
 }
 
-void T_FloatSector(levelspecthink_t *floater)
+void T_FloatSector(floatthink_t *floater)
 {
 	fixed_t cheeseheight;
+	fixed_t waterheight;
 	sector_t *actionsector;
 	INT32 secnum;
 
-	cheeseheight = (floater->sector->ceilingheight + floater->sector->floorheight)>>1;
-
 	// Just find the first sector with the tag.
 	// Doesn't work with multiple sectors that have different floor/ceiling heights.
-	secnum = P_FindSectorFromTag((INT16)floater->vars[0], -1);
-
-	if (secnum > 0)
-		actionsector = &sectors[secnum];
-	else
-		actionsector = NULL;
-
-	if (actionsector)
-	{
-		//boolean floatanyway = false; // Ignore the crumblestate setting.
-		fixed_t waterheight = P_SectorCheckWater(actionsector, floater->sector); // find the highest suitable water block around
-
-		if (waterheight == cheeseheight) // same height, no floating needed
-			;
-		else if (floater->sector->floorheight == actionsector->floorheight && waterheight < cheeseheight) // too low
-			;
-		else if (floater->sector->ceilingheight == actionsector->ceilingheight && waterheight > cheeseheight) // too high
-			;
-		// we have something to float in! Or we're for some reason above the ground, let's fall anyway
-		else if (floater->sector->crumblestate == 0 || floater->sector->crumblestate >= 3/* || floatanyway*/)
-			EV_BounceSector(floater->sector, FRACUNIT, floater->sourceline);
-
-		P_RecalcPrecipInSector(actionsector);
-	}
-}
-
-//
-// T_BridgeThinker
-//
-// Kind of like T_RaiseSector,
-// but spreads out across
-// multiple FOFs at varying
-// intensity.
-//
-void T_BridgeThinker(levelspecthink_t *bridge)
-{
-	msecnode_t *node;
-	mobj_t *thing;
-	sector_t *sector;
-	sector_t *controlsec = NULL;
-	INT32 i, k;
-
-	INT16 j;
-	boolean playeronme = false;
-	fixed_t ceilingdestination = 0, floordestination = 0;
-	result_e res = 0;
-
-#define ORIGFLOORHEIGHT (bridge->vars[0])
-#define ORIGCEILINGHEIGHT (bridge->vars[1])
-#define BASESPEED (bridge->vars[2])
-#define CURSPEED (bridge->vars[3])
-#define STARTTAG ((INT16)bridge->vars[4])
-#define ENDTAG ((INT16)bridge->vars[5])
-#define DIRECTION (bridge->vars[8])
-#define SAGAMT (8*FRACUNIT)
-	fixed_t lowceilheight = ORIGCEILINGHEIGHT - SAGAMT;
-	fixed_t lowfloorheight = ORIGFLOORHEIGHT - SAGAMT;
-#define LOWCEILINGHEIGHT (lowceilheight)
-#define LOWFLOORHEIGHT (lowfloorheight)
-#define STARTCONTROLTAG (ENDTAG + 1)
-#define ENDCONTROLTAG (ENDTAG + (ENDTAG - STARTTAG) + 1)
-
-	// Is someone standing on it?
-	for (j = STARTTAG; j <= ENDTAG; j++)
-	{
-		for (i = -1; (i = P_FindSectorFromTag(j, i)) >= 0 ;)
-		{
-			sector = &sectors[i];
-
-			// Nab the control sector that this sector belongs to.
-			k = P_FindSectorFromTag((INT16)(j + (ENDTAG-STARTTAG) + 1), -1);
-
-			if (k == -1)
-				break;
-
-			controlsec = &sectors[k];
-
-			// Is a player standing on me?
-			for (node = sector->touching_thinglist; node; node = node->m_thinglist_next)
-			{
-				thing = node->m_thing;
-
-				if (!thing->player)
-					continue;
-
-				if (!(thing->z == controlsec->ceilingheight))
-					continue;
-
-				playeronme = true;
-				goto wegotit; // Just take the first one?
-			}
-		}
-	}
-wegotit:
-	if (playeronme)
-	{
-		// Lower controlsec like a regular T_RaiseSector
-		// Set the heights of all the other control sectors to
-		// be a gradient of this height toward the edges
-	}
-	else
-	{
-		// Raise controlsec like a regular T_RaiseSector
-		// Set the heights of all the other control sectors to
-		// be a gradient of this height toward the edges.
-	}
-
-	if (playeronme && controlsec)
-	{
-		INT32 dist;
-
-		bridge->sector = controlsec;
-		CURSPEED = BASESPEED;
-
-		{
-			// Translate tags to - 0 + range
-			/*so you have a number in [min, max].
-			let range = max - min, subtract min
-			from your number to get [0, range].
-			subtract range/2 to get [-range/2, range/2].
-			take absolute value and get [0, range/2] where
-			lower number = closer to midpoint. divide by
-			range/2 to get [0, 1]. subtract that number
-			from 1 to get [0, 1] with higher number = closer
-			to midpoint. multiply this by max sag amount*/
-
-			INT32 midpoint = STARTCONTROLTAG + ((ENDCONTROLTAG-STARTCONTROLTAG) + 1)/2;
-//			INT32 tagstart = STARTTAG - midpoint;
-//			INT32 tagend = ENDTAG - midpoint;
-
-//			CONS_Debug(DBG_GAMELOGIC, "tagstart is %d, tagend is %d\n", tagstart, tagend);
-
-			// Sag is adjusted by how close you are to the center
-			dist = ((ENDCONTROLTAG - STARTCONTROLTAG))/2 - abs(bridge->sector->tag - midpoint);
-
-//			CONS_Debug(DBG_GAMELOGIC, "Dist is %d\n", dist);
-			LOWCEILINGHEIGHT -= (SAGAMT) * dist;
-			LOWFLOORHEIGHT -= (SAGAMT) * dist;
-		}
-
-		// go down
-		if (bridge->sector->ceilingheight <= LOWCEILINGHEIGHT)
-		{
-			bridge->sector->floorheight = LOWCEILINGHEIGHT - (bridge->sector->ceilingheight - bridge->sector->floorheight);
-			bridge->sector->ceilingheight = LOWCEILINGHEIGHT;
-			bridge->sector->ceilspeed = 0;
-			bridge->sector->floorspeed = 0;
-			goto dorest;
-		}
-
-		DIRECTION = -1;
-		ceilingdestination = LOWCEILINGHEIGHT;
-		floordestination = LOWFLOORHEIGHT;
-
-		if ((bridge->sector->ceilingheight - LOWCEILINGHEIGHT)
-			< (ORIGCEILINGHEIGHT - bridge->sector->ceilingheight))
-		{
-			fixed_t origspeed = CURSPEED;
-
-			// Slow down as you get closer to the bottom
-			CURSPEED = FixedMul(CURSPEED,FixedDiv(bridge->sector->ceilingheight - LOWCEILINGHEIGHT, (ORIGCEILINGHEIGHT - LOWCEILINGHEIGHT)>>5));
-
-			if (CURSPEED <= origspeed/16)
-				CURSPEED = origspeed/16;
-			else if (CURSPEED > origspeed)
-				CURSPEED = origspeed;
-		}
-		else
-		{
-			fixed_t origspeed = CURSPEED;
-			// Slow down as you get closer to the top
-			CURSPEED = FixedMul(CURSPEED,FixedDiv(ORIGCEILINGHEIGHT - bridge->sector->ceilingheight, (ORIGCEILINGHEIGHT - LOWCEILINGHEIGHT)>>5));
-
-			if (CURSPEED <= origspeed/16)
-				CURSPEED = origspeed/16;
-			else if (CURSPEED > origspeed)
-				CURSPEED = origspeed;
-		}
-
-//		CONS_Debug(DBG_GAMELOGIC, "Curspeed is %d\n", CURSPEED>>FRACBITS);
-
-		res = T_MovePlane
-		(
-			bridge->sector,         // sector
-			CURSPEED,          // speed
-			ceilingdestination, // dest
-			0,                        // crush
-			1,                        // floor or ceiling (1 for ceiling)
-			DIRECTION       // direction
-		);
-
-		if (res == ok || res == pastdest)
-			T_MovePlane
-			(
-				bridge->sector,           // sector
-				CURSPEED,            // speed
-				floordestination, // dest
-				0,                          // crush
-				0,                          // floor or ceiling (0 for floor)
-				DIRECTION         // direction
-			);
-
-		bridge->sector->ceilspeed = 42;
-		bridge->sector->floorspeed = CURSPEED*DIRECTION;
-
-	dorest:
-		// Adjust joined sector heights
-		{
-			sector_t *sourcesec = bridge->sector;
-
-			INT32 divisor = sourcesec->tag - ENDTAG + 1;
-			fixed_t heightdiff = ORIGCEILINGHEIGHT - sourcesec->ceilingheight;
-			fixed_t interval;
-			INT32 plusplusme = 0;
-
-			if (divisor > 0)
-			{
-				interval = heightdiff/divisor;
-
-//				CONS_Debug(DBG_GAMELOGIC, "interval is %d\n", interval>>FRACBITS);
-
-				// TODO: Use T_MovePlane
-
-				for (j = (INT16)(ENDTAG+1); j <= sourcesec->tag; j++, plusplusme++)
-				{
-					for (i = -1; (i = P_FindSectorFromTag(j, i)) >= 0 ;)
-					{
-						if (sectors[i].ceilingheight >= sourcesec->ceilingheight)
-						{
-							sectors[i].ceilingheight = ORIGCEILINGHEIGHT - (interval*plusplusme);
-							sectors[i].floorheight = ORIGFLOORHEIGHT - (interval*plusplusme);
-						}
-						else // Do the regular rise
-						{
-							bridge->sector = &sectors[i];
-
-							CURSPEED = BASESPEED/2;
-
-							// rise back up
-							if (bridge->sector->ceilingheight >= ORIGCEILINGHEIGHT)
-							{
-								bridge->sector->floorheight = ORIGCEILINGHEIGHT - (bridge->sector->ceilingheight - bridge->sector->floorheight);
-								bridge->sector->ceilingheight = ORIGCEILINGHEIGHT;
-								bridge->sector->ceilspeed = 0;
-								bridge->sector->floorspeed = 0;
-								continue;
-							}
-
-							DIRECTION = 1;
-							ceilingdestination = ORIGCEILINGHEIGHT;
-							floordestination = ORIGFLOORHEIGHT;
-
-//							CONS_Debug(DBG_GAMELOGIC, "ceildest: %d, floordest: %d\n", ceilingdestination>>FRACBITS, floordestination>>FRACBITS);
-
-							if ((bridge->sector->ceilingheight - LOWCEILINGHEIGHT)
-								< (ORIGCEILINGHEIGHT - bridge->sector->ceilingheight))
-							{
-								fixed_t origspeed = CURSPEED;
-
-								// Slow down as you get closer to the bottom
-								CURSPEED = FixedMul(CURSPEED,FixedDiv(bridge->sector->ceilingheight - LOWCEILINGHEIGHT, (ORIGCEILINGHEIGHT - LOWCEILINGHEIGHT)>>5));
-
-								if (CURSPEED <= origspeed/16)
-									CURSPEED = origspeed/16;
-								else if (CURSPEED > origspeed)
-									CURSPEED = origspeed;
-							}
-							else
-							{
-								fixed_t origspeed = CURSPEED;
-								// Slow down as you get closer to the top
-								CURSPEED = FixedMul(CURSPEED,FixedDiv(ORIGCEILINGHEIGHT - bridge->sector->ceilingheight, (ORIGCEILINGHEIGHT - LOWCEILINGHEIGHT)>>5));
-
-								if (CURSPEED <= origspeed/16)
-									CURSPEED = origspeed/16;
-								else if (CURSPEED > origspeed)
-									CURSPEED = origspeed;
-							}
-
-							res = T_MovePlane
-							(
-								bridge->sector,         // sector
-								CURSPEED,          // speed
-								ceilingdestination, // dest
-								0,                        // crush
-								1,                        // floor or ceiling (1 for ceiling)
-								DIRECTION       // direction
-							);
-
-							if (res == ok || res == pastdest)
-								T_MovePlane
-								(
-									bridge->sector,           // sector
-									CURSPEED,            // speed
-									floordestination, // dest
-									0,                          // crush
-									0,                          // floor or ceiling (0 for floor)
-									DIRECTION         // direction
-								);
-
-							bridge->sector->ceilspeed = 42;
-							bridge->sector->floorspeed = CURSPEED*DIRECTION;
-						}
-					}
-				}
-			}
-
-			// Now the other side
-			divisor = ENDTAG + (ENDTAG-STARTTAG) + 1;
-			divisor -= sourcesec->tag;
-
-			if (divisor > 0)
-			{
-				interval = heightdiff/divisor;
-				plusplusme = 0;
-
-//				CONS_Debug(DBG_GAMELOGIC, "interval2 is %d\n", interval>>FRACBITS);
-
-				for (j = (INT16)(sourcesec->tag+1); j <= ENDTAG + (ENDTAG-STARTTAG) + 1; j++, plusplusme++)
-				{
-					for (i = -1; (i = P_FindSectorFromTag(j, i)) >= 0 ;)
-					{
-						if (sectors[i].ceilingheight >= sourcesec->ceilingheight)
-						{
-							sectors[i].ceilingheight = sourcesec->ceilingheight + (interval*plusplusme);
-							sectors[i].floorheight = sourcesec->floorheight + (interval*plusplusme);
-						}
-						else // Do the regular rise
-						{
-							bridge->sector = &sectors[i];
-
-							CURSPEED = BASESPEED/2;
-
-							// rise back up
-							if (bridge->sector->ceilingheight >= ORIGCEILINGHEIGHT)
-							{
-								bridge->sector->floorheight = ORIGCEILINGHEIGHT - (bridge->sector->ceilingheight - bridge->sector->floorheight);
-								bridge->sector->ceilingheight = ORIGCEILINGHEIGHT;
-								bridge->sector->ceilspeed = 0;
-								bridge->sector->floorspeed = 0;
-								continue;
-							}
-
-							DIRECTION = 1;
-							ceilingdestination = ORIGCEILINGHEIGHT;
-							floordestination = ORIGFLOORHEIGHT;
-
-//							CONS_Debug(DBG_GAMELOGIC, "ceildest: %d, floordest: %d\n", ceilingdestination>>FRACBITS, floordestination>>FRACBITS);
-
-							if ((bridge->sector->ceilingheight - LOWCEILINGHEIGHT)
-								< (ORIGCEILINGHEIGHT - bridge->sector->ceilingheight))
-							{
-								fixed_t origspeed = CURSPEED;
-
-								// Slow down as you get closer to the bottom
-								CURSPEED = FixedMul(CURSPEED,FixedDiv(bridge->sector->ceilingheight - LOWCEILINGHEIGHT, (ORIGCEILINGHEIGHT - LOWCEILINGHEIGHT)>>5));
-
-								if (CURSPEED <= origspeed/16)
-									CURSPEED = origspeed/16;
-								else if (CURSPEED > origspeed)
-									CURSPEED = origspeed;
-							}
-							else
-							{
-								fixed_t origspeed = CURSPEED;
-								// Slow down as you get closer to the top
-								CURSPEED = FixedMul(CURSPEED,FixedDiv(ORIGCEILINGHEIGHT - bridge->sector->ceilingheight, (ORIGCEILINGHEIGHT - LOWCEILINGHEIGHT)>>5));
-
-								if (CURSPEED <= origspeed/16)
-									CURSPEED = origspeed/16;
-								else if (CURSPEED > origspeed)
-									CURSPEED = origspeed;
-							}
-
-							res = T_MovePlane
-							(
-								bridge->sector,         // sector
-								CURSPEED,          // speed
-								ceilingdestination, // dest
-								0,                        // crush
-								1,                        // floor or ceiling (1 for ceiling)
-								DIRECTION       // direction
-							);
-
-							if (res == ok || res == pastdest)
-								T_MovePlane
-								(
-									bridge->sector,           // sector
-									CURSPEED,            // speed
-									floordestination, // dest
-									0,                          // crush
-									0,                          // floor or ceiling (0 for floor)
-									DIRECTION         // direction
-								);
-
-							bridge->sector->ceilspeed = 42;
-							bridge->sector->floorspeed = CURSPEED*DIRECTION;
-						}
-					}
-				}
-			}
-		}
-
-	//	for (i = -1; (i = P_FindSectorFromTag(bridge->sourceline->tag, i)) >= 0 ;)
-	//		P_RecalcPrecipInSector(&sectors[i]);
-	}
-	else
-	{
-		// Iterate control sectors
-		for (j = (INT16)(ENDTAG+1); j <= (ENDTAG+(ENDTAG-STARTTAG)+1); j++)
-		{
-			for (i = -1; (i = P_FindSectorFromTag(j, i)) >= 0 ;)
-			{
-				bridge->sector = &sectors[i];
-
-				CURSPEED = BASESPEED/2;
-
-				// rise back up
-				if (bridge->sector->ceilingheight >= ORIGCEILINGHEIGHT)
-				{
-					bridge->sector->floorheight = ORIGCEILINGHEIGHT - (bridge->sector->ceilingheight - bridge->sector->floorheight);
-					bridge->sector->ceilingheight = ORIGCEILINGHEIGHT;
-					bridge->sector->ceilspeed = 0;
-					bridge->sector->floorspeed = 0;
-					continue;
-				}
-
-				DIRECTION = 1;
-				ceilingdestination = ORIGCEILINGHEIGHT;
-				floordestination = ORIGFLOORHEIGHT;
-
-//				CONS_Debug(DBG_GAMELOGIC, "ceildest: %d, floordest: %d\n", ceilingdestination>>FRACBITS, floordestination>>FRACBITS);
+	secnum = P_FindSectorFromTag(floater->tag, -1);
+	if (secnum <= 0)
+		return;
+	actionsector = &sectors[secnum];
 
-				if ((bridge->sector->ceilingheight - LOWCEILINGHEIGHT)
-					< (ORIGCEILINGHEIGHT - bridge->sector->ceilingheight))
-				{
-					fixed_t origspeed = CURSPEED;
+	cheeseheight = (floater->sector->ceilingheight + floater->sector->floorheight)>>1;
 
-					// Slow down as you get closer to the bottom
-					CURSPEED = FixedMul(CURSPEED,FixedDiv(bridge->sector->ceilingheight - LOWCEILINGHEIGHT, (ORIGCEILINGHEIGHT - LOWCEILINGHEIGHT)>>5));
+	//boolean floatanyway = false; // Ignore the crumblestate setting.
+	waterheight = P_SectorCheckWater(actionsector, floater->sector); // find the highest suitable water block around
 
-					if (CURSPEED <= origspeed/16)
-						CURSPEED = origspeed/16;
-					else if (CURSPEED > origspeed)
-						CURSPEED = origspeed;
-				}
-				else
-				{
-					fixed_t origspeed = CURSPEED;
-					// Slow down as you get closer to the top
-					CURSPEED = FixedMul(CURSPEED,FixedDiv(ORIGCEILINGHEIGHT - bridge->sector->ceilingheight, (ORIGCEILINGHEIGHT - LOWCEILINGHEIGHT)>>5));
-
-					if (CURSPEED <= origspeed/16)
-						CURSPEED = origspeed/16;
-					else if (CURSPEED > origspeed)
-						CURSPEED = origspeed;
-				}
+	if (waterheight == cheeseheight) // same height, no floating needed
+		return;
 
-				res = T_MovePlane
-				(
-					bridge->sector,         // sector
-					CURSPEED,          // speed
-					ceilingdestination, // dest
-					0,                        // crush
-					1,                        // floor or ceiling (1 for ceiling)
-					DIRECTION       // direction
-				);
+	if (floater->sector->floorheight == actionsector->floorheight && waterheight < cheeseheight) // too low
+		return;
 
-				if (res == ok || res == pastdest)
-					T_MovePlane
-					(
-						bridge->sector,           // sector
-						CURSPEED,            // speed
-						floordestination, // dest
-						0,                          // crush
-						0,                          // floor or ceiling (0 for floor)
-						DIRECTION         // direction
-					);
-
-				bridge->sector->ceilspeed = 42;
-				bridge->sector->floorspeed = CURSPEED*DIRECTION;
-			}
-		}
-		// Update precip
-	}
+	if (floater->sector->ceilingheight == actionsector->ceilingheight && waterheight > cheeseheight) // too high
+		return;
 
-#undef SAGAMT
-#undef LOWFLOORHEIGHT
-#undef LOWCEILINGHEIGHT
-#undef ORIGFLOORHEIGHT
-#undef ORIGCEILINGHEIGHT
-#undef BASESPEED
-#undef CURSPEED
-#undef STARTTAG
-#undef ENDTAG
-#undef DIRECTION
+	// we have something to float in! Or we're for some reason above the ground, let's fall anyway
+	if (floater->sector->crumblestate == CRUMBLE_NONE || floater->sector->crumblestate >= CRUMBLE_FALL/* || floatanyway*/)
+		EV_BounceSector(floater->sector, FRACUNIT, floater->sourceline);
 }
 
 static mobj_t *SearchMarioNode(msecnode_t *node)
@@ -1812,204 +1071,204 @@ static mobj_t *SearchMarioNode(msecnode_t *node)
 	return thing;
 }
 
-void T_MarioBlockChecker(levelspecthink_t *block)
+void T_MarioBlockChecker(mariocheck_t *block)
 {
 	line_t *masterline = block->sourceline;
-	if (block->vars[2] == 1) // Don't update the textures when the block's being bumped upwards.
-		return;
 	if (SearchMarioNode(block->sector->touching_thinglist))
 	{
 		sides[masterline->sidenum[0]].midtexture = sides[masterline->sidenum[0]].bottomtexture; // Update textures
 		if (masterline->backsector)
-		{
 			block->sector->ceilingpic = block->sector->floorpic = masterline->backsector->ceilingpic; // Update flats to be backside's ceiling
-		}
 	}
 	else
 	{
 		sides[masterline->sidenum[0]].midtexture = sides[masterline->sidenum[0]].toptexture;
 		if (masterline->backsector)
-		{
 			block->sector->ceilingpic = block->sector->floorpic = masterline->backsector->floorpic; // Update flats to be backside's floor
-		}
 	}
 }
 
+static boolean P_IsPlayerValid(size_t playernum)
+{
+	if (!playeringame[playernum])
+		return false;
+
+	if (!players[playernum].mo)
+		return false;
+
+	if (players[playernum].mo->health <= 0)
+		return false;
+
+	if (players[playernum].spectator)
+		return false;
+
+	return true;
+}
+
 // This is the Thwomp's 'brain'. It looks around for players nearby, and if
 // it finds any, **SMASH**!!! Muahahhaa....
-void T_ThwompSector(levelspecthink_t *thwomp)
+void T_ThwompSector(thwomp_t *thwomp)
 {
-#define speed vars[1]
-#define direction vars[2]
-#define distance vars[3]
-#define floorwasheight vars[4]
-#define ceilingwasheight vars[5]
 	fixed_t thwompx, thwompy;
 	sector_t *actionsector;
 	ffloor_t *rover = NULL;
 	INT32 secnum;
+	fixed_t speed;
 
 	// If you just crashed down, wait a second before coming back up.
-	if (--thwomp->distance > 0)
-	{
-		sides[thwomp->sourceline->sidenum[0]].midtexture = sides[thwomp->sourceline->sidenum[0]].bottomtexture;
+	if (--thwomp->delay > 0)
 		return;
-	}
 
 	// Just find the first sector with the tag.
 	// Doesn't work with multiple sectors that have different floor/ceiling heights.
-	secnum = P_FindSectorFromTag((INT16)thwomp->vars[0], -1);
+	secnum = P_FindSectorFromTag(thwomp->tag, -1);
+
+	if (secnum <= 0)
+		return; // Bad bad bad!
+
+	actionsector = &sectors[secnum];
 
-	if (secnum > 0)
+	// Look for thwomp FOF
+	for (rover = actionsector->ffloors; rover; rover = rover->next)
 	{
-		actionsector = &sectors[secnum];
+		if (rover->master == thwomp->sourceline)
+			break;
+	}
+
+	if (!rover)
+		return; // Didn't find any FOFs, so bail out
+
+	thwompx = actionsector->soundorg.x;
+	thwompy = actionsector->soundorg.y;
 
-		// Look for thwomp FFloor
-		for (rover = actionsector->ffloors; rover; rover = rover->next)
+	if (thwomp->direction == 0) // Not going anywhere, so look for players.
+	{
+		if (rover->flags & FF_EXISTS)
 		{
-			if (rover->master == thwomp->sourceline)
+			UINT8 i;
+			// scan the players to find victims!
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!P_IsPlayerValid(i))
+					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;
+
+				thwomp->direction = -1;
 				break;
+			}
 		}
+
+		thwomp->sector->ceilspeed = 0;
+		thwomp->sector->floorspeed = 0;
 	}
 	else
-		return; // Bad bad bad!
-
-	thwompx = actionsector->soundorg.x;
-	thwompy = actionsector->soundorg.y;
-
-	if (thwomp->direction > 0) // Moving back up..
 	{
 		result_e res = 0;
 
-		// Set the texture from the lower one (normal)
-		sides[thwomp->sourceline->sidenum[0]].midtexture = sides[thwomp->sourceline->sidenum[0]].bottomtexture;
-		/// \note this should only have to be done once, but is already done repeatedly, above
-
-		if (thwomp->sourceline->flags & ML_EFFECT5)
-			thwomp->speed = thwomp->sourceline->dx/8;
-		else
-			thwomp->speed = 2*FRACUNIT;
+		if (thwomp->direction > 0) //Moving back up..
+		{
+			// Set the texture from the lower one (normal)
+			sides[thwomp->sourceline->sidenum[0]].midtexture = sides[thwomp->sourceline->sidenum[0]].bottomtexture;
 
-		res = T_MovePlane
-		(
-			thwomp->sector,         // sector
-			thwomp->speed,          // speed
-			thwomp->floorwasheight, // dest
-			0,                      // crush
-			0,                      // floor or ceiling (0 for floor)
-			thwomp->direction       // direction
-		);
+			speed = thwomp->retractspeed;
 
-		if (res == ok || res == pastdest)
-			T_MovePlane
+			res = T_MovePlane
 			(
 				thwomp->sector,           // sector
-				thwomp->speed,            // speed
-				thwomp->ceilingwasheight, // dest
-				0,                        // crush
-				1,                        // floor or ceiling (1 for ceiling)
+				speed,                    // speed
+				thwomp->floorstartheight, // dest
+				false,                    // crush
+				false,                    // ceiling?
 				thwomp->direction         // direction
 			);
 
-		if (res == pastdest)
-			thwomp->direction = 0; // stop moving
-
-		thwomp->sector->ceilspeed = 42;
-		thwomp->sector->floorspeed = thwomp->speed*thwomp->direction;
-	}
-	else if (thwomp->direction < 0) // Crashing down!
-	{
-		result_e res = 0;
-
-		// Set the texture from the upper one (angry)
-		sides[thwomp->sourceline->sidenum[0]].midtexture = sides[thwomp->sourceline->sidenum[0]].toptexture;
+			if (res == ok || res == pastdest)
+				T_MovePlane
+				(
+					thwomp->sector,             // sector
+					speed,                      // speed
+					thwomp->ceilingstartheight, // dest
+					false,                      // crush
+					true,                       // ceiling?
+					thwomp->direction           // direction
+				);
 
-		if (thwomp->sourceline->flags & ML_EFFECT5)
-			thwomp->speed = thwomp->sourceline->dy/8;
-		else
-			thwomp->speed = 10*FRACUNIT;
+			if (res == pastdest)
+				thwomp->direction = 0; // stop moving
+			}
+		else // Crashing down!
+		{
+			// Set the texture from the upper one (angry)
+			sides[thwomp->sourceline->sidenum[0]].midtexture = sides[thwomp->sourceline->sidenum[0]].toptexture;
 
-		res = T_MovePlane
-		(
-			thwomp->sector,   // sector
-			thwomp->speed,    // speed
-			P_FloorzAtPos(thwompx, thwompy, thwomp->sector->floorheight,
-				thwomp->sector->ceilingheight - thwomp->sector->floorheight), // dest
-			0,                  // crush
-			0,                  // floor or ceiling (0 for floor)
-			thwomp->direction // direction
-		);
+			speed = thwomp->crushspeed;
 
-		if (res == ok || res == pastdest)
-			T_MovePlane
+			res = T_MovePlane
 			(
 				thwomp->sector,   // sector
-				thwomp->speed,    // speed
+				speed,            // speed
 				P_FloorzAtPos(thwompx, thwompy, thwomp->sector->floorheight,
-					thwomp->sector->ceilingheight
-					- (thwomp->sector->floorheight + thwomp->speed))
-					+ (thwomp->sector->ceilingheight
-					- (thwomp->sector->floorheight + thwomp->speed/2)), // dest
-				0,                  // crush
-				1,                  // floor or ceiling (1 for ceiling)
+					thwomp->sector->ceilingheight - thwomp->sector->floorheight), // dest
+				false,              // crush
+				false,              // ceiling?
 				thwomp->direction // direction
 			);
 
-		if (res == pastdest)
-		{
-			mobj_t *mp = (void *)&actionsector->soundorg;
+			if (res == ok || res == pastdest)
+				T_MovePlane
+				(
+					thwomp->sector,   // sector
+					speed,            // speed
+					P_FloorzAtPos(thwompx, thwompy, thwomp->sector->floorheight,
+						thwomp->sector->ceilingheight
+						- (thwomp->sector->floorheight + speed))
+						+ (thwomp->sector->ceilingheight
+						- (thwomp->sector->floorheight + speed/2)), // dest
+					false,             // crush
+					true,              // ceiling?
+					thwomp->direction // direction
+				);
 
-			if (!rover || (rover->flags & FF_EXISTS))
+			if (res == pastdest)
 			{
-				if (thwomp->sourceline->flags & ML_EFFECT4)
-					S_StartSound(mp, sides[thwomp->sourceline->sidenum[0]].textureoffset>>FRACBITS);
-				else
-					S_StartSound(mp, sfx_thwomp);
-			}
+				if (rover->flags & FF_EXISTS)
+					S_StartSound((void *)&actionsector->soundorg, thwomp->sound);
 
-			thwomp->direction = 1; // start heading back up
-			thwomp->distance = TICRATE; // but only after a small delay
+				thwomp->direction = 1; // start heading back up
+				thwomp->delay = TICRATE; // but only after a small delay
+			}
 		}
 
 		thwomp->sector->ceilspeed = 42;
-		thwomp->sector->floorspeed = thwomp->speed*thwomp->direction;
+		thwomp->sector->floorspeed = speed*thwomp->direction;
 	}
-	else // Not going anywhere, so look for players.
+
+	P_RecalcPrecipInSector(actionsector);
+}
+
+static boolean T_SectorHasEnemies(sector_t *sec)
+{
+	msecnode_t *node = sec->touching_thinglist; // things touching this sector
+	mobj_t *mo;
+	while (node)
 	{
-		if (!rover || (rover->flags & FF_EXISTS))
-		{
-			UINT8 i;
-			// scan the players to find victims!
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				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 = node->m_thing;
 
-				thwomp->direction = -1;
-				break;
-			}
-		}
+		if ((mo->flags & (MF_ENEMY|MF_BOSS))
+			&& mo->health > 0
+			&& mo->z < sec->ceilingheight
+			&& mo->z + mo->height > sec->floorheight)
+			return true;
 
-		thwomp->sector->ceilspeed = 0;
-		thwomp->sector->floorspeed = 0;
+		node = node->m_thinglist_next;
 	}
 
-	P_RecalcPrecipInSector(actionsector);
-#undef speed
-#undef direction
-#undef distance
-#undef floorwasheight
-#undef ceilingwasheight
+	return false;
 }
 
 //
@@ -2018,18 +1277,14 @@ void T_ThwompSector(levelspecthink_t *thwomp)
 // Runs a linedef exec when no more MF_ENEMY/MF_BOSS objects with health are in the area
 // \sa P_AddNoEnemiesThinker
 //
-void T_NoEnemiesSector(levelspecthink_t *nobaddies)
+void T_NoEnemiesSector(noenemies_t *nobaddies)
 {
 	size_t i;
-	fixed_t upperbound, lowerbound;
 	sector_t *sec = NULL;
-	sector_t *targetsec = NULL;
 	INT32 secnum = -1;
-	msecnode_t *node;
-	mobj_t *thing;
 	boolean FOFsector = false;
 
-	while ((secnum = P_FindSectorFromLineTag(nobaddies->sourceline, secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(nobaddies->sourceline->tag, secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -2045,42 +1300,15 @@ void T_NoEnemiesSector(levelspecthink_t *nobaddies)
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromLineTag(sec->lines[i], targetsecnum)) >= 0)
-			{
-				targetsec = &sectors[targetsecnum];
-
-				upperbound = targetsec->ceilingheight;
-				lowerbound = targetsec->floorheight;
-				node = targetsec->touching_thinglist; // things touching this sector
-				while (node)
-				{
-					thing = node->m_thing;
-
-					if ((thing->flags & (MF_ENEMY|MF_BOSS)) && thing->health > 0
-					&& thing->z < upperbound && thing->z+thing->height > lowerbound)
-						return;
-
-					node = node->m_thinglist_next;
-				}
-			}
-		}
-
-		if (!FOFsector)
-		{
-			upperbound = sec->ceilingheight;
-			lowerbound = sec->floorheight;
-			node = sec->touching_thinglist; // things touching this sector
-			while (node)
+			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
 			{
-				thing = node->m_thing;
-
-				if ((thing->flags & (MF_ENEMY|MF_BOSS)) && thing->health > 0
-				&& thing->z < upperbound && thing->z+thing->height > lowerbound)
+				if (T_SectorHasEnemies(&sectors[targetsecnum]))
 					return;
-
-				node = node->m_thinglist_next;
 			}
 		}
+
+		if (!FOFsector && T_SectorHasEnemies(sec))
+			return;
 	}
 
 	CONS_Debug(DBG_GAMELOGIC, "Running no-more-enemies exec with tag of %d\n", nobaddies->sourceline->tag);
@@ -2117,30 +1345,23 @@ static boolean P_IsObjectOnRealGround(mobj_t *mo, sector_t *sec)
 	return false;
 }
 
-//
-// P_HavePlayersEnteredArea
-//
-// Helper function for T_EachTimeThinker
-//
-static INT32 P_HavePlayersEnteredArea(boolean *curPlayers, boolean *oldPlayers, boolean inAndOut)
+static boolean P_IsMobjTouchingSector(mobj_t *mo, sector_t *sec)
 {
-	INT32 i;
+	msecnode_t *node;
 
-	// Easy check... nothing has changed
-	if (!memcmp(curPlayers, oldPlayers, sizeof(boolean)*MAXPLAYERS))
-		return -1;
+	if (mo->subsector->sector == sec)
+		return true;
 
-	// Otherwise, we have to check if any new players have entered
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (inAndOut && !curPlayers[i] && oldPlayers[i])
-			return i;
+	if (!(sec->flags & SF_TRIGGERSPECIAL_TOUCH))
+		return false;
 
-		if (curPlayers[i] && !oldPlayers[i])
-			return i;
+	for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+	{
+		if (node->m_sector == sec)
+			return true;
 	}
 
-	return -1;
+	return false;
 }
 
 //
@@ -2151,47 +1372,30 @@ static INT32 P_HavePlayersEnteredArea(boolean *curPlayers, boolean *oldPlayers,
 //
 // \sa P_AddEachTimeThinker
 //
-void T_EachTimeThinker(levelspecthink_t *eachtime)
+void T_EachTimeThinker(eachtime_t *eachtime)
 {
 	size_t i, j;
 	sector_t *sec = NULL;
 	sector_t *targetsec = NULL;
-	//sector_t *usesec = NULL;
 	INT32 secnum = -1;
-	INT32 affectPlayer = 0;
 	boolean oldPlayersInArea[MAXPLAYERS];
-	boolean playersInArea[MAXPLAYERS];
 	boolean oldPlayersOnArea[MAXPLAYERS];
-	boolean playersOnArea[MAXPLAYERS];
 	boolean *oldPlayersArea;
 	boolean *playersArea;
 	boolean FOFsector = false;
-	boolean inAndOut = false;
 	boolean floortouch = false;
 	fixed_t bottomheight, topheight;
-	msecnode_t *node;
 	ffloor_t *rover;
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (i & 1)
-		{
-			oldPlayersInArea[i] = eachtime->vars[i/2] & 65535;
-			oldPlayersOnArea[i] = eachtime->var2s[i/2] & 65535;
-			eachtime->vars[i/2] = 0;
-			eachtime->var2s[i/2] = 0;
-		}
-		else
-		{
-			oldPlayersInArea[i] = eachtime->vars[i/2] >> 16;
-			oldPlayersOnArea[i] = eachtime->var2s[i/2] >> 16;
-		}
-
-		playersInArea[i] = false;
-		playersOnArea[i] = false;
+		oldPlayersInArea[i] = eachtime->playersInArea[i];
+		oldPlayersOnArea[i] = eachtime->playersOnArea[i];
+		eachtime->playersInArea[i] = false;
+		eachtime->playersOnArea[i] = false;
 	}
 
-	while ((secnum = P_FindSectorFromLineTag(eachtime->sourceline, secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(eachtime->sourceline->tag, secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -2214,7 +1418,7 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromLineTag(sec->lines[i], targetsecnum)) >= 0)
+			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
 			{
 				targetsec = &sectors[targetsecnum];
 
@@ -2233,35 +1437,10 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 
 				for (j = 0; j < MAXPLAYERS; j++)
 				{
-					if (!playeringame[j])
-						continue;
-
-					if (!players[j].mo)
+					if (!P_IsPlayerValid(j))
 						continue;
 
-					if (players[j].mo->health <= 0)
-						continue;
-
-					if ((netgame || multiplayer) && players[j].spectator)
-						continue;
-
-					if (players[j].mo->subsector->sector == targetsec)
-						;
-					else if (sec->flags & SF_TRIGGERSPECIAL_TOUCH)
-					{
-						boolean insector = false;
-						for (node = players[j].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
-						{
-							if (node->m_sector == targetsec)
-							{
-								insector = true;
-								break;
-							}
-						}
-						if (!insector)
-							continue;
-					}
-					else
+					if (!P_IsMobjTouchingSector(players[j].mo, targetsec))
 						continue;
 
 					topheight = P_GetSpecialTopZ(players[j].mo, sec, targetsec);
@@ -2273,24 +1452,10 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 					if (players[j].mo->z + players[j].mo->height < bottomheight)
 						continue;
 
-					if (floortouch == true && P_IsObjectOnGroundIn(players[j].mo, targetsec))
-					{
-						if (j & 1)
-							eachtime->var2s[j/2] |= 1;
-						else
-							eachtime->var2s[j/2] |= 1 << 16;
-
-						playersOnArea[j] = true;
-					}
+					if (floortouch && P_IsObjectOnGroundIn(players[j].mo, targetsec))
+						eachtime->playersOnArea[j] = true;
 					else
-					{
-						if (j & 1)
-							eachtime->vars[j/2] |= 1;
-						else
-							eachtime->vars[j/2] |= 1 << 16;
-
-						playersInArea[j] = true;
-					}
+						eachtime->playersInArea[j] = true;
 				}
 			}
 		}
@@ -2299,102 +1464,61 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 		{
 			for (i = 0; i < MAXPLAYERS; i++)
 			{
-				if (!playeringame[i])
-					continue;
-
-				if (!players[i].mo)
-					continue;
-
-				if (players[i].mo->health <= 0)
-					continue;
-
-				if ((netgame || multiplayer) && players[i].spectator)
+				if (!P_IsPlayerValid(i))
 					continue;
 
-				if (players[i].mo->subsector->sector == sec)
-					;
-				else if (sec->flags & SF_TRIGGERSPECIAL_TOUCH)
-				{
-					boolean insector = false;
-					for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
-					{
-						if (node->m_sector == sec)
-						{
-							insector = true;
-							break;
-						}
-					}
-					if (!insector)
-						continue;
-				}
-				else
+				if (!P_IsMobjTouchingSector(players[i].mo, sec))
 					continue;
 
 				if (!(players[i].mo->subsector->sector == sec
 					|| P_PlayerTouchingSectorSpecial(&players[i], 2, (GETSECSPECIAL(sec->special, 2))) == sec))
 					continue;
 
-				if (floortouch == true && P_IsObjectOnRealGround(players[i].mo, sec))
-				{
-					if (i & 1)
-						eachtime->var2s[i/2] |= 1;
-					else
-						eachtime->var2s[i/2] |= 1 << 16;
-
-					playersOnArea[i] = true;
-				}
+				if (floortouch && P_IsObjectOnRealGround(players[i].mo, sec))
+					eachtime->playersOnArea[i] = true;
 				else
-				{
-					if (i & 1)
-						eachtime->vars[i/2] |= 1;
-					else
-						eachtime->vars[i/2] |= 1 << 16;
-
-					playersInArea[i] = true;
-				}
+					eachtime->playersInArea[i] = true;
 			}
 		}
 	}
 
-	if ((eachtime->sourceline->flags & ML_BOUNCY) == ML_BOUNCY)
-		inAndOut = true;
-
 	// Check if a new player entered.
 	// If not, check if a player hit the floor.
 	// If either condition is true, execute.
-	if (floortouch == true)
+	if (floortouch)
 	{
-		playersArea = playersOnArea;
+		playersArea = eachtime->playersOnArea;
 		oldPlayersArea = oldPlayersOnArea;
 	}
 	else
 	{
-		playersArea = playersInArea;
+		playersArea = eachtime->playersInArea;
 		oldPlayersArea = oldPlayersInArea;
 	}
 
-	while ((affectPlayer = P_HavePlayersEnteredArea(playersArea, oldPlayersArea, inAndOut)) != -1)
+	// Easy check... nothing has changed
+	if (!memcmp(playersArea, oldPlayersArea, sizeof(boolean)*MAXPLAYERS))
+		return;
+
+	// If sector has an "all players" trigger type, all players need to be in area
+	if (GETSECSPECIAL(sec->special, 2) == 2 || GETSECSPECIAL(sec->special, 2) == 3)
 	{
-		if (GETSECSPECIAL(sec->special, 2) == 2 || GETSECSPECIAL(sec->special, 2) == 3)
+		for (i = 0; i < MAXPLAYERS; i++)
 		{
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i])
-					continue;
-
-				if (!players[i].mo)
-					continue;
-
-				if (players[i].mo->health <= 0)
-					continue;
+			if (P_IsPlayerValid(i) && playersArea[i])
+				continue;
+		}
+	}
 
-				if ((netgame || multiplayer) && players[i].spectator)
-					continue;
+	// Trigger for every player who has entered (and exited, if triggerOnExit)
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playersArea[i] == oldPlayersArea[i])
+			continue;
 
-				if (!playersArea[i])
-					return;
-			}
-		}
+		// If player has just left, check if still valid
+		if (!playersArea[i] && (!eachtime->triggerOnExit || !P_IsPlayerValid(i)))
+			continue;
 
 		CONS_Debug(DBG_GAMELOGIC, "Trying to activate each time executor with tag %d\n", eachtime->sourceline->tag);
 
@@ -2402,12 +1526,10 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 		// No more stupid hacks involving changing eachtime->sourceline's tag or special or whatever!
 		// This should now run ONLY the stuff for eachtime->sourceline itself, instead of all trigger linedefs sharing the same tag.
 		// Makes much more sense doing it this way, honestly.
-		P_RunTriggerLinedef(eachtime->sourceline, players[affectPlayer].mo, sec);
+		P_RunTriggerLinedef(eachtime->sourceline, players[i].mo, sec);
 
 		if (!eachtime->sourceline->special) // this happens only for "Trigger on X calls" linedefs
 			P_RemoveThinker(&eachtime->thinker);
-
-		oldPlayersArea[affectPlayer]=playersArea[affectPlayer];
 	}
 }
 
@@ -2417,20 +1539,24 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 // Rises up to its topmost position when a
 // player steps on it. Lowers otherwise.
 //
-void T_RaiseSector(levelspecthink_t *raise)
+void T_RaiseSector(raise_t *raise)
 {
 	msecnode_t *node;
 	mobj_t *thing;
 	sector_t *sector;
 	INT32 i;
 	boolean playeronme = false, active = false;
+	boolean moveUp;
 	fixed_t ceilingdestination, floordestination;
+	fixed_t speed, origspeed;
+	fixed_t distToNearestEndpoint;
+	INT32 direction;
 	result_e res = 0;
 
-	if (raise->sector->crumblestate >= 3 || raise->sector->ceilingdata)
+	if (raise->sector->crumblestate >= CRUMBLE_FALL || raise->sector->ceilingdata)
 		return;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->sourceline->tag, i)) >= 0 ;)
+	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
 	{
 		sector = &sectors[i];
 
@@ -2447,7 +1573,7 @@ void T_RaiseSector(levelspecthink_t *raise)
 				continue;
 
 			// Option to require spindashing.
-			if (raise->vars[1] && !(thing->player->pflags & PF_STARTDASH))
+			if (raise->flags & RF_SPINDASH && !(thing->player->pflags & PF_STARTDASH))
 				continue;
 
 			if (!(thing->z == P_GetSpecialTopZ(thing, raise->sector, sector)))
@@ -2458,43 +1584,43 @@ void T_RaiseSector(levelspecthink_t *raise)
 		}
 	}
 
-	if (raise->vars[9]) // Dynamically Sinking Platform^tm
+	if (raise->flags & RF_DYNAMIC) // Dynamically Sinking Platform^tm
 	{
 #define shaketime 10
-		if (raise->vars[11] > shaketime) // State: moving
+		if (raise->shaketimer > shaketime) // State: moving
 		{
 			if (playeronme) // If player is standing on the platform, accelerate
 			{
-				raise->vars[10] += (FRACUNIT >> 5);
+				raise->extraspeed += (FRACUNIT >> 5);
 			}
 			else // otherwise, decelerate until inflection
 			{
-				raise->vars[10] -= FRACUNIT >> 3;
-				if (raise->vars[10] <= 0) // inflection!
+				raise->extraspeed -= FRACUNIT >> 3;
+				if (raise->extraspeed <= 0) // inflection!
 				{
-					raise->vars[10] = 0;
-					raise->vars[11] = 0; // allow the shake to occur again (fucks over players attempting to jump-cheese)
+					raise->extraspeed = 0;
+					raise->shaketimer = 0; // allow the shake to occur again (fucks over players attempting to jump-cheese)
 				}
 			}
-			active = raise->vars[10] > 0;
+			active = raise->extraspeed > 0;
 		}
 		else // State: shaking
 		{
-			if (playeronme || raise->vars[11])
+			if (playeronme || raise->shaketimer)
 			{
 				active = true;
-				if (++raise->vars[11] > shaketime)
+				if (++raise->shaketimer > shaketime)
 				{
 					if (playeronme)
-						raise->vars[10] = FRACUNIT >> 5;
+						raise->extraspeed = FRACUNIT >> 5;
 					else
-						raise->vars[10] = FRACUNIT << 1;
+						raise->extraspeed = FRACUNIT << 1;
 				}
 				else
 				{
-					raise->vars[10] = ((shaketime/2) - raise->vars[11]) << FRACBITS;
-					if (raise->vars[10] < -raise->vars[2]/2)
-						raise->vars[10] = -raise->vars[2]/2;
+					raise->extraspeed = ((shaketime/2) - raise->shaketimer) << FRACBITS;
+					if (raise->extraspeed < -raise->basespeed/2)
+						raise->extraspeed = -raise->basespeed/2;
 				}
 			}
 		}
@@ -2503,127 +1629,61 @@ void T_RaiseSector(levelspecthink_t *raise)
 	else // Air bobbing platform (not a Dynamically Sinking Platform^tm)
 		active = playeronme;
 
-	if (active)
-	{
-		raise->vars[3] = raise->vars[2];
-
-		if (raise->vars[0] == 1)
-		{
-			if (raise->sector->ceilingheight <= raise->vars[7])
-			{
-				raise->sector->floorheight = raise->vars[7] - (raise->sector->ceilingheight - raise->sector->floorheight);
-				raise->sector->ceilingheight = raise->vars[7];
-				raise->sector->ceilspeed = 0;
-				raise->sector->floorspeed = 0;
-				return;
-			}
-
-			raise->vars[8] = -1;
-			ceilingdestination = raise->vars[7];
-			floordestination = raise->vars[6];
-		}
-		else // elevateUp
-		{
-			if (raise->sector->ceilingheight >= raise->vars[5])
-			{
-				raise->sector->floorheight = raise->vars[5] - (raise->sector->ceilingheight - raise->sector->floorheight);
-				raise->sector->ceilingheight = raise->vars[5];
-				raise->sector->ceilspeed = 0;
-				raise->sector->floorspeed = 0;
-				return;
-			}
+	moveUp = active ^ (raise->flags & RF_REVERSE);
+	ceilingdestination = moveUp ? raise->ceilingtop : raise->ceilingbottom;
+	floordestination = ceilingdestination - (raise->sector->ceilingheight - raise->sector->floorheight);
 
-			raise->vars[8] = 1;
-			ceilingdestination = raise->vars[5];
-			floordestination = raise->vars[4];
-		}
-	}
-	else
+	if ((moveUp && raise->sector->ceilingheight >= ceilingdestination)
+		|| (!moveUp && raise->sector->ceilingheight <= ceilingdestination))
 	{
-		raise->vars[3] = raise->vars[2]/2;
-
-		if (raise->vars[0] == 1)
-		{
-			if (raise->sector->ceilingheight >= raise->vars[5])
-			{
-				raise->sector->floorheight = raise->vars[5] - (raise->sector->ceilingheight - raise->sector->floorheight);
-				raise->sector->ceilingheight = raise->vars[5];
-				raise->sector->ceilspeed = 0;
-				raise->sector->floorspeed = 0;
-				return;
-			}
-			raise->vars[8] = 1;
-			ceilingdestination = raise->vars[5];
-			floordestination = raise->vars[4];
-		}
-		else // elevateUp
-		{
-			if (raise->sector->ceilingheight <= raise->vars[7])
-			{
-				raise->sector->floorheight = raise->vars[7] - (raise->sector->ceilingheight - raise->sector->floorheight);
-				raise->sector->ceilingheight = raise->vars[7];
-				raise->sector->ceilspeed = 0;
-				raise->sector->floorspeed = 0;
-				return;
-			}
-			raise->vars[8] = -1;
-			ceilingdestination = raise->vars[7];
-			floordestination = raise->vars[6];
-		}
+		raise->sector->floorheight = floordestination;
+		raise->sector->ceilingheight = ceilingdestination;
+		raise->sector->ceilspeed = 0;
+		raise->sector->floorspeed = 0;
+		return;
 	}
+	direction = moveUp ? 1 : -1;
 
-	if ((raise->sector->ceilingheight - raise->vars[7])
-		< (raise->vars[5] - raise->sector->ceilingheight))
-	{
-		fixed_t origspeed = raise->vars[3];
+	origspeed = raise->basespeed;
+	if (!active)
+		origspeed /= 2;
 
-		// Slow down as you get closer to the bottom
-		raise->vars[3] = FixedMul(raise->vars[3],FixedDiv(raise->sector->ceilingheight - raise->vars[7], (raise->vars[5] - raise->vars[7])>>5));
+	// Speed up as you get closer to the middle, then slow down again
+	distToNearestEndpoint = min(raise->sector->ceilingheight - raise->ceilingbottom, raise->ceilingtop - raise->sector->ceilingheight);
+	speed = FixedMul(origspeed, FixedDiv(distToNearestEndpoint, (raise->ceilingtop - raise->ceilingbottom) >> 5));
 
-		if (raise->vars[3] <= origspeed/16)
-			raise->vars[3] = origspeed/16;
-		else if (raise->vars[3] > origspeed)
-			raise->vars[3] = origspeed;
-	}
-	else
-	{
-		fixed_t origspeed = raise->vars[3];
-		// Slow down as you get closer to the top
-		raise->vars[3] = FixedMul(raise->vars[3],FixedDiv(raise->vars[5] - raise->sector->ceilingheight, (raise->vars[5] - raise->vars[7])>>5));
-
-		if (raise->vars[3] <= origspeed/16)
-			raise->vars[3] = origspeed/16;
-		else if (raise->vars[3] > origspeed)
-			raise->vars[3] = origspeed;
-	}
+	if (speed <= origspeed/16)
+		speed = origspeed/16;
+	else if (speed > origspeed)
+		speed = origspeed;
 
-	raise->vars[3] += raise->vars[10];
+	speed += raise->extraspeed;
 
 	res = T_MovePlane
 	(
-		raise->sector,         // sector
-		raise->vars[3],          // speed
+		raise->sector,      // sector
+		speed,              // speed
 		ceilingdestination, // dest
-		0,                        // crush
-		1,                        // floor or ceiling (1 for ceiling)
-		raise->vars[8]       // direction
+		false,              // crush
+		true,               // ceiling?
+		direction           // direction
 	);
 
 	if (res == ok || res == pastdest)
 		T_MovePlane
 		(
-			raise->sector,           // sector
-			raise->vars[3],            // speed
+			raise->sector,    // sector
+			speed,            // speed
 			floordestination, // dest
-			0,                          // crush
-			0,                          // floor or ceiling (0 for floor)
-			raise->vars[8]         // direction
+			false,            // crush
+			false,            // ceiling?
+			direction         // direction
 		);
 
 	raise->sector->ceilspeed = 42;
-	raise->sector->floorspeed = raise->vars[3]*raise->vars[8];
+	raise->sector->floorspeed = speed*direction;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->sourceline->tag, i)) >= 0 ;)
+	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
 		P_RecalcPrecipInSector(&sectors[i]);
 }
 
@@ -2718,9 +1778,9 @@ void T_PlaneDisplace(planedisplace_t *pd)
 	}
 
 	if (pd->type == pd_floor || pd->type == pd_both)
-		T_MovePlane(target, INT32_MAX/2, target->floorheight+diff, 0, 0, direction); // move floor
+		T_MovePlane(target, INT32_MAX/2, target->floorheight+diff, false, false, direction); // move floor
 	if (pd->type == pd_ceiling || pd->type == pd_both)
-		T_MovePlane(target, INT32_MAX/2, target->ceilingheight+diff, 0, 1, direction); // move ceiling
+		T_MovePlane(target, INT32_MAX/2, target->ceilingheight+diff, false, true, direction); // move ceiling
 
 	pd->last_height = control->floorheight;
 }
@@ -2734,14 +1794,14 @@ void T_PlaneDisplace(planedisplace_t *pd)
 // (egg capsule button), P_PlayerInSpecialSector (buttons),
 // and P_SpawnSpecials (continuous floor movers and instant lower).
 //
-INT32 EV_DoFloor(line_t *line, floor_e floortype)
+void EV_DoFloor(line_t *line, floor_e floortype)
 {
-	INT32 rtn = 0, firstone = 1;
+	INT32 firstone = 1;
 	INT32 secnum = -1;
 	sector_t *sec;
 	floormove_t *dofloor;
 
-	while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -2749,7 +1809,6 @@ INT32 EV_DoFloor(line_t *line, floor_e floortype)
 			continue; // then don't add another one
 
 		// new floor thinker
-		rtn = 1;
 		dofloor = Z_Calloc(sizeof (*dofloor), PU_LEVSPEC, NULL);
 		P_AddThinker(THINK_MAIN, &dofloor->thinker);
 
@@ -2939,8 +1998,6 @@ INT32 EV_DoFloor(line_t *line, floor_e floortype)
 
 		firstone = 0;
 	}
-
-	return rtn;
 }
 
 // SoM: Boom elevator support.
@@ -2953,15 +2010,14 @@ INT32 EV_DoFloor(line_t *line, floor_e floortype)
 //
 // jff 2/22/98 new type to move floor and ceiling in parallel
 //
-INT32 EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
+void EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 {
 	INT32 secnum = -1;
-	INT32 rtn = 0;
 	sector_t *sec;
 	elevator_t *elevator;
 
 	// act on all sectors with the same tag as the triggering linedef
-	while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -2970,7 +2026,6 @@ INT32 EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 			continue;
 
 		// create and initialize new elevator thinker
-		rtn = 1;
 		elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
 		P_AddThinker(THINK_MAIN, &elevator->thinker);
 		sec->floordata = elevator;
@@ -3072,7 +2127,6 @@ INT32 EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 				break;
 		}
 	}
-	return rtn;
 }
 
 void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
@@ -3205,16 +2259,13 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 }
 
 // Used for bobbing platforms on the water
-INT32 EV_BounceSector(sector_t *sec, fixed_t momz, line_t *sourceline)
+void EV_BounceSector(sector_t *sec, fixed_t momz, line_t *sourceline)
 {
-#define speed vars[0]
-#define distance vars[1]
-#define low vars[2]
-	levelspecthink_t *bouncer;
+	bouncecheese_t *bouncer;
 
 	// create and initialize new thinker
 	if (sec->ceilingdata) // One at a time, ma'am.
-		return 0;
+		return;
 
 	bouncer = Z_Calloc(sizeof (*bouncer), PU_LEVSPEC, NULL);
 	P_AddThinker(THINK_MAIN, &bouncer->thinker);
@@ -3222,31 +2273,20 @@ INT32 EV_BounceSector(sector_t *sec, fixed_t momz, line_t *sourceline)
 	bouncer->thinker.function.acp1 = (actionf_p1)T_BounceCheese;
 
 	// set up the fields according to the type of elevator action
+	bouncer->sourceline = sourceline;
 	bouncer->sector = sec;
 	bouncer->speed = momz/2;
-	bouncer->sourceline = sourceline;
 	bouncer->distance = FRACUNIT;
-	bouncer->low = 1;
-
-	return 1;
-#undef speed
-#undef distance
-#undef low
+	bouncer->low = true;
 }
 
 // For T_ContinuousFalling special
-INT32 EV_DoContinuousFall(sector_t *sec, sector_t *backsector, fixed_t spd, boolean backwards)
+void EV_DoContinuousFall(sector_t *sec, sector_t *backsector, fixed_t spd, boolean backwards)
 {
-#define speed vars[0]
-#define direction vars[1]
-#define floorwasheight vars[2]
-#define ceilingwasheight vars[3]
-#define floordestheight vars[4]
-#define ceilingdestheight vars[5]
-	levelspecthink_t *faller;
+	continuousfall_t *faller;
 
 	// workaround for when there is no back sector
-	if (backsector == NULL)
+	if (!backsector)
 		backsector = sec;
 
 	// create and initialize new thinker
@@ -3258,36 +2298,18 @@ INT32 EV_DoContinuousFall(sector_t *sec, sector_t *backsector, fixed_t spd, bool
 	faller->sector = sec;
 	faller->speed = spd;
 
-	faller->floorwasheight = sec->floorheight;
-	faller->ceilingwasheight = sec->ceilingheight;
+	faller->floorstartheight = sec->floorheight;
+	faller->ceilingstartheight = sec->ceilingheight;
 
-	if (backwards)
-	{
-		faller->ceilingdestheight = backsector->ceilingheight;
-		faller->floordestheight = faller->ceilingdestheight;
-		faller->direction = 1; // Up!
-	}
-	else
-	{
-		faller->floordestheight = backsector->floorheight;
-		faller->ceilingdestheight = faller->floordestheight;
-		faller->direction = -1;
-	}
-
-	return 1;
-#undef speed
-#undef direction
-#undef floorwasheight
-#undef ceilingwasheight
-#undef floordestheight
-#undef ceilingdestheight
+	faller->destheight = backwards ? backsector->ceilingheight : backsector->floorheight;
+	faller->direction = backwards ? 1 : -1;
 }
 
 // Some other 3dfloor special things Tails 03-11-2002 (Search p_mobj.c for description)
 INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 	player_t *player, fixed_t origalpha, boolean crumblereturn)
 {
-	elevator_t *elevator;
+	crumble_t *crumble;
 	sector_t *foundsec;
 	INT32 i;
 
@@ -3295,68 +2317,58 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 	if (sec->floordata)
 		return 0;
 
-	if (sec->crumblestate > 1)
+	if (sec->crumblestate >= CRUMBLE_ACTIVATED)
 		return 0;
 
-	// create and initialize new elevator thinker
-	elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
-	P_AddThinker(THINK_MAIN, &elevator->thinker);
-	elevator->thinker.function.acp1 = (actionf_p1)T_StartCrumble;
+	// create and initialize new crumble thinker
+	crumble = Z_Calloc(sizeof (*crumble), PU_LEVSPEC, NULL);
+	P_AddThinker(THINK_MAIN, &crumble->thinker);
+	crumble->thinker.function.acp1 = (actionf_p1)T_StartCrumble;
 
-	// Does this crumbler return?
-	if (crumblereturn)
-		elevator->type = elevateBounce;
-	else
-		elevator->type = elevateContinuous;
-
-	// set up the fields according to the type of elevator action
-	elevator->sector = sec;
-	elevator->speed = 0;
+	// set up the fields
+	crumble->sector = sec;
+	crumble->speed = 0;
 
 	if (player && player->mo && (player->mo->eflags & MFE_VERTICALFLIP))
 	{
-		elevator->direction = 1; // Up
-		elevator->floordestheight = 1;
+		crumble->direction = 1; // Up
+		crumble->flags |= CF_REVERSE;
 	}
 	else
-	{
-		elevator->direction = -1; // Down
-		elevator->floordestheight = 0;
-	}
+		crumble->direction = -1; // Down
 
-	elevator->floorwasheight = elevator->sector->floorheight;
-	elevator->ceilingwasheight = elevator->sector->ceilingheight;
-	elevator->distance = TICRATE; // Used for delay time
-	elevator->low = 0;
-	elevator->player = player;
-	elevator->origspeed = origalpha;
+	crumble->floorwasheight = crumble->sector->floorheight;
+	crumble->ceilingwasheight = crumble->sector->ceilingheight;
+	crumble->timer = TICRATE;
+	crumble->player = player;
+	crumble->origalpha = origalpha;
 
-	elevator->sourceline = rover->master;
+	crumble->sourceline = rover->master;
 
-	sec->floordata = elevator;
+	sec->floordata = crumble;
 
+	if (crumblereturn)
+		crumble->flags |= CF_RETURN;
 	if (floating)
-		elevator->high = 42;
-	else
-		elevator->high = 0;
+		crumble->flags |= CF_FLOATBOB;
 
-	elevator->sector->crumblestate = 2;
+	crumble->sector->crumblestate = CRUMBLE_ACTIVATED;
 
-	for (i = -1; (i = P_FindSectorFromTag(elevator->sourceline->tag, i)) >= 0 ;)
+	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
 	{
 		foundsec = &sectors[i];
 
-		P_SpawnMobj(foundsec->soundorg.x, foundsec->soundorg.y, elevator->direction == 1 ? elevator->sector->floorheight : elevator->sector->ceilingheight, MT_CRUMBLEOBJ);
+		P_SpawnMobj(foundsec->soundorg.x, foundsec->soundorg.y, crumble->direction == 1 ? crumble->sector->floorheight : crumble->sector->ceilingheight, MT_CRUMBLEOBJ);
 	}
 
 	return 1;
 }
 
-INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
+void EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 {
 	sector_t *roversec = rover->master->frontsector;
 	fixed_t topheight = *rover->topheight;
-	levelspecthink_t *block;
+	mariothink_t *block;
 	mobj_t *thing;
 	fixed_t oldx = 0, oldy = 0, oldz = 0;
 
@@ -3364,7 +2376,7 @@ INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 	I_Assert(puncher->player != NULL);
 
 	if (roversec->floordata || roversec->ceilingdata)
-		return 0;
+		return;
 
 	if (!(rover->flags & FF_SOLID))
 		rover->flags |= (FF_SOLID|FF_RENDERALL|FF_CUTLEVEL);
@@ -3372,8 +2384,9 @@ INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 	// Find an item to pop out!
 	thing = SearchMarioNode(roversec->touching_thinglist);
 
-	// Found something!
-	if (thing)
+	if (!thing)
+		S_StartSound(puncher, sfx_mario1); // "Thunk!" sound - puncher is "close enough".
+	else // Found something!
 	{
 		const boolean itsamonitor = (thing->flags & MF_MONITOR) == MF_MONITOR;
 		// create and initialize new elevator thinker
@@ -3386,13 +2399,11 @@ INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 
 		// Set up the fields
 		block->sector = roversec;
-		block->vars[0] = sector->tag; // actionsector
-		block->vars[1] = 4*FRACUNIT; // speed
-		block->vars[2] = 1; // Up // direction
-		block->vars[3] = block->sector->floorheight; // floorwasheight
-		block->vars[4] = block->sector->ceilingheight; // ceilingwasheight
-		block->vars[5] = FRACUNIT; // distance
-		block->vars[6] = 1; // low
+		block->speed = 4*FRACUNIT;
+		block->direction = 1;
+		block->floorstartheight = block->sector->floorheight;
+		block->ceilingstartheight = block->sector->ceilingheight;
+		block->tag = (INT16)sector->tag;
 
 		if (itsamonitor)
 		{
@@ -3433,8 +2444,4 @@ INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 			P_SetThingPosition(thing);
 		}
 	}
-	else
-		S_StartSound(puncher, sfx_mario1); // "Thunk!" sound - puncher is "close enough".
-
-	return 1;
 }
diff --git a/src/p_inter.c b/src/p_inter.c
index 67d197375bfe46f393533d9c1e43b1dbef744166..3d2c5e45e4f46017fe3284b5831da4d8aca04123 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -633,7 +633,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 			if (ALL7EMERALDS(emeralds)) // Got all 7
 			{
-				if (!(netgame || multiplayer))
+				if (continuesInSession)
 				{
 					player->continues += 1;
 					player->gotcontinue = true;
@@ -643,7 +643,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						S_StartSound(toucher, sfx_chchng);
 				}
 				else
+				{
+					P_GiveCoopLives(player, 1, true); // if continues are disabled, a life is a reasonable substitute
 					S_StartSound(toucher, sfx_chchng);
+				}
 			}
 			else
 			{
@@ -3150,7 +3153,7 @@ static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 			return false;
 
 		// In COOP/RACE, you can't hurt other players unless cv_friendlyfire is on
-		if (!(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE)) && (G_PlatformGametype()))
+		if (!(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE)) && (gametyperules & GTR_FRIENDLY))
 		{
 			if (gametype == GT_COOP && inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK)) // co-op only
 			{
diff --git a/src/p_map.c b/src/p_map.c
index 5bad91db90a6725dc267a6d0a3433387c58086a0..6e4992473be3c81f03040175bd4c82c6f1373b63 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2157,7 +2157,6 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 
 	BMBOUNDFIX(xl, xh, yl, yh);
 
-#ifdef POLYOBJECTS
 	// Check polyobjects and see if tmfloorz/tmceilingz need to be altered
 	{
 		validcount++;
@@ -2229,7 +2228,6 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 				}
 			}
 	}
-#endif
 
 	// tmfloorthing is set when tmfloorz comes from a thing's top
 	tmfloorthing = NULL;
@@ -2387,7 +2385,6 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 
 	BMBOUNDFIX(xl, xh, yl, yh);
 
-#ifdef POLYOBJECTS
 	// Check polyobjects and see if tmfloorz/tmceilingz need to be altered
 	{
 		validcount++;
@@ -2458,7 +2455,6 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 				}
 			}
 	}
-#endif
 
 	// check lines
 	for (bx = xl; bx <= xh; bx++)
@@ -4192,21 +4188,19 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 					{
 						//If the thing was crushed by a crumbling FOF, reward the player who made it crumble!
 						thinker_t *think;
-						elevator_t *crumbler;
+						crumble_t *crumbler;
 
 						for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
 						{
 							if (think->function.acp1 != (actionf_p1)T_StartCrumble)
 								continue;
 
-							crumbler = (elevator_t *)think;
+							crumbler = (crumble_t *)think;
 
 							if (crumbler->player && crumbler->player->mo
 								&& crumbler->player->mo != thing
 								&& crumbler->actionsector == thing->subsector->sector
-								&& crumbler->sector == rover->master->frontsector
-								&& (crumbler->type == elevateBounce
-								|| crumbler->type == elevateContinuous))
+								&& crumbler->sector == rover->master->frontsector)
 							{
 								killer = crumbler->player->mo;
 							}
diff --git a/src/p_maputl.c b/src/p_maputl.c
index 5554030f1e728ec93b5a70f67e8ad10d717edcf3..c6e064d184a722ef2c6fb371ff7e5f9767f2afcd 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -439,7 +439,6 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 	I_Assert(back != NULL);
 
 	openfloorrover = openceilingrover = NULL;
-#ifdef POLYOBJECTS
 	if (linedef->polyobj)
 	{
 		// set these defaults so that polyobjects don't interfere with collision above or below them
@@ -450,7 +449,6 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 		opentopslope = openbottomslope = NULL;
 	}
 	else
-#endif
 	{ // Set open and high/low values here
 		fixed_t frontheight, backheight;
 
@@ -505,7 +503,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				texheight = textures[texnum]->height << FRACBITS;
 
 				// Set texbottom and textop to the Z coordinates of the texture's boundaries
-#if 0 // #ifdef POLYOBJECTS
+#if 0
 				// don't remove this code unless solid midtextures
 				// on non-solid polyobjects should NEVER happen in the future
 				if (linedef->polyobj && (linedef->polyobj->flags & POF_TESTHEIGHT)) {
@@ -548,7 +546,6 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				}
 			}
 		}
-#ifdef POLYOBJECTS
 		if (linedef->polyobj)
 		{
 			// Treat polyobj's backsector like a 3D Floor
@@ -585,94 +582,95 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 			// otherwise don't do anything special, pretend there's nothing else there
 		}
 		else
-#endif
-		// Check for fake floors in the sector.
-		if (front->ffloors || back->ffloors)
 		{
-			ffloor_t *rover;
-			fixed_t delta1, delta2;
-
-			// Check for frontsector's fake floors
-			for (rover = front->ffloors; rover; rover = rover->next)
+			// Check for fake floors in the sector.
+			if (front->ffloors || back->ffloors)
 			{
-				fixed_t topheight, bottomheight;
-				if (!(rover->flags & FF_EXISTS))
-					continue;
+				ffloor_t *rover;
+				fixed_t delta1, delta2;
 
-				if (mobj->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mobj->player, rover)))
-					;
-				else if (!((rover->flags & FF_BLOCKPLAYER && mobj->player)
-					|| (rover->flags & FF_BLOCKOTHERS && !mobj->player)))
-					continue;
+				// Check for frontsector's fake floors
+				for (rover = front->ffloors; rover; rover = rover->next)
+				{
+					fixed_t topheight, bottomheight;
+					if (!(rover->flags & FF_EXISTS))
+						continue;
 
-				topheight = P_GetFOFTopZ(mobj, front, rover, tmx, tmy, linedef);
-				bottomheight = P_GetFOFBottomZ(mobj, front, rover, tmx, tmy, linedef);
+					if (mobj->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mobj->player, rover)))
+						;
+					else if (!((rover->flags & FF_BLOCKPLAYER && mobj->player)
+						|| (rover->flags & FF_BLOCKOTHERS && !mobj->player)))
+						continue;
 
-				delta1 = abs(mobj->z - (bottomheight + ((topheight - bottomheight)/2)));
-				delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
+					topheight = P_GetFOFTopZ(mobj, front, rover, tmx, tmy, linedef);
+					bottomheight = P_GetFOFBottomZ(mobj, front, rover, tmx, tmy, linedef);
 
-				if (delta1 >= delta2 && (rover->flags & FF_INTANGIBLEFLATS) != FF_PLATFORM) // thing is below FOF
-				{
-					if (bottomheight < opentop) {
-						opentop = bottomheight;
-						opentopslope = *rover->b_slope;
-						openceilingrover = rover;
+					delta1 = abs(mobj->z - (bottomheight + ((topheight - bottomheight)/2)));
+					delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
+
+					if (delta1 >= delta2 && (rover->flags & FF_INTANGIBLEFLATS) != FF_PLATFORM) // thing is below FOF
+					{
+						if (bottomheight < opentop) {
+							opentop = bottomheight;
+							opentopslope = *rover->b_slope;
+							openceilingrover = rover;
+						}
+						else if (bottomheight < highceiling)
+							highceiling = bottomheight;
 					}
-					else if (bottomheight < highceiling)
-						highceiling = bottomheight;
-				}
 
-				if (delta1 < delta2 && (rover->flags & FF_INTANGIBLEFLATS) != FF_REVERSEPLATFORM) // thing is above FOF
-				{
-					if (topheight > openbottom) {
-						openbottom = topheight;
-						openbottomslope = *rover->t_slope;
-						openfloorrover = rover;
+					if (delta1 < delta2 && (rover->flags & FF_INTANGIBLEFLATS) != FF_REVERSEPLATFORM) // thing is above FOF
+					{
+						if (topheight > openbottom) {
+							openbottom = topheight;
+							openbottomslope = *rover->t_slope;
+							openfloorrover = rover;
+						}
+						else if (topheight > lowfloor)
+							lowfloor = topheight;
 					}
-					else if (topheight > lowfloor)
-						lowfloor = topheight;
 				}
-			}
 
-			// Check for backsectors fake floors
-			for (rover = back->ffloors; rover; rover = rover->next)
-			{
-				fixed_t topheight, bottomheight;
-				if (!(rover->flags & FF_EXISTS))
-					continue;
+				// Check for backsectors fake floors
+				for (rover = back->ffloors; rover; rover = rover->next)
+				{
+					fixed_t topheight, bottomheight;
+					if (!(rover->flags & FF_EXISTS))
+						continue;
 
-				if (mobj->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mobj->player, rover)))
-					;
-				else if (!((rover->flags & FF_BLOCKPLAYER && mobj->player)
-					|| (rover->flags & FF_BLOCKOTHERS && !mobj->player)))
-					continue;
+					if (mobj->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mobj->player, rover)))
+						;
+					else if (!((rover->flags & FF_BLOCKPLAYER && mobj->player)
+						|| (rover->flags & FF_BLOCKOTHERS && !mobj->player)))
+						continue;
 
-				topheight = P_GetFOFTopZ(mobj, back, rover, tmx, tmy, linedef);
-				bottomheight = P_GetFOFBottomZ(mobj, back, rover, tmx, tmy, linedef);
+					topheight = P_GetFOFTopZ(mobj, back, rover, tmx, tmy, linedef);
+					bottomheight = P_GetFOFBottomZ(mobj, back, rover, tmx, tmy, linedef);
 
-				delta1 = abs(mobj->z - (bottomheight + ((topheight - bottomheight)/2)));
-				delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
+					delta1 = abs(mobj->z - (bottomheight + ((topheight - bottomheight)/2)));
+					delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
 
-				if (delta1 >= delta2 && (rover->flags & FF_INTANGIBLEFLATS) != FF_PLATFORM) // thing is below FOF
-				{
-					if (bottomheight < opentop) {
-						opentop = bottomheight;
-						opentopslope = *rover->b_slope;
-						openceilingrover = rover;
+					if (delta1 >= delta2 && (rover->flags & FF_INTANGIBLEFLATS) != FF_PLATFORM) // thing is below FOF
+					{
+						if (bottomheight < opentop) {
+							opentop = bottomheight;
+							opentopslope = *rover->b_slope;
+							openceilingrover = rover;
+						}
+						else if (bottomheight < highceiling)
+							highceiling = bottomheight;
 					}
-					else if (bottomheight < highceiling)
-						highceiling = bottomheight;
-				}
 
-				if (delta1 < delta2 && (rover->flags & FF_INTANGIBLEFLATS) != FF_REVERSEPLATFORM) // thing is above FOF
-				{
-					if (topheight > openbottom) {
-						openbottom = topheight;
-						openbottomslope = *rover->t_slope;
-						openfloorrover = rover;
+					if (delta1 < delta2 && (rover->flags & FF_INTANGIBLEFLATS) != FF_REVERSEPLATFORM) // thing is above FOF
+					{
+						if (topheight > openbottom) {
+							openbottom = topheight;
+							openbottomslope = *rover->t_slope;
+							openfloorrover = rover;
+						}
+						else if (topheight > lowfloor)
+							lowfloor = topheight;
 					}
-					else if (topheight > lowfloor)
-						lowfloor = topheight;
 				}
 			}
 		}
@@ -922,9 +920,7 @@ boolean P_BlockLinesIterator(INT32 x, INT32 y, boolean (*func)(line_t *))
 {
 	INT32 offset;
 	const INT32 *list; // Big blockmap
-#ifdef POLYOBJECTS
 	polymaplink_t *plink; // haleyjd 02/22/06
-#endif
 	line_t *ld;
 
 	if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
@@ -932,7 +928,6 @@ boolean P_BlockLinesIterator(INT32 x, INT32 y, boolean (*func)(line_t *))
 
 	offset = y*bmapwidth + x;
 
-#ifdef POLYOBJECTS
 	// haleyjd 02/22/06: consider polyobject lines
 	plink = polyblocklinks[offset];
 
@@ -956,7 +951,6 @@ boolean P_BlockLinesIterator(INT32 x, INT32 y, boolean (*func)(line_t *))
 		}
 		plink = (polymaplink_t *)(plink->link.next);
 	}
-#endif
 
 	offset = *(blockmap + offset); // offset = blockmap[y*bmapwidth+x];
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 20b9a1144b7031046732abeba23a05bfff0348f3..8fb2d97abdfaac618589a7bfda063f3ff4ab84f7 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1686,7 +1686,7 @@ static void P_PushableCheckBustables(mobj_t *mo)
 				// Needs ML_EFFECT4 flag for pushables to break it
 				if (!(rover->master->flags & ML_EFFECT4)) continue;
 
-				if (!rover->master->frontsector->crumblestate)
+				if (rover->master->frontsector->crumblestate == CRUMBLE_NONE)
 				{
 					topheight = P_GetFOFTopZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
 					bottomheight = P_GetFOFBottomZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
@@ -2890,7 +2890,6 @@ static void P_PlayerZMovement(mobj_t *mo)
 			mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
 
 			{
-#ifdef POLYOBJECTS
 				// Check if we're on a polyobject
 				// that triggers a linedef executor.
 				msecnode_t *node;
@@ -2950,8 +2949,6 @@ static void P_PlayerZMovement(mobj_t *mo)
 				}
 
 			if (!stopmovecut)
-#endif
-
 				// Cut momentum in half when you hit the ground and
 				// aren't pressing any controls.
 				if (!(mo->player->cmd.forwardmove || mo->player->cmd.sidemove) && !mo->player->cmomx && !mo->player->cmomy && !(mo->player->pflags & PF_SPINNING))
@@ -7026,8 +7023,7 @@ static void P_MobjScaleThink(mobj_t *mobj)
 	fixed_t oldheight = mobj->height;
 	UINT8 correctionType = 0; // Don't correct Z position, just gain height
 
-	if ((mobj->flags & MF_NOCLIPHEIGHT || (mobj->z > mobj->floorz && mobj->z + mobj->height < mobj->ceilingz))
-		&& mobj->type != MT_EGGMOBILE_FIRE)
+	if (mobj->flags & MF_NOCLIPHEIGHT || (mobj->z > mobj->floorz && mobj->z + mobj->height < mobj->ceilingz))
 		correctionType = 1; // Correct Z position by centering
 	else if (mobj->eflags & MFE_VERTICALFLIP)
 		correctionType = 2; // Correct Z position by moving down
@@ -7048,10 +7044,6 @@ static void P_MobjScaleThink(mobj_t *mobj)
 		/// \todo Lua hook for "reached destscale"?
 		switch (mobj->type)
 		{
-		case MT_EGGMOBILE_FIRE:
-			mobj->destscale = FRACUNIT;
-			mobj->scalespeed = FRACUNIT>>4;
-			break;
 		default:
 			break;
 		}
@@ -8227,6 +8219,7 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
 // See Linedef Exec 457 (Track mobj angle to point)
 static void P_TracerAngleThink(mobj_t *mobj)
 {
+	angle_t looking;
 	angle_t ang;
 
 	if (!mobj->tracer)
@@ -8241,7 +8234,12 @@ static void P_TracerAngleThink(mobj_t *mobj)
 	// mobj->cvval - Allowable failure delay
 	// mobj->cvmem - Failure timer
 
-	ang = mobj->angle - R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y);
+	if (mobj->player)
+		looking = ( mobj->player->cmd.angleturn << 16 );/* fixes CS_LMAOGALOG */
+	else
+		looking = mobj->angle;
+
+	ang = looking - R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y);
 
 	// \todo account for distance between mobj and tracer
 	// Because closer mobjs can be facing beyond the angle tolerance
@@ -9250,8 +9248,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 			if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0
 				&& P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius*16)
 			{
+				var1 = mobj->info->speed;
+				var2 = 1;
+
 				// Home in on the target.
-				P_HomingAttack(mobj, mobj->tracer);
+				A_HomingChase(mobj);
 
 				if (mobj->z < mobj->floorz)
 					mobj->z = mobj->floorz;
@@ -11564,7 +11565,7 @@ void P_MovePlayerToStarpost(INT32 playernum)
 mapthing_t *huntemeralds[MAXHUNTEMERALDS];
 INT32 numhuntemeralds;
 
-static fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t offset, const boolean flip)
+fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t offset, const boolean flip)
 {
 	const subsector_t *ss = R_PointInSubsector(x, y);
 
@@ -11579,7 +11580,7 @@ static fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x,
 		return P_GetSectorFloorZAt(ss->sector, x, y) + offset;
 }
 
-static fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y)
+fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y)
 {
 	fixed_t offset = mthing->z << FRACBITS;
 	boolean flip = (!!(mobjinfo[mobjtype].flags & MF_SPAWNCEILING) ^ !!(mthing->options & MTF_OBJECTFLIP));
@@ -11619,6 +11620,7 @@ static fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthin
 
 	// Ring-like items, may float additional units with MTF_AMBUSH.
 	case MT_SPIKEBALL:
+	case MT_EMERHUNT:
 	case MT_EMERALDSPAWN:
 	case MT_TOKEN:
 	case MT_EMBLEM:
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 5deb288e42c8969a336ab119d37f88c3fb9a9876..eda7383df2efdeb03b7846ad2310db01b0a8aad0 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -451,6 +451,9 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing);
 void P_MovePlayerToStarpost(INT32 playernum);
 void P_AfterPlayerSpawn(INT32 playernum);
 
+fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t offset, const boolean flip);
+fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y);
+
 mobj_t *P_SpawnMapThing(mapthing_t *mthing);
 void P_SpawnHoop(mapthing_t *mthing);
 void P_SetBonusTime(mobj_t *mobj);
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 7e1ff1f4948737385f8cdfdbdc1e4a39cc8012fb..cd63f4509cbd0c792054d837186a8c089acdcaa3 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -28,12 +28,6 @@
 #include "r_state.h"
 #include "r_defs.h"
 
-
-#define POLYOBJECTS
-
-
-#ifdef POLYOBJECTS
-
 /*
    Theory behind Polyobjects:
 
@@ -146,11 +140,6 @@ FUNCINLINE static ATTRINLINE void Polyobj_vecSub2(vertex_t *dst, vertex_t *v1, v
 	dst->y = v1->y - v2->y;
 }
 
-//
-// P_PointInsidePolyobj
-//
-// Returns TRUE if the XY point is inside the polyobject
-//
 boolean P_PointInsidePolyobj(polyobj_t *po, fixed_t x, fixed_t y)
 {
 	size_t i;
@@ -164,11 +153,6 @@ boolean P_PointInsidePolyobj(polyobj_t *po, fixed_t x, fixed_t y)
 	return true;
 }
 
-//
-// P_MobjTouchingPolyobj
-//
-// Returns TRUE if the mobj is touching the edge of a polyobject
-//
 boolean P_MobjTouchingPolyobj(polyobj_t *po, mobj_t *mo)
 {
 	fixed_t mbbox[4];
@@ -188,11 +172,6 @@ boolean P_MobjTouchingPolyobj(polyobj_t *po, mobj_t *mo)
 	return false;
 }
 
-//
-// P_MobjInsidePolyobj
-//
-// Returns TRUE if the mobj is inside the polyobject
-//
 boolean P_MobjInsidePolyobj(polyobj_t *po, mobj_t *mo)
 {
 	fixed_t mbbox[4];
@@ -212,11 +191,6 @@ boolean P_MobjInsidePolyobj(polyobj_t *po, mobj_t *mo)
 	return true;
 }
 
-//
-// P_BBoxInsidePolyobj
-//
-// Returns TRUE if the bbox is inside the polyobject
-//
 boolean P_BBoxInsidePolyobj(polyobj_t *po, fixed_t *bbox)
 {
 	size_t i;
@@ -230,55 +204,53 @@ boolean P_BBoxInsidePolyobj(polyobj_t *po, fixed_t *bbox)
 	return true;
 }
 
-//
-// Polyobj_GetInfo
-//
 // Finds the 'polyobject settings' linedef for a polyobject
 // the polyobject's id should be set as its tag
-//
-void Polyobj_GetInfo(INT16 poid, INT32 *poflags, INT32 *parentID, INT32 *potrans)
+static void Polyobj_GetInfo(polyobj_t *po)
 {
-	INT32 i = P_FindSpecialLineFromTag(POLYINFO_SPECIALNUM, poid, -1);
+	INT32 i = P_FindSpecialLineFromTag(POLYINFO_SPECIALNUM, po->id, -1);
+
+	po->flags = POF_SOLID|POF_TESTHEIGHT|POF_RENDERSIDES;
 
 	if (i == -1)
 		return; // no extra settings to apply, let's leave it
 
-	if (parentID)
-		*parentID = lines[i].frontsector->special;
+	po->parent = lines[i].frontsector->special;
+	if (po->parent == po->id) // do not allow a self-reference
+		po->parent = -1;
 
-	if (potrans)
-		*potrans = (lines[i].frontsector->floorheight>>FRACBITS) / 100;
+	po->translucency = (lines[i].flags & ML_DONTPEGTOP)
+						? (sides[lines[i].sidenum[0]].textureoffset>>FRACBITS)
+						: ((lines[i].frontsector->floorheight>>FRACBITS) / 100);
+
+	po->translucency = max(min(po->translucency, NUMTRANSMAPS), 0);
 
 	if (lines[i].flags & ML_EFFECT1)
-		*poflags |= POF_ONESIDE;
+		po->flags |= POF_ONESIDE;
 
 	if (lines[i].flags & ML_EFFECT2)
-		*poflags &= ~POF_SOLID;
+		po->flags &= ~POF_SOLID;
 
 	if (lines[i].flags & ML_EFFECT3)
-		*poflags |= POF_PUSHABLESTOP;
+		po->flags |= POF_PUSHABLESTOP;
 
 	if (lines[i].flags & ML_EFFECT4)
-		*poflags |= POF_RENDERPLANES;
+		po->flags |= POF_RENDERPLANES;
 
 	/*if (lines[i].flags & ML_EFFECT5)
-		*poflags &= ~POF_CLIPPLANES;*/
+		po->flags &= ~POF_CLIPPLANES;*/
 
 	if (lines[i].flags & ML_NOCLIMB) // Has a linedef executor
-		*poflags |= POF_LDEXEC;
+		po->flags |= POF_LDEXEC;
 }
 
 // Reallocating array maintenance
 
-//
-// Polyobj_addVertex
-//
 // Adds a vertex to a polyobject's reallocating vertex arrays, provided
 // that such a vertex isn't already in the array. Each vertex must only
 // be translated once during polyobject movement. Keeping track of them
 // this way results in much more clear and efficient code than what
 // Hexen used.
-//
 static void Polyobj_addVertex(polyobj_t *po, vertex_t *v)
 {
 	size_t i;
@@ -314,14 +286,10 @@ static void Polyobj_addVertex(polyobj_t *po, vertex_t *v)
 	po->numVertices++;
 }
 
-//
-// Polyobj_addLine
-//
 // Adds a linedef to a polyobject's reallocating linedefs array, provided
 // that such a linedef isn't already in the array. Each linedef must only
 // be adjusted once during polyobject movement. Keeping track of them
 // this way provides the same benefits as for vertices.
-//
 static void Polyobj_addLine(polyobj_t *po, line_t *l)
 {
 	size_t i;
@@ -346,14 +314,10 @@ static void Polyobj_addLine(polyobj_t *po, line_t *l)
 	po->lines[po->numLines++] = l;
 }
 
-//
-// Polyobj_addSeg
-//
 // Adds a single seg to a polyobject's reallocating seg pointer array.
 // Most polyobjects will have between 4 and 16 segs, so the array size
 // begins much smaller than usual. Calls Polyobj_addVertex and Polyobj_addLine
 // to add those respective structures for this seg, as well.
-//
 static void Polyobj_addSeg(polyobj_t *po, seg_t *seg)
 {
 	if (po->segCount >= po->numSegsAlloc)
@@ -379,14 +343,10 @@ static void Polyobj_addSeg(polyobj_t *po, seg_t *seg)
 
 // Seg-finding functions
 
-//
-// Polyobj_findSegs
-//
 // This method adds segs to a polyobject by following segs from vertex to
 // vertex.  The process stops when the original starting point is reached
 // or if a particular search ends unexpectedly (ie, the polyobject is not
 // closed).
-//
 static void Polyobj_findSegs(polyobj_t *po, seg_t *seg)
 {
 	fixed_t startx, starty;
@@ -400,25 +360,29 @@ static void Polyobj_findSegs(polyobj_t *po, seg_t *seg)
 		// Find backfacings
 		for (s = 0;  s < numsegs; s++)
 		{
+			size_t r;
+
 			if (segs[s].glseg)
 				continue;
-			if (segs[s].linedef == seg->linedef
-				&& segs[s].side == 1)
-			{
-				size_t r;
-				for (r = 0; r < po->segCount; r++)
-				{
-					if (po->segs[r] == &segs[s])
-						break;
-				}
 
-				if (r != po->segCount)
-					continue;
+			if (segs[s].linedef != seg->linedef)
+				continue;
 
-				segs[s].dontrenderme = true;
+			if (segs[s].side != 1)
+				continue;
 
-				Polyobj_addSeg(po, &segs[s]);
+			for (r = 0; r < po->segCount; r++)
+			{
+				if (po->segs[r] == &segs[s])
+					break;
 			}
+
+			if (r != po->segCount)
+				continue;
+
+			segs[s].dontrenderme = true;
+
+			Polyobj_addSeg(po, &segs[s]);
 		}
 	}
 
@@ -438,56 +402,60 @@ newseg:
 	// seg's ending vertex.
 	for (i = 0; i < numsegs; ++i)
 	{
+		size_t q;
+
 		if (segs[i].glseg)
 			continue;
 		if (segs[i].side != 0) // needs to be frontfacing
 			continue;
-		if (segs[i].v1->x == seg->v2->x && segs[i].v1->y == seg->v2->y)
+		if (segs[i].v1->x != seg->v2->x)
+			continue;
+		if (segs[i].v1->y != seg->v2->y)
+			continue;
+
+		// Make sure you didn't already add this seg...
+		for (q = 0; q < po->segCount; q++)
 		{
-			// Make sure you didn't already add this seg...
-			size_t q;
-			for (q = 0; q < po->segCount; q++)
-			{
-				if (po->segs[q] == &segs[i])
-					break;
-			}
+			if (po->segs[q] == &segs[i])
+				break;
+		}
 
-			if (q != po->segCount)
-				continue;
+		if (q != po->segCount)
+			continue;
 
-			// add the new seg and recurse
-			Polyobj_addSeg(po, &segs[i]);
-			seg = &segs[i];
+		// add the new seg and recurse
+		Polyobj_addSeg(po, &segs[i]);
+		seg = &segs[i];
 
-			if (!(po->flags & POF_ONESIDE))
+		if (!(po->flags & POF_ONESIDE))
+		{
+			// Find backfacings
+			for (q = 0; q < numsegs; q++)
 			{
-				// Find backfacings
-				for (q = 0;  q < numsegs; q++)
-				{
-					if (segs[q].glseg)
-						continue;
-
-					if (segs[q].linedef == segs[i].linedef
-						&& segs[q].side == 1)
-					{
-						size_t r;
-						for (r=0; r < po->segCount; r++)
-						{
-							if (po->segs[r] == &segs[q])
-								break;
-						}
+				size_t r;
 
-						if (r != po->segCount)
-							continue;
+				if (segs[q].glseg)
+					continue;
+				if (segs[q].linedef != segs[i].linedef)
+					continue;
+				if (segs[q].side != 1)
+					continue;
 
-						segs[q].dontrenderme = true;
-						Polyobj_addSeg(po, &segs[q]);
-					}
+				for (r = 0; r < po->segCount; r++)
+				{
+					if (po->segs[r] == &segs[q])
+						break;
 				}
-			}
 
-			goto newseg;
+				if (r != po->segCount)
+					continue;
+
+				segs[q].dontrenderme = true;
+				Polyobj_addSeg(po, &segs[q]);
+			}
 		}
+
+		goto newseg;
 	}
 
 	// error: if we reach here, the seg search never found another seg to
@@ -496,91 +464,8 @@ newseg:
 	CONS_Debug(DBG_POLYOBJ, "Polyobject %d is not closed\n", po->id);
 }
 
-/*
-// structure used to store segs during explicit search process
-typedef struct segitem_s
-{
-	seg_t *seg;
-	INT32   num;
-} segitem_t;
-
-//
-// Polyobj_segCompare
-//
-// Callback for qsort that compares two segitems.
-//
-static int Polyobj_segCompare(const void *s1, const void *s2)
-{
-	const segitem_t *si1 = s1;
-	const segitem_t *si2 = s2;
-
-	return si2->num - si1->num;
-}
-
-//
-// Polyobj_findExplicit
-//
-// Searches for segs to put into a polyobject in an explicitly provided order.
-//
-static void Polyobj_findExplicit(polyobj_t *po)
-{
-	// temporary dynamic seg array
-	segitem_t *segitems = NULL;
-	size_t numSegItems = 0;
-	size_t numSegItemsAlloc = 0;
-
-	size_t i;
-
-	// first loop: save off all segs with polyobject's id number
-	for (i = 0; i < numsegs; ++i)
-	{
-		INT32 polyID, parentID;
-
-		if (segs[i].linedef->special != POLYOBJ_EXPLICIT_LINE)
-			continue;
-
-		Polyobj_GetInfo(segs[i].linedef->tag, &polyID, &parentID, NULL);
-
-		if (polyID == po->id && parentID > 0)
-		{
-			if (numSegItems >= numSegItemsAlloc)
-			{
-				numSegItemsAlloc = numSegItemsAlloc ? numSegItemsAlloc*2 : 4;
-				segitems = Z_Realloc(segitems, numSegItemsAlloc*sizeof(segitem_t), PU_STATIC, NULL);
-			}
-			segitems[numSegItems].seg = &segs[i];
-			segitems[numSegItems].num = parentID;
-			++numSegItems;
-		}
-	}
-
-	// make sure array isn't empty
-	if (numSegItems == 0)
-	{
-		po->isBad = true;
-		CONS_Debug(DBG_POLYOBJ, "Polyobject %d is empty\n", po->id);
-		return;
-	}
-
-	// sort the array if necessary
-	if (numSegItems >= 2)
-		qsort(segitems, numSegItems, sizeof(segitem_t), Polyobj_segCompare);
-
-	// second loop: put the sorted segs into the polyobject
-	for (i = 0; i < numSegItems; ++i)
-		Polyobj_addSeg(po, segitems[i].seg);
-
-	// free the temporary array
-	Z_Free(segitems);
-}*/
-
 // Setup functions
 
-//
-// Polyobj_spawnPolyObj
-//
-// Sets up a Polyobject.
-//
 static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 {
 	size_t i;
@@ -604,14 +489,12 @@ static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 	po->thrust = FRACUNIT;
 	po->spawnflags = po->flags = 0;
 
-	// 1. Search segs for "line start" special with tag matching this
-	//    polyobject's id number. If found, iterate through segs which
-	//    share common vertices and record them into the polyobject.
+	// Search segs for "line start" special with tag matching this
+	// polyobject's id number. If found, iterate through segs which
+	// share common vertices and record them into the polyobject.
 	for (i = 0; i < numsegs; ++i)
 	{
 		seg_t *seg = &segs[i];
-		INT32 poflags = POF_SOLID|POF_TESTHEIGHT|POF_RENDERSIDES;
-		INT32 parentID = 0, potrans = 0;
 
 		if (seg->glseg)
 			continue;
@@ -625,17 +508,13 @@ static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 		if (seg->linedef->tag != po->id)
 			continue;
 
-		Polyobj_GetInfo(po->id, &poflags, &parentID, &potrans); // apply extra settings if they exist!
+		Polyobj_GetInfo(po); // apply extra settings if they exist!
 
 		// save original flags and translucency to reference later for netgames!
-		po->spawnflags = po->flags = poflags;
-		po->spawntrans = po->translucency = potrans;
+		po->spawnflags = po->flags;
+		po->spawntrans = po->translucency;
 
 		Polyobj_findSegs(po, seg);
-		po->parent = parentID;
-		if (po->parent == po->id) // do not allow a self-reference
-			po->parent = -1;
-		// TODO: sound sequence is in args[2]
 		break;
 	}
 
@@ -645,29 +524,7 @@ static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 	if (po->isBad)
 		return;
 
-	/*
-	// 2. If no such line existed in the first step, look for a seg with the
-	//    "explicit" special with tag matching this polyobject's id number. If
-	//    found, continue to search for all such lines, storing them in a
-	//    temporary list of segs which is then copied into the polyobject in
-	//    sorted order.
-	if (po->segCount == 0)
-	{
-		UINT16 parent;
-		Polyobj_findExplicit(po);
-		// if an error occurred above, quit processing this object
-		if (po->isBad)
-			return;
-
-		Polyobj_GetInfo(po->segs[0]->linedef->tag, NULL, NULL, &parent);
-		po->parent = parent;
-		if (po->parent == po->id) // do not allow a self-reference
-			po->parent = -1;
-		// TODO: sound sequence is in args[3]
-	}*/
-
 	// make sure array isn't empty
-	// since Polyobj_findExplicit is disabled currently, we have to do things here instead now!
 	if (po->segCount == 0)
 	{
 		po->isBad = true;
@@ -696,12 +553,8 @@ static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 
 static void Polyobj_attachToSubsec(polyobj_t *po);
 
-//
-// Polyobj_moveToSpawnSpot
-//
 // Translates the polyobject's vertices with respect to the difference between
 // the anchor and spawn spots. Updates linedef bounding boxes as well.
-//
 static void Polyobj_moveToSpawnSpot(mapthing_t *anchor)
 {
 	polyobj_t *po;
@@ -748,11 +601,7 @@ static void Polyobj_moveToSpawnSpot(mapthing_t *anchor)
 	Polyobj_attachToSubsec(po);
 }
 
-//
-// Polyobj_attachToSubsec
-//
 // Attaches a polyobject to its appropriate subsector.
-//
 static void Polyobj_attachToSubsec(polyobj_t *po)
 {
 	subsector_t  *ss;
@@ -787,11 +636,7 @@ static void Polyobj_attachToSubsec(polyobj_t *po)
 	po->attached = true;
 }
 
-//
-// Polyobj_removeFromSubsec
-//
 // Removes a polyobject from the subsector to which it is attached.
-//
 static void Polyobj_removeFromSubsec(polyobj_t *po)
 {
 	if (po->attached)
@@ -803,11 +648,7 @@ static void Polyobj_removeFromSubsec(polyobj_t *po)
 
 // Blockmap Functions
 
-//
-// Polyobj_getLink
-//
 // Retrieves a polymaplink object from the free list or creates a new one.
-//
 static polymaplink_t *Polyobj_getLink(void)
 {
 	polymaplink_t *l;
@@ -826,11 +667,7 @@ static polymaplink_t *Polyobj_getLink(void)
 	return l;
 }
 
-//
-// Polyobj_putLink
-//
 // Puts a polymaplink object into the free list.
-//
 static void Polyobj_putLink(polymaplink_t *l)
 {
 	memset(l, 0, sizeof(*l));
@@ -838,14 +675,10 @@ static void Polyobj_putLink(polymaplink_t *l)
 	bmap_freelist = l;
 }
 
-//
-// Polyobj_linkToBlockmap
-//
 // Inserts a polyobject into the polyobject blockmap. Unlike, mobj_t's,
 // polyobjects need to be linked into every blockmap cell which their
 // bounding box intersects. This ensures the accurate level of clipping
 // which is present with linedefs but absent from most mobj interactions.
-//
 static void Polyobj_linkToBlockmap(polyobj_t *po)
 {
 	fixed_t *blockbox = po->blockbox;
@@ -890,12 +723,8 @@ static void Polyobj_linkToBlockmap(polyobj_t *po)
 	po->linked = true;
 }
 
-//
-// Polyobj_removeFromBlockmap
-//
 // Unlinks a polyobject from all blockmap cells it intersects and returns
 // its polymaplink objects to the free list.
-//
 static void Polyobj_removeFromBlockmap(polyobj_t *po)
 {
 	polymaplink_t *rover;
@@ -934,13 +763,9 @@ static void Polyobj_removeFromBlockmap(polyobj_t *po)
 
 // Movement functions
 
-//
-// Polyobj_untouched
-//
 // A version of Lee's routine from p_maputl.c that accepts an mobj pointer
 // argument instead of using tmthing. Returns true if the line isn't contacted
 // and false otherwise.
-//
 static inline boolean Polyobj_untouched(line_t *ld, mobj_t *mo)
 {
 	fixed_t x, y, ptmbbox[4];
@@ -953,13 +778,9 @@ static inline boolean Polyobj_untouched(line_t *ld, mobj_t *mo)
 		P_BoxOnLineSide(ptmbbox, ld) != -1;
 }
 
-//
-// Polyobj_pushThing
-//
 // Inflicts thrust and possibly damage on a thing which has been found to be
 // blocking the motion of a polyobject. The default thrust amount is only one
 // unit, but the motion of the polyobject can be used to change this.
-//
 static void Polyobj_pushThing(polyobj_t *po, line_t *line, mobj_t *mo)
 {
 	angle_t lineangle;
@@ -994,11 +815,7 @@ static void Polyobj_pushThing(polyobj_t *po, line_t *line, mobj_t *mo)
 	}
 }
 
-//
-// Polyobj_slideThing
-//
 // Moves an object resting on top of a polyobject by (x, y). Template function to make alteration easier.
-//
 static void Polyobj_slideThing(mobj_t *mo, fixed_t dx, fixed_t dy)
 {
 	if (mo->player) { // Finally this doesn't suck eggs -fickle
@@ -1046,11 +863,7 @@ static void Polyobj_slideThing(mobj_t *mo, fixed_t dx, fixed_t dy)
 		P_TryMove(mo, mo->x+dx, mo->y+dy, true);
 }
 
-//
-// Polyobj_carryThings
-//
 // Causes objects resting on top of the polyobject to 'ride' with its movement.
-//
 static void Polyobj_carryThings(polyobj_t *po, fixed_t dx, fixed_t dy)
 {
 	static INT32 pomovecount = 0;
@@ -1102,12 +915,8 @@ static void Polyobj_carryThings(polyobj_t *po, fixed_t dx, fixed_t dy)
 	}
 }
 
-//
-// Polyobj_clipThings
-//
 // Checks for things that are in the way of a polyobject line move.
 // Returns true if something was hit.
-//
 static INT32 Polyobj_clipThings(polyobj_t *po, line_t *line)
 {
 	INT32 hitflags = 0;
@@ -1169,11 +978,8 @@ static INT32 Polyobj_clipThings(polyobj_t *po, line_t *line)
 	return hitflags;
 }
 
-//
-// Polyobj_moveXY
-//
+
 // Moves a polyobject on the x-y plane.
-//
 static boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y, boolean checkmobjs)
 {
 	size_t i;
@@ -1229,14 +1035,10 @@ static boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y, boolean check
 	return !(hitflags & 2);
 }
 
-//
-// Polyobj_rotatePoint
-//
 // Rotates a point and then translates it relative to point c.
 // The formula for this can be found here:
 // http://www.inversereality.org/tutorials/graphics%20programming/2dtransformations.html
 // It is, of course, just a vector-matrix multiplication.
-//
 static inline void Polyobj_rotatePoint(vertex_t *v, const vertex_t *c, angle_t ang)
 {
 	vertex_t tmp = *v;
@@ -1248,12 +1050,8 @@ static inline void Polyobj_rotatePoint(vertex_t *v, const vertex_t *c, angle_t a
 	v->y += c->y;
 }
 
-//
-// Polyobj_rotateLine
-//
 // Taken from P_LoadLineDefs; simply updates the linedef's dx, dy, slopetype,
 // and bounding box to be consistent with its vertices.
-//
 static void Polyobj_rotateLine(line_t *ld)
 {
 	vertex_t *v1, *v2;
@@ -1293,11 +1091,7 @@ static void Polyobj_rotateLine(line_t *ld)
 	}
 }
 
-//
-// Polyobj_rotateThings
-//
 // Causes objects resting on top of the rotating polyobject to 'ride' with its movement.
-//
 static void Polyobj_rotateThings(polyobj_t *po, vertex_t origin, angle_t delta, UINT8 turnthings)
 {
 	static INT32 pomovecount = 10000;
@@ -1373,11 +1167,7 @@ static void Polyobj_rotateThings(polyobj_t *po, vertex_t origin, angle_t delta,
 	}
 }
 
-//
-// Polyobj_rotate
-//
 // Rotates a polyobject around its start point.
-//
 static boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, boolean checkmobjs)
 {
 	size_t i;
@@ -1451,12 +1241,8 @@ static boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, bo
 // Global Functions
 //
 
-//
-// Polyobj_GetForNum
-//
 // Retrieves a polyobject by its numeric id using hashing.
 // Returns NULL if no such polyobject exists.
-//
 polyobj_t *Polyobj_GetForNum(INT32 id)
 {
 	INT32 curidx  = PolyObjects[id % numPolyObjects].first;
@@ -1467,12 +1253,9 @@ polyobj_t *Polyobj_GetForNum(INT32 id)
 	return curidx == numPolyObjects ? NULL : &PolyObjects[curidx];
 }
 
-//
-// Polyobj_GetParent
-//
+
 // Retrieves the parenting polyobject if one exists. Returns NULL
 // otherwise.
-//
 #if 0 //unused function
 static polyobj_t *Polyobj_GetParent(polyobj_t *po)
 {
@@ -1480,12 +1263,8 @@ static polyobj_t *Polyobj_GetParent(polyobj_t *po)
 }
 #endif
 
-//
-// Polyobj_GetChild
-//
 // Iteratively retrieves the children POs of a parent,
 // sorta like P_FindSectorSpecialFromTag.
-//
 static polyobj_t *Polyobj_GetChild(polyobj_t *po, INT32 *start)
 {
 	for (; *start < numPolyObjects; (*start)++)
@@ -1504,12 +1283,8 @@ typedef struct mobjqitem_s
 	mobj_t *mo;
 } mobjqitem_t;
 
-//
-// Polyobj_InitLevel
-//
 // Called at the beginning of each map after all other line and thing
 // processing is finished.
-//
 void Polyobj_InitLevel(void)
 {
 	thinker_t   *th;
@@ -1628,9 +1403,6 @@ void Polyobj_InitLevel(void)
 	M_QueueFree(&anchorqueue);
 }
 
-//
-// Polyobj_MoveOnLoad
-//
 // Called when a savegame is being loaded. Rotates and translates an
 // existing polyobject to its position when the game was saved.
 //
@@ -1655,11 +1427,7 @@ void Polyobj_MoveOnLoad(polyobj_t *po, angle_t angle, fixed_t x, fixed_t y)
 
 // Thinker Functions
 
-//
-// T_PolyObjRotate
-//
 // Thinker function for PolyObject rotation.
-//
 void T_PolyObjRotate(polyrotate_t *th)
 {
 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
@@ -1720,11 +1488,7 @@ void T_PolyObjRotate(polyrotate_t *th)
 	}
 }
 
-//
-// Polyobj_componentSpeed
-//
 // Calculates the speed components from the desired resultant velocity.
-//
 FUNCINLINE static ATTRINLINE void Polyobj_componentSpeed(INT32 resVel, INT32 angle,
                                             fixed_t *xVel, fixed_t *yVel)
 {
@@ -1805,11 +1569,6 @@ void T_PolyObjMove(polymove_t *th)
 	}
 }
 
-//
-// T_PolyObjWaypoint
-//
-// Kinda like 'Zoom Tubes for PolyObjects'
-//
 void T_PolyObjWaypoint(polywaypoint_t *th)
 {
 	mobj_t *mo2;
@@ -2303,7 +2062,7 @@ void T_PolyDoorSwing(polyswingdoor_t *th)
 	}
 }
 
-// T_PolyObjDisplace: shift a polyobject based on a control sector's heights.
+// Shift a polyobject based on a control sector's heights.
 void T_PolyObjDisplace(polydisplace_t *th)
 {
 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
@@ -2343,7 +2102,7 @@ void T_PolyObjDisplace(polydisplace_t *th)
 		th->oldHeights = newheights;
 }
 
-// T_PolyObjRotDisplace: rotate a polyobject based on a control sector's heights.
+// Rotate a polyobject based on a control sector's heights.
 void T_PolyObjRotDisplace(polyrotdisplace_t *th)
 {
 	polyobj_t *po = Polyobj_GetForNum(th->polyObjNum);
@@ -2389,7 +2148,7 @@ static inline INT32 Polyobj_AngSpeed(INT32 speed)
 
 // Linedef Handlers
 
-INT32 EV_DoPolyObjRotate(polyrotdata_t *prdata)
+boolean EV_DoPolyObjRotate(polyrotdata_t *prdata)
 {
 	polyobj_t *po;
 	polyobj_t *oldpo;
@@ -2399,16 +2158,16 @@ INT32 EV_DoPolyObjRotate(polyrotdata_t *prdata)
 	if (!(po = Polyobj_GetForNum(prdata->polyObjNum)))
 	{
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjRotate: bad polyobj %d\n", prdata->polyObjNum);
-		return 0;
+		return false;
 	}
 
 	// don't allow line actions to affect bad polyobjects
 	if (po->isBad)
-		return 0;
+		return false;
 
 	// check for override if this polyobj already has a thinker
 	if (po->thinker && !prdata->overRide)
-		return 0;
+		return false;
 
 	// create a new thinker
 	th = Z_Malloc(sizeof(polyrotate_t), PU_LEVSPEC, NULL);
@@ -2451,10 +2210,10 @@ INT32 EV_DoPolyObjRotate(polyrotdata_t *prdata)
 	}
 
 	// action was successful
-	return 1;
+	return true;
 }
 
-INT32 EV_DoPolyObjMove(polymovedata_t *pmdata)
+boolean EV_DoPolyObjMove(polymovedata_t *pmdata)
 {
 	polyobj_t *po;
 	polyobj_t *oldpo;
@@ -2464,16 +2223,16 @@ INT32 EV_DoPolyObjMove(polymovedata_t *pmdata)
 	if (!(po = Polyobj_GetForNum(pmdata->polyObjNum)))
 	{
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjMove: bad polyobj %d\n", pmdata->polyObjNum);
-		return 0;
+		return false;
 	}
 
 	// don't allow line actions to affect bad polyobjects
 	if (po->isBad)
-		return 0;
+		return false;
 
 	// check for override if this polyobj already has a thinker
 	if (po->thinker && !pmdata->overRide)
-		return 0;
+		return false;
 
 	// create a new thinker
 	th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL);
@@ -2510,10 +2269,10 @@ INT32 EV_DoPolyObjMove(polymovedata_t *pmdata)
 	}
 
 	// action was successful
-	return 1;
+	return true;
 }
 
-INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
+boolean EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 {
 	polyobj_t *po;
 	polywaypoint_t *th;
@@ -2526,15 +2285,15 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 	if (!(po = Polyobj_GetForNum(pwdata->polyObjNum)))
 	{
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjWaypoint: bad polyobj %d\n", pwdata->polyObjNum);
-		return 0;
+		return false;
 	}
 
 	// don't allow line actions to affect bad polyobjects
 	if (po->isBad)
-		return 0;
+		return false;
 
 	if (po->thinker) // Don't crowd out another thinker.
-		return 0;
+		return false;
 
 	// create a new thinker
 	th = Z_Malloc(sizeof(polywaypoint_t), PU_LEVSPEC, NULL);
@@ -2601,7 +2360,7 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjWaypoint: Missing starting waypoint!\n");
 		po->thinker = NULL;
 		P_RemoveThinker(&th->thinker);
-		return 0;
+		return false;
 	}
 
 	// Hotfix to not crash on single-waypoint sequences -Red
@@ -2664,7 +2423,7 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjWaypoint: Missing target waypoint!\n");
 		po->thinker = NULL;
 		P_RemoveThinker(&th->thinker);
-		return 0;
+		return false;
 	}
 
 	// Set pointnum
@@ -2675,7 +2434,7 @@ INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata)
 
 	// We don't deal with the mirror crap here, we'll
 	// handle that in the T_Thinker function.
-	return 1;
+	return true;
 }
 
 static void Polyobj_doSlideDoor(polyobj_t *po, polydoordata_t *doordata)
@@ -2767,20 +2526,20 @@ static void Polyobj_doSwingDoor(polyobj_t *po, polydoordata_t *doordata)
 		Polyobj_doSwingDoor(po, doordata);
 }
 
-INT32 EV_DoPolyDoor(polydoordata_t *doordata)
+boolean EV_DoPolyDoor(polydoordata_t *doordata)
 {
 	polyobj_t *po;
 
 	if (!(po = Polyobj_GetForNum(doordata->polyObjNum)))
 	{
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyDoor: bad polyobj %d\n", doordata->polyObjNum);
-		return 0;
+		return false;
 	}
 
 	// don't allow line actions to affect bad polyobjects;
 	// polyobject doors don't allow action overrides
 	if (po->isBad || po->thinker)
-		return 0;
+		return false;
 
 	switch (doordata->doorType)
 	{
@@ -2792,13 +2551,13 @@ INT32 EV_DoPolyDoor(polydoordata_t *doordata)
 		break;
 	default:
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyDoor: unknown door type %d", doordata->doorType);
-		return 0;
+		return false;
 	}
 
-	return 1;
+	return true;
 }
 
-INT32 EV_DoPolyObjDisplace(polydisplacedata_t *prdata)
+boolean EV_DoPolyObjDisplace(polydisplacedata_t *prdata)
 {
 	polyobj_t *po;
 	polyobj_t *oldpo;
@@ -2808,12 +2567,12 @@ INT32 EV_DoPolyObjDisplace(polydisplacedata_t *prdata)
 	if (!(po = Polyobj_GetForNum(prdata->polyObjNum)))
 	{
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjRotate: bad polyobj %d\n", prdata->polyObjNum);
-		return 0;
+		return false;
 	}
 
 	// don't allow line actions to affect bad polyobjects
 	if (po->isBad)
-		return 0;
+		return false;
 
 	// create a new thinker
 	th = Z_Malloc(sizeof(polydisplace_t), PU_LEVSPEC, NULL);
@@ -2841,10 +2600,10 @@ INT32 EV_DoPolyObjDisplace(polydisplacedata_t *prdata)
 	}
 
 	// action was successful
-	return 1;
+	return true;
 }
 
-INT32 EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *prdata)
+boolean EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *prdata)
 {
 	polyobj_t *po;
 	polyobj_t *oldpo;
@@ -2854,12 +2613,12 @@ INT32 EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *prdata)
 	if (!(po = Polyobj_GetForNum(prdata->polyObjNum)))
 	{
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjRotate: bad polyobj %d\n", prdata->polyObjNum);
-		return 0;
+		return false;
 	}
 
 	// don't allow line actions to affect bad polyobjects
 	if (po->isBad)
-		return 0;
+		return false;
 
 	// create a new thinker
 	th = Z_Malloc(sizeof(polyrotdisplace_t), PU_LEVSPEC, NULL);
@@ -2887,7 +2646,7 @@ INT32 EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *prdata)
 	}
 
 	// action was successful
-	return 1;
+	return true;
 }
 
 void T_PolyObjFlag(polymove_t *th)
@@ -2936,7 +2695,7 @@ void T_PolyObjFlag(polymove_t *th)
 	Polyobj_attachToSubsec(po);     // relink to subsector
 }
 
-INT32 EV_DoPolyObjFlag(line_t *pfdata)
+boolean EV_DoPolyObjFlag(polyflagdata_t *pfdata)
 {
 	polyobj_t *po;
 	polyobj_t *oldpo;
@@ -2944,22 +2703,22 @@ INT32 EV_DoPolyObjFlag(line_t *pfdata)
 	size_t i;
 	INT32 start;
 
-	if (!(po = Polyobj_GetForNum(pfdata->tag)))
+	if (!(po = Polyobj_GetForNum(pfdata->polyObjNum)))
 	{
-		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: bad polyobj %d\n", pfdata->tag);
-		return 0;
+		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: bad polyobj %d\n", pfdata->polyObjNum);
+		return false;
 	}
 
 	// don't allow line actions to affect bad polyobjects,
 	// polyobject doors don't allow action overrides
 	if (po->isBad || po->thinker)
-		return 0;
+		return false;
 
 	// Must have even # of vertices
 	if (po->numVertices & 1)
 	{
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: Polyobject has odd # of vertices!\n");
-		return 0;
+		return false;
 	}
 
 	// create a new thinker
@@ -2969,11 +2728,11 @@ INT32 EV_DoPolyObjFlag(line_t *pfdata)
 	po->thinker = &th->thinker;
 
 	// set fields
-	th->polyObjNum = pfdata->tag;
+	th->polyObjNum = pfdata->polyObjNum;
 	th->distance   = 0;
-	th->speed      = P_AproxDistance(pfdata->dx, pfdata->dy)>>FRACBITS;
-	th->angle      = R_PointToAngle2(pfdata->v1->x, pfdata->v1->y, pfdata->v2->x, pfdata->v2->y)>>ANGLETOFINESHIFT;
-	th->momx       = sides[pfdata->sidenum[0]].textureoffset>>FRACBITS;
+	th->speed      = pfdata->speed;
+	th->angle      = pfdata->angle;
+	th->momx       = pfdata->momx;
 
 	// save current positions
 	for (i = 0; i < po->numVertices; ++i)
@@ -2985,12 +2744,12 @@ INT32 EV_DoPolyObjFlag(line_t *pfdata)
 	start = 0;
 	while ((po = Polyobj_GetChild(oldpo, &start)))
 	{
-		pfdata->tag = po->id;
+		pfdata->polyObjNum = po->id;
 		EV_DoPolyObjFlag(pfdata);
 	}
 
 	// action was successful
-	return 1;
+	return true;
 }
 
 void T_PolyObjFade(polyfade_t *th)
@@ -3088,7 +2847,7 @@ void T_PolyObjFade(polyfade_t *th)
 	}
 }
 
-INT32 EV_DoPolyObjFade(polyfadedata_t *pfdata)
+boolean EV_DoPolyObjFade(polyfadedata_t *pfdata)
 {
 	polyobj_t *po;
 	polyobj_t *oldpo;
@@ -3098,16 +2857,16 @@ INT32 EV_DoPolyObjFade(polyfadedata_t *pfdata)
 	if (!(po = Polyobj_GetForNum(pfdata->polyObjNum)))
 	{
 		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjFade: bad polyobj %d\n", pfdata->polyObjNum);
-		return 0;
+		return false;
 	}
 
 	// don't allow line actions to affect bad polyobjects
 	if (po->isBad)
-		return 0;
+		return false;
 
 	// already equal, nothing to do
 	if (po->translucency == pfdata->destvalue)
-		return 1;
+		return true;
 
 	if (po->thinker && po->thinker->function.acp1 == (actionf_p1)T_PolyObjFade)
 		P_RemoveThinker(po->thinker);
@@ -3149,9 +2908,7 @@ INT32 EV_DoPolyObjFade(polyfadedata_t *pfdata)
 	}
 
 	// action was successful
-	return 1;
+	return true;
 }
 
-#endif // ifdef POLYOBJECTS
-
 // EOF
diff --git a/src/p_polyobj.h b/src/p_polyobj.h
index d56701d2d9666021629d5bcc632680de3460413d..68aff4bf189ec518a5faeb69ecdee3894c403be5 100644
--- a/src/p_polyobj.h
+++ b/src/p_polyobj.h
@@ -18,8 +18,6 @@
 #include "p_mobj.h"
 #include "r_defs.h"
 
-// haleyjd: temporary define
-#ifdef POLYOBJECTS
 //
 // Defines
 //
@@ -31,7 +29,6 @@
 #define POLYOBJ_SPAWNCRUSH_DOOMEDNUM 762 // todo: REMOVE
 
 #define POLYOBJ_START_LINE    20
-#define POLYOBJ_EXPLICIT_LINE 21
 #define POLYINFO_SPECIALNUM   22
 
 typedef enum
@@ -301,6 +298,14 @@ typedef struct polyrotdisplacedata_s
 	UINT8 turnobjs;
 } polyrotdisplacedata_t;
 
+typedef struct polyflagdata_s
+{
+	INT32 polyObjNum;
+	INT32 speed;
+	UINT32 angle;
+	fixed_t momx;
+} polyflagdata_t;
+
 typedef struct polyfadedata_s
 {
 	INT32 polyObjNum;
@@ -322,7 +327,6 @@ boolean P_PointInsidePolyobj(polyobj_t *po, fixed_t x, fixed_t y);
 boolean P_MobjTouchingPolyobj(polyobj_t *po, mobj_t *mo);
 boolean P_MobjInsidePolyobj(polyobj_t *po, mobj_t *mo);
 boolean P_BBoxInsidePolyobj(polyobj_t *po, fixed_t *bbox);
-void Polyobj_GetInfo(INT16 poid, INT32 *poflags, INT32 *parentID, INT32 *potrans);
 
 // thinkers (needed in p_saveg.c)
 void T_PolyObjRotate(polyrotate_t *);
@@ -335,14 +339,14 @@ void T_PolyObjRotDisplace  (polyrotdisplace_t *);
 void T_PolyObjFlag  (polymove_t *);
 void T_PolyObjFade  (polyfade_t *);
 
-INT32 EV_DoPolyDoor(polydoordata_t *);
-INT32 EV_DoPolyObjMove(polymovedata_t *);
-INT32 EV_DoPolyObjWaypoint(polywaypointdata_t *);
-INT32 EV_DoPolyObjRotate(polyrotdata_t *);
-INT32 EV_DoPolyObjDisplace(polydisplacedata_t *);
-INT32 EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *);
-INT32 EV_DoPolyObjFlag(struct line_s *);
-INT32 EV_DoPolyObjFade(polyfadedata_t *);
+boolean EV_DoPolyDoor(polydoordata_t *);
+boolean EV_DoPolyObjMove(polymovedata_t *);
+boolean EV_DoPolyObjWaypoint(polywaypointdata_t *);
+boolean EV_DoPolyObjRotate(polyrotdata_t *);
+boolean EV_DoPolyObjDisplace(polydisplacedata_t *);
+boolean EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *);
+boolean EV_DoPolyObjFlag(polyflagdata_t *);
+boolean EV_DoPolyObjFade(polyfadedata_t *);
 
 
 //
@@ -353,8 +357,6 @@ extern polyobj_t *PolyObjects;
 extern INT32 numPolyObjects;
 extern polymaplink_t **polyblocklinks; // polyobject blockmap
 
-#endif // ifdef POLYOBJECTS
-
 #endif
 
 // EOF
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 716904432f5ef6289a1d2def09fcf923b69e86c5..d00845879d231ac0286ff4f4979a46ff247a69f8 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -59,9 +59,6 @@ typedef enum
 	DRONE      = 0x80,
 } player_saveflags;
 
-//
-// P_ArchivePlayer
-//
 static inline void P_ArchivePlayer(void)
 {
 	const player_t *player = &players[consoleplayer];
@@ -77,9 +74,6 @@ static inline void P_ArchivePlayer(void)
 	WRITEINT32(save_p, player->continues);
 }
 
-//
-// P_UnArchivePlayer
-//
 static inline void P_UnArchivePlayer(void)
 {
 	INT16 skininfo = READUINT16(save_p);
@@ -92,9 +86,6 @@ static inline void P_UnArchivePlayer(void)
 	savedata.continues = READINT32(save_p);
 }
 
-//
-// P_NetArchivePlayers
-//
 static void P_NetArchivePlayers(void)
 {
 	INT32 i, j;
@@ -300,9 +291,6 @@ static void P_NetArchivePlayers(void)
 	}
 }
 
-//
-// P_NetUnArchivePlayers
-//
 static void P_NetUnArchivePlayers(void)
 {
 	INT32 i, j;
@@ -755,6 +743,7 @@ static void P_NetUnArchiveColormaps(void)
 // diff3 flags
 #define SD_TAGLIST   0x01
 #define SD_COLORMAP  0x02
+#define SD_CRUMBLESTATE 0x04
 
 #define LD_FLAG     0x01
 #define LD_SPECIAL  0x02
@@ -771,29 +760,102 @@ static void P_NetUnArchiveColormaps(void)
 #define LD_S2BOTTEX 0x04
 #define LD_S2MIDTEX 0x08
 
-//
-// P_NetArchiveWorld
-//
-static void P_NetArchiveWorld(void)
+#define FD_FLAGS 0x01
+#define FD_ALPHA 0x02
+
+// Check if any of the sector's FOFs differ from how they spawned
+static boolean CheckFFloorDiff(const sector_t *ss)
 {
-	size_t i;
-	INT32 statsec = 0, statline = 0;
-	const line_t *li = lines;
-	const line_t *spawnli = spawnlines;
-	const side_t *si;
-	const side_t *spawnsi;
-	UINT8 *put;
+	ffloor_t *rover;
+
+	for (rover = ss->ffloors; rover; rover = rover->next)
+	{
+		if (rover->flags != rover->spawnflags
+		|| rover->alpha != rover->spawnalpha)
+			{
+				return true; // we found an FOF that changed!
+				// don't bother checking for more, we do that later
+			}
+	}
+	return false;
+}
 
+// Special case: save the stats of all modified ffloors along with their ffloor "number"s
+// we don't bother with ffloors that haven't changed, that would just add to savegame even more than is really needed
+static void ArchiveFFloors(const sector_t *ss)
+{
+	size_t j = 0; // ss->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
+	ffloor_t *rover;
+	UINT8 fflr_diff;
+	for (rover = ss->ffloors; rover; rover = rover->next)
+	{
+		fflr_diff = 0; // reset diff flags
+		if (rover->flags != rover->spawnflags)
+			fflr_diff |= FD_FLAGS;
+		if (rover->alpha != rover->spawnalpha)
+			fflr_diff |= FD_ALPHA;
+
+		if (fflr_diff)
+		{
+			WRITEUINT16(save_p, j); // save ffloor "number"
+			WRITEUINT8(save_p, fflr_diff);
+			if (fflr_diff & FD_FLAGS)
+				WRITEUINT32(save_p, rover->flags);
+			if (fflr_diff & FD_ALPHA)
+				WRITEINT16(save_p, rover->alpha);
+		}
+		j++;
+	}
+	WRITEUINT16(save_p, 0xffff);
+}
+
+static void UnArchiveFFloors(const sector_t *ss)
+{
+	UINT16 j = 0; // number of current ffloor in loop
+	UINT16 fflr_i; // saved ffloor "number" of next modified ffloor
+	UINT16 fflr_diff; // saved ffloor diff
+	ffloor_t *rover;
+
+	rover = ss->ffloors;
+	if (!rover) // it is assumed sectors[i].ffloors actually exists, but just in case...
+		I_Error("Sector does not have any ffloors!");
+
+	fflr_i = READUINT16(save_p); // get first modified ffloor's number ready
+	for (;;) // for some reason the usual for (rover = x; ...) thing doesn't work here?
+	{
+		if (fflr_i == 0xffff) // end of modified ffloors list, let's stop already
+			break;
+		// should NEVER need to be checked
+		//if (rover == NULL)
+			//break;
+		if (j != fflr_i) // this ffloor was not modified
+		{
+			j++;
+			rover = rover->next;
+			continue;
+		}
+
+		fflr_diff = READUINT8(save_p);
+
+		if (fflr_diff & FD_FLAGS)
+			rover->flags = READUINT32(save_p);
+		if (fflr_diff & FD_ALPHA)
+			rover->alpha = READINT16(save_p);
+
+		fflr_i = READUINT16(save_p); // get next ffloor "number" ready
+
+		j++;
+		rover = rover->next;
+	}
+}
+
+static void ArchiveSectors(void)
+{
+	size_t i;
 	const sector_t *ss = sectors;
 	const sector_t *spawnss = spawnsectors;
 	UINT8 diff, diff2, diff3;
 
-	// initialize colormap vars because paranoia
-	ClearNetColormaps();
-
-	WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD);
-	put = save_p;
-
 	for (i = 0; i < numsectors; i++, ss++, spawnss++)
 	{
 		diff = diff2 = diff3 = 0;
@@ -834,21 +896,11 @@ static void P_NetArchiveWorld(void)
 
 		if (ss->extra_colormap != spawnss->extra_colormap)
 			diff3 |= SD_COLORMAP;
+		if (ss->crumblestate)
+			diff3 |= SD_CRUMBLESTATE;
 
-		// Check if any of the sector's FOFs differ from how they spawned
-		if (ss->ffloors)
-		{
-			ffloor_t *rover;
-			for (rover = ss->ffloors; rover; rover = rover->next)
-			{
-				if (rover->flags != rover->spawnflags
-				|| rover->alpha != rover->spawnalpha)
-					{
-						diff |= SD_FFLOORS; // we found an FOF that changed!
-						break; // don't bother checking for more, we do that later
-					}
-			}
-		}
+		if (ss->ffloors && CheckFFloorDiff(ss))
+			diff |= SD_FFLOORS;
 
 		if (diff3)
 			diff2 |= SD_DIFF3;
@@ -858,87 +910,142 @@ static void P_NetArchiveWorld(void)
 
 		if (diff)
 		{
-			statsec++;
-
-			WRITEUINT16(put, i);
-			WRITEUINT8(put, diff);
+			WRITEUINT16(save_p, i);
+			WRITEUINT8(save_p, diff);
 			if (diff & SD_DIFF2)
-				WRITEUINT8(put, diff2);
+				WRITEUINT8(save_p, diff2);
 			if (diff2 & SD_DIFF3)
-				WRITEUINT8(put, diff3);
+				WRITEUINT8(save_p, diff3);
 			if (diff & SD_FLOORHT)
-				WRITEFIXED(put, ss->floorheight);
+				WRITEFIXED(save_p, ss->floorheight);
 			if (diff & SD_CEILHT)
-				WRITEFIXED(put, ss->ceilingheight);
+				WRITEFIXED(save_p, ss->ceilingheight);
 			if (diff & SD_FLOORPIC)
-				WRITEMEM(put, levelflats[ss->floorpic].name, 8);
+				WRITEMEM(save_p, levelflats[ss->floorpic].name, 8);
 			if (diff & SD_CEILPIC)
-				WRITEMEM(put, levelflats[ss->ceilingpic].name, 8);
+				WRITEMEM(save_p, levelflats[ss->ceilingpic].name, 8);
 			if (diff & SD_LIGHT)
-				WRITEINT16(put, ss->lightlevel);
+				WRITEINT16(save_p, ss->lightlevel);
 			if (diff & SD_SPECIAL)
-				WRITEINT16(put, ss->special);
+				WRITEINT16(save_p, ss->special);
 			if (diff2 & SD_FXOFFS)
-				WRITEFIXED(put, ss->floor_xoffs);
+				WRITEFIXED(save_p, ss->floor_xoffs);
 			if (diff2 & SD_FYOFFS)
-				WRITEFIXED(put, ss->floor_yoffs);
+				WRITEFIXED(save_p, ss->floor_yoffs);
 			if (diff2 & SD_CXOFFS)
-				WRITEFIXED(put, ss->ceiling_xoffs);
+				WRITEFIXED(save_p, ss->ceiling_xoffs);
 			if (diff2 & SD_CYOFFS)
-				WRITEFIXED(put, ss->ceiling_yoffs);
+				WRITEFIXED(save_p, ss->ceiling_yoffs);
 			if (diff2 & SD_FLOORANG)
-				WRITEANGLE(put, ss->floorpic_angle);
+				WRITEANGLE(save_p, ss->floorpic_angle);
 			if (diff2 & SD_CEILANG)
-				WRITEANGLE(put, ss->ceilingpic_angle);
+				WRITEANGLE(save_p, ss->ceilingpic_angle);
 			if (diff2 & SD_TAG) // save only the tag
-				WRITEINT16(put, ss->tag);
+				WRITEINT16(save_p, ss->tag);
 			if (diff3 & SD_TAGLIST) // save both firsttag and nexttag
 			{ // either of these could be changed even if tag isn't
-				WRITEINT32(put, ss->firsttag);
-				WRITEINT32(put, ss->nexttag);
+				WRITEINT32(save_p, ss->firsttag);
+				WRITEINT32(save_p, ss->nexttag);
 			}
 
 			if (diff3 & SD_COLORMAP)
-				WRITEUINT32(put, CheckAddNetColormapToList(ss->extra_colormap));
+				WRITEUINT32(save_p, CheckAddNetColormapToList(ss->extra_colormap));
 					// returns existing index if already added, or appends to net_colormaps and returns new index
-
-			// Special case: save the stats of all modified ffloors along with their ffloor "number"s
-			// we don't bother with ffloors that haven't changed, that would just add to savegame even more than is really needed
+			if (diff3 & SD_CRUMBLESTATE)
+				WRITEINT32(save_p, ss->crumblestate);
 			if (diff & SD_FFLOORS)
-			{
-				size_t j = 0; // ss->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
-				ffloor_t *rover;
-				UINT8 fflr_diff;
-				for (rover = ss->ffloors; rover; rover = rover->next)
-				{
-					fflr_diff = 0; // reset diff flags
-					if (rover->flags != rover->spawnflags)
-						fflr_diff |= 1;
-					if (rover->alpha != rover->spawnalpha)
-						fflr_diff |= 2;
-
-					if (fflr_diff)
-					{
-						WRITEUINT16(put, j); // save ffloor "number"
-						WRITEUINT8(put, fflr_diff);
-						if (fflr_diff & 1)
-							WRITEUINT32(put, rover->flags);
-						if (fflr_diff & 2)
-							WRITEINT16(put, rover->alpha);
-					}
-					j++;
-				}
-				WRITEUINT16(put, 0xffff);
-			}
+				ArchiveFFloors(ss);
+		}
+	}
+
+	WRITEUINT16(save_p, 0xffff);
+}
+
+static void UnArchiveSectors(void)
+{
+	UINT16 i;
+	UINT8 diff, diff2, diff3;
+	for (;;)
+	{
+		i = READUINT16(save_p);
+
+		if (i == 0xffff)
+			break;
+
+		if (i > numsectors)
+			I_Error("Invalid sector number %u from server (expected end at %s)", i, sizeu1(numsectors));
+
+		diff = READUINT8(save_p);
+		if (diff & SD_DIFF2)
+			diff2 = READUINT8(save_p);
+		else
+			diff2 = 0;
+		if (diff2 & SD_DIFF3)
+			diff3 = READUINT8(save_p);
+		else
+			diff3 = 0;
+
+		if (diff & SD_FLOORHT)
+			sectors[i].floorheight = READFIXED(save_p);
+		if (diff & SD_CEILHT)
+			sectors[i].ceilingheight = READFIXED(save_p);
+		if (diff & SD_FLOORPIC)
+		{
+			sectors[i].floorpic = P_AddLevelFlatRuntime((char *)save_p);
+			save_p += 8;
+		}
+		if (diff & SD_CEILPIC)
+		{
+			sectors[i].ceilingpic = P_AddLevelFlatRuntime((char *)save_p);
+			save_p += 8;
 		}
+		if (diff & SD_LIGHT)
+			sectors[i].lightlevel = READINT16(save_p);
+		if (diff & SD_SPECIAL)
+			sectors[i].special = READINT16(save_p);
+
+		if (diff2 & SD_FXOFFS)
+			sectors[i].floor_xoffs = READFIXED(save_p);
+		if (diff2 & SD_FYOFFS)
+			sectors[i].floor_yoffs = READFIXED(save_p);
+		if (diff2 & SD_CXOFFS)
+			sectors[i].ceiling_xoffs = READFIXED(save_p);
+		if (diff2 & SD_CYOFFS)
+			sectors[i].ceiling_yoffs = READFIXED(save_p);
+		if (diff2 & SD_FLOORANG)
+			sectors[i].floorpic_angle  = READANGLE(save_p);
+		if (diff2 & SD_CEILANG)
+			sectors[i].ceilingpic_angle = READANGLE(save_p);
+		if (diff2 & SD_TAG)
+			sectors[i].tag = READINT16(save_p); // DON'T use P_ChangeSectorTag
+		if (diff3 & SD_TAGLIST)
+		{
+			sectors[i].firsttag = READINT32(save_p);
+			sectors[i].nexttag = READINT32(save_p);
+		}
+
+		if (diff3 & SD_COLORMAP)
+			sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save_p));
+		if (diff3 & SD_CRUMBLESTATE)
+			sectors[i].crumblestate = READINT32(save_p);
+
+		if (diff & SD_FFLOORS)
+			UnArchiveFFloors(&sectors[i]);
 	}
+}
 
-	WRITEUINT16(put, 0xffff);
+static void ArchiveLines(void)
+{
+	size_t i;
+	const line_t *li = lines;
+	const line_t *spawnli = spawnlines;
+	const side_t *si;
+	const side_t *spawnsi;
+	UINT8 diff, diff2; // no diff3
 
-	// do lines
 	for (i = 0; i < numlines; i++, spawnli++, li++)
 	{
-		diff = diff2 = diff3 = 0;
+		diff = diff2 = 0;
 
 		if (li->special != spawnli->special)
 			diff |= LD_SPECIAL;
@@ -978,223 +1085,126 @@ static void P_NetArchiveWorld(void)
 
 		if (diff)
 		{
-			statline++;
-			WRITEINT16(put, i);
-			WRITEUINT8(put, diff);
+			WRITEINT16(save_p, i);
+			WRITEUINT8(save_p, diff);
 			if (diff & LD_DIFF2)
-				WRITEUINT8(put, diff2);
+				WRITEUINT8(save_p, diff2);
 			if (diff & LD_FLAG)
-				WRITEINT16(put, li->flags);
+				WRITEINT16(save_p, li->flags);
 			if (diff & LD_SPECIAL)
-				WRITEINT16(put, li->special);
+				WRITEINT16(save_p, li->special);
 			if (diff & LD_CLLCOUNT)
-				WRITEINT16(put, li->callcount);
+				WRITEINT16(save_p, li->callcount);
 
 			si = &sides[li->sidenum[0]];
 			if (diff & LD_S1TEXOFF)
-				WRITEFIXED(put, si->textureoffset);
+				WRITEFIXED(save_p, si->textureoffset);
 			if (diff & LD_S1TOPTEX)
-				WRITEINT32(put, si->toptexture);
+				WRITEINT32(save_p, si->toptexture);
 			if (diff & LD_S1BOTTEX)
-				WRITEINT32(put, si->bottomtexture);
+				WRITEINT32(save_p, si->bottomtexture);
 			if (diff & LD_S1MIDTEX)
-				WRITEINT32(put, si->midtexture);
+				WRITEINT32(save_p, si->midtexture);
 
 			si = &sides[li->sidenum[1]];
 			if (diff2 & LD_S2TEXOFF)
-				WRITEFIXED(put, si->textureoffset);
+				WRITEFIXED(save_p, si->textureoffset);
 			if (diff2 & LD_S2TOPTEX)
-				WRITEINT32(put, si->toptexture);
+				WRITEINT32(save_p, si->toptexture);
 			if (diff2 & LD_S2BOTTEX)
-				WRITEINT32(put, si->bottomtexture);
+				WRITEINT32(save_p, si->bottomtexture);
 			if (diff2 & LD_S2MIDTEX)
-				WRITEINT32(put, si->midtexture);
+				WRITEINT32(save_p, si->midtexture);
 		}
 	}
-	WRITEUINT16(put, 0xffff);
-	R_ClearTextureNumCache(false);
-
-	save_p = put;
+	WRITEUINT16(save_p, 0xffff);
 }
 
-//
-// P_NetUnArchiveWorld
-//
-static void P_NetUnArchiveWorld(void)
+static void UnArchiveLines(void)
 {
 	UINT16 i;
 	line_t *li;
 	side_t *si;
-	UINT8 *get;
-	UINT8 diff, diff2, diff3;
-
-	if (READUINT32(save_p) != ARCHIVEBLOCK_WORLD)
-		I_Error("Bad $$$.sav at archive block World");
-
-	// initialize colormap vars because paranoia
-	ClearNetColormaps();
-
-	// count the level's ffloors so that colormap loading can have an upper limit
-	for (i = 0; i < numsectors; i++)
-	{
-		ffloor_t *rover;
-		for (rover = sectors[i].ffloors; rover; rover = rover->next)
-			num_ffloors++;
-	}
-
-	get = save_p;
+	UINT8 diff, diff2; // no diff3
 
 	for (;;)
 	{
-		i = READUINT16(get);
-
-		if (i == 0xffff)
-			break;
-
-		if (i > numsectors)
-			I_Error("Invalid sector number %u from server (expected end at %s)", i, sizeu1(numsectors));
-
-		diff = READUINT8(get);
-		if (diff & SD_DIFF2)
-			diff2 = READUINT8(get);
-		else
-			diff2 = 0;
-		if (diff2 & SD_DIFF3)
-			diff3 = READUINT8(get);
-		else
-			diff3 = 0;
-
-		if (diff & SD_FLOORHT)
-			sectors[i].floorheight = READFIXED(get);
-		if (diff & SD_CEILHT)
-			sectors[i].ceilingheight = READFIXED(get);
-		if (diff & SD_FLOORPIC)
-		{
-			sectors[i].floorpic = P_AddLevelFlatRuntime((char *)get);
-			get += 8;
-		}
-		if (diff & SD_CEILPIC)
-		{
-			sectors[i].ceilingpic = P_AddLevelFlatRuntime((char *)get);
-			get += 8;
-		}
-		if (diff & SD_LIGHT)
-			sectors[i].lightlevel = READINT16(get);
-		if (diff & SD_SPECIAL)
-			sectors[i].special = READINT16(get);
-
-		if (diff2 & SD_FXOFFS)
-			sectors[i].floor_xoffs = READFIXED(get);
-		if (diff2 & SD_FYOFFS)
-			sectors[i].floor_yoffs = READFIXED(get);
-		if (diff2 & SD_CXOFFS)
-			sectors[i].ceiling_xoffs = READFIXED(get);
-		if (diff2 & SD_CYOFFS)
-			sectors[i].ceiling_yoffs = READFIXED(get);
-		if (diff2 & SD_FLOORANG)
-			sectors[i].floorpic_angle  = READANGLE(get);
-		if (diff2 & SD_CEILANG)
-			sectors[i].ceilingpic_angle = READANGLE(get);
-		if (diff2 & SD_TAG)
-			sectors[i].tag = READINT16(get); // DON'T use P_ChangeSectorTag
-		if (diff3 & SD_TAGLIST)
-		{
-			sectors[i].firsttag = READINT32(get);
-			sectors[i].nexttag = READINT32(get);
-		}
-
-		if (diff3 & SD_COLORMAP)
-			sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(get));
-
-		if (diff & SD_FFLOORS)
-		{
-			UINT16 j = 0; // number of current ffloor in loop
-			UINT16 fflr_i; // saved ffloor "number" of next modified ffloor
-			UINT16 fflr_diff; // saved ffloor diff
-			ffloor_t *rover;
-
-			rover = sectors[i].ffloors;
-			if (!rover) // it is assumed sectors[i].ffloors actually exists, but just in case...
-				I_Error("Sector does not have any ffloors!");
-
-			fflr_i = READUINT16(get); // get first modified ffloor's number ready
-			for (;;) // for some reason the usual for (rover = x; ...) thing doesn't work here?
-			{
-				if (fflr_i == 0xffff) // end of modified ffloors list, let's stop already
-					break;
-				// should NEVER need to be checked
-				//if (rover == NULL)
-					//break;
-				if (j != fflr_i) // this ffloor was not modified
-				{
-					j++;
-					rover = rover->next;
-					continue;
-				}
-
-				fflr_diff = READUINT8(get);
-
-				if (fflr_diff & 1)
-					rover->flags = READUINT32(get);
-				if (fflr_diff & 2)
-					rover->alpha = READINT16(get);
-
-				fflr_i = READUINT16(get); // get next ffloor "number" ready
-
-				j++;
-				rover = rover->next;
-			}
-		}
-	}
-
-	for (;;)
-	{
-		i = READUINT16(get);
+		i = READUINT16(save_p);
 
 		if (i == 0xffff)
 			break;
 		if (i > numlines)
 			I_Error("Invalid line number %u from server", i);
 
-		diff = READUINT8(get);
+		diff = READUINT8(save_p);
 		li = &lines[i];
 
 		if (diff & LD_DIFF2)
-			diff2 = READUINT8(get);
+			diff2 = READUINT8(save_p);
 		else
 			diff2 = 0;
 
-		diff3 = 0;
-
 		if (diff & LD_FLAG)
-			li->flags = READINT16(get);
+			li->flags = READINT16(save_p);
 		if (diff & LD_SPECIAL)
-			li->special = READINT16(get);
+			li->special = READINT16(save_p);
 		if (diff & LD_CLLCOUNT)
-			li->callcount = READINT16(get);
+			li->callcount = READINT16(save_p);
 
 		si = &sides[li->sidenum[0]];
 		if (diff & LD_S1TEXOFF)
-			si->textureoffset = READFIXED(get);
+			si->textureoffset = READFIXED(save_p);
 		if (diff & LD_S1TOPTEX)
-			si->toptexture = READINT32(get);
+			si->toptexture = READINT32(save_p);
 		if (diff & LD_S1BOTTEX)
-			si->bottomtexture = READINT32(get);
+			si->bottomtexture = READINT32(save_p);
 		if (diff & LD_S1MIDTEX)
-			si->midtexture = READINT32(get);
+			si->midtexture = READINT32(save_p);
 
 		si = &sides[li->sidenum[1]];
 		if (diff2 & LD_S2TEXOFF)
-			si->textureoffset = READFIXED(get);
+			si->textureoffset = READFIXED(save_p);
 		if (diff2 & LD_S2TOPTEX)
-			si->toptexture = READINT32(get);
+			si->toptexture = READINT32(save_p);
 		if (diff2 & LD_S2BOTTEX)
-			si->bottomtexture = READINT32(get);
+			si->bottomtexture = READINT32(save_p);
 		if (diff2 & LD_S2MIDTEX)
-			si->midtexture = READINT32(get);
+			si->midtexture = READINT32(save_p);
+	}
+}
+
+static void P_NetArchiveWorld(void)
+{
+	// initialize colormap vars because paranoia
+	ClearNetColormaps();
+
+	WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD);
+
+	ArchiveSectors();
+	ArchiveLines();
+	R_ClearTextureNumCache(false);
+}
+
+static void P_NetUnArchiveWorld(void)
+{
+	UINT16 i;
+
+	if (READUINT32(save_p) != ARCHIVEBLOCK_WORLD)
+		I_Error("Bad $$$.sav at archive block World");
+
+	// initialize colormap vars because paranoia
+	ClearNetColormaps();
+
+	// count the level's ffloors so that colormap loading can have an upper limit
+	for (i = 0; i < numsectors; i++)
+	{
+		ffloor_t *rover;
+		for (rover = sectors[i].ffloors; rover; rover = rover->next)
+			num_ffloors++;
 	}
 
-	save_p = get;
+	UnArchiveSectors();
+	UnArchiveLines();
 }
 
 //
@@ -1273,9 +1283,7 @@ typedef enum
 	tc_startcrumble,
 	tc_marioblock,
 	tc_marioblockchecker,
-	tc_spikesector,
 	tc_floatsector,
-	tc_bridgethinker,
 	tc_crushceiling,
 	tc_scroll,
 	tc_friction,
@@ -1292,7 +1300,6 @@ typedef enum
 	tc_planedisplace,
 	tc_dynslopeline,
 	tc_dynslopevert,
-#ifdef POLYOBJECTS
 	tc_polyrotate, // haleyjd 03/26/06: polyobjects
 	tc_polymove,
 	tc_polywaypoint,
@@ -1302,7 +1309,6 @@ typedef enum
 	tc_polydisplace,
 	tc_polyrotdisplace,
 	tc_polyfade,
-#endif
 	tc_end
 } specials_e;
 
@@ -1336,11 +1342,6 @@ static UINT32 SaveSlope(const pslope_t *slope)
 	return 0xFFFFFFFF;
 }
 
-//
-// SaveMobjThinker
-//
-// Saves a mobj_t thinker
-//
 static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 {
 	const mobj_t *mobj = (const mobj_t *)th;
@@ -1492,42 +1493,14 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 
 	if (diff2 & MD2_FLOORROVER)
 	{
-		ffloor_t *rover;
-		size_t i = 0;
-		UINT32 roverindex = 0;
-
-		for (rover = mobj->floorrover->target->ffloors; rover; rover = rover->next)
-		{
-			if (rover == mobj->floorrover)
-			{
-				roverindex = i;
-				break;
-			}
-			i++;
-		}
-
-		WRITEUINT32(save_p, (UINT32)(mobj->floorrover->target - sectors));
-		WRITEUINT32(save_p, rover ? roverindex : i); // store max index to denote invalid ffloor ref
+		WRITEUINT32(save_p, SaveSector(mobj->floorrover->target));
+		WRITEUINT16(save_p, P_GetFFloorID(mobj->floorrover));
 	}
 
 	if (diff2 & MD2_CEILINGROVER)
 	{
-		ffloor_t *rover;
-		size_t i = 0;
-		UINT32 roverindex = 0;
-
-		for (rover = mobj->ceilingrover->target->ffloors; rover; rover = rover->next)
-		{
-			if (rover == mobj->ceilingrover)
-			{
-				roverindex = i;
-				break;
-			}
-			i++;
-		}
-
-		WRITEUINT32(save_p, (UINT32)(mobj->ceilingrover->target - sectors));
-		WRITEUINT32(save_p, rover ? roverindex : i); // store max index to denote invalid ffloor ref
+		WRITEUINT32(save_p, SaveSector(mobj->ceilingrover->target));
+		WRITEUINT16(save_p, P_GetFFloorID(mobj->ceilingrover));
 	}
 
 	if (diff & MD_SPAWNPOINT)
@@ -1641,30 +1614,111 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
 
-//
-// SaveSpecialLevelThinker
-//
-// Saves a levelspecthink_t thinker
-//
-static void SaveSpecialLevelThinker(const thinker_t *th, const UINT8 type)
+static void SaveNoEnemiesThinker(const thinker_t *th, const UINT8 type)
 {
-	const levelspecthink_t *ht  = (const void *)th;
+	const noenemies_t *ht  = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+}
+
+static void SaveBounceCheeseThinker(const thinker_t *th, const UINT8 type)
+{
+	const bouncecheese_t *ht  = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEUINT32(save_p, SaveSector(ht->sector));
+	WRITEFIXED(save_p, ht->speed);
+	WRITEFIXED(save_p, ht->distance);
+	WRITEFIXED(save_p, ht->floorwasheight);
+	WRITEFIXED(save_p, ht->ceilingwasheight);
+	WRITECHAR(save_p, ht->low);
+}
+
+static void SaveContinuousFallThinker(const thinker_t *th, const UINT8 type)
+{
+	const continuousfall_t *ht  = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveSector(ht->sector));
+	WRITEFIXED(save_p, ht->speed);
+	WRITEINT32(save_p, ht->direction);
+	WRITEFIXED(save_p, ht->floorstartheight);
+	WRITEFIXED(save_p, ht->ceilingstartheight);
+	WRITEFIXED(save_p, ht->destheight);
+}
+
+static void SaveMarioBlockThinker(const thinker_t *th, const UINT8 type)
+{
+	const mariothink_t *ht  = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveSector(ht->sector));
+	WRITEFIXED(save_p, ht->speed);
+	WRITEINT32(save_p, ht->direction);
+	WRITEFIXED(save_p, ht->floorstartheight);
+	WRITEFIXED(save_p, ht->ceilingstartheight);
+	WRITEINT16(save_p, ht->tag);
+}
+
+static void SaveMarioCheckThinker(const thinker_t *th, const UINT8 type)
+{
+	const mariocheck_t *ht  = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEUINT32(save_p, SaveSector(ht->sector));
+}
+
+static void SaveThwompThinker(const thinker_t *th, const UINT8 type)
+{
+	const thwomp_t *ht  = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEUINT32(save_p, SaveSector(ht->sector));
+	WRITEFIXED(save_p, ht->crushspeed);
+	WRITEFIXED(save_p, ht->retractspeed);
+	WRITEINT32(save_p, ht->direction);
+	WRITEFIXED(save_p, ht->floorstartheight);
+	WRITEFIXED(save_p, ht->ceilingstartheight);
+	WRITEINT32(save_p, ht->delay);
+	WRITEINT16(save_p, ht->tag);
+	WRITEUINT16(save_p, ht->sound);
+}
+
+static void SaveFloatThinker(const thinker_t *th, const UINT8 type)
+{
+	const floatthink_t *ht  = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEUINT32(save_p, SaveSector(ht->sector));
+	WRITEINT16(save_p, ht->tag);
+}
+
+static void SaveEachTimeThinker(const thinker_t *th, const UINT8 type)
+{
+	const eachtime_t *ht  = (const void *)th;
 	size_t i;
 	WRITEUINT8(save_p, type);
-	for (i = 0; i < 16; i++)
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		WRITEFIXED(save_p, ht->vars[i]); //var[16]
-		WRITEFIXED(save_p, ht->var2s[i]); //var[16]
+		WRITECHAR(save_p, ht->playersInArea[i]);
+		WRITECHAR(save_p, ht->playersOnArea[i]);
 	}
-	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITECHAR(save_p, ht->triggerOnExit);
+}
+
+static void SaveRaiseThinker(const thinker_t *th, const UINT8 type)
+{
+	const raise_t *ht  = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEINT16(save_p, ht->tag);
 	WRITEUINT32(save_p, SaveSector(ht->sector));
+	WRITEFIXED(save_p, ht->ceilingbottom);
+	WRITEFIXED(save_p, ht->ceilingtop);
+	WRITEFIXED(save_p, ht->basespeed);
+	WRITEFIXED(save_p, ht->extraspeed);
+	WRITEUINT8(save_p, ht->shaketimer);
+	WRITEUINT8(save_p, ht->flags);
 }
 
-//
-// SaveCeilingThinker
-//
-// Saves a ceiling_t thinker
-//
 static void SaveCeilingThinker(const thinker_t *th, const UINT8 type)
 {
 	const ceiling_t *ht = (const void *)th;
@@ -1686,11 +1740,6 @@ static void SaveCeilingThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->sourceline);
 }
 
-//
-// SaveFloormoveThinker
-//
-// Saves a floormove_t thinker
-//
 static void SaveFloormoveThinker(const thinker_t *th, const UINT8 type)
 {
 	const floormove_t *ht = (const void *)th;
@@ -1707,11 +1756,6 @@ static void SaveFloormoveThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->delaytimer);
 }
 
-//
-// SaveLightflashThinker
-//
-// Saves a lightflash_t thinker
-//
 static void SaveLightflashThinker(const thinker_t *th, const UINT8 type)
 {
 	const lightflash_t *ht = (const void *)th;
@@ -1721,11 +1765,6 @@ static void SaveLightflashThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->minlight);
 }
 
-//
-// SaveStrobeThinker
-//
-// Saves a strobe_t thinker
-//
 static void SaveStrobeThinker(const thinker_t *th, const UINT8 type)
 {
 	const strobe_t *ht = (const void *)th;
@@ -1738,11 +1777,6 @@ static void SaveStrobeThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->brighttime);
 }
 
-//
-// SaveGlowThinker
-//
-// Saves a glow_t thinker
-//
 static void SaveGlowThinker(const thinker_t *th, const UINT8 type)
 {
 	const glow_t *ht = (const void *)th;
@@ -1753,11 +1787,7 @@ static void SaveGlowThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->direction);
 	WRITEINT32(save_p, ht->speed);
 }
-//
-// SaveFireflickerThinker
-//
-// Saves a fireflicker_t thinker
-//
+
 static inline void SaveFireflickerThinker(const thinker_t *th, const UINT8 type)
 {
 	const fireflicker_t *ht = (const void *)th;
@@ -1768,11 +1798,7 @@ static inline void SaveFireflickerThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->maxlight);
 	WRITEINT32(save_p, ht->minlight);
 }
-//
-// SaveElevatorThinker
-//
-// Saves a elevator_t thinker
-//
+
 static void SaveElevatorThinker(const thinker_t *th, const UINT8 type)
 {
 	const elevator_t *ht = (const void *)th;
@@ -1792,15 +1818,26 @@ static void SaveElevatorThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->delaytimer);
 	WRITEFIXED(save_p, ht->floorwasheight);
 	WRITEFIXED(save_p, ht->ceilingwasheight);
-	WRITEUINT32(save_p, SavePlayer(ht->player)); // was dummy
 	WRITEUINT32(save_p, SaveLine(ht->sourceline));
 }
 
-//
-// SaveScrollThinker
-//
-// Saves a scroll_t thinker
-//
+static void SaveCrumbleThinker(const thinker_t *th, const UINT8 type)
+{
+	const crumble_t *ht = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEUINT32(save_p, SaveSector(ht->sector));
+	WRITEUINT32(save_p, SaveSector(ht->actionsector));
+	WRITEUINT32(save_p, SavePlayer(ht->player)); // was dummy
+	WRITEINT32(save_p, ht->direction);
+	WRITEINT32(save_p, ht->origalpha);
+	WRITEINT32(save_p, ht->timer);
+	WRITEFIXED(save_p, ht->speed);
+	WRITEFIXED(save_p, ht->floorwasheight);
+	WRITEFIXED(save_p, ht->ceilingwasheight);
+	WRITEUINT8(save_p, ht->flags);
+}
+
 static inline void SaveScrollThinker(const thinker_t *th, const UINT8 type)
 {
 	const scroll_t *ht = (const void *)th;
@@ -1817,11 +1854,6 @@ static inline void SaveScrollThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->type);
 }
 
-//
-// SaveFrictionThinker
-//
-// Saves a friction_t thinker
-//
 static inline void SaveFrictionThinker(const thinker_t *th, const UINT8 type)
 {
 	const friction_t *ht = (const void *)th;
@@ -1833,11 +1865,6 @@ static inline void SaveFrictionThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->roverfriction);
 }
 
-//
-// SavePusherThinker
-//
-// Saves a pusher_t thinker
-//
 static inline void SavePusherThinker(const thinker_t *th, const UINT8 type)
 {
 	const pusher_t *ht = (const void *)th;
@@ -1857,25 +1884,15 @@ static inline void SavePusherThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->slider);
 }
 
-//
-// SaveLaserThinker
-//
-// Saves a laserthink_t thinker
-//
 static void SaveLaserThinker(const thinker_t *th, const UINT8 type)
 {
 	const laserthink_t *ht = (const void *)th;
 	WRITEUINT8(save_p, type);
-	WRITEUINT32(save_p, SaveSector(ht->sector));
-	WRITEUINT32(save_p, SaveSector(ht->sec));
+	WRITEINT16(save_p, ht->tag);
 	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEUINT8(save_p, ht->nobosses);
 }
 
-//
-// SaveLightlevelThinker
-//
-// Saves a lightlevel_t thinker
-//
 static void SaveLightlevelThinker(const thinker_t *th, const UINT8 type)
 {
 	const lightlevel_t *ht = (const void *)th;
@@ -1888,11 +1905,6 @@ static void SaveLightlevelThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->timer);
 }
 
-//
-// SaveExecutorThinker
-//
-// Saves a executor_t thinker
-//
 static void SaveExecutorThinker(const thinker_t *th, const UINT8 type)
 {
 	const executor_t *ht = (const void *)th;
@@ -1903,11 +1915,6 @@ static void SaveExecutorThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->timer);
 }
 
-//
-// SaveDisappearThinker
-//
-// Saves a disappear_t thinker
-//
 static void SaveDisappearThinker(const thinker_t *th, const UINT8 type)
 {
 	const disappear_t *ht = (const void *)th;
@@ -1921,11 +1928,6 @@ static void SaveDisappearThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->exists);
 }
 
-//
-// SaveFadeThinker
-//
-// Saves a fade_t thinker
-//
 static void SaveFadeThinker(const thinker_t *th, const UINT8 type)
 {
 	const fade_t *ht = (const void *)th;
@@ -1949,11 +1951,6 @@ static void SaveFadeThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->exactalpha);
 }
 
-//
-// SaveFadeColormapThinker
-//
-// Saves a fadecolormap_t thinker
-//
 static void SaveFadeColormapThinker(const thinker_t *th, const UINT8 type)
 {
 	const fadecolormap_t *ht = (const void *)th;
@@ -1966,11 +1963,6 @@ static void SaveFadeColormapThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->timer);
 }
 
-//
-// SavePlaneDisplaceThinker
-//
-// Saves a planedisplace_t thinker
-//
 static void SavePlaneDisplaceThinker(const thinker_t *th, const UINT8 type)
 {
 	const planedisplace_t *ht = (const void *)th;
@@ -1982,7 +1974,6 @@ static void SavePlaneDisplaceThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->type);
 }
 
-/// Save a dynamic slope thinker.
 static inline void SaveDynamicSlopeThinker(const thinker_t *th, const UINT8 type)
 {
 	const dynplanethink_t* ht = (const void*)th;
@@ -1997,13 +1988,6 @@ static inline void SaveDynamicSlopeThinker(const thinker_t *th, const UINT8 type
     WRITEMEM(save_p, ht->vex, sizeof(ht->vex));
 }
 
-#ifdef POLYOBJECTS
-
-//
-// SavePolyrotateThinker
-//
-// Saves a polyrotate_t thinker
-//
 static inline void SavePolyrotatetThinker(const thinker_t *th, const UINT8 type)
 {
 	const polyrotate_t *ht = (const void *)th;
@@ -2013,11 +1997,6 @@ static inline void SavePolyrotatetThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->distance);
 }
 
-//
-// SavePolymoveThinker
-//
-// Saves a polymovet_t thinker
-//
 static void SavePolymoveThinker(const thinker_t *th, const UINT8 type)
 {
 	const polymove_t *ht = (const void *)th;
@@ -2030,11 +2009,6 @@ static void SavePolymoveThinker(const thinker_t *th, const UINT8 type)
 	WRITEANGLE(save_p, ht->angle);
 }
 
-//
-// SavePolywaypointThinker
-//
-// Saves a polywaypoint_t thinker
-//
 static void SavePolywaypointThinker(const thinker_t *th, UINT8 type)
 {
 	const polywaypoint_t *ht = (const void *)th;
@@ -2054,11 +2028,6 @@ static void SavePolywaypointThinker(const thinker_t *th, UINT8 type)
 	WRITEUINT32(save_p, SaveMobjnum(ht->target));
 }
 
-//
-// SavePolyslidedoorThinker
-//
-// Saves a polyslidedoor_t thinker
-//
 static void SavePolyslidedoorThinker(const thinker_t *th, const UINT8 type)
 {
 	const polyslidedoor_t *ht = (const void *)th;
@@ -2078,11 +2047,6 @@ static void SavePolyslidedoorThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->closing);
 }
 
-//
-// SavePolyswingdoorThinker
-//
-// Saves a polyswingdoor_t thinker
-//
 static void SavePolyswingdoorThinker(const thinker_t *th, const UINT8 type)
 {
 	const polyswingdoor_t *ht = (const void *)th;
@@ -2133,24 +2097,6 @@ static void SavePolyfadeThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->timer);
 }
 
-#endif
-/*
-//
-// SaveWhatThinker
-//
-// Saves a what_t thinker
-//
-static inline void SaveWhatThinker(const thinker_t *th, const UINT8 type)
-{
-	const what_t *ht = (const void *)th;
-	WRITEUINT8(save_p, type);
-}
-*/
-
-//
-// P_NetArchiveThinkers
-//
-//
 static void P_NetArchiveThinkers(void)
 {
 	const thinker_t *th;
@@ -2218,27 +2164,27 @@ static void P_NetArchiveThinkers(void)
 			}
 			else if (th->function.acp1 == (actionf_p1)T_ContinuousFalling)
 			{
-				SaveSpecialLevelThinker(th, tc_continuousfalling);
+				SaveContinuousFallThinker(th, tc_continuousfalling);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_ThwompSector)
 			{
-				SaveSpecialLevelThinker(th, tc_thwomp);
+				SaveThwompThinker(th, tc_thwomp);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_NoEnemiesSector)
 			{
-				SaveSpecialLevelThinker(th, tc_noenemies);
+				SaveNoEnemiesThinker(th, tc_noenemies);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_EachTimeThinker)
 			{
-				SaveSpecialLevelThinker(th, tc_eachtime);
+				SaveEachTimeThinker(th, tc_eachtime);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_RaiseSector)
 			{
-				SaveSpecialLevelThinker(th, tc_raisesector);
+				SaveRaiseThinker(th, tc_raisesector);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_CameraScanner)
@@ -2263,37 +2209,27 @@ static void P_NetArchiveThinkers(void)
 			}
 			else if (th->function.acp1 == (actionf_p1)T_BounceCheese)
 			{
-				SaveSpecialLevelThinker(th, tc_bouncecheese);
+				SaveBounceCheeseThinker(th, tc_bouncecheese);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_StartCrumble)
 			{
-				SaveElevatorThinker(th, tc_startcrumble);
+				SaveCrumbleThinker(th, tc_startcrumble);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_MarioBlock)
 			{
-				SaveSpecialLevelThinker(th, tc_marioblock);
+				SaveMarioBlockThinker(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);
+				SaveMarioCheckThinker(th, tc_marioblockchecker);
 				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);
+				SaveFloatThinker(th, tc_floatsector);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_LaserFlash)
@@ -2331,7 +2267,6 @@ static void P_NetArchiveThinkers(void)
 				SavePlaneDisplaceThinker(th, tc_planedisplace);
 				continue;
 			}
-#ifdef POLYOBJECTS
 			else if (th->function.acp1 == (actionf_p1)T_PolyObjRotate)
 			{
 				SavePolyrotatetThinker(th, tc_polyrotate);
@@ -2377,7 +2312,6 @@ static void P_NetArchiveThinkers(void)
 				SavePolyfadeThinker(th, tc_polyfade);
 				continue;
 			}
-#endif
 			else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeLine)
 			{
 				SaveDynamicSlopeThinker(th, tc_dynslopeline);
@@ -2460,11 +2394,6 @@ static inline pslope_t *LoadSlope(UINT32 slopeid)
 	return NULL;
 }
 
-//
-// LoadMobjThinker
-//
-// Loads a mobj_t from a save game
-//
 static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 {
 	thinker_t *next;
@@ -2489,34 +2418,16 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 
 	if (diff2 & MD2_FLOORROVER)
 	{
-		size_t floor_sectornum = (size_t)READUINT32(save_p);
-		size_t floor_rovernum = (size_t)READUINT32(save_p);
-		ffloor_t *rover = NULL;
-		size_t rovernum = 0;
-
-		for (rover = sectors[floor_sectornum].ffloors; rover; rover = rover->next)
-		{
-			if (rovernum == floor_rovernum)
-				break;
-			rovernum++;
-		}
-		floorrover = rover;
+		sector_t *sec = LoadSector(READUINT32(save_p));
+		UINT16 id = READUINT16(save_p);
+		floorrover = P_GetFFloorByID(sec, id);
 	}
 
 	if (diff2 & MD2_CEILINGROVER)
 	{
-		size_t ceiling_sectornum = (size_t)READUINT32(save_p);
-		size_t ceiling_rovernum = (size_t)READUINT32(save_p);
-		ffloor_t *rover = NULL;
-		size_t rovernum = 0;
-
-		for (rover = sectors[ceiling_sectornum].ffloors; rover; rover = rover->next)
-		{
-			if (rovernum == ceiling_rovernum)
-				break;
-			rovernum++;
-		}
-		ceilingrover = rover;
+		sector_t *sec = LoadSector(READUINT32(save_p));
+		UINT16 id = READUINT16(save_p);
+		ceilingrover = P_GetFFloorByID(sec, id);
 	}
 
 	if (diff & MD_SPAWNPOINT)
@@ -2741,46 +2652,145 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 	return &mobj->thinker;
 }
 
-//
-// LoadSpecialLevelThinker
-//
-// Loads a levelspecthink_t from a save game
-//
-// floorOrCeiling:
-//		0 - Don't set
-//		1 - Floor Only
-//		2 - Ceiling Only
-//		3 - Both
-//
-static thinker_t* LoadSpecialLevelThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
+static thinker_t* LoadNoEnemiesThinker(actionf_p1 thinker)
 {
-	levelspecthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
-	size_t i;
+	noenemies_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->sourceline = LoadLine(READUINT32(save_p));
+	return &ht->thinker;
+}
+
+static thinker_t* LoadBounceCheeseThinker(actionf_p1 thinker)
+{
+	bouncecheese_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->sourceline = LoadLine(READUINT32(save_p));
+	ht->sector = LoadSector(READUINT32(save_p));
+	ht->speed = READFIXED(save_p);
+	ht->distance = READFIXED(save_p);
+	ht->floorwasheight = READFIXED(save_p);
+	ht->ceilingwasheight = READFIXED(save_p);
+	ht->low = READCHAR(save_p);
+
+	if (ht->sector)
+		ht->sector->ceilingdata = ht;
+
+	return &ht->thinker;
+}
+
+static thinker_t* LoadContinuousFallThinker(actionf_p1 thinker)
+{
+	continuousfall_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->sector = LoadSector(READUINT32(save_p));
+	ht->speed = READFIXED(save_p);
+	ht->direction = READINT32(save_p);
+	ht->floorstartheight = READFIXED(save_p);
+	ht->ceilingstartheight = READFIXED(save_p);
+	ht->destheight = READFIXED(save_p);
+
+	if (ht->sector)
+	{
+		ht->sector->ceilingdata = ht;
+		ht->sector->floordata = ht;
+	}
+
+	return &ht->thinker;
+}
+
+static thinker_t* LoadMarioBlockThinker(actionf_p1 thinker)
+{
+	mariothink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
-	for (i = 0; i < 16; i++)
+	ht->sector = LoadSector(READUINT32(save_p));
+	ht->speed = READFIXED(save_p);
+	ht->direction = READINT32(save_p);
+	ht->floorstartheight = READFIXED(save_p);
+	ht->ceilingstartheight = READFIXED(save_p);
+	ht->tag = READINT16(save_p);
+
+	if (ht->sector)
 	{
-		ht->vars[i] = READFIXED(save_p); //var[16]
-		ht->var2s[i] = READFIXED(save_p); //var[16]
+		ht->sector->ceilingdata = ht;
+		ht->sector->floordata = ht;
 	}
+
+	return &ht->thinker;
+}
+
+static thinker_t* LoadMarioCheckThinker(actionf_p1 thinker)
+{
+	mariocheck_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->sourceline = LoadLine(READUINT32(save_p));
+	ht->sector = LoadSector(READUINT32(save_p));
+	return &ht->thinker;
+}
+
+static thinker_t* LoadThwompThinker(actionf_p1 thinker)
+{
+	thwomp_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
 	ht->sourceline = LoadLine(READUINT32(save_p));
 	ht->sector = LoadSector(READUINT32(save_p));
+	ht->crushspeed = READFIXED(save_p);
+	ht->retractspeed = READFIXED(save_p);
+	ht->direction = READINT32(save_p);
+	ht->floorstartheight = READFIXED(save_p);
+	ht->ceilingstartheight = READFIXED(save_p);
+	ht->delay = READINT32(save_p);
+	ht->tag = READINT16(save_p);
+	ht->sound = READUINT16(save_p);
 
 	if (ht->sector)
 	{
-		if (floorOrCeiling & 2)
-			ht->sector->ceilingdata = ht;
-		if (floorOrCeiling & 1)
-			ht->sector->floordata = ht;
+		ht->sector->ceilingdata = ht;
+		ht->sector->floordata = ht;
 	}
 
 	return &ht->thinker;
 }
 
-//
-// LoadCeilingThinker
-//
-// Loads a ceiling_t from a save game
-//
+static thinker_t* LoadFloatThinker(actionf_p1 thinker)
+{
+	floatthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->sourceline = LoadLine(READUINT32(save_p));
+	ht->sector = LoadSector(READUINT32(save_p));
+	ht->tag = READINT16(save_p);
+	return &ht->thinker;
+}
+
+static thinker_t* LoadEachTimeThinker(actionf_p1 thinker)
+{
+	size_t i;
+	eachtime_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->sourceline = LoadLine(READUINT32(save_p));
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		ht->playersInArea[i] = READCHAR(save_p);
+		ht->playersOnArea[i] = READCHAR(save_p);
+	}
+	ht->triggerOnExit = READCHAR(save_p);
+	return &ht->thinker;
+}
+
+static thinker_t* LoadRaiseThinker(actionf_p1 thinker)
+{
+	raise_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->tag = READINT16(save_p);
+	ht->sector = LoadSector(READUINT32(save_p));
+	ht->ceilingbottom = READFIXED(save_p);
+	ht->ceilingtop = READFIXED(save_p);
+	ht->basespeed = READFIXED(save_p);
+	ht->extraspeed = READFIXED(save_p);
+	ht->shaketimer = READUINT8(save_p);
+	ht->flags = READUINT8(save_p);
+	return &ht->thinker;
+}
+
 static thinker_t* LoadCeilingThinker(actionf_p1 thinker)
 {
 	ceiling_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2805,11 +2815,6 @@ static thinker_t* LoadCeilingThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadFloormoveThinker
-//
-// Loads a floormove_t from a save game
-//
 static thinker_t* LoadFloormoveThinker(actionf_p1 thinker)
 {
 	floormove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2829,11 +2834,6 @@ static thinker_t* LoadFloormoveThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadLightflashThinker
-//
-// Loads a lightflash_t from a save game
-//
 static thinker_t* LoadLightflashThinker(actionf_p1 thinker)
 {
 	lightflash_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2846,11 +2846,6 @@ static thinker_t* LoadLightflashThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadStrobeThinker
-//
-// Loads a strobe_t from a save game
-//
 static thinker_t* LoadStrobeThinker(actionf_p1 thinker)
 {
 	strobe_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2866,11 +2861,6 @@ static thinker_t* LoadStrobeThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadGlowThinker
-//
-// Loads a glow_t from a save game
-//
 static thinker_t* LoadGlowThinker(actionf_p1 thinker)
 {
 	glow_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2884,11 +2874,7 @@ static thinker_t* LoadGlowThinker(actionf_p1 thinker)
 		ht->sector->lightingdata = ht;
 	return &ht->thinker;
 }
-//
-// LoadFireflickerThinker
-//
-// Loads a fireflicker_t from a save game
-//
+
 static thinker_t* LoadFireflickerThinker(actionf_p1 thinker)
 {
 	fireflicker_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2902,12 +2888,8 @@ static thinker_t* LoadFireflickerThinker(actionf_p1 thinker)
 		ht->sector->lightingdata = ht;
 	return &ht->thinker;
 }
-//
-// LoadElevatorThinker
-//
-// Loads a elevator_t from a save game
-//
-static thinker_t* LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
+
+static thinker_t* LoadElevatorThinker(actionf_p1 thinker, boolean setplanedata)
 {
 	elevator_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -2926,25 +2908,39 @@ static thinker_t* LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 	ht->delaytimer = READFIXED(save_p);
 	ht->floorwasheight = READFIXED(save_p);
 	ht->ceilingwasheight = READFIXED(save_p);
-	ht->player = LoadPlayer(READUINT32(save_p)); // was dummy
 	ht->sourceline = LoadLine(READUINT32(save_p));
 
-	if (ht->sector)
+	if (ht->sector && setplanedata)
 	{
-		if (floorOrCeiling & 2)
-			ht->sector->ceilingdata = ht;
-		if (floorOrCeiling & 1)
-			ht->sector->floordata = ht;
+		ht->sector->ceilingdata = ht;
+		ht->sector->floordata = ht;
 	}
 
 	return &ht->thinker;
 }
 
-//
-// LoadScrollThinker
-//
-// Loads a scroll_t from a save game
-//
+static thinker_t* LoadCrumbleThinker(actionf_p1 thinker)
+{
+	crumble_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->sourceline = LoadLine(READUINT32(save_p));
+	ht->sector = LoadSector(READUINT32(save_p));
+	ht->actionsector = LoadSector(READUINT32(save_p));
+	ht->player = LoadPlayer(READUINT32(save_p));
+	ht->direction = READINT32(save_p);
+	ht->origalpha = READINT32(save_p);
+	ht->timer = READINT32(save_p);
+	ht->speed = READFIXED(save_p);
+	ht->floorwasheight = READFIXED(save_p);
+	ht->ceilingwasheight = READFIXED(save_p);
+	ht->flags = READUINT8(save_p);
+
+	if (ht->sector)
+		ht->sector->floordata = ht;
+
+	return &ht->thinker;
+}
+
 static thinker_t* LoadScrollThinker(actionf_p1 thinker)
 {
 	scroll_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2962,11 +2958,6 @@ static thinker_t* LoadScrollThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadFrictionThinker
-//
-// Loads a friction_t from a save game
-//
 static inline thinker_t* LoadFrictionThinker(actionf_p1 thinker)
 {
 	friction_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2979,11 +2970,6 @@ static inline thinker_t* LoadFrictionThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPusherThinker
-//
-// Loads a pusher_t from a save game
-//
 static thinker_t* LoadPusherThinker(actionf_p1 thinker)
 {
 	pusher_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3005,31 +2991,16 @@ static thinker_t* LoadPusherThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadLaserThinker
-//
-// Loads a laserthink_t from a save game
-//
 static inline thinker_t* LoadLaserThinker(actionf_p1 thinker)
 {
 	laserthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
-	ffloor_t *rover = NULL;
 	ht->thinker.function.acp1 = thinker;
-	ht->sector = LoadSector(READUINT32(save_p));
-	ht->sec = LoadSector(READUINT32(save_p));
+	ht->tag = READINT16(save_p);
 	ht->sourceline = LoadLine(READUINT32(save_p));
-	for (rover = ht->sector->ffloors; rover; rover = rover->next)
-		if (rover->secnum == (size_t)(ht->sec - sectors)
-		&& rover->master == ht->sourceline)
-			ht->ffloor = rover;
+	ht->nobosses = READUINT8(save_p);
 	return &ht->thinker;
 }
 
-//
-// LoadLightlevelThinker
-//
-// Loads a lightlevel_t from a save game
-//
 static inline thinker_t* LoadLightlevelThinker(actionf_p1 thinker)
 {
 	lightlevel_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3045,11 +3016,6 @@ static inline thinker_t* LoadLightlevelThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadExecutorThinker
-//
-// Loads a executor_t from a save game
-//
 static inline thinker_t* LoadExecutorThinker(actionf_p1 thinker)
 {
 	executor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3061,11 +3027,6 @@ static inline thinker_t* LoadExecutorThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadDisappearThinker
-//
-// Loads a disappear_t thinker
-//
 static inline thinker_t* LoadDisappearThinker(actionf_p1 thinker)
 {
 	disappear_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3080,11 +3041,6 @@ static inline thinker_t* LoadDisappearThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadFadeThinker
-//
-// Loads a fade_t thinker
-//
 static inline thinker_t* LoadFadeThinker(actionf_p1 thinker)
 {
 	sector_t *ss;
@@ -3127,10 +3083,6 @@ static inline thinker_t* LoadFadeThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-// LoadFadeColormapThinker
-//
-// Loads a fadecolormap_t from a save game
-//
 static inline thinker_t* LoadFadeColormapThinker(actionf_p1 thinker)
 {
 	fadecolormap_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3146,11 +3098,6 @@ static inline thinker_t* LoadFadeColormapThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPlaneDisplaceThinker
-//
-// Loads a planedisplace_t thinker
-//
 static inline thinker_t* LoadPlaneDisplaceThinker(actionf_p1 thinker)
 {
 	planedisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3164,7 +3111,6 @@ static inline thinker_t* LoadPlaneDisplaceThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-/// Save a dynamic slope thinker.
 static inline thinker_t* LoadDynamicSlopeThinker(actionf_p1 thinker)
 {
 	dynplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL);
@@ -3179,13 +3125,6 @@ static inline thinker_t* LoadDynamicSlopeThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-#ifdef POLYOBJECTS
-
-//
-// LoadPolyrotateThinker
-//
-// Loads a polyrotate_t thinker
-//
 static inline thinker_t* LoadPolyrotatetThinker(actionf_p1 thinker)
 {
 	polyrotate_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3196,11 +3135,6 @@ static inline thinker_t* LoadPolyrotatetThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolymoveThinker
-//
-// Loads a polymovet_t thinker
-//
 static thinker_t* LoadPolymoveThinker(actionf_p1 thinker)
 {
 	polymove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3214,11 +3148,6 @@ static thinker_t* LoadPolymoveThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolywaypointThinker
-//
-// Loads a polywaypoint_t thinker
-//
 static inline thinker_t* LoadPolywaypointThinker(actionf_p1 thinker)
 {
 	polywaypoint_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3239,11 +3168,6 @@ static inline thinker_t* LoadPolywaypointThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolyslidedoorThinker
-//
-// loads a polyslidedoor_t thinker
-//
 static inline thinker_t* LoadPolyslidedoorThinker(actionf_p1 thinker)
 {
 	polyslidedoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3264,11 +3188,6 @@ static inline thinker_t* LoadPolyslidedoorThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolyswingdoorThinker
-//
-// Loads a polyswingdoor_t thinker
-//
 static inline thinker_t* LoadPolyswingdoorThinker(actionf_p1 thinker)
 {
 	polyswingdoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3284,11 +3203,6 @@ static inline thinker_t* LoadPolyswingdoorThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolydisplaceThinker
-//
-// Loads a polydisplace_t thinker
-//
 static inline thinker_t* LoadPolydisplaceThinker(actionf_p1 thinker)
 {
 	polydisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3313,11 +3227,6 @@ static inline thinker_t* LoadPolyrotdisplaceThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolyfadeThinker
-//
-// Loads a polyfadet_t thinker
-//
 static thinker_t* LoadPolyfadeThinker(actionf_p1 thinker)
 {
 	polyfade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3332,24 +3241,7 @@ static thinker_t* LoadPolyfadeThinker(actionf_p1 thinker)
 	ht->timer = READINT32(save_p);
 	return &ht->thinker;
 }
-#endif
-
-/*
-//
-// LoadWhatThinker
-//
-// load a what_t thinker
-//
-static inline void LoadWhatThinker(actionf_p1 thinker)
-{
-	what_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
-	ht->thinker.function.acp1 = thinker;
-}
-*/
 
-//
-// P_NetUnArchiveThinkers
-//
 static void P_NetUnArchiveThinkers(void)
 {
 	thinker_t *currentthinker;
@@ -3434,61 +3326,51 @@ static void P_NetUnArchiveThinkers(void)
 					break;
 
 				case tc_elevator:
-					th = LoadElevatorThinker((actionf_p1)T_MoveElevator, 3);
+					th = LoadElevatorThinker((actionf_p1)T_MoveElevator, true);
 					break;
 
 				case tc_continuousfalling:
-					th = LoadSpecialLevelThinker((actionf_p1)T_ContinuousFalling, 3);
+					th = LoadContinuousFallThinker((actionf_p1)T_ContinuousFalling);
 					break;
 
 				case tc_thwomp:
-					th = LoadSpecialLevelThinker((actionf_p1)T_ThwompSector, 3);
+					th = LoadThwompThinker((actionf_p1)T_ThwompSector);
 					break;
 
 				case tc_noenemies:
-					th = LoadSpecialLevelThinker((actionf_p1)T_NoEnemiesSector, 0);
+					th = LoadNoEnemiesThinker((actionf_p1)T_NoEnemiesSector);
 					break;
 
 				case tc_eachtime:
-					th = LoadSpecialLevelThinker((actionf_p1)T_EachTimeThinker, 0);
+					th = LoadEachTimeThinker((actionf_p1)T_EachTimeThinker);
 					break;
 
 				case tc_raisesector:
-					th = LoadSpecialLevelThinker((actionf_p1)T_RaiseSector, 0);
+					th = LoadRaiseThinker((actionf_p1)T_RaiseSector);
 					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);
+					th = LoadElevatorThinker((actionf_p1)T_CameraScanner, false);
 					break;
 
 				case tc_bouncecheese:
-					th = LoadSpecialLevelThinker((actionf_p1)T_BounceCheese, 2);
+					th = LoadBounceCheeseThinker((actionf_p1)T_BounceCheese);
 					break;
 
 				case tc_startcrumble:
-					th = LoadElevatorThinker((actionf_p1)T_StartCrumble, 1);
+					th = LoadCrumbleThinker((actionf_p1)T_StartCrumble);
 					break;
 
 				case tc_marioblock:
-					th = LoadSpecialLevelThinker((actionf_p1)T_MarioBlock, 3);
+					th = LoadMarioBlockThinker((actionf_p1)T_MarioBlock);
 					break;
 
 				case tc_marioblockchecker:
-					th = LoadSpecialLevelThinker((actionf_p1)T_MarioBlockChecker, 0);
-					break;
-
-				case tc_spikesector:
-					th = LoadSpecialLevelThinker((actionf_p1)T_SpikeSector, 0);
+					th = LoadMarioCheckThinker((actionf_p1)T_MarioBlockChecker);
 					break;
 
 				case tc_floatsector:
-					th = LoadSpecialLevelThinker((actionf_p1)T_FloatSector, 0);
-					break;
-
-				case tc_bridgethinker:
-					th = LoadSpecialLevelThinker((actionf_p1)T_BridgeThinker, 3);
+					th = LoadFloatThinker((actionf_p1)T_FloatSector);
 					break;
 
 				case tc_laserflash:
@@ -3519,7 +3401,6 @@ static void P_NetUnArchiveThinkers(void)
 				case tc_planedisplace:
 					th = LoadPlaneDisplaceThinker((actionf_p1)T_PlaneDisplace);
 					break;
-#ifdef POLYOBJECTS
 				case tc_polyrotate:
 					th = LoadPolyrotatetThinker((actionf_p1)T_PolyObjRotate);
 					break;
@@ -3556,7 +3437,6 @@ static void P_NetUnArchiveThinkers(void)
 				case tc_polyfade:
 					th = LoadPolyfadeThinker((actionf_p1)T_PolyObjFade);
 					break;
-#endif
 
 				case tc_dynslopeline:
 					th = LoadDynamicSlopeThinker((actionf_p1)T_DynamicSlopeLine);
@@ -3618,7 +3498,6 @@ static void P_NetUnArchiveThinkers(void)
 //
 // haleyjd 03/26/06: PolyObject saving code
 //
-#ifdef POLYOBJECTS
 #define PD_FLAGS  0x01
 #define PD_TRANS   0x02
 
@@ -3707,10 +3586,7 @@ static inline void P_UnArchivePolyObjects(void)
 	for (i = 0; i < numSavedPolys; ++i)
 		P_UnArchivePolyObj(&PolyObjects[i]);
 }
-#endif
-//
-// P_FinishMobjs
-//
+
 static inline void P_FinishMobjs(void)
 {
 	thinker_t *currentthinker;
@@ -3819,9 +3695,6 @@ static void P_RelinkPointers(void)
 	}
 }
 
-//
-// P_NetArchiveSpecials
-//
 static inline void P_NetArchiveSpecials(void)
 {
 	size_t i, z;
@@ -3862,9 +3735,6 @@ static inline void P_NetArchiveSpecials(void)
 		WRITEUINT8(save_p, 0x00);
 }
 
-//
-// P_NetUnArchiveSpecials
-//
 static void P_NetUnArchiveSpecials(void)
 {
 	size_t i;
@@ -4189,9 +4059,7 @@ void P_SaveNetGame(void)
 	if (gamestate == GS_LEVEL)
 	{
 		P_NetArchiveWorld();
-#ifdef POLYOBJECTS
 		P_ArchivePolyObjects();
-#endif
 		P_NetArchiveThinkers();
 		P_NetArchiveSpecials();
 		P_NetArchiveColormaps();
@@ -4229,9 +4097,7 @@ boolean P_LoadNetGame(void)
 	if (gamestate == GS_LEVEL)
 	{
 		P_NetUnArchiveWorld();
-#ifdef POLYOBJECTS
 		P_UnArchivePolyObjects();
-#endif
 		P_NetUnArchiveThinkers();
 		P_NetUnArchiveSpecials();
 		P_NetUnArchiveColormaps();
diff --git a/src/p_setup.c b/src/p_setup.c
index cdf867d6179501c4e272523a466441695dac1133..b4a5f2c3c75a49b2fdd6d61229c581b67f615414 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -218,6 +218,9 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->typeoflevel = 0;
 	mapheaderinfo[num]->nextlevel = (INT16)(i + 1);
 	mapheaderinfo[num]->startrings = 0;
+	mapheaderinfo[num]->sstimer = 90;
+	mapheaderinfo[num]->ssspheres = 1;
+	mapheaderinfo[num]->gravity = FRACUNIT/2;
 	mapheaderinfo[num]->keywords[0] = '\0';
 	snprintf(mapheaderinfo[num]->musname, 7, "%sM", G_BuildMapName(i));
 	mapheaderinfo[num]->musname[6] = 0;
@@ -693,47 +696,27 @@ void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum)
 
 static void P_SpawnEmeraldHunt(void)
 {
-	INT32 emer1, emer2, emer3;
-	INT32 timeout = 0; // keeps from getting stuck
+	INT32 emer[3], num[MAXHUNTEMERALDS], i, randomkey;
+	fixed_t x, y, z;
 
-	emer1 = emer2 = emer3 = 0;
+	for (i = 0; i < numhuntemeralds; i++)
+		num[i] = i;
 
-	//increment spawn numbers because zero is valid.
-	emer1 = (P_RandomKey(numhuntemeralds)) + 1;
-	while (timeout++ < 100)
+	for (i = 0; i < 3; i++)
 	{
-		emer2 = (P_RandomKey(numhuntemeralds)) + 1;
+		// generate random index, shuffle afterwards
+		randomkey = P_RandomKey(numhuntemeralds--);
+		emer[i] = num[randomkey];
+		num[randomkey] = num[numhuntemeralds];
+		num[numhuntemeralds] = emer[i];
 
-		if (emer2 != emer1)
-			break;
+		// spawn emerald
+		x = huntemeralds[emer[i]]->x<<FRACBITS;
+		y = huntemeralds[emer[i]]->y<<FRACBITS;
+		z = P_GetMapThingSpawnHeight(MT_EMERHUNT, huntemeralds[emer[i]], x, y);
+		P_SetMobjStateNF(P_SpawnMobj(x, y, z, MT_EMERHUNT),
+			mobjinfo[MT_EMERHUNT].spawnstate+i);
 	}
-
-	timeout = 0;
-	while (timeout++ < 100)
-	{
-		emer3 = (P_RandomKey(numhuntemeralds)) + 1;
-
-		if (emer3 != emer2 && emer3 != emer1)
-			break;
-	}
-
-	//decrement spawn values to the actual number because zero is valid.
-	if (emer1--)
-		P_SpawnMobj(huntemeralds[emer1]->x<<FRACBITS,
-			huntemeralds[emer1]->y<<FRACBITS,
-			huntemeralds[emer1]->z<<FRACBITS, MT_EMERHUNT);
-
-	if (emer2--)
-		P_SetMobjStateNF(P_SpawnMobj(huntemeralds[emer2]->x<<FRACBITS,
-			huntemeralds[emer2]->y<<FRACBITS,
-			huntemeralds[emer2]->z<<FRACBITS, MT_EMERHUNT),
-		mobjinfo[MT_EMERHUNT].spawnstate+1);
-
-	if (emer3--)
-		P_SetMobjStateNF(P_SpawnMobj(huntemeralds[emer3]->x<<FRACBITS,
-			huntemeralds[emer3]->y<<FRACBITS,
-			huntemeralds[emer3]->z<<FRACBITS, MT_EMERHUNT),
-		mobjinfo[MT_EMERHUNT].spawnstate+2);
 }
 
 static void P_SpawnMapThings(boolean spawnemblems)
@@ -866,13 +849,12 @@ static void P_InitializeSector(sector_t *ss)
 	ss->camsec = -1;
 
 	ss->floorlightsec = ss->ceilinglightsec = -1;
-	ss->crumblestate = 0;
+	ss->crumblestate = CRUMBLE_NONE;
 
 	ss->touching_thinglist = NULL;
 
 	ss->linecount = 0;
 	ss->lines = NULL;
-	ss->tagline = NULL;
 
 	ss->ffloors = NULL;
 	ss->attached = NULL;
@@ -974,9 +956,7 @@ static void P_InitializeLinedef(line_t *ld)
 	ld->splats = NULL;
 #endif
 	ld->firsttag = ld->nexttag = -1;
-#ifdef POLYOBJECTS
 	ld->polyobj = NULL;
-#endif
 
 	ld->text = NULL;
 	ld->callcount = 0;
@@ -1181,10 +1161,14 @@ static void P_LoadSidedefs(UINT8 *data)
 			case 9: // Mace parameters
 			case 14: // Bustable block parameters
 			case 15: // Fan particle spawner parameters
+			case 334: // Trigger linedef executor: Object dye - Continuous
+			case 335: // Trigger linedef executor: Object dye - Each time
+			case 336: // Trigger linedef executor: Object dye - Once
 			case 425: // Calls P_SetMobjState on calling mobj
 			case 434: // Custom Power
 			case 442: // Calls P_SetMobjState on mobjs of a given type in the tagged sectors
 			case 461: // Spawns an object on the map based on texture offsets
+			case 463: // Colorizes an object
 			{
 				char process[8*3+1];
 				memset(process,0,8*3+1);
@@ -1886,10 +1870,8 @@ static void P_InitializeSeg(seg_t *seg)
 
 	seg->numlights = 0;
 	seg->rlights = NULL;
-#ifdef POLYOBJECTS
 	seg->polyseg = NULL;
 	seg->dontrenderme = false;
-#endif
 }
 
 static void P_LoadSegs(UINT8 *data)
@@ -2252,11 +2234,9 @@ static boolean P_LoadBlockMap(UINT8 *data, size_t count)
 	blocklinks = Z_Calloc(count, PU_LEVEL, NULL);
 	blockmap = blockmaplump+4;
 
-#ifdef POLYOBJECTS
 	// haleyjd 2/22/06: setup polyobject blockmap
 	count = sizeof(*polyblocklinks) * bmapwidth * bmapheight;
 	polyblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
-#endif
 	return true;
 }
 
@@ -2507,11 +2487,9 @@ static void P_CreateBlockMap(void)
 		blocklinks = Z_Calloc(count, PU_LEVEL, NULL);
 		blockmap = blockmaplump + 4;
 
-#ifdef POLYOBJECTS
 		// haleyjd 2/22/06: setup polyobject blockmap
 		count = sizeof(*polyblocklinks) * bmapwidth * bmapheight;
 		polyblocklinks = Z_Calloc(count, PU_LEVEL, NULL);
-#endif
 	}
 }
 
@@ -3774,12 +3752,12 @@ static lumpinfo_t* FindFolder(const char *folName, UINT16 *start, UINT16 *end, l
 {
 	UINT16 numlumps = *pnumlumps;
 	size_t i = *pi;
-	if (!stricmp(lumpinfo->name2, folName))
+	if (!stricmp(lumpinfo->fullname, folName))
 	{
 		lumpinfo++;
 		*start = ++i;
 		for (; i < numlumps; i++, lumpinfo++)
-			if (strnicmp(lumpinfo->name2, folName, strlen(folName)))
+			if (strnicmp(lumpinfo->fullname, folName, strlen(folName)))
 				break;
 		lumpinfo--;
 		*end = i-- - *start;
diff --git a/src/p_sight.c b/src/p_sight.c
index f8044dffd2f2a15c8686cb77ebae6ef5ecfd97d9..2e1e499970418d23f5129e9271199592dbb9ce23 100644
--- a/src/p_sight.c
+++ b/src/p_sight.c
@@ -100,7 +100,6 @@ static fixed_t P_InterceptVector2(divline_t *v2, divline_t *v1)
 	return frac;
 }
 
-#ifdef POLYOBJECTS
 static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los)
 {
 	size_t i;
@@ -169,7 +168,6 @@ static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los)
 
 	return true;
 }
-#endif
 
 //
 // P_CrossSubsector
@@ -180,9 +178,7 @@ static boolean P_CrossSubsector(size_t num, register los_t *los)
 {
 	seg_t *seg;
 	INT32 count;
-#ifdef POLYOBJECTS
 	polyobj_t *po; // haleyjd 02/23/06
-#endif
 
 #ifdef RANGECHECK
 	if (num >= numsubsectors)
@@ -192,7 +188,6 @@ static boolean P_CrossSubsector(size_t num, register los_t *los)
 	// haleyjd 02/23/06: this assignment should be after the above check
 	seg = segs + subsectors[num].firstline;
 
-#ifdef POLYOBJECTS
 	// haleyjd 02/23/06: check polyobject lines
 	if ((po = subsectors[num].polyList))
 	{
@@ -207,7 +202,6 @@ static boolean P_CrossSubsector(size_t num, register los_t *los)
 			po = (polyobj_t *)(po->link.next);
 		}
 	}
-#endif
 
 	for (count = subsectors[num].numlines; --count >= 0; seg++)  // check lines
 	{
@@ -413,15 +407,10 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
 
 	// killough 11/98: shortcut for melee situations
 	// same subsector? obviously visible
-#ifndef POLYOBJECTS
-	if (t1->subsector == t2->subsector)
-		return true;
-#else
 	// haleyjd 02/23/06: can't do this if there are polyobjects in the subsec
 	if (!t1->subsector->polyList &&
 		t1->subsector == t2->subsector)
 		return true;
-#endif
 
 	// An unobstructed LOS is possible.
 	// Now look from eyes of t1 to any part of t2.
@@ -484,7 +473,7 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
 			if (rover->flags & FF_SOLID)
 				continue; // shortcut since neither mobj can be inside the 3dfloor
 
-			if (!(rover->flags & FF_INVERTPLANES))
+			if (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES))
 			{
 				if (los.sightzstart >= topz1 && t2->z + t2->height < topz2)
 					return false; // blocked by upper outside plane
@@ -493,7 +482,7 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
 					return false; // blocked by lower outside plane
 			}
 
-			if (rover->flags & FF_INVERTPLANES || rover->flags & FF_BOTHPLANES)
+			if (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES)
 			{
 				if (los.sightzstart < topz1 && t2->z >= topz2)
 					return false; // blocked by upper inside plane
diff --git a/src/p_slopes.c b/src/p_slopes.c
index 940a37f19dcbc8b6d30f2bd3b8300eb5ae0e94fe..6aeb1b02596633e7c4d6a9db1e0f0853b6c244c3 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -566,7 +566,7 @@ void P_CopySectorSlope(line_t *line)
 	int i, special = line->special;
 
 	// Check for copy linedefs
-	for (i = -1; (i = P_FindSectorFromLineTag(line, i)) >= 0;)
+	for (i = -1; (i = P_FindSectorFromTag(line->tag, i)) >= 0;)
 	{
 		sector_t *srcsec = sectors + i;
 
diff --git a/src/p_spec.c b/src/p_spec.c
index ae7d5b614788643c5729a4de2e3285e9fe7ed932..549068372156e48342245a920b2ffabbeaa353bf 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -114,12 +114,11 @@ static void P_ResetColormapFader(sector_t *sector);
 static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, extracolormap_t *dest_exc,
 	boolean ticbased, INT32 duration);
 static void P_AddBlockThinker(sector_t *sec, line_t *sourceline);
-static void P_AddFloatThinker(sector_t *sec, INT32 tag, line_t *sourceline);
+static void P_AddFloatThinker(sector_t *sec, UINT16 tag, line_t *sourceline);
 //static void P_AddBridgeThinker(line_t *sourceline, sector_t *sec);
 static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers);
 static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec);
 static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer);
-static void P_AddSpikeThinker(sector_t *sec, INT32 referrer);
 static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee, UINT8 reverse);
 
 
@@ -988,42 +987,12 @@ static sector_t *P_FindModelCeilingSector(fixed_t ceildestheight, INT32 secnum)
 }
 #endif
 
-/** Searches the tag lists for the next sector tagged to a line.
-  *
-  * \param line  Tagged line used as a reference.
-  * \param start -1 to start at the beginning, or the result of a previous call
-  *              to keep searching.
-  * \return Number of the next tagged sector found.
-  * \sa P_FindSectorFromTag, P_FindLineFromLineTag
-  */
-INT32 P_FindSectorFromLineTag(line_t *line, INT32 start)
-{
-	if (line->tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numsectors)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? sectors[start].nexttag :
-			sectors[(unsigned)line->tag % numsectors].firsttag;
-		while (start >= 0 && sectors[start].tag != line->tag)
-			start = sectors[start].nexttag;
-		return start;
-	}
-}
-
 /** Searches the tag lists for the next sector with a given tag.
   *
   * \param tag   Tag number to look for.
   * \param start -1 to start anew, or the result of a previous call to keep
   *              searching.
   * \return Number of the next tagged sector found.
-  * \sa P_FindSectorFromLineTag
   */
 INT32 P_FindSectorFromTag(INT16 tag, INT32 start)
 {
@@ -1046,42 +1015,12 @@ INT32 P_FindSectorFromTag(INT16 tag, INT32 start)
 	}
 }
 
-/** Searches the tag lists for the next line tagged to a line.
-  *
-  * \param line  Tagged line used as a reference.
-  * \param start -1 to start anew, or the result of a previous call to keep
-  *              searching.
-  * \return Number of the next tagged line found.
-  * \sa P_FindSectorFromLineTag
-  */
-static INT32 P_FindLineFromLineTag(const line_t *line, INT32 start)
-{
-	if (line->tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numlines)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? lines[start].nexttag :
-			lines[(unsigned)line->tag % numlines].firsttag;
-		while (start >= 0 && lines[start].tag != line->tag)
-			start = lines[start].nexttag;
-		return start;
-	}
-}
-#if 0
 /** Searches the tag lists for the next line with a given tag and special.
   *
   * \param tag     Tag number.
   * \param start   -1 to start anew, or the result of a previous call to keep
   *                searching.
   * \return Number of next suitable line found.
-  * \sa P_FindLineFromLineTag
   * \author Graue <graue@oceanbase.org>
   */
 static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
@@ -1090,7 +1029,7 @@ static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
 	{
 		start++;
 
-		if (start >= numlines)
+		if (start >= (INT32)numlines)
 			return -1;
 
 		return start;
@@ -1104,10 +1043,7 @@ static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
 		return start;
 	}
 }
-#endif
-//
-// P_FindSpecialLineFromTag
-//
+
 INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
 {
 	if (tag == -1)
@@ -1137,14 +1073,8 @@ INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
 	}
 }
 
-// haleyjd: temporary define
-#ifdef POLYOBJECTS
 
-//
-// PolyDoor
-//
 // Parses arguments for parameterized polyobject door types
-//
 static boolean PolyDoor(line_t *line)
 {
 	polydoordata_t pdd;
@@ -1181,11 +1111,7 @@ static boolean PolyDoor(line_t *line)
 	return EV_DoPolyDoor(&pdd);
 }
 
-//
-// PolyMove
-//
 // Parses arguments for parameterized polyobject move specials
-//
 static boolean PolyMove(line_t *line)
 {
 	polymovedata_t pmd;
@@ -1200,12 +1126,8 @@ static boolean PolyMove(line_t *line)
 	return EV_DoPolyObjMove(&pmd);
 }
 
-//
-// PolyInvisible
-//
 // Makes a polyobject invisible and intangible
 // If NOCLIMB is ticked, the polyobject will still be tangible, just not visible.
-//
 static void PolyInvisible(line_t *line)
 {
 	INT32 polyObjNum = line->tag;
@@ -1228,12 +1150,8 @@ static void PolyInvisible(line_t *line)
 	po->flags &= ~POF_RENDERALL;
 }
 
-//
-// PolyVisible
-//
 // Makes a polyobject visible and tangible
 // If NOCLIMB is ticked, the polyobject will not be tangible, just visible.
-//
 static void PolyVisible(line_t *line)
 {
 	INT32 polyObjNum = line->tag;
@@ -1256,16 +1174,14 @@ static void PolyVisible(line_t *line)
 	po->flags |= (po->spawnflags & POF_RENDERALL);
 }
 
-//
-// PolyTranslucency
-//
+
 // Sets the translucency of a polyobject
 // Frontsector floor / 100 = translevel
-//
 static void PolyTranslucency(line_t *line)
 {
 	INT32 polyObjNum = line->tag;
 	polyobj_t *po;
+	INT32 value;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
 	{
@@ -1277,37 +1193,28 @@ static void PolyTranslucency(line_t *line)
 	if (po->isBad)
 		return;
 
-	// if DONTPEGBOTTOM, specify raw translucency value in Front X Offset
-	// else, take it out of 1000. If Front X Offset is specified, use that. Else, use floorheight.
+	// If Front X Offset is specified, use that. Else, use floorheight.
+	value = (sides[line->sidenum[0]].textureoffset ? sides[line->sidenum[0]].textureoffset : line->frontsector->floorheight) >> FRACBITS;
+
+	// If DONTPEGBOTTOM, specify raw translucency value. Else, take it out of 1000.
+	if (!(line->flags & ML_DONTPEGBOTTOM))
+		value /= 100;
+
 	if (line->flags & ML_EFFECT3) // relative calc
-		po->translucency = max(min(po->translucency + ((line->flags & ML_DONTPEGBOTTOM) ?
-			(sides[line->sidenum[0]].textureoffset ?
-				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS)
-				: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS))
-			: (sides[line->sidenum[0]].textureoffset ?
-				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), -1000) / 100
-				: max(min(line->frontsector->floorheight>>FRACBITS, 1000), -1000) / 100)),
-			NUMTRANSMAPS), 0);
+		po->translucency += value;
 	else
-		po->translucency = (line->flags & ML_DONTPEGBOTTOM) ?
-			(sides[line->sidenum[0]].textureoffset ?
-				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), 0)
-				: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), 0))
-			: (sides[line->sidenum[0]].textureoffset ?
-				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), 0) / 100
-				: max(min(line->frontsector->floorheight>>FRACBITS, 1000), 0) / 100);
+		po->translucency = value;
+
+	po->translucency = max(min(po->translucency, NUMTRANSMAPS), 0);
 }
 
-//
-// PolyFade
-//
 // Makes a polyobject translucency fade and applies tangibility
-//
 static boolean PolyFade(line_t *line)
 {
 	INT32 polyObjNum = line->tag;
 	polyobj_t *po;
 	polyfadedata_t pfd;
+	INT32 value;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
 	{
@@ -1330,25 +1237,19 @@ static boolean PolyFade(line_t *line)
 
 	pfd.polyObjNum = polyObjNum;
 
-	// if DONTPEGBOTTOM, specify raw translucency value in Front X Offset
-	// else, take it out of 1000. If Front X Offset is specified, use that. Else, use floorheight.
+	// If Front X Offset is specified, use that. Else, use floorheight.
+	value = (sides[line->sidenum[0]].textureoffset ? sides[line->sidenum[0]].textureoffset : line->frontsector->floorheight) >> FRACBITS;
+
+	// If DONTPEGBOTTOM, specify raw translucency value. Else, take it out of 1000.
+	if (!(line->flags & ML_DONTPEGBOTTOM))
+		value /= 100;
+
 	if (line->flags & ML_EFFECT3) // relative calc
-		pfd.destvalue = max(min(po->translucency + ((line->flags & ML_DONTPEGBOTTOM) ?
-			(sides[line->sidenum[0]].textureoffset ?
-				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS)
-				: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), -NUMTRANSMAPS))
-			: (sides[line->sidenum[0]].textureoffset ?
-				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), -1000) / 100
-				: max(min(line->frontsector->floorheight>>FRACBITS, 1000), -1000) / 100)),
-			NUMTRANSMAPS), 0);
+		pfd.destvalue = po->translucency + value;
 	else
-		pfd.destvalue = (line->flags & ML_DONTPEGBOTTOM) ?
-			(sides[line->sidenum[0]].textureoffset ?
-				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, NUMTRANSMAPS), 0)
-				: max(min(line->frontsector->floorheight>>FRACBITS, NUMTRANSMAPS), 0))
-			: (sides[line->sidenum[0]].textureoffset ?
-				max(min(sides[line->sidenum[0]].textureoffset>>FRACBITS, 1000), 0) / 100
-				: max(min(line->frontsector->floorheight>>FRACBITS, 1000), 0) / 100);
+		pfd.destvalue = value;
+
+	pfd.destvalue = max(min(pfd.destvalue, NUMTRANSMAPS), 0);
 
 	// already equal, nothing to do
 	if (po->translucency == pfd.destvalue)
@@ -1367,11 +1268,7 @@ static boolean PolyFade(line_t *line)
 	return EV_DoPolyObjFade(&pfd);
 }
 
-//
-// PolyWaypoint
-//
 // Parses arguments for parameterized polyobject waypoint movement
-//
 static boolean PolyWaypoint(line_t *line)
 {
 	polywaypointdata_t pwd;
@@ -1387,11 +1284,7 @@ static boolean PolyWaypoint(line_t *line)
 	return EV_DoPolyObjWaypoint(&pwd);
 }
 
-//
-// PolyRotate
-//
 // Parses arguments for parameterized polyobject rotate specials
-//
 static boolean PolyRotate(line_t *line)
 {
 	polyrotdata_t prd;
@@ -1416,11 +1309,20 @@ static boolean PolyRotate(line_t *line)
 	return EV_DoPolyObjRotate(&prd);
 }
 
-//
-// PolyDisplace
-//
+// Parses arguments for polyobject flag waving special
+static boolean PolyFlag(line_t *line)
+{
+	polyflagdata_t pfd;
+
+	pfd.polyObjNum = line->tag;
+	pfd.speed = P_AproxDistance(line->dx, line->dy) >> FRACBITS;
+	pfd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y) >> ANGLETOFINESHIFT;
+	pfd.momx = sides[line->sidenum[0]].textureoffset >> FRACBITS;
+
+	return EV_DoPolyObjFlag(&pfd);
+}
+
 // Parses arguments for parameterized polyobject move-by-sector-heights specials
-//
 static boolean PolyDisplace(line_t *line)
 {
 	polydisplacedata_t pdd;
@@ -1435,8 +1337,7 @@ static boolean PolyDisplace(line_t *line)
 }
 
 
-/** Similar to PolyDisplace().
- */
+// Parses arguments for parameterized polyobject rotate-by-sector-heights specials
 static boolean PolyRotDisplace(line_t *line)
 {
 	polyrotdisplacedata_t pdd;
@@ -1462,8 +1363,6 @@ static boolean PolyRotDisplace(line_t *line)
 	return EV_DoPolyObjRotDisplace(&pdd);
 }
 
-#endif // ifdef POLYOBJECTS
-
 /** Changes a sector's tag.
   * Used by the linedef executor tag changer and by crumblers.
   *
@@ -2034,6 +1933,17 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			if (!(actor && actor->player && ((stricmp(triggerline->text, skins[actor->player->skin].name) == 0) ^ ((triggerline->flags & ML_NOCLIMB) == ML_NOCLIMB))))
 				return false;
 			break;
+		case 334: // object dye - continuous
+		case 335: // object dye - each time
+		case 336: // object dye - once
+			{
+				INT32 triggercolor = (INT32)sides[triggerline->sidenum[0]].toptexture;
+				UINT8 color = (actor->player ? actor->player->powers[pw_dye] : actor->color);
+				boolean invert = (triggerline->flags & ML_NOCLIMB ? true : false);
+
+				if (invert ^ (triggercolor != color))
+					return false;
+			}
 		default:
 			break;
 	}
@@ -2167,6 +2077,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 	 || specialtype == 328 // Nights lap - Once
 	 || specialtype == 330 // Nights Bonus Time - Once
 	 || specialtype == 333 // Skin - Once
+	 || specialtype == 336 // Dye - Once
 	 || specialtype == 399) // Level Load
 		triggerline->special = 0; // Clear it out
 
@@ -2208,7 +2119,8 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
 		 || 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 == 332)// Skin - Each time
+		 || lines[masterline].special == 332 // Skin - Each time
+		 || lines[masterline].special == 335)// Dye - Each time
 			continue;
 
 		if (lines[masterline].special < 300
@@ -2488,7 +2400,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				newceilinglightsec = line->frontsector->ceilinglightsec;
 
 				// act on all sectors with the same tag as the triggering linedef
-				while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+				while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 				{
 					if (sectors[secnum].lightingdata)
 					{
@@ -2543,7 +2455,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 409: // Change tagged sectors' tag
 		// (formerly "Change calling sectors' tag", but behavior was changed)
 		{
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 				P_ChangeSectorTag(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
 			break;
 		}
@@ -2553,7 +2465,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 411: // Stop floor/ceiling movement in tagged sector(s)
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (sectors[secnum].floordata)
 				{
@@ -2623,7 +2535,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 				else
 				{
-					if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
+					if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
 						return;
 
 					dest = P_GetObjectTypeInSectorNum(MT_TELEPORTMAN, secnum);
@@ -2738,7 +2650,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						// Additionally play the sound from tagged sectors' soundorgs
 						sector_t *sec;
 
-						while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+						while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 						{
 							sec = &sectors[secnum];
 							S_StartSound(&sec->soundorg, sfxnum);
@@ -2853,7 +2765,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 416: // Spawn adjustable fire flicker
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2887,7 +2799,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 417: // Spawn adjustable glowing light
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2921,7 +2833,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 418: // Spawn adjustable strobe flash (unsynchronized)
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2955,7 +2867,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 419: // Spawn adjustable strobe flash (synchronized)
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -3003,7 +2915,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 421: // Stop lighting effect in tagged sectors
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 				if (sectors[secnum].lightingdata)
 				{
 					P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
@@ -3018,7 +2930,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens
 					return;
 
-				if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
+				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
 					return;
 
 				altview = P_GetObjectTypeInSectorNum(MT_ALTVIEWMAN, secnum);
@@ -3337,7 +3249,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			if (line->sidenum[1] != 0xffff)
 				state = (statenum_t)sides[line->sidenum[1]].toptexture;
 
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				boolean tryagain;
 				sec = sectors + secnum;
@@ -3492,7 +3404,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			// Except it is activated by linedef executor, not level load
 			// This could even override existing colormaps I believe
 			// -- Monster Iestyn 14/06/18
-			for (secnum = -1; (secnum = P_FindSectorFromLineTag(line, secnum)) >= 0 ;)
+			for (secnum = -1; (secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0 ;)
 			{
 				P_ResetColormapFader(&sectors[secnum]);
 
@@ -3820,7 +3732,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		}
 
 		case 455: // Fade colormap
-			for (secnum = -1; (secnum = P_FindSectorFromLineTag(line, secnum)) >= 0 ;)
+			for (secnum = -1; (secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0 ;)
 			{
 				extracolormap_t *source_exc, *dest_exc, *exc;
 				INT32 speed = (INT32)((line->flags & ML_DONTPEGBOTTOM) || !sides[line->sidenum[0]].rowoffset) && line->sidenum[1] != 0xFFFF ?
@@ -3909,7 +3821,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 456: // Stop fade colormap
-			for (secnum = -1; (secnum = P_FindSectorFromLineTag(line, secnum)) >= 0 ;)
+			for (secnum = -1; (secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0 ;)
 				P_ResetColormapFader(&sectors[secnum]);
 			break;
 
@@ -3923,7 +3835,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				boolean persist = (line->flags & ML_EFFECT2);
 				mobj_t *anchormo;
 
-				if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
+				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
 					return;
 
 				anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
@@ -4014,7 +3926,11 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				mobj = P_SpawnMobj(x, y, z, type);
 				if (mobj)
+				{
+					if (line->flags & ML_EFFECT1)
+						mobj->angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y);
 					CONS_Debug(DBG_GAMELOGIC, "Linedef Type %d - Spawn Object: %d spawned at (%d, %d, %d)\n", line->special, mobj->type, mobj->x>>FRACBITS, mobj->y>>FRACBITS, mobj->z>>FRACBITS); //TODO: Convert mobj->type to a string somehow.
+				}
 				else
 					CONS_Alert(CONS_ERROR,"Linedef Type %d - Spawn Object: Object did not spawn!\n", line->special);
 			}
@@ -4038,7 +3954,63 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
-#ifdef POLYOBJECTS
+		case 463: // Dye object
+			{
+				INT32 color = sides[line->sidenum[0]].toptexture;
+
+				if (mo)
+				{
+					if (color < 0 || color >= MAXTRANSLATIONS)
+						return;
+
+					var1 = 0;
+					var2 = color;
+					A_Dye(mo);
+				}
+			}
+			break;
+
+		case 464: // Trigger Egg Capsule
+			{
+				thinker_t *th;
+				mobj_t *mo2;
+
+				// Find the center of the Eggtrap and release all the pretty animals!
+				// The chimps are my friends.. heeheeheheehehee..... - LouisJM
+				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_EGGTRAP)
+						continue;
+
+					if (!mo2->spawnpoint)
+						continue;
+
+					if (mo2->spawnpoint->angle != line->tag)
+						continue;
+
+					P_KillMobj(mo2, NULL, mo, 0);
+				}
+
+				if (!(line->flags & ML_NOCLIMB))
+				{
+					INT32 i;
+
+					// Mark all players with the time to exit thingy!
+					for (i = 0; i < MAXPLAYERS; i++)
+					{
+						if (!playeringame[i])
+							continue;
+						P_DoPlayerExit(&players[i]);
+					}
+				}
+			}
+			break;
+
 		case 480: // Polyobj_DoorSlide
 		case 481: // Polyobj_DoorSwing
 			PolyDoor(line);
@@ -4068,7 +4040,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 492:
 			PolyFade(line);
 			break;
-#endif
 
 		default:
 			break;
@@ -4417,7 +4388,8 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_ELECTRIC);
 			break;
 		case 5: // Spikes
-			// Don't do anything. In Soviet Russia, spikes find you.
+			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
+				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPIKE);
 			break;
 		case 6: // Death Pit (Camera Mod)
 		case 7: // Death Pit (No Camera Mod)
@@ -5645,6 +5617,35 @@ void P_UpdateSpecials(void)
 	}
 }
 
+//
+// Floor over floors (FOFs), 3Dfloors, 3Dblocks, fake floors (ffloors), rovers, or whatever you want to call them
+//
+
+/** Gets the ID number for a 3Dfloor in its target sector.
+  *
+  * \param fflr The 3Dfloor we want an ID for.
+  * \return ID of 3Dfloor in target sector. Note that the first FOF's ID is 0. UINT16_MAX is given if invalid.
+  * \sa P_GetFFloorByID
+  */
+UINT16 P_GetFFloorID(ffloor_t *fflr)
+{
+	ffloor_t *rover;
+	sector_t *sec;
+	UINT16 i = 0;
+
+	if (!fflr)
+		return UINT16_MAX;
+
+	sec = fflr->target;
+
+	if (!sec->ffloors)
+		return UINT16_MAX;
+	for (rover = sec->ffloors; rover; rover = rover->next, i++)
+		if (rover == fflr)
+			return i;
+	return UINT16_MAX;
+}
+
 /** Gets a 3Dfloor by control sector.
   *
   * \param sec  Target sector.
@@ -5669,7 +5670,7 @@ static inline ffloor_t *P_GetFFloorBySec(sector_t *sec, sector_t *sec2)
   * \param sec Target sector.
   * \param id  ID of 3Dfloor in target sector. Note that the first FOF's ID is 0.
   * \return Pointer to found 3Dfloor, or NULL.
-  * \sa P_GetFFloorBySec
+  * \sa P_GetFFloorBySec, P_GetFFloorID
   */
 ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id)
 {
@@ -5725,7 +5726,6 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	thinker_t *th;
 	friction_t *f;
 	pusher_t *p;
-	levelspecthink_t *lst;
 	size_t sec2num;
 	size_t i;
 
@@ -5743,8 +5743,6 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 		sec2->floorheight = tempceiling;
 	}
 
-	sec2->tagline = master;
-
 	if (sec2->numattached == 0)
 	{
 		sec2->attached = Z_Malloc(sizeof (*sec2->attached) * sec2->maxattached, PU_STATIC, NULL);
@@ -5828,16 +5826,8 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 		else if (th == &thlist[THINK_MAIN])
 			break;
 
-		// Should this FOF have spikeness?
-		if (th->function.acp1 == (actionf_p1)T_SpikeSector)
-		{
-			lst = (levelspecthink_t *)th;
-
-			if (lst->sector == sec2)
-				P_AddSpikeThinker(sec, (INT32)sec2num);
-		}
 		// Should this FOF have friction?
-		else if(th->function.acp1 == (actionf_p1)T_Friction)
+		if(th->function.acp1 == (actionf_p1)T_Friction)
 		{
 			f = (friction_t *)th;
 
@@ -5884,7 +5874,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	}
 
 	if ((flags & FF_CRUMBLE))
-		sec2->crumblestate = 1;
+		sec2->crumblestate = CRUMBLE_WAIT;
 
 	if ((flags & FF_FLOATBOB))
 	{
@@ -5901,28 +5891,6 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 // SPECIAL SPAWNING
 //
 
-/** Adds a spike thinker.
-  * Sector type Section1:5 will result in this effect.
-  *
-  * \param sec Sector in which to add the thinker.
-  * \param referrer If != sec, then we're dealing with a FOF
-  * \sa P_SpawnSpecials, T_SpikeSector
-  * \author SSNTails <http://www.ssntails.org>
-  */
-static void P_AddSpikeThinker(sector_t *sec, INT32 referrer)
-{
-	levelspecthink_t *spikes;
-
-	// create and initialize new thinker
-	spikes = Z_Calloc(sizeof (*spikes), PU_LEVSPEC, NULL);
-	P_AddThinker(THINK_MAIN, &spikes->thinker);
-
-	spikes->thinker.function.acp1 = (actionf_p1)T_SpikeSector;
-
-	spikes->sector = sec;
-	spikes->vars[0] = referrer;
-}
-
 /** Adds a float thinker.
   * Float thinkers cause solid 3Dfloors to float on water.
   *
@@ -5931,9 +5899,9 @@ static void P_AddSpikeThinker(sector_t *sec, INT32 referrer)
   * \sa P_SpawnSpecials, T_FloatSector
   * \author SSNTails <http://www.ssntails.org>
   */
-static void P_AddFloatThinker(sector_t *sec, INT32 tag, line_t *sourceline)
+static void P_AddFloatThinker(sector_t *sec, UINT16 tag, line_t *sourceline)
 {
-	levelspecthink_t *floater;
+	floatthink_t *floater;
 
 	// create and initialize new thinker
 	floater = Z_Calloc(sizeof (*floater), PU_LEVSPEC, NULL);
@@ -5942,43 +5910,10 @@ static void P_AddFloatThinker(sector_t *sec, INT32 tag, line_t *sourceline)
 	floater->thinker.function.acp1 = (actionf_p1)T_FloatSector;
 
 	floater->sector = sec;
-	floater->vars[0] = tag;
+	floater->tag = (INT16)tag;
 	floater->sourceline = sourceline;
 }
 
-/** Adds a bridge thinker.
-  * Bridge thinkers cause a group of FOFs to behave like
-  * a bridge made up of pieces, that bows under weight.
-  *
-  * \param sec          Control sector.
-  * \sa P_SpawnSpecials, T_BridgeThinker
-  * \author SSNTails <http://www.ssntails.org>
-  */
-/*
-static inline void P_AddBridgeThinker(line_t *sourceline, sector_t *sec)
-{
-	levelspecthink_t *bridge;
-
-	// create an initialize new thinker
-	bridge = Z_Calloc(sizeof (*bridge), PU_LEVSPEC, NULL);
-	P_AddThinker(THINK_MAIN, &bridge->thinker);
-
-	bridge->thinker.function.acp1 = (actionf_p1)T_BridgeThinker;
-
-	bridge->sector = sec;
-	bridge->vars[0] = sourceline->frontsector->floorheight;
-	bridge->vars[1] = sourceline->frontsector->ceilingheight;
-	bridge->vars[2] = P_AproxDistance(sourceline->dx, sourceline->dy); // Speed
-	bridge->vars[2] = FixedDiv(bridge->vars[2], 16*FRACUNIT);
-	bridge->vars[3] = bridge->vars[2];
-
-	// Start tag and end tag are TARGET SECTORS, not CONTROL SECTORS
-	// Control sector tags should be End_Tag + (End_Tag - Start_Tag)
-	bridge->vars[4] = sourceline->tag; // Start tag
-	bridge->vars[5] = (sides[sourceline->sidenum[0]].textureoffset>>FRACBITS); // End tag
-}
-*/
-
 /**
   * Adds a plane displacement thinker.
   * Whenever the "control" sector moves,
@@ -6020,7 +5955,7 @@ static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control,
   */
 static void P_AddBlockThinker(sector_t *sec, line_t *sourceline)
 {
-	levelspecthink_t *block;
+	mariocheck_t *block;
 
 	// create and initialize new elevator thinker
 	block = Z_Calloc(sizeof (*block), PU_LEVSPEC, NULL);
@@ -6041,109 +5976,67 @@ static void P_AddBlockThinker(sector_t *sec, line_t *sourceline)
   * there already.
   *
   * \param sec          Control sector.
-  * \param actionsector Target sector.
-  * \param sourceline   Control linedef.
   * \sa P_SpawnSpecials, T_RaiseSector
   * \author SSNTails <http://www.ssntails.org>
   */
-static void P_AddRaiseThinker(sector_t *sec, line_t *sourceline)
+static void P_AddRaiseThinker(sector_t *sec, INT16 tag, fixed_t speed, fixed_t ceilingtop, fixed_t ceilingbottom, boolean lower, boolean spindash)
 {
-	levelspecthink_t *raise;
+	raise_t *raise;
 
 	raise = Z_Calloc(sizeof (*raise), PU_LEVSPEC, NULL);
 	P_AddThinker(THINK_MAIN, &raise->thinker);
 
 	raise->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
 
-	if (sourceline->flags & ML_BLOCKMONSTERS)
-		raise->vars[0] = 1;
-	else
-		raise->vars[0] = 0;
-
-	// set up the fields
+	raise->tag = tag;
 	raise->sector = sec;
 
-	// Require a spindash to activate
-	if (sourceline->flags & ML_NOCLIMB)
-		raise->vars[1] = 1;
-	else
-		raise->vars[1] = 0;
-
-	raise->vars[2] = P_AproxDistance(sourceline->dx, sourceline->dy);
-	raise->vars[2] = FixedDiv(raise->vars[2], 4*FRACUNIT);
-	raise->vars[3] = raise->vars[2];
-
-	raise->vars[5] = P_FindHighestCeilingSurrounding(sec);
-	raise->vars[4] = raise->vars[5]
-		- (sec->ceilingheight - sec->floorheight);
+	raise->ceilingtop = ceilingtop;
+	raise->ceilingbottom = ceilingbottom;
 
-	raise->vars[7] = P_FindLowestCeilingSurrounding(sec);
-	raise->vars[6] = raise->vars[7]
-		- (sec->ceilingheight - sec->floorheight);
+	raise->basespeed =  speed;
 
-	raise->sourceline = sourceline;
+	if (lower)
+		raise->flags |= RF_REVERSE;
+	if (spindash)
+		raise->flags |= RF_SPINDASH;
 }
 
-static void P_AddAirbob(sector_t *sec, line_t *sourceline, boolean noadjust, boolean dynamic)
+static void P_AddAirbob(sector_t *sec, INT16 tag, fixed_t dist, boolean raise, boolean spindash, boolean dynamic)
 {
-	levelspecthink_t *airbob;
+	raise_t *airbob;
 
 	airbob = Z_Calloc(sizeof (*airbob), PU_LEVSPEC, NULL);
 	P_AddThinker(THINK_MAIN, &airbob->thinker);
 
 	airbob->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
 
-	// set up the fields
+	airbob->tag = tag;
 	airbob->sector = sec;
 
-	// Require a spindash to activate
-	if (sourceline->flags & ML_NOCLIMB)
-		airbob->vars[1] = 1;
-	else
-		airbob->vars[1] = 0;
-
-	airbob->vars[2] = FRACUNIT;
-
-	if (noadjust)
-		airbob->vars[7] = airbob->sector->ceilingheight-16*FRACUNIT;
-	else
-		airbob->vars[7] = airbob->sector->ceilingheight - P_AproxDistance(sourceline->dx, sourceline->dy);
-	airbob->vars[6] = airbob->vars[7]
-		- (sec->ceilingheight - sec->floorheight);
-
-	airbob->vars[3] = airbob->vars[2];
-
-	if (sourceline->flags & ML_BLOCKMONSTERS)
-		airbob->vars[0] = 1;
-	else
-		airbob->vars[0] = 0;
+	airbob->ceilingtop = sec->ceilingheight;
+	airbob->ceilingbottom = sec->ceilingheight - dist;
 
-	airbob->vars[5] = sec->ceilingheight;
-	airbob->vars[4] = airbob->vars[5]
-			- (sec->ceilingheight - sec->floorheight);
+	airbob->basespeed = FRACUNIT;
 
-	airbob->vars[9] = dynamic ? 1 : 0;
-
-	airbob->sourceline = sourceline;
+	if (!raise)
+		airbob->flags |= RF_REVERSE;
+	if (spindash)
+		airbob->flags |= RF_SPINDASH;
+	if (dynamic)
+		airbob->flags |= RF_DYNAMIC;
 }
 
 /** Adds a thwomp thinker.
   * Even thwomps need to think!
   *
   * \param sec          Control sector.
-  * \param actionsector Target sector.
-  * \param sourceline   Control linedef.
   * \sa P_SpawnSpecials, T_ThwompSector
   * \author SSNTails <http://www.ssntails.org>
   */
-static inline void P_AddThwompThinker(sector_t *sec, sector_t *actionsector, line_t *sourceline)
+static inline void P_AddThwompThinker(sector_t *sec, INT16 tag, line_t *sourceline, fixed_t crushspeed, fixed_t retractspeed, UINT16 sound)
 {
-#define speed vars[1]
-#define direction vars[2]
-#define distance vars[3]
-#define floorwasheight vars[4]
-#define ceilingwasheight vars[5]
-	levelspecthink_t *thwomp;
+	thwomp_t *thwomp;
 
 	// You *probably* already have a thwomp in this sector. If you've combined it with something
 	// else that uses the floordata/ceilingdata, you must be weird.
@@ -6157,34 +6050,33 @@ static inline void P_AddThwompThinker(sector_t *sec, sector_t *actionsector, lin
 	thwomp->thinker.function.acp1 = (actionf_p1)T_ThwompSector;
 
 	// set up the fields according to the type of elevator action
+	thwomp->sourceline = sourceline;
 	thwomp->sector = sec;
-	thwomp->vars[0] = actionsector->tag;
-	thwomp->floorwasheight = thwomp->sector->floorheight;
-	thwomp->ceilingwasheight = thwomp->sector->ceilingheight;
+	thwomp->crushspeed = crushspeed;
+	thwomp->retractspeed = retractspeed;
 	thwomp->direction = 0;
-	thwomp->distance = 1;
-	thwomp->sourceline = sourceline;
-	thwomp->sector->floordata = thwomp;
-	thwomp->sector->ceilingdata = thwomp;
-	return;
-#undef speed
-#undef direction
-#undef distance
-#undef floorwasheight
-#undef ceilingwasheight
+	thwomp->floorstartheight = sec->floorheight;
+	thwomp->ceilingstartheight = sec->ceilingheight;
+	thwomp->delay = 1;
+	thwomp->tag = tag;
+	thwomp->sound = sound;
+
+	sec->floordata = thwomp;
+	sec->ceilingdata = thwomp;
+	// Start with 'resting' texture
+	sides[sourceline->sidenum[0]].midtexture = sides[sourceline->sidenum[0]].bottomtexture;
 }
 
 /** Adds a thinker which checks if any MF_ENEMY objects with health are in the defined area.
   * If not, a linedef executor is run once.
   *
-  * \param sec          Control sector.
   * \param sourceline   Control linedef.
   * \sa P_SpawnSpecials, T_NoEnemiesSector
   * \author SSNTails <http://www.ssntails.org>
   */
-static inline void P_AddNoEnemiesThinker(sector_t *sec, line_t *sourceline)
+static inline void P_AddNoEnemiesThinker(line_t *sourceline)
 {
-	levelspecthink_t *nobaddies;
+	noenemies_t *nobaddies;
 
 	// create and initialize new thinker
 	nobaddies = Z_Calloc(sizeof (*nobaddies), PU_LEVSPEC, NULL);
@@ -6192,21 +6084,19 @@ static inline void P_AddNoEnemiesThinker(sector_t *sec, line_t *sourceline)
 
 	nobaddies->thinker.function.acp1 = (actionf_p1)T_NoEnemiesSector;
 
-	nobaddies->sector = sec;
 	nobaddies->sourceline = sourceline;
 }
 
 /** Adds a thinker for Each-Time linedef executors. A linedef executor is run
   * only when a player enters the area and doesn't run again until they re-enter.
   *
-  * \param sec          Control sector that contains the lines of executors we will want to run.
   * \param sourceline   Control linedef.
   * \sa P_SpawnSpecials, T_EachTimeThinker
   * \author SSNTails <http://www.ssntails.org>
   */
-static void P_AddEachTimeThinker(sector_t *sec, line_t *sourceline)
+static void P_AddEachTimeThinker(line_t *sourceline)
 {
-	levelspecthink_t *eachtime;
+	eachtime_t *eachtime;
 
 	// create and initialize new thinker
 	eachtime = Z_Calloc(sizeof (*eachtime), PU_LEVSPEC, NULL);
@@ -6214,8 +6104,8 @@ static void P_AddEachTimeThinker(sector_t *sec, line_t *sourceline)
 
 	eachtime->thinker.function.acp1 = (actionf_p1)T_EachTimeThinker;
 
-	eachtime->sector = sec;
 	eachtime->sourceline = sourceline;
+	eachtime->triggerOnExit = !!(sourceline->flags & ML_BOUNCY);
 }
 
 /** Adds a camera scanner.
@@ -6230,6 +6120,8 @@ static inline void P_AddCameraScanner(sector_t *sourcesec, sector_t *actionsecto
 {
 	elevator_t *elevator; // Why not? LOL
 
+	CONS_Alert(CONS_WARNING, M_GetText("Detected a camera scanner effect (linedef type 5). This effect is deprecated and will be removed in the future!\n"));
+
 	// create and initialize new elevator thinker
 	elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
 	P_AddThinker(THINK_MAIN, &elevator->thinker);
@@ -6243,92 +6135,85 @@ static inline void P_AddCameraScanner(sector_t *sourcesec, sector_t *actionsecto
 	elevator->distance = FixedInt(AngleFixed(angle));
 }
 
-static const ffloortype_e laserflags = FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA|FF_TRANSLUCENT;
-
 /** Flashes a laser block.
   *
   * \param flash Thinker structure for this laser.
-  * \sa EV_AddLaserThinker
+  * \sa P_AddLaserThinker
   * \author SSNTails <http://www.ssntails.org>
   */
 void T_LaserFlash(laserthink_t *flash)
 {
 	msecnode_t *node;
 	mobj_t *thing;
-	sector_t *sourcesec;
-	ffloor_t *fflr = flash->ffloor;
-	sector_t *sector = flash->sector;
+	INT32 s;
+	ffloor_t *fflr;
+	sector_t *sector;
+	sector_t *sourcesec = flash->sourceline->frontsector;
 	fixed_t top, bottom;
 
-	if (!fflr || !(fflr->flags & FF_EXISTS))
-		return;
+	for (s = -1; (s = P_FindSectorFromTag(flash->tag, s)) >= 0 ;)
+	{
+		sector = &sectors[s];
+		for (fflr = sector->ffloors; fflr; fflr = fflr->next)
+		{
+			if (fflr->master != flash->sourceline)
+				continue;
 
-	if (leveltime & 2)
-		//fflr->flags |= FF_RENDERALL;
-		fflr->alpha = 0xB0;
-	else
-		//fflr->flags &= ~FF_RENDERALL;
-		fflr->alpha = 0x90;
+			if (!(fflr->flags & FF_EXISTS))
+				break;
 
-	sourcesec = fflr->master->frontsector; // Less to type!
+			if (leveltime & 2)
+				//fflr->flags |= FF_RENDERALL;
+				fflr->alpha = 0xB0;
+			else
+				//fflr->flags &= ~FF_RENDERALL;
+				fflr->alpha = 0x90;
 
-	top    = P_GetFFloorTopZAt   (fflr, sector->soundorg.x, sector->soundorg.y);
-	bottom = P_GetFFloorBottomZAt(fflr, sector->soundorg.x, sector->soundorg.y);
-	sector->soundorg.z = (top + bottom)/2;
-	S_StartSound(&sector->soundorg, sfx_laser);
+			top    = P_GetFFloorTopZAt   (fflr, sector->soundorg.x, sector->soundorg.y);
+			bottom = P_GetFFloorBottomZAt(fflr, sector->soundorg.x, sector->soundorg.y);
+			sector->soundorg.z = (top + bottom)/2;
+			S_StartSound(&sector->soundorg, sfx_laser);
 
-	// Seek out objects to DESTROY! MUAHAHHAHAHAA!!!*cough*
-	for (node = sector->touching_thinglist; node && node->m_thing; node = node->m_thinglist_next)
-	{
-		thing = node->m_thing;
+			// Seek out objects to DESTROY! MUAHAHHAHAHAA!!!*cough*
+			for (node = sector->touching_thinglist; node && node->m_thing; node = node->m_thinglist_next)
+			{
+				thing = node->m_thing;
 
-		if ((fflr->master->flags & ML_EFFECT1)
-			&& thing->flags & MF_BOSS)
-			continue; // Don't hurt bosses
+				if (flash->nobosses && thing->flags & MF_BOSS)
+					continue; // Don't hurt bosses
 
-		// Don't endlessly kill egg guard shields (or anything else for that matter)
-		if (thing->health <= 0)
-			continue;
+				// Don't endlessly kill egg guard shields (or anything else for that matter)
+				if (thing->health <= 0)
+					continue;
 
-		top = P_GetSpecialTopZ(thing, sourcesec, sector);
-		bottom = P_GetSpecialBottomZ(thing, sourcesec, sector);
+				top = P_GetSpecialTopZ(thing, sourcesec, sector);
+				bottom = P_GetSpecialBottomZ(thing, sourcesec, sector);
 
-		if (thing->z >= top
-		|| thing->z + thing->height <= bottom)
-			continue;
+				if (thing->z >= top
+				|| thing->z + thing->height <= bottom)
+					continue;
+
+				if (thing->flags & MF_SHOOTABLE)
+					P_DamageMobj(thing, NULL, NULL, 1, 0);
+				else if (thing->type == MT_EGGSHIELD)
+					P_KillMobj(thing, NULL, NULL, 0);
+			}
 
-		if (thing->flags & MF_SHOOTABLE)
-			P_DamageMobj(thing, NULL, NULL, 1, 0);
-		else if (thing->type == MT_EGGSHIELD)
-			P_KillMobj(thing, NULL, NULL, 0);
+			break;
+		}
 	}
 }
 
-/** Adds a laser thinker to a 3Dfloor.
-  *
-  * \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
-  * \author SSNTails <http://www.ssntails.org>
-  */
-static inline void EV_AddLaserThinker(sector_t *sec, sector_t *sec2, line_t *line, thinkerlist_t *secthinkers)
+static inline void P_AddLaserThinker(INT16 tag, line_t *line, boolean nobosses)
 {
-	laserthink_t *flash;
-	ffloor_t *fflr = P_AddFakeFloor(sec, sec2, line, laserflags, secthinkers);
-
-	if (!fflr)
-		return;
-
-	flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
+	laserthink_t *flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
 
 	P_AddThinker(THINK_MAIN, &flash->thinker);
 
 	flash->thinker.function.acp1 = (actionf_p1)T_LaserFlash;
-	flash->ffloor = fflr;
-	flash->sector = sec; // For finding mobjs
-	flash->sec = sec2;
+	flash->tag = tag;
 	flash->sourceline = line;
+	flash->nobosses = nobosses;
 }
 
 //
@@ -6358,11 +6243,11 @@ static void P_RunLevelLoadExecutors(void)
 void P_InitSpecials(void)
 {
 	// Set the default gravity. Custom gravity overrides this setting.
-	gravity = FRACUNIT/2;
+	gravity = mapheaderinfo[gamemap-1]->gravity;
 
 	// Defaults in case levels don't have them set.
-	sstimer = 90*TICRATE + 6;
-	ssspheres = 1;
+	sstimer = mapheaderinfo[gamemap-1]->sstimer*TICRATE + 6;
+	ssspheres = mapheaderinfo[gamemap-1]->ssspheres;
 
 	CheckForBustableBlocks = CheckForBouncySector = CheckForQuicksand = CheckForMarioBlocks = CheckForFloatBob = CheckForReverseGravity = false;
 
@@ -6440,9 +6325,11 @@ void P_SpawnSpecials(boolean fromnetsave)
 		switch(GETSECSPECIAL(sector->special, 1))
 		{
 			case 5: // Spikes
-				P_AddSpikeThinker(sector, (INT32)(sector-sectors));
+				//Terrible hack to replace an even worse hack:
+				//Spike damage automatically sets SF_TRIGGERSPECIAL_TOUCH.
+				//Yes, this also affects other specials on the same sector. Sorry.
+				sector->flags |= SF_TRIGGERSPECIAL_TOUCH;
 				break;
-
 			case 15: // Bouncy sector
 				CheckForBouncySector = true;
 				break;
@@ -6488,9 +6375,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 	// Firstly, find out how many there are in each sector
 	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++;
-		else if (th->function.acp1 == (actionf_p1)T_Friction)
+		if (th->function.acp1 == (actionf_p1)T_Friction)
 			secthinkers[((friction_t *)th)->affectee].count++;
 		else if (th->function.acp1 == (actionf_p1)T_Pusher)
 			secthinkers[((pusher_t *)th)->affectee].count++;
@@ -6510,9 +6395,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 	{
 		size_t secnum = (size_t)-1;
 
-		if (th->function.acp1 == (actionf_p1)T_SpikeSector)
-			secnum = ((levelspecthink_t *)th)->sector - sectors;
-		else if (th->function.acp1 == (actionf_p1)T_Friction)
+		if (th->function.acp1 == (actionf_p1)T_Friction)
 			secnum = ((friction_t *)th)->affectee;
 		else if (th->function.acp1 == (actionf_p1)T_Pusher)
 			secnum = ((pusher_t *)th)->affectee;
@@ -6551,7 +6434,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 1: // Definable gravity per sector
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 				{
 					sectors[s].gravity = &sectors[sec].floorheight; // This allows it to change in realtime!
 
@@ -6575,7 +6458,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 5: // Change camera info
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					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;
 
@@ -6602,7 +6485,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 						P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
 					else
 					{
-						for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0;)
+						for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0;)
 							P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
 					}
 				}
@@ -6613,7 +6496,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 8: // Sector Parameters
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 				{
 					if (lines[i].flags & ML_NOCLIMB)
 					{
@@ -6641,7 +6524,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 10: // Vertical culling plane for sprites and FOFs
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					sectors[s].cullheight = &lines[i]; // This allows it to change in realtime!
 				break;
 
@@ -6702,18 +6585,18 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 63: // support for drawn heights coming from different sector
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					sectors[s].heightsec = (INT32)sec;
 				break;
 
 			case 64: // Appearing/Disappearing FOF option
 				if (lines[i].flags & ML_BLOCKMONSTERS) { // Find FOFs by control sector tag
-					for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+					for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 						for (j = 0; (unsigned)j < sectors[s].linecount; j++)
 							if (sectors[s].lines[j]->special >= 100 && sectors[s].lines[j]->special < 300)
 								Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), (INT32)(sectors[s].lines[j]-lines), (INT32)i);
 				} else // Find FOFs by effect sector tag
-					for (s = -1; (s = P_FindLineFromLineTag(lines + i, s)) >= 0 ;)
+					for (s = -1; (s = P_FindLineFromTag(lines[i].tag, s)) >= 0 ;)
 					{
 						if ((size_t)s == i)
 							continue;
@@ -6722,23 +6605,16 @@ void P_SpawnSpecials(boolean fromnetsave)
 					}
 				break;
 
-			case 65: // Bridge Thinker
-				/*
-				// Disable this until it's working right!
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-					P_AddBridgeThinker(&lines[i], &sectors[s]);*/
-				break;
-
 			case 66: // Displace floor by front sector
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_AddPlaneDisplaceThinker(pd_floor, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 67: // Displace ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_AddPlaneDisplaceThinker(pd_ceiling, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 68: // Displace both floor AND ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_AddPlaneDisplaceThinker(pd_both, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 
@@ -6932,18 +6808,19 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 150: // Air bobbing platform
 			case 151: // Adjustable air bobbing platform
+			{
+				fixed_t dist = (lines[i].special == 150) ? 16*FRACUNIT : P_AproxDistance(lines[i].dx, lines[i].dy);
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				lines[i].flags |= ML_BLOCKMONSTERS;
-				P_AddAirbob(lines[i].frontsector, lines + i, (lines[i].special != 151), false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
+			}
 			case 152: // Adjustable air bobbing platform in reverse
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines + i, true, false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 			case 153: // Dynamic Sinking Platform
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				lines[i].flags |= ML_BLOCKMONSTERS;
-				P_AddAirbob(lines[i].frontsector, lines + i, false, true);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
 				break;
 
 			case 160: // Float/bob platform
@@ -6993,15 +6870,13 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 176: // Air bobbing platform that will crumble and bob on the water when it falls and hits
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB|FF_CRUMBLE, secthinkers);
-				lines[i].flags |= ML_BLOCKMONSTERS;
-				P_AddAirbob(lines[i].frontsector, lines + i, true, false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 177: // Air bobbing platform that will crumble and bob on
 				// the water when it falls and hits, then never return
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_CRUMBLE|FF_NORETURN, secthinkers);
-				lines[i].flags |= ML_BLOCKMONSTERS;
-				P_AddAirbob(lines[i].frontsector, lines + i, true, false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 178: // Crumbling platform that will float when it hits water
@@ -7014,49 +6889,36 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 180: // Air bobbing platform that will crumble
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
-				lines[i].flags |= ML_BLOCKMONSTERS;
-				P_AddAirbob(lines[i].frontsector, lines + i, true, false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 190: // Rising Platform FOF (solid, opaque, shadows)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
-				break;
-
 			case 191: // Rising Platform FOF (solid, opaque, no shadows)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_CUTLEVEL, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
-				break;
-
 			case 192: // Rising Platform TL block: FOF (solid, translucent)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
-				break;
-
 			case 193: // Rising Platform FOF (solid, invisible)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_NOSHADE, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
-				break;
-
 			case 194: // Rising Platform 'Platform' - You can jump up through it
-				// If line has no-climb set, don't give it shadows, otherwise do
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_NOSHADE;
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
-				break;
-
 			case 195: // Rising Platform Translucent "platform"
-				// If line has no-climb set, don't give it shadows, otherwise do
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES|FF_EXTRA|FF_CUTEXTRA;
-				if (lines[i].flags & ML_NOCLIMB)
+			{
+				fixed_t speed = FixedDiv(P_AproxDistance(lines[i].dx, lines[i].dy), 4*FRACUNIT);
+				fixed_t ceilingtop = P_FindHighestCeilingSurrounding(lines[i].frontsector);
+				fixed_t ceilingbottom = P_FindLowestCeilingSurrounding(lines[i].frontsector);
+
+				ffloorflags = FF_EXISTS|FF_SOLID;
+				if (lines[i].special != 193)
+					ffloorflags |= FF_RENDERALL;
+				if (lines[i].special <= 191)
+					ffloorflags |= FF_CUTLEVEL;
+				if (lines[i].special == 192 || lines[i].special == 195)
+					ffloorflags |= FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
+				if (lines[i].special >= 194)
+					ffloorflags |= FF_PLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
+				if (lines[i].special != 190 && (lines[i].special <= 193 || lines[i].flags & ML_NOCLIMB))
 					ffloorflags |= FF_NOSHADE;
-
 				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i]);
+
+				P_AddRaiseThinker(lines[i].frontsector, lines[i].tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
 				break;
+			}
 
 			case 200: // Double light effect
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_CUTSPRITES|FF_DOUBLESHADOW, secthinkers);
@@ -7067,7 +6929,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 202: // Fog
-				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_FOG|FF_BOTHPLANES|FF_INVERTPLANES|FF_ALLSIDES|FF_INVERTSIDES|FF_CUTEXTRA|FF_EXTRA|FF_DOUBLESHADOW|FF_CUTSPRITES;
+				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_FOG|FF_INVERTPLANES|FF_INVERTSIDES|FF_CUTEXTRA|FF_EXTRA|FF_DOUBLESHADOW|FF_CUTSPRITES;
 				sec = sides[*lines[i].sidenum].sector - sectors;
 				// SoM: Because it's fog, check for an extra colormap and set the fog flag...
 				if (sectors[sec].extra_colormap)
@@ -7112,14 +6974,14 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 251: // A THWOMP!
-				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-				{
-					P_AddThwompThinker(&sectors[sec], &sectors[s], &lines[i]);
-					P_AddFakeFloor(&sectors[s], &sectors[sec], lines + i,
-						FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				}
+			{
+				fixed_t crushspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dy >> 3 : 10*FRACUNIT;
+				fixed_t retractspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dx >> 3 : 2*FRACUNIT;
+				UINT16 sound = (lines[i].flags & ML_EFFECT4) ? sides[lines[i].sidenum[0]].textureoffset >> FRACBITS : sfx_thwomp;
+				P_AddThwompThinker(lines[i].frontsector, lines[i].tag, &lines[i], crushspeed, retractspeed, sound);
+				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
 				break;
+			}
 
 			case 252: // Shatter block (breaks when touched)
 				ffloorflags = FF_EXISTS|FF_BLOCKOTHERS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER;
@@ -7158,11 +7020,8 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 258: // Laser block
-				sec = sides[*lines[i].sidenum].sector - sectors;
-
-				// No longer totally disrupts netgames
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-					EV_AddLaserThinker(&sectors[s], &sectors[sec], lines + i, secthinkers);
+				P_AddLaserThinker(lines[i].tag, lines + i, !!(lines[i].flags & ML_EFFECT1));
+				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA|FF_TRANSLUCENT, secthinkers);
 				break;
 
 			case 259: // Custom FOF
@@ -7203,14 +7062,13 @@ void P_SpawnSpecials(boolean fromnetsave)
 			case 310:
 			case 312:
 			case 332:
-				sec = sides[*lines[i].sidenum].sector - sectors;
-				P_AddEachTimeThinker(&sectors[sec], &lines[i]);
+			case 335:
+				P_AddEachTimeThinker(&lines[i]);
 				break;
 
 			// No More Enemies Linedef Exec
 			case 313:
-				sec = sides[*lines[i].sidenum].sector - sectors;
-				P_AddNoEnemiesThinker(&sectors[sec], &lines[i]);
+				P_AddNoEnemiesThinker(&lines[i]);
 				break;
 
 			// Pushable linedef executors (count # of pushables)
@@ -7234,10 +7092,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				else
 					lines[i].callcount = sides[lines[i].sidenum[0]].textureoffset>>FRACBITS;
 				if (lines[i].special == 322) // Each time
-				{
-					sec = sides[*lines[i].sidenum].sector - sectors;
-					P_AddEachTimeThinker(&sectors[sec], &lines[i]);
-				}
+					P_AddEachTimeThinker(&lines[i]);
 				break;
 
 			// NiGHTS trigger executors
@@ -7256,6 +7111,11 @@ void P_SpawnSpecials(boolean fromnetsave)
 			case 333:
 				break;
 
+			// Object dye executors
+			case 334:
+			case 336:
+				break;
+
 			case 399: // Linedef execute on map load
 				// This is handled in P_RunLevelLoadExecutors.
 				break;
@@ -7347,46 +7207,46 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 600: // floor lighting independently (e.g. lava)
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					sectors[s].floorlightsec = (INT32)sec;
 				break;
 
 			case 601: // ceiling lighting independently
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					sectors[s].ceilinglightsec = (INT32)sec;
 				break;
 
 			case 602: // Adjustable pulsating light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_SpawnAdjustableGlowingLight(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 603: // Adjustable flickering light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_SpawnAdjustableFireFlicker(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 604: // Adjustable Blinking Light (unsynchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, false);
 				break;
 
 			case 605: // Adjustable Blinking Light (synchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, true);
 				break;
 
 			case 606: // HACK! Copy colormaps. Just plain colormaps.
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					sectors[s].extra_colormap = sectors[s].spawn_extra_colormap = sides[lines[i].sidenum[0]].colormap_data;
 				break;
 
@@ -7402,7 +7262,6 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 	Z_Free(secthinkers);
 
-#ifdef POLYOBJECTS
 	// haleyjd 02/20/06: spawn polyobjects
 	Polyobj_InitLevel();
 
@@ -7411,7 +7270,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 		switch (lines[i].special)
 		{
 			case 30: // Polyobj_Flag
-				EV_DoPolyObjFlag(&lines[i]);
+				PolyFlag(&lines[i]);
 				break;
 
 			case 31: // Polyobj_Displace
@@ -7423,7 +7282,6 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 		}
 	}
-#endif
 
 	P_RunLevelLoadExecutors();
 }
@@ -7441,7 +7299,7 @@ static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinker
 	INT32 s;
 	size_t sec = sides[*lines[line].sidenum].sector-sectors;
 
-	for (s = -1; (s = P_FindSectorFromLineTag(lines+line, s)) >= 0 ;)
+	for (s = -1; (s = P_FindSectorFromTag(lines[line].tag, s)) >= 0 ;)
 		P_AddFakeFloor(&sectors[s], &sectors[sec], lines+line, ffloorflags, secthinkers);
 }
 
@@ -7803,7 +7661,7 @@ static void P_SpawnScrollers(void)
 
 			case 513: // scroll effect ceiling
 			case 533: // scroll and carry objects on ceiling
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Scroller(sc_ceiling, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 533)
 					break;
@@ -7812,13 +7670,13 @@ static void P_SpawnScrollers(void)
 			case 523:	// carry objects on ceiling
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Scroller(sc_carry_ceiling, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			case 510: // scroll effect floor
 			case 530: // scroll and carry objects on floor
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Scroller(sc_floor, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 530)
 					break;
@@ -7827,14 +7685,14 @@ static void P_SpawnScrollers(void)
 			case 520:	// carry objects on floor
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Scroller(sc_carry, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			// scroll wall according to linedef
 			// (same direction and speed as scrolling floors)
 			case 502:
-				for (s = -1; (s = P_FindLineFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindLineFromTag(l->tag, s)) >= 0 ;)
 					if (s != (INT32)i)
 						Add_Scroller(sc_side, dx, dy, control, lines[s].sidenum[0], accel, 0);
 				break;
@@ -7904,7 +7762,7 @@ void T_Disappear(disappear_t *d)
 		ffloor_t *rover;
 		register INT32 s;
 
-		for (s = -1; (s = P_FindSectorFromLineTag(&lines[d->affectee], s)) >= 0 ;)
+		for (s = -1; (s = P_FindSectorFromTag(lines[d->affectee].tag, s)) >= 0 ;)
 		{
 			for (rover = sectors[s].ffloors; rover; rover = rover->next)
 			{
@@ -8658,7 +8516,7 @@ static void P_SpawnFriction(void)
 			else
 				movefactor = FRACUNIT;
 
-			for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+			for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 				Add_Friction(friction, movefactor, s, -1);
 		}
 }
@@ -9193,15 +9051,15 @@ static void P_SpawnPushers(void)
 		switch (l->special)
 		{
 			case 541: // wind
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_wind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 544: // current
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_current, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 547: // push/pull
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 				{
 					thing = P_GetPushThing(s);
 					if (thing) // No MT_P* means no effect
@@ -9209,19 +9067,19 @@ static void P_SpawnPushers(void)
 				}
 				break;
 			case 545: // current up
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_upcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 546: // current down
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_downcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 542: // wind up
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_upwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 543: // wind down
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_downwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 		}
diff --git a/src/p_spec.h b/src/p_spec.h
index 6377059b6ccd066c08abc3554cb844e86df427cf..596d8171d2acfb8b8b337f8a4fc4c686f2d3ce9b 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -55,7 +55,6 @@ fixed_t P_FindNextLowestFloor(sector_t *sec, fixed_t currentheight);
 fixed_t P_FindLowestCeilingSurrounding(sector_t *sec);
 fixed_t P_FindHighestCeilingSurrounding(sector_t *sec);
 
-INT32 P_FindSectorFromLineTag(line_t *line, INT32 start);
 INT32 P_FindSectorFromTag(INT16 tag, INT32 start);
 INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start);
 
@@ -74,6 +73,7 @@ void P_RunDeNightserizeExecutors(mobj_t *actor);
 void P_RunNightsLapExecutors(mobj_t *actor);
 void P_RunNightsCapsuleTouchExecutors(mobj_t *actor, boolean entering, boolean enoughspheres);
 
+UINT16 P_GetFFloorID(ffloor_t *fflr);
 ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id);
 
 //
@@ -104,10 +104,9 @@ typedef struct
 typedef struct
 {
 	thinker_t thinker; ///< Thinker structure for laser.
-	ffloor_t *ffloor;  ///< 3Dfloor that is a laser.
-	sector_t *sector;  ///< Sector in which the effect takes place.
-	sector_t *sec;
+	INT16 tag;
 	line_t *sourceline;
+	UINT8 nobosses;
 } laserthink_t;
 
 /** Strobe light action structure..
@@ -307,18 +306,130 @@ typedef struct
 	fixed_t delaytimer;
 	fixed_t floorwasheight; // Height the floor WAS at
 	fixed_t ceilingwasheight; // Height the ceiling WAS at
-	player_t *player; // Player who initiated the thinker (used for airbob)
 	line_t *sourceline;
 } elevator_t;
 
+typedef enum
+{
+	CF_RETURN   = 1,    // Return after crumbling
+	CF_FLOATBOB = 1<<1, // Float on water
+	CF_REVERSE  = 1<<2, // Reverse gravity
+} crumbleflag_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	line_t *sourceline;
+	sector_t *sector;
+	sector_t *actionsector; // The sector the rover action is taking place in.
+	player_t *player; // Player who initiated the thinker (used for airbob)
+	INT32 direction;
+	INT32 origalpha;
+	INT32 timer;
+	fixed_t speed;
+	fixed_t floorwasheight; // Height the floor WAS at
+	fixed_t ceilingwasheight; // Height the ceiling WAS at
+	UINT8 flags;
+} crumble_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	line_t *sourceline; // Source line of the thinker
+} noenemies_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	sector_t *sector;
+	fixed_t speed;
+	INT32 direction;
+	fixed_t floorstartheight;
+	fixed_t ceilingstartheight;
+	fixed_t destheight;
+} continuousfall_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	line_t *sourceline;
+	sector_t *sector;
+	fixed_t speed;
+	fixed_t distance;
+	fixed_t floorwasheight;
+	fixed_t ceilingwasheight;
+	boolean low;
+} bouncecheese_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	sector_t *sector;
+	fixed_t speed;
+	INT32 direction;
+	fixed_t floorstartheight;
+	fixed_t ceilingstartheight;
+	INT16 tag;
+} mariothink_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	line_t *sourceline;
+	sector_t *sector;
+} mariocheck_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	line_t *sourceline;
+	sector_t *sector;
+	fixed_t crushspeed;
+	fixed_t retractspeed;
+	INT32 direction;
+	fixed_t floorstartheight;
+	fixed_t ceilingstartheight;
+	INT32 delay;
+	INT16 tag;
+	UINT16 sound;
+} thwomp_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	line_t *sourceline;
+	sector_t *sector;
+	INT16 tag;
+} floatthink_t;
+
 typedef struct
 {
 	thinker_t thinker;
-	fixed_t vars[16];   // Misc. variables
-	fixed_t var2s[16];   // Second misc variables buffer.
 	line_t *sourceline; // Source line of the thinker
-	sector_t *sector;   // Sector the thinker is from
-} levelspecthink_t;
+	boolean playersInArea[MAXPLAYERS];
+	boolean playersOnArea[MAXPLAYERS];
+	boolean triggerOnExit;
+} eachtime_t;
+
+typedef enum
+{
+	RF_REVERSE  = 1,    //Lower when stood on
+	RF_SPINDASH = 1<<1, //Require spindash to move
+	RF_DYNAMIC  = 1<<2, //Dynamically sinking platform
+} raiseflag_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	INT16 tag;
+	sector_t *sector;
+	fixed_t ceilingbottom;
+	fixed_t ceilingtop;
+	fixed_t basespeed;
+	fixed_t extraspeed; //For dynamically sinking platform
+	UINT8 shaketimer; //For dynamically sinking platform
+	UINT8 flags;
+} raise_t;
 
 #define ELEVATORSPEED (FRACUNIT*4)
 #define FLOORSPEED (FRACUNIT)
@@ -331,36 +442,34 @@ typedef enum
 } result_e;
 
 result_e T_MovePlane(sector_t *sector, fixed_t speed, fixed_t dest, boolean crush,
-	INT32 floorOrCeiling, INT32 direction);
-INT32 EV_DoFloor(line_t *line, floor_e floortype);
-INT32 EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed);
+	boolean ceiling, INT32 direction);
+void EV_DoFloor(line_t *line, floor_e floortype);
+void EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed);
 void EV_CrumbleChain(sector_t *sec, ffloor_t *rover);
-INT32 EV_BounceSector(sector_t *sector, fixed_t momz, line_t *sourceline);
+void EV_BounceSector(sector_t *sector, fixed_t momz, line_t *sourceline);
 
 // Some other special 3dfloor types
 INT32 EV_StartCrumble(sector_t *sector, ffloor_t *rover,
 	boolean floating, player_t *player, fixed_t origalpha, boolean crumblereturn);
 
-INT32 EV_DoContinuousFall(sector_t *sec, sector_t *pbacksector, fixed_t spd, boolean backwards);
+void EV_DoContinuousFall(sector_t *sec, sector_t *backsector, fixed_t spd, boolean backwards);
 
-INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher);
+void EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher);
 
 void T_MoveFloor(floormove_t *movefloor);
 
 void T_MoveElevator(elevator_t *elevator);
-void T_ContinuousFalling(levelspecthink_t *faller);
-void T_BounceCheese(levelspecthink_t *bouncer);
-void T_StartCrumble(elevator_t *elevator);
-void T_MarioBlock(levelspecthink_t *block);
-void T_SpikeSector(levelspecthink_t *spikes);
-void T_FloatSector(levelspecthink_t *floater);
-void T_BridgeThinker(levelspecthink_t *bridge);
-void T_MarioBlockChecker(levelspecthink_t *block);
-void T_ThwompSector(levelspecthink_t *thwomp);
-void T_NoEnemiesSector(levelspecthink_t *nobaddies);
-void T_EachTimeThinker(levelspecthink_t *eachtime);
+void T_ContinuousFalling(continuousfall_t *faller);
+void T_BounceCheese(bouncecheese_t *bouncer);
+void T_StartCrumble(crumble_t *crumble);
+void T_MarioBlock(mariothink_t *block);
+void T_FloatSector(floatthink_t *floater);
+void T_MarioBlockChecker(mariocheck_t *block);
+void T_ThwompSector(thwomp_t *thwomp);
+void T_NoEnemiesSector(noenemies_t *nobaddies);
+void T_EachTimeThinker(eachtime_t *eachtime);
 void T_CameraScanner(elevator_t *elevator);
-void T_RaiseSector(levelspecthink_t *sraise);
+void T_RaiseSector(raise_t *raise);
 
 typedef struct
 {
diff --git a/src/p_user.c b/src/p_user.c
index 6b43c6f9caf11ffaf9c258cf554deaf893c3ab77..988e985382471ca2f2514f6f8e05993cbae70529 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1388,7 +1388,7 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
 
 					// Continues are worthless in netgames.
 					// If that stops being the case uncomment this.
-/*					if (!ultimatemode && players[i].marescore > 50000
+/*					if (!ultimatemode && continuesInSession && players[i].marescore > 50000
 					&& oldscore < 50000)
 					{
 						players[i].continues += 1;
@@ -1408,7 +1408,7 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
 			else
 				player->marescore = MAXSCORE;
 
-			if (!ultimatemode && !(netgame || multiplayer) && G_IsSpecialStage(gamemap)
+			if (!ultimatemode && continuesInSession && G_IsSpecialStage(gamemap)
 			&& player->marescore >= 50000 && oldscore < 50000)
 			{
 				player->continues += 1;
@@ -1490,17 +1490,10 @@ void P_PlayLivesJingle(player_t *player)
 	if (player && !P_IsLocalPlayer(player))
 		return;
 
-	if (use1upSound)
+	if (use1upSound || cv_1upsound.value)
 		S_StartSound(NULL, sfx_oneup);
 	else if (mariomode)
 		S_StartSound(NULL, sfx_marioa);
-	else if (cv_1upsound.value)
-	{
-		if (S_sfx[sfx_oneup].lumpnum != LUMPERROR)
-			S_StartSound(NULL, sfx_oneup);
-		else
-			S_StartSound(NULL, sfx_chchng);/* at least play something! */
-	}
 	else
 	{
 		P_PlayJingle(player, JT_1UP);
@@ -2562,7 +2555,7 @@ static void P_CheckBustableBlocks(player_t *player)
 			{
 				if (!(rover->flags & FF_EXISTS)) continue;
 
-				if ((rover->flags & FF_BUSTUP)/* && !rover->master->frontsector->crumblestate*/)
+				if ((rover->flags & FF_BUSTUP)/* && rover->master->frontsector->crumblestate == CRUMBLE_NONE*/)
 				{
 					// If it's an FF_SHATTER, you can break it just by touching it.
 					if (rover->flags & FF_SHATTER)
@@ -3833,7 +3826,6 @@ static void P_DoTeeter(player_t *player)
 		BMBOUNDFIX(xl, xh, yl, yh);
 
 	// Polyobjects
-#ifdef POLYOBJECTS
 		validcount++;
 
 		for (by = yl; by <= yh; by++)
@@ -3927,7 +3919,6 @@ static void P_DoTeeter(player_t *player)
 					plink = (polymaplink_t *)(plink->link.next);
 				}
 			}
-#endif
 		if (teeter) // only bother with objects as a last resort if you were already teetering
 		{
 			mobj_t *oldtmthing = tmthing;
@@ -4428,13 +4419,16 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		else if (player->powers[pw_carry] == CR_ROLLOUT)
 		{
 			player->mo->momz = 9*FRACUNIT;
-			if (P_MobjFlip(player->mo->tracer)*player->mo->tracer->momz > 0)
-				player->mo->momz += player->mo->tracer->momz;
-			if (!P_IsObjectOnGround(player->mo->tracer))
-				P_SetObjectMomZ(player->mo->tracer, -9*FRACUNIT, true);
+			if (player->mo->tracer)
+			{
+				if (P_MobjFlip(player->mo->tracer)*player->mo->tracer->momz > 0)
+					player->mo->momz += player->mo->tracer->momz;
+				if (!P_IsObjectOnGround(player->mo->tracer))
+					P_SetObjectMomZ(player->mo->tracer, -9*FRACUNIT, true);
+				player->mo->tracer->flags |= MF_PUSHABLE;
+				P_SetTarget(&player->mo->tracer->tracer, NULL);
+			}
 			player->powers[pw_carry] = CR_NONE;
-			player->mo->tracer->flags |= MF_PUSHABLE;
-			P_SetTarget(&player->mo->tracer->tracer, NULL);
 			P_SetTarget(&player->mo->tracer, NULL);
 		}
 		else if (player->mo->eflags & MFE_GOOWATER)
@@ -4992,6 +4986,15 @@ void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range)
 	player->pflags |= PF_THOKKED;
 }
 
+static void P_DoTwinSpin(player_t *player)
+{
+	player->pflags &= ~PF_NOJUMPDAMAGE;
+	player->pflags |= P_GetJumpFlags(player) | PF_THOKKED;
+	S_StartSound(player->mo, sfx_s3k42);
+	player->mo->frame = 0;
+	P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
+}
+
 //
 // P_DoJumpStuff
 //
@@ -5162,12 +5165,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						break;
 					case CA_TWINSPIN:
 						if ((player->charability2 == CA2_MELEE) && (!(player->pflags & (PF_THOKKED|PF_USEDOWN)) || player->charflags & SF_MULTIABILITY))
-						{
-							player->pflags |= PF_THOKKED;
-							S_StartSound(player->mo, sfx_s3k42);
-							player->mo->frame = 0;
-							P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
-						}
+							P_DoTwinSpin(player);
 						break;
 					default:
 						break;
@@ -5424,12 +5422,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					break;
 				case CA_TWINSPIN:
 					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
-					{
-						player->pflags |= PF_THOKKED;
-						S_StartSound(player->mo, sfx_s3k42);
-						player->mo->frame = 0;
-						P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
-					}
+						P_DoTwinSpin(player);
 					break;
 				default:
 					break;
@@ -5688,11 +5681,7 @@ static void P_2dMovement(player_t *player)
 	}
 	else if (player->onconveyor == 4 && !P_IsObjectOnGround(player->mo)) // Actual conveyor belt
 		player->cmomx = player->cmomy = 0;
-	else if (player->onconveyor != 2 && player->onconveyor != 4
-#ifdef POLYOBJECTS
-				&& player->onconveyor != 1
-#endif
-	)
+	else if (player->onconveyor != 2 && player->onconveyor != 4 && player->onconveyor != 1)
 		player->cmomx = player->cmomy = 0;
 
 	player->rmomx = player->mo->momx - player->cmomx;
@@ -5887,11 +5876,7 @@ static void P_3dMovement(player_t *player)
 	}
 	else if (player->onconveyor == 4 && !P_IsObjectOnGround(player->mo)) // Actual conveyor belt
 		player->cmomx = player->cmomy = 0;
-	else if (player->onconveyor != 2 && player->onconveyor != 4
-#ifdef POLYOBJECTS
-				&& player->onconveyor != 1
-#endif
-	)
+	else if (player->onconveyor != 2 && player->onconveyor != 4 && player->onconveyor != 1)
 		player->cmomx = player->cmomy = 0;
 
 	player->rmomx = player->mo->momx - player->cmomx;
@@ -7968,7 +7953,7 @@ static void P_MovePlayer(player_t *player)
 			&& player->mo->state < &states[S_PLAY_NIGHTS_TRANS6]))) // Note the < instead of <=
 		{
 			skin_t *skin = ((skin_t *)(player->mo->skin));
-			if (skin->flags & SF_SUPER)
+			if (( skin->flags & (SF_SUPER|SF_NONIGHTSSUPER) ) == SF_SUPER)
 			{
 				player->mo->color = skin->supercolor
 					+ ((player->nightstime == player->startedtime)
@@ -9317,7 +9302,7 @@ boolean P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 	if (enemy->health <= 0) // dead
 		return false;
 
-	if (!((enemy->flags & (MF_ENEMY|MF_BOSS|MF_MONITOR) && (enemy->flags & MF_SHOOTABLE)) || (enemy->flags & MF_SPRING)) == !(enemy->flags2 & MF2_INVERTAIMABLE)) // allows if it has the flags desired XOR it has the invert aimable flag
+	if (source->player && (!((enemy->flags & (MF_ENEMY|MF_BOSS|MF_MONITOR) && (enemy->flags & MF_SHOOTABLE)) || (enemy->flags & MF_SPRING)) == !(enemy->flags2 & MF2_INVERTAIMABLE))) // allows if it has the flags desired XOR it has the invert aimable flag
 		return false;
 
 	if (enemy->flags2 & MF2_FRET)
@@ -9529,7 +9514,7 @@ static void P_DeathThink(player_t *player)
 	// continue logic
 	if (!(netgame || multiplayer) && player->lives <= 0)
 	{
-		if (player->deadtimer > (3*TICRATE) && (cmd->buttons & BT_USE || cmd->buttons & BT_JUMP) && player->continues > 0)
+		if (player->deadtimer > (3*TICRATE) && (cmd->buttons & BT_USE || cmd->buttons & BT_JUMP) && (!continuesInSession || player->continues > 0))
 			G_UseContinue();
 		else if (player->deadtimer >= gameovertics)
 			G_UseContinue(); // Even if we don't have one this handles ending the game
@@ -10221,7 +10206,6 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 			}
 		}
 
-#ifdef POLYOBJECTS
 	// Check polyobjects and see if floorz/ceilingz need to be altered
 	{
 		INT32 xl, xh, yl, yh, bx, by;
@@ -10300,7 +10284,6 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 				}
 			}
 	}
-#endif
 
 		// crushed camera
 		if (myceilingz <= myfloorz + thiscam->height && !resetcalled && !cameranoclip)
@@ -11711,10 +11694,8 @@ void P_PlayerThink(player_t *player)
 	P_MobjCheckWater(player->mo);
 
 #ifndef SECTORSPECIALSAFTERTHINK
-#ifdef POLYOBJECTS
 	if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo))
-#endif
-	player->onconveyor = 0;
+		player->onconveyor = 0;
 	// check special sectors : damage & secrets
 
 	if (!player->spectator)
@@ -11927,6 +11908,9 @@ void P_PlayerThink(player_t *player)
 						factor = 4;
 					}
 					break;
+				case CR_DUSTDEVIL:
+					player->drawangle += ANG20;
+					break;
 				/* -- in case we wanted to have the camera freely movable during zoom tubes
 				case CR_ZOOMTUBE:*/
 				case CR_ROPEHANG:
@@ -12072,12 +12056,10 @@ void P_PlayerThink(player_t *player)
 	// it lasts for one tic.
 	player->pflags &= ~PF_FULLSTASIS;
 
-#ifdef POLYOBJECTS
 	if (player->onconveyor == 1)
 		player->onconveyor = 3;
 	else if (player->onconveyor == 3)
 		player->cmomy = player->cmomx = 0;
-#endif
 
 	P_DoSuperStuff(player);
 	P_CheckSneakerAndLivesTimer(player);
@@ -12410,10 +12392,8 @@ void P_PlayerAfterThink(player_t *player)
 	cmd = &player->cmd;
 
 #ifdef SECTORSPECIALSAFTERTHINK
-#ifdef POLYOBJECTS
 	if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo))
-#endif
-	player->onconveyor = 0;
+		player->onconveyor = 0;
 	// check special sectors : damage & secrets
 
 	if (!player->spectator)
@@ -12703,6 +12683,19 @@ void P_PlayerAfterThink(player_t *player)
 				}
 				break;
 			}
+			case CR_DUSTDEVIL:
+			{
+				mobj_t *mo = player->mo, *dustdevil = player->mo->tracer;
+
+				if (abs(mo->x - dustdevil->x) > dustdevil->radius || abs(mo->y - dustdevil->y) > dustdevil->radius)
+				{
+					P_SetTarget(&player->mo->tracer, NULL);
+					player->powers[pw_carry] = CR_NONE;
+					break;
+				}
+
+				break;
+			}
 			case CR_ROLLOUT:
 			{
 				mobj_t *mo = player->mo, *rock = player->mo->tracer;
@@ -12844,6 +12837,12 @@ void P_PlayerAfterThink(player_t *player)
 		player->mo->flags |= MF_NOGRAVITY;
 	}
 
+	if (player->powers[pw_dye])
+	{
+		player->mo->colorized = true;
+		player->mo->color = player->powers[pw_dye];
+	}
+
 	if (player->followmobj && (player->spectator || player->mo->health <= 0 || player->followmobj->type != player->followitem))
 	{
 		P_RemoveMobj(player->followmobj);
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 3474b5f69414e7a04a280b52b87c9dd991515822..60291da0ff12b64fd5f55ba52e044ce9d966cf56 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -354,9 +354,7 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back)
 {
 	return (
-#ifdef POLYOBJECTS
 		!line->polyseg &&
-#endif
 		back->ceilingpic == front->ceilingpic
 		&& back->floorpic == front->floorpic
 		&& back->f_slope == front->f_slope
@@ -482,13 +480,10 @@ static void R_AddLine(seg_t *line)
 
 	if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then
 	{
-		if (
-#ifdef POLYOBJECTS
-		!line->polyseg &&
-#endif
-		!line->sidedef->midtexture
-		&& ((!frontsector->ffloors && !backsector->ffloors)
-		|| (frontsector->tag == backsector->tag)))
+		if (!line->polyseg &&
+			!line->sidedef->midtexture
+			&& ((!frontsector->ffloors && !backsector->ffloors)
+				|| (frontsector->tag == backsector->tag)))
 			return; // line is empty, don't even bother
 
 		goto clippass; // treat like wide open window instead
@@ -651,8 +646,6 @@ static boolean R_CheckBBox(const fixed_t *bspcoord)
 	return true;
 }
 
-#ifdef POLYOBJECTS
-
 size_t numpolys;        // number of polyobjects in current subsector
 size_t num_po_ptrs;     // number of polyobject pointers allocated
 polyobj_t **po_ptrs; // temp ptr array to sort polyobject pointers
@@ -816,7 +809,6 @@ static void R_AddPolyObjects(subsector_t *sub)
 			R_AddLine(po_ptrs[i]->segs[j]);
 	}
 }
-#endif
 
 //
 // R_Subsector
@@ -888,11 +880,7 @@ static void R_Subsector(size_t num)
 		|| (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum))
 	{
 		floorplane = R_FindPlane(frontsector->floorheight, frontsector->floorpic, floorlightlevel,
-			frontsector->floor_xoffs, frontsector->floor_yoffs, frontsector->floorpic_angle, floorcolormap, NULL
-#ifdef POLYOBJECTS_PLANES
-			, NULL
-#endif
-			, frontsector->f_slope);
+			frontsector->floor_xoffs, frontsector->floor_yoffs, frontsector->floorpic_angle, floorcolormap, NULL, NULL, frontsector->f_slope);
 	}
 	else
 		floorplane = NULL;
@@ -903,11 +891,7 @@ static void R_Subsector(size_t num)
 	{
 		ceilingplane = R_FindPlane(frontsector->ceilingheight, frontsector->ceilingpic,
 			ceilinglightlevel, frontsector->ceiling_xoffs, frontsector->ceiling_yoffs, frontsector->ceilingpic_angle,
-			ceilingcolormap, NULL
-#ifdef POLYOBJECTS_PLANES
-			, NULL
-#endif
-			, frontsector->c_slope);
+			ceilingcolormap, NULL, NULL, frontsector->c_slope);
 	}
 	else
 		ceilingplane = NULL;
@@ -943,19 +927,15 @@ static void R_Subsector(size_t num)
 			planecenterz = P_GetFFloorBottomZAt(rover, frontsector->soundorg.x, frontsector->soundorg.y);
 			if (planecenterz <= ceilingcenterz
 				&& planecenterz >= floorcenterz
-				&& ((viewz < heightcheck && !(rover->flags & FF_INVERTPLANES))
-				|| (viewz > heightcheck && (rover->flags & FF_BOTHPLANES))))
+				&& ((viewz < heightcheck && (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES)))
+				|| (viewz > heightcheck && (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES))))
 			{
 				light = R_GetPlaneLight(frontsector, planecenterz,
 					viewz < heightcheck);
 
 				ffloor[numffloors].plane = R_FindPlane(*rover->bottomheight, *rover->bottompic,
 					*frontsector->lightlist[light].lightlevel, *rover->bottomxoffs,
-					*rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover
-#ifdef POLYOBJECTS_PLANES
-					, NULL
-#endif
-					, *rover->b_slope);
+					*rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope);
 
 				ffloor[numffloors].slope = *rover->b_slope;
 
@@ -977,18 +957,14 @@ static void R_Subsector(size_t num)
 			planecenterz = P_GetFFloorTopZAt(rover, frontsector->soundorg.x, frontsector->soundorg.y);
 			if (planecenterz >= floorcenterz
 				&& planecenterz <= ceilingcenterz
-				&& ((viewz > heightcheck && !(rover->flags & FF_INVERTPLANES))
-				|| (viewz < heightcheck && (rover->flags & FF_BOTHPLANES))))
+				&& ((viewz > heightcheck && (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES)))
+				|| (viewz < heightcheck && (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES))))
 			{
 				light = R_GetPlaneLight(frontsector, planecenterz, viewz < heightcheck);
 
 				ffloor[numffloors].plane = R_FindPlane(*rover->topheight, *rover->toppic,
 					*frontsector->lightlist[light].lightlevel, *rover->topxoffs, *rover->topyoffs, *rover->topangle,
-					*frontsector->lightlist[light].extra_colormap, rover
-#ifdef POLYOBJECTS_PLANES
-					, NULL
-#endif
-					, *rover->t_slope);
+					*frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->t_slope);
 
 				ffloor[numffloors].slope = *rover->t_slope;
 
@@ -1003,7 +979,6 @@ static void R_Subsector(size_t num)
 		}
 	}
 
-#ifdef POLYOBJECTS_PLANES
 	// Polyobjects have planes, too!
 	if (sub->polyList)
 	{
@@ -1069,7 +1044,6 @@ static void R_Subsector(size_t num)
 			po = (polyobj_t *)(po->link.next);
 		}
 	}
-#endif
 
 #ifdef FLOORSPLATS
 	if (sub->splats)
@@ -1092,21 +1066,15 @@ static void R_Subsector(size_t num)
 
 	firstseg = NULL;
 
-#ifdef POLYOBJECTS
 	// haleyjd 02/19/06: draw polyobjects before static lines
 	if (sub->polyList)
 		R_AddPolyObjects(sub);
-#endif
 
 	while (count--)
 	{
 //		CONS_Debug(DBG_GAMELOGIC, "Adding normal line %d...(%d)\n", line->linedef-lines, leveltime);
-		if (!line->glseg
-#ifdef POLYOBJECTS
-		&& !line->polyseg // ignore segs that belong to polyobjects
-#endif
-		)
-		R_AddLine(line);
+		if (!line->glseg && !line->polyseg) // ignore segs that belong to polyobjects
+			R_AddLine(line);
 		line++;
 		curline = NULL; /* cph 2001/11/18 - must clear curline now we're done with it, so stuff doesn't try using it for other things */
 	}
diff --git a/src/r_bsp.h b/src/r_bsp.h
index 1562b79f6333bffabba3fd3c3f26d0f3015e8ae2..e2da8ebaf54e2226140ee008897d0eefb2432751 100644
--- a/src/r_bsp.h
+++ b/src/r_bsp.h
@@ -40,13 +40,11 @@ void R_PortalClearClipSegs(INT32 start, INT32 end);
 void R_ClearDrawSegs(void);
 void R_RenderBSPNode(INT32 bspnum);
 
-#ifdef POLYOBJECTS
 void R_SortPolyObjects(subsector_t *sub);
 
 extern size_t numpolys;        // number of polyobjects in current subsector
 extern size_t num_po_ptrs;     // number of polyobject pointers allocated
 extern polyobj_t **po_ptrs; // temp ptr array to sort polyobject pointers
-#endif
 
 sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 	INT32 *ceilinglightlevel, boolean back);
diff --git a/src/r_data.c b/src/r_data.c
index 3d80bbda3cdda75b170ec2456853b7f0e31ab9d0..831e75bef64276bae116ee376b31e2e3eda52a3c 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -721,14 +721,12 @@ Rloadflats (INT32 i, INT32 w)
 	}
 	else
 	{
-		texstart = W_CheckNumForNamePwad("F_START", (UINT16)w, 0);
+		texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
 		texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
 	}
 
 	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
 	{
-		texstart++; // Do not count the first marker
-
 		// Work through each lump between the markers in the WAD.
 		for (j = 0; j < (texend - texstart); j++)
 		{
@@ -841,7 +839,7 @@ Rloadtextures (INT32 i, INT32 w)
 	}
 	else
 	{
-		texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0);
+		texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
 		texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
 		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
 		if (texturesLumpPos != INT16_MAX)
@@ -850,8 +848,6 @@ Rloadtextures (INT32 i, INT32 w)
 
 	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
 	{
-		texstart++; // Do not count the first marker
-
 		// Work through each lump between the markers in the WAD.
 		for (j = 0; j < (texend - texstart); j++)
 		{
@@ -958,14 +954,12 @@ void R_LoadTextures(void)
 		}
 		else
 		{
-			texstart = W_CheckNumForNamePwad("F_START", (UINT16)w, 0);
+			texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
 			texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
 		}
 
 		if (!( texstart == INT16_MAX || texend == INT16_MAX ))
 		{
-			texstart++; // Do not count the first marker
-
 			// PK3s have subfolders, so we can't just make a simple sum
 			if (wadfiles[w]->type == RET_PK3)
 			{
@@ -998,15 +992,13 @@ void R_LoadTextures(void)
 		}
 		else
 		{
-			texstart = W_CheckNumForNamePwad(TX_START, (UINT16)w, 0);
+			texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
 			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
 		}
 
 		if (texstart == INT16_MAX || texend == INT16_MAX)
 			continue;
 
-		texstart++; // Do not count the first marker
-
 		// PK3s have subfolders, so we can't just make a simple sum
 		if (wadfiles[w]->type == RET_PK3)
 		{
@@ -1592,9 +1584,9 @@ lumpnum_t R_GetFlatNumForName(const char *name)
 		switch (wadfiles[i]->type)
 		{
 		case RET_WAD:
-			if ((start = W_CheckNumForNamePwad("F_START", (UINT16)i, 0)) == INT16_MAX)
+			if ((start = W_CheckNumForMarkerStartPwad("F_START", (UINT16)i, 0)) == INT16_MAX)
 			{
-				if ((start = W_CheckNumForNamePwad("FF_START", (UINT16)i, 0)) == INT16_MAX)
+				if ((start = W_CheckNumForMarkerStartPwad("FF_START", (UINT16)i, 0)) == INT16_MAX)
 					continue;
 				else if ((end = W_CheckNumForNamePwad("FF_END", (UINT16)i, start)) == INT16_MAX)
 					continue;
diff --git a/src/r_defs.h b/src/r_defs.h
index 0c6ff1d30b6a5620c8e38c5c1dd83fbc7d5532f2..a36568192ebbd320a58b88a58c079e15ba76d833 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -28,8 +28,6 @@
 #include "m_aatree.h"
 #endif
 
-#define POLYOBJECTS
-
 //
 // ClipWallSegment
 // Clips the given range of columns
@@ -107,9 +105,7 @@ typedef struct
 	fixed_t z;         ///< Z coordinate.
 } degenmobj_t;
 
-#ifdef POLYOBJECTS
 #include "p_polyobj.h"
-#endif
 
 // Store fake planes in a resizable array insted of just by
 // heightsec. Allows for multiple fake planes.
@@ -130,11 +126,11 @@ typedef enum
 	FF_CUTEXTRA          = 0x100,      ///< Cuts out hidden translucent pixels.
 	FF_CUTLEVEL          = 0x180,      ///< Cuts out all hidden pixels.
 	FF_CUTSPRITES        = 0x200,      ///< Final step in making 3D water.
-	FF_BOTHPLANES        = 0x400,      ///< Renders both planes all the time.
+	FF_BOTHPLANES        = 0x400,      ///< Render inside and outside planes.
 	FF_EXTRA             = 0x800,      ///< Gets cut by ::FF_CUTEXTRA.
 	FF_TRANSLUCENT       = 0x1000,     ///< See through!
 	FF_FOG               = 0x2000,     ///< Fog "brush."
-	FF_INVERTPLANES      = 0x4000,     ///< Reverse the plane visibility rules.
+	FF_INVERTPLANES      = 0x4000,     ///< Only render inside planes.
 	FF_ALLSIDES          = 0x8000,     ///< Render inside and outside sides.
 	FF_INVERTSIDES       = 0x10000,    ///< Only render inside sides.
 	FF_DOUBLESHADOW      = 0x20000,    ///< Make two lightlist entries to reset light?
@@ -277,6 +273,16 @@ typedef enum
 	SF_INVERTPRECIP            =  1<<4,
 } sectorflags_t;
 
+
+typedef enum
+{
+	CRUMBLE_NONE, // No crumble thinker
+	CRUMBLE_WAIT, // Don't float on water because this is supposed to wait for a crumble
+	CRUMBLE_ACTIVATED, // Crumble thinker activated, but hasn't fallen yet
+	CRUMBLE_FALL, // Crumble thinker is falling
+	CRUMBLE_RESTORE, // Crumble thinker is about to restore to original position
+} crumblestate_t;
+
 //
 // The SECTORS record, at runtime.
 // Stores things/mobjs.
@@ -328,11 +334,6 @@ typedef struct sector_s
 
 	size_t linecount;
 	struct line_s **lines; // [linecount] size
-	// Hack: store special line tagging to some sectors
-	// to efficiently help work around bugs by directly
-	// referencing the specific line that the problem happens in.
-	// (used in T_MovePlane mobj physics)
-	struct line_s *tagline;
 
 	// Improved fake floor hack
 	ffloor_t *ffloors;
@@ -429,9 +430,7 @@ typedef struct line_s
 	void *splats; // wallsplat_t list
 #endif
 	INT32 firsttag, nexttag; // improves searches for tags.
-#ifdef POLYOBJECTS
 	polyobj_t *polyobj; // Belongs to a polyobject?
-#endif
 
 	char *text; // a concatenation of all front and back texture names, for linedef specials that require a string.
 	INT16 callcount; // no. of calls left before triggering, for the "X calls" linedef specials, defaults to 0
@@ -474,9 +473,7 @@ typedef struct subsector_s
 	sector_t *sector;
 	INT16 numlines;
 	UINT16 firstline;
-#ifdef POLYOBJECTS
 	struct polyobj_s *polyList; // haleyjd 02/19/06: list of polyobjects
-#endif
 #if 1//#ifdef FLOORSPLATS
 	void *splats; // floorsplat_t list
 #endif
@@ -579,10 +576,8 @@ typedef struct seg_s
 	// Why slow things down by calculating lightlists for every thick side?
 	size_t numlights;
 	r_lightlist_t *rlights;
-#ifdef POLYOBJECTS
 	polyobj_t *polyseg;
 	boolean dontrenderme;
-#endif
 	boolean glseg;
 } seg_t;
 
diff --git a/src/r_draw.c b/src/r_draw.c
index b983db0aadd44c56c1df4c89b3732f5be0c229c5..0155ec113365735bd1e76ac1bceac4d121875b4f 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -233,11 +233,11 @@ const UINT8 Color_Index[MAXTRANSLATIONS-1][16] = {
 	{0x00, 0xd0, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x44, 0x45, 0x46}, // SKINCOLOR_SUPERORANGE4
 	{0xd0, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x44, 0x45, 0x46, 0x47}, // SKINCOLOR_SUPERORANGE5
 
-	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x51, 0x52, 0x53, 0x48}, // SKINCOLOR_SUPERGOLD1
-	{0x00, 0x50, 0x51, 0x52, 0x53, 0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41}, // SKINCOLOR_SUPERGOLD2
-	{0x51, 0x52, 0x53, 0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43}, // SKINCOLOR_SUPERGOLD3
-	{0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46}, // SKINCOLOR_SUPERGOLD4
-	{0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47}, // SKINCOLOR_SUPERGOLD5
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x51, 0x52, 0x53, 0x48, 0x48, 0x48}, // SKINCOLOR_SUPERGOLD1
+	{0x00, 0x50, 0x51, 0x52, 0x53, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x41, 0x41}, // SKINCOLOR_SUPERGOLD2
+	{0x51, 0x52, 0x53, 0x53, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x43, 0x43}, // SKINCOLOR_SUPERGOLD3
+	{0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x46, 0x46}, // SKINCOLOR_SUPERGOLD4
+	{0x48, 0x48, 0x49, 0x49, 0x49, 0x40, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x47}, // SKINCOLOR_SUPERGOLD5
 
 	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x58, 0x58, 0xbc, 0xbc, 0xbc}, // SKINCOLOR_SUPERPERIDOT1
 	{0x00, 0x58, 0x58, 0x58, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe}, // SKINCOLOR_SUPERPERIDOT2
diff --git a/src/r_main.c b/src/r_main.c
index 9a3d98870fe1d206400fed4e4d0240deea52b682..e47bb06e3fb116b3735988d1c0ac90f93b0b64af 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1388,7 +1388,7 @@ void R_RenderPlayerView(player_t *player)
 	else
 	{
 		portalclipstart = 0;
-		portalclipend = viewwidth-1;
+		portalclipend = viewwidth;
 		R_ClearClipSegs();
 	}
 	R_ClearDrawSegs();
diff --git a/src/r_patch.c b/src/r_patch.c
index 9e31d4d191b8a4e8876722c3c17156096eee92af..ad4b3329a439daf4a5605fb3e8569b2fdc0bd680 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -1231,9 +1231,9 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 		if (!R_CheckIfPatch(lump))
 			return;
 
-		width = patch->width;
-		height = patch->height;
-		leftoffset = patch->leftoffset;
+		width = SHORT(patch->width);
+		height = SHORT(patch->height);
+		leftoffset = SHORT(patch->leftoffset);
 
 		// rotation pivot
 		px = SPRITE_XCENTER;
@@ -1348,7 +1348,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 			newpatch = R_MaskedFlatToPatch(rawdst, newwidth, newheight, 0, 0, &size);
 			{
 				newpatch->leftoffset = (newpatch->width / 2) + (leftoffset - px);
-				newpatch->topoffset = (newpatch->height / 2) + (patch->topoffset - py);
+				newpatch->topoffset = (newpatch->height / 2) + (SHORT(patch->topoffset) - py);
 			}
 
 			//BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
@@ -1358,6 +1358,12 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 			// P_PrecacheLevel
 			if (devparm) spritememory += size;
 
+			// convert everything to little-endian, for big-endian support
+			newpatch->width = SHORT(newpatch->width);
+			newpatch->height = SHORT(newpatch->height);
+			newpatch->leftoffset = SHORT(newpatch->leftoffset);
+			newpatch->topoffset = SHORT(newpatch->topoffset);
+
 #ifdef HWRENDER
 			if (rendermode == render_opengl)
 			{
diff --git a/src/r_plane.c b/src/r_plane.c
index 79273c33a4971e6b2c175679501cb6d6f82fb1f0..6857b6dca9b131cd025f69599ffc6123ac7a0cdb 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -337,11 +337,7 @@ static visplane_t *new_visplane(unsigned hash)
 //
 visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 	fixed_t xoff, fixed_t yoff, angle_t plangle, extracolormap_t *planecolormap,
-	ffloor_t *pfloor
-#ifdef POLYOBJECTS_PLANES
-			, polyobj_t *polyobj
-#endif
-			, pslope_t *slope)
+	ffloor_t *pfloor, polyobj_t *polyobj, pslope_t *slope)
 {
 	visplane_t *check;
 	unsigned hash;
@@ -361,7 +357,6 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 		}
 	}
 
-#ifdef POLYOBJECTS_PLANES
 	if (polyobj)
 	{
 		if (polyobj->angle != 0)
@@ -376,7 +371,6 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 			yoff += polyobj->centerPt.y;
 		}
 	}
-#endif
 
 	// This appears to fix the Nimbus Ruins sky bug.
 	if (picnum == skyflatnum && pfloor)
@@ -390,12 +384,10 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 
 	for (check = visplanes[hash]; check; check = check->next)
 	{
-#ifdef POLYOBJECTS_PLANES
 		if (check->polyobj && pfloor)
 			continue;
 		if (polyobj != check->polyobj)
 			continue;
-#endif
 		if (height == check->height && picnum == check->picnum
 			&& lightlevel == check->lightlevel
 			&& xoff == check->xoffs && yoff == check->yoffs
@@ -426,9 +418,7 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 	check->viewz = viewz;
 	check->viewangle = viewangle;
 	check->plangle = plangle;
-#ifdef POLYOBJECTS_PLANES
 	check->polyobj = polyobj;
-#endif
 	check->slope = slope;
 
 	memset(check->top, 0xff, sizeof (check->top));
@@ -496,9 +486,7 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop)
 		new_pl->viewz = pl->viewz;
 		new_pl->viewangle = pl->viewangle;
 		new_pl->plangle = pl->plangle;
-#ifdef POLYOBJECTS_PLANES
 		new_pl->polyobj = pl->polyobj;
-#endif
 		new_pl->slope = pl->slope;
 		pl = new_pl;
 		pl->minx = start;
@@ -523,11 +511,9 @@ void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop)
 //	INT32 unionl, unionh;
 //	INT32 x;
 
-#ifdef POLYOBJECTS_PLANES
 	// Don't expand polyobject planes here - we do that on our own.
 	if (pl->polyobj)
 		return;
-#endif
 
 	if (pl->minx > start) pl->minx = start;
 	if (pl->maxx < stop)  pl->maxx = stop;
@@ -603,11 +589,7 @@ void R_DrawPlanes(void)
 	{
 		for (pl = visplanes[i]; pl; pl = pl->next)
 		{
-			if (pl->ffloor != NULL
-#ifdef POLYOBJECTS_PLANES
-			|| pl->polyobj != NULL
-#endif
-			)
+			if (pl->ffloor != NULL || pl->polyobj != NULL)
 				continue;
 
 			R_DrawSinglePlane(pl);
@@ -961,7 +943,6 @@ void R_DrawSinglePlane(visplane_t *pl)
 #endif
 	spanfunc = spanfuncs[BASEDRAWFUNC];
 
-#ifdef POLYOBJECTS_PLANES
 	if (pl->polyobj && pl->polyobj->translucency != 0)
 	{
 		spanfunctype = SPANDRAWFUNC_TRANS;
@@ -979,95 +960,98 @@ void R_DrawSinglePlane(visplane_t *pl)
 		else
 			light = LIGHTLEVELS-1;
 
-	} else
-#endif
-	if (pl->ffloor)
+	}
+	else
 	{
-		// Don't draw planes that shouldn't be drawn.
-		for (rover = pl->ffloor->target->ffloors; rover; rover = rover->next)
+		if (pl->ffloor)
 		{
-			if ((pl->ffloor->flags & FF_CUTEXTRA) && (rover->flags & FF_EXTRA))
+			// Don't draw planes that shouldn't be drawn.
+			for (rover = pl->ffloor->target->ffloors; rover; rover = rover->next)
 			{
-				if (pl->ffloor->flags & FF_EXTRA)
+				if ((pl->ffloor->flags & FF_CUTEXTRA) && (rover->flags & FF_EXTRA))
 				{
-					// The plane is from an extra 3D floor... Check the flags so
-					// there are no undesired cuts.
-					if (((pl->ffloor->flags & (FF_FOG|FF_SWIMMABLE)) == (rover->flags & (FF_FOG|FF_SWIMMABLE)))
-						&& pl->height < *rover->topheight
-						&& pl->height > *rover->bottomheight)
-						return;
+					if (pl->ffloor->flags & FF_EXTRA)
+					{
+						// The plane is from an extra 3D floor... Check the flags so
+						// there are no undesired cuts.
+						if (((pl->ffloor->flags & (FF_FOG|FF_SWIMMABLE)) == (rover->flags & (FF_FOG|FF_SWIMMABLE)))
+							&& pl->height < *rover->topheight
+							&& pl->height > *rover->bottomheight)
+							return;
+					}
 				}
 			}
-		}
 
-		if (pl->ffloor->flags & FF_TRANSLUCENT)
-		{
-			spanfunctype = SPANDRAWFUNC_TRANS;
-
-			// Hacked up support for alpha value in software mode Tails 09-24-2002
-			if (pl->ffloor->alpha < 12)
-				return; // Don't even draw it
-			else if (pl->ffloor->alpha < 38)
-				ds_transmap = transtables + ((tr_trans90-1)<<FF_TRANSSHIFT);
-			else if (pl->ffloor->alpha < 64)
-				ds_transmap = transtables + ((tr_trans80-1)<<FF_TRANSSHIFT);
-			else if (pl->ffloor->alpha < 89)
-				ds_transmap = transtables + ((tr_trans70-1)<<FF_TRANSSHIFT);
-			else if (pl->ffloor->alpha < 115)
-				ds_transmap = transtables + ((tr_trans60-1)<<FF_TRANSSHIFT);
-			else if (pl->ffloor->alpha < 140)
-				ds_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
-			else if (pl->ffloor->alpha < 166)
-				ds_transmap = transtables + ((tr_trans40-1)<<FF_TRANSSHIFT);
-			else if (pl->ffloor->alpha < 192)
-				ds_transmap = transtables + ((tr_trans30-1)<<FF_TRANSSHIFT);
-			else if (pl->ffloor->alpha < 217)
-				ds_transmap = transtables + ((tr_trans20-1)<<FF_TRANSSHIFT);
-			else if (pl->ffloor->alpha < 243)
-				ds_transmap = transtables + ((tr_trans10-1)<<FF_TRANSSHIFT);
-			else // Opaque, but allow transparent flat pixels
-				spanfunctype = SPANDRAWFUNC_SPLAT;
-
-			if ((spanfunctype == SPANDRAWFUNC_SPLAT) || (pl->extra_colormap && (pl->extra_colormap->flags & CMF_FOG)))
+			if (pl->ffloor->flags & FF_TRANSLUCENT)
+			{
+				spanfunctype = SPANDRAWFUNC_TRANS;
+
+				// Hacked up support for alpha value in software mode Tails 09-24-2002
+				if (pl->ffloor->alpha < 12)
+					return; // Don't even draw it
+				else if (pl->ffloor->alpha < 38)
+					ds_transmap = transtables + ((tr_trans90-1)<<FF_TRANSSHIFT);
+				else if (pl->ffloor->alpha < 64)
+					ds_transmap = transtables + ((tr_trans80-1)<<FF_TRANSSHIFT);
+				else if (pl->ffloor->alpha < 89)
+					ds_transmap = transtables + ((tr_trans70-1)<<FF_TRANSSHIFT);
+				else if (pl->ffloor->alpha < 115)
+					ds_transmap = transtables + ((tr_trans60-1)<<FF_TRANSSHIFT);
+				else if (pl->ffloor->alpha < 140)
+					ds_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
+				else if (pl->ffloor->alpha < 166)
+					ds_transmap = transtables + ((tr_trans40-1)<<FF_TRANSSHIFT);
+				else if (pl->ffloor->alpha < 192)
+					ds_transmap = transtables + ((tr_trans30-1)<<FF_TRANSSHIFT);
+				else if (pl->ffloor->alpha < 217)
+					ds_transmap = transtables + ((tr_trans20-1)<<FF_TRANSSHIFT);
+				else if (pl->ffloor->alpha < 243)
+					ds_transmap = transtables + ((tr_trans10-1)<<FF_TRANSSHIFT);
+				else // Opaque, but allow transparent flat pixels
+					spanfunctype = SPANDRAWFUNC_SPLAT;
+
+				if ((spanfunctype == SPANDRAWFUNC_SPLAT) || (pl->extra_colormap && (pl->extra_colormap->flags & CMF_FOG)))
+					light = (pl->lightlevel >> LIGHTSEGSHIFT);
+				else
+					light = LIGHTLEVELS-1;
+			}
+			else if (pl->ffloor->flags & FF_FOG)
+			{
+				spanfunctype = SPANDRAWFUNC_FOG;
 				light = (pl->lightlevel >> LIGHTSEGSHIFT);
-			else
-				light = LIGHTLEVELS-1;
-		}
-		else if (pl->ffloor->flags & FF_FOG)
-		{
-			spanfunctype = SPANDRAWFUNC_FOG;
-			light = (pl->lightlevel >> LIGHTSEGSHIFT);
-		}
-		else light = (pl->lightlevel >> LIGHTSEGSHIFT);
-
-#ifndef NOWATER
-		if (pl->ffloor->flags & FF_RIPPLE)
-		{
-			INT32 top, bottom;
+			}
+			else light = (pl->lightlevel >> LIGHTSEGSHIFT);
 
-			itswater = true;
-			if (spanfunctype == SPANDRAWFUNC_TRANS)
+	#ifndef NOWATER
+			if (pl->ffloor->flags & FF_RIPPLE)
 			{
-				spanfunctype = SPANDRAWFUNC_WATER;
+				INT32 top, bottom;
+
+				itswater = true;
+				if (spanfunctype == SPANDRAWFUNC_TRANS)
+				{
+					spanfunctype = SPANDRAWFUNC_WATER;
 
-				// Copy the current scene, ugh
-				top = pl->high-8;
-				bottom = pl->low+8;
+					// Copy the current scene, ugh
+					top = pl->high-8;
+					bottom = pl->low+8;
 
-				if (top < 0)
-					top = 0;
-				if (bottom > vid.height)
-					bottom = vid.height;
+					if (top < 0)
+						top = 0;
+					if (bottom > vid.height)
+						bottom = vid.height;
 
-				// Only copy the part of the screen we need
-				VID_BlitLinearScreen((splitscreen && viewplayer == &players[secondarydisplayplayer]) ? screens[0] + (top+(vid.height>>1))*vid.width : screens[0]+((top)*vid.width), screens[1]+((top)*vid.width),
-				                     vid.width, bottom-top,
-				                     vid.width, vid.width);
+					// Only copy the part of the screen we need
+					VID_BlitLinearScreen((splitscreen && viewplayer == &players[secondarydisplayplayer]) ? screens[0] + (top+(vid.height>>1))*vid.width : screens[0]+((top)*vid.width), screens[1]+((top)*vid.width),
+										 vid.width, bottom-top,
+										 vid.width, vid.width);
+				}
 			}
+	#endif
 		}
-#endif
+		else
+			light = (pl->lightlevel >> LIGHTSEGSHIFT);
 	}
-	else light = (pl->lightlevel >> LIGHTSEGSHIFT);
 
 	if (!pl->slope // Don't mess with angle on slopes! We'll handle this ourselves later
 		&& viewangle != pl->viewangle+pl->plangle)
diff --git a/src/r_plane.h b/src/r_plane.h
index a1a5b7a78388bb7c835ab4763329731514f990a2..67fa19f38bedd3535f6ad1ec6f501139aa8bd426 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -47,9 +47,7 @@ typedef struct visplane_s
 	fixed_t xoffs, yoffs; // Scrolling flats.
 
 	struct ffloor_s *ffloor;
-#ifdef POLYOBJECTS_PLANES
 	polyobj_t *polyobj;
-#endif
 	pslope_t *slope;
 } visplane_t;
 
@@ -80,11 +78,7 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2);
 void R_MakeSpans(INT32 x, INT32 t1, INT32 b1, INT32 t2, INT32 b2);
 void R_DrawPlanes(void);
 visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, angle_t plangle,
-	extracolormap_t *planecolormap, ffloor_t *ffloor
-#ifdef POLYOBJECTS_PLANES
-	, polyobj_t *polyobj
-#endif
-	, pslope_t *slope);
+	extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope);
 visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_PlaneBounds(visplane_t *plane);
@@ -112,9 +106,7 @@ typedef struct planemgr_s
 	struct pslope_s *slope;
 
 	struct ffloor_s *ffloor;
-#ifdef POLYOBJECTS_PLANES
 	polyobj_t *polyobj;
-#endif
 } visffloor_t;
 
 extern visffloor_t ffloor[MAXFFLOORS];
diff --git a/src/r_segs.c b/src/r_segs.c
index 15dec20dc91d94c314e67ae30af58d4b5c1c4af8..741a25254037fbc1dd15017603e116d3ee4f8f7d 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -236,14 +236,13 @@ static void R_DrawWallSplats(void)
 //  way we don't have to store extra post_t info with each column for
 //  multi-patch textures. They are not normally needed as multi-patch
 //  textures don't have holes in it. At least not for now.
-static INT32 column2s_length; // column->length : for multi-patch on 2sided wall = texture->height
 
 static void R_Render2sidedMultiPatchColumn(column_t *column)
 {
 	INT32 topscreen, bottomscreen;
 
 	topscreen = sprtopscreen; // + spryscale*column->topdelta;  topdelta is 0 for the wall
-	bottomscreen = topscreen + spryscale * column2s_length;
+	bottomscreen = topscreen + spryscale * lengthcol;
 
 	dc_yl = (sprtopscreen+FRACUNIT-1)>>FRACBITS;
 	dc_yh = (bottomscreen-1)>>FRACBITS;
@@ -275,13 +274,6 @@ static void R_Render2sidedMultiPatchColumn(column_t *column)
 	}
 }
 
-// quick wrapper for R_DrawFlippedMaskedColumn so it can be set as a colfunc_2s value
-// uses column2s_length for texture->height as above
-static void R_DrawFlippedMaskedSegColumn(column_t *column)
-{
-	R_DrawFlippedMaskedColumn(column, column2s_length);
-}
-
 void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 {
 	size_t pindex;
@@ -356,8 +348,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	{
 		if (textures[texnum]->flip & 2) // vertically flipped?
 		{
-			colfunc_2s = R_DrawFlippedMaskedSegColumn;
-			column2s_length = textures[texnum]->height;
+			colfunc_2s = R_DrawFlippedMaskedColumn;
+			lengthcol = textures[texnum]->height;
 		}
 		else
 			colfunc_2s = R_DrawMaskedColumn; // render the usual 2sided single-patch packed texture
@@ -365,7 +357,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	else
 	{
 		colfunc_2s = R_Render2sidedMultiPatchColumn; // render multipatch with no holes (no post_t info)
-		column2s_length = textures[texnum]->height;
+		lengthcol = textures[texnum]->height;
 	}
 
 	// Setup lighting based on the presence/lack-of 3D floors.
@@ -615,7 +607,6 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 				// draw the texture
 				col = (column_t *)((UINT8 *)R_GetColumn(texnum, maskedtexturecol[dc_x]) - 3);
 
-//#ifdef POLYOBJECTS_PLANES
 #if 0 // Disabling this allows inside edges to render below the planes, for until the clipping is fixed to work right when POs are near the camera. -Red
 				if (curline->dontrenderme && curline->polyseg && (curline->polyseg->flags & POF_RENDERPLANES))
 				{
@@ -692,7 +683,7 @@ static void R_DrawRepeatMaskedColumn(column_t *col)
 static void R_DrawRepeatFlippedMaskedColumn(column_t *col)
 {
 	do {
-		R_DrawFlippedMaskedColumn(col, column2s_length);
+		R_DrawFlippedMaskedColumn(col);
 		sprtopscreen += dc_texheight*spryscale;
 	} while (sprtopscreen < sprbotscreen);
 }
@@ -978,7 +969,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 		if (textures[texnum]->flip & 2) // vertically flipped?
 		{
 			colfunc_2s = R_DrawRepeatFlippedMaskedColumn;
-			column2s_length = textures[texnum]->height;
+			lengthcol = textures[texnum]->height;
 		}
 		else
 			colfunc_2s = R_DrawRepeatMaskedColumn; // render the usual 2sided single-patch packed texture
@@ -986,7 +977,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	else
 	{
 		colfunc_2s = R_Render2sidedMultiPatchColumn;        //render multipatch with no holes (no post_t info)
-		column2s_length = textures[texnum]->height;
+		lengthcol = textures[texnum]->height;
 	}
 
 	// Set heights according to plane, or slope, whichever
@@ -1298,10 +1289,8 @@ static void R_RenderSegLoop (void)
 
 			for (i = 0; i < numffloors; i++)
 			{
-#ifdef POLYOBJECTS_PLANES
 				if (ffloor[i].polyobj && (!curline->polyseg || ffloor[i].polyobj != curline->polyseg))
 					continue;
-#endif
 
 				if (ffloor[i].height < viewz)
 				{
@@ -1314,18 +1303,19 @@ static void R_RenderSegLoop (void)
 					if (bottom_w > bottom)
 						bottom_w = bottom;
 
-#ifdef POLYOBJECTS_PLANES
 					// Polyobject-specific hack to fix plane leaking -Red
-					if (ffloor[i].polyobj && top_w >= bottom_w) {
+					if (ffloor[i].polyobj && top_w >= bottom_w)
+					{
 						ffloor[i].plane->top[rw_x] = 0xFFFF;
 						ffloor[i].plane->bottom[rw_x] = 0x0000; // fix for sky plane drawing crashes - Monster Iestyn 25/05/18
-					} else
-#endif
-
-					if (top_w <= bottom_w)
+					}
+					else
 					{
-						ffloor[i].plane->top[rw_x] = (INT16)top_w;
-						ffloor[i].plane->bottom[rw_x] = (INT16)bottom_w;
+						if (top_w <= bottom_w)
+						{
+							ffloor[i].plane->top[rw_x] = (INT16)top_w;
+							ffloor[i].plane->bottom[rw_x] = (INT16)bottom_w;
+						}
 					}
 				}
 				else if (ffloor[i].height > viewz)
@@ -1339,18 +1329,19 @@ static void R_RenderSegLoop (void)
 					if (bottom_w > bottom)
 						bottom_w = bottom;
 
-#ifdef POLYOBJECTS_PLANES
 					// Polyobject-specific hack to fix plane leaking -Red
-					if (ffloor[i].polyobj && top_w >= bottom_w) {
+					if (ffloor[i].polyobj && top_w >= bottom_w)
+					{
 						ffloor[i].plane->top[rw_x] = 0xFFFF;
 						ffloor[i].plane->bottom[rw_x] = 0x0000; // fix for sky plane drawing crashes - Monster Iestyn 25/05/18
-					} else
-#endif
-
-					if (top_w <= bottom_w)
+					}
+					else
 					{
-						ffloor[i].plane->top[rw_x] = (INT16)top_w;
-						ffloor[i].plane->bottom[rw_x] = (INT16)bottom_w;
+						if (top_w <= bottom_w)
+						{
+							ffloor[i].plane->top[rw_x] = (INT16)top_w;
+							ffloor[i].plane->bottom[rw_x] = (INT16)bottom_w;
+						}
 					}
 				}
 			}
@@ -1804,10 +1795,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	{
 		for (i = 0; i < numffloors; i++)
 		{
-#ifdef POLYOBJECTS_PLANES
 			if (ffloor[i].polyobj && (!ds_p->curline->polyseg || ffloor[i].polyobj != ds_p->curline->polyseg))
 				continue;
-#endif
 
 			ffloor[i].f_pos       = P_GetZAt(ffloor[i].slope, segleft .x, segleft .y, ffloor[i].height) - viewz;
 			ffloor[i].f_pos_slope = P_GetZAt(ffloor[i].slope, segright.x, segright.y, ffloor[i].height) - viewz;
@@ -2139,7 +2128,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 				{
 					if (!(rover->flags & FF_RENDERSIDES) || !(rover->flags & FF_EXISTS))
 						continue;
-					if (rover->flags & FF_INVERTSIDES)
+					if (!(rover->flags & FF_ALLSIDES) && rover->flags & FF_INVERTSIDES)
 						continue;
 
 					if (rover->norender == leveltime)
@@ -2194,7 +2183,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 				{
 					if (!(rover->flags & FF_RENDERSIDES) || !(rover->flags & FF_EXISTS))
 						continue;
-					if (!(rover->flags & FF_ALLSIDES))
+					if (!(rover->flags & FF_ALLSIDES || rover->flags & FF_INVERTSIDES))
 						continue;
 
 					if (rover->norender == leveltime)
@@ -2249,7 +2238,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			{
 				for (rover = backsector->ffloors, i = 0; rover && i < MAXFFLOORS; rover = rover->next)
 				{
-					if (!(rover->flags & FF_RENDERSIDES) || !(rover->flags & FF_EXISTS) || rover->flags & FF_INVERTSIDES)
+					if (!(rover->flags & FF_RENDERSIDES) || !(rover->flags & FF_EXISTS))
+						continue;
+					if (!(rover->flags & FF_ALLSIDES) && rover->flags & FF_INVERTSIDES)
 						continue;
 					if (rover->norender == leveltime)
 						continue;
@@ -2269,7 +2260,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			{
 				for (rover = frontsector->ffloors, i = 0; rover && i < MAXFFLOORS; rover = rover->next)
 				{
-					if (!(rover->flags & FF_RENDERSIDES) || !(rover->flags & FF_EXISTS) || !(rover->flags & FF_ALLSIDES))
+					if (!(rover->flags & FF_RENDERSIDES) || !(rover->flags & FF_EXISTS))
+						continue;
+					if (!(rover->flags & FF_ALLSIDES || rover->flags & FF_INVERTSIDES))
 						continue;
 					if (rover->norender == leveltime)
 						continue;
@@ -2306,33 +2299,40 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 			maskedtextureheight = ds_p->maskedtextureheight; // note to red, this == &(ds_p->maskedtextureheight[0])
 
-#ifdef POLYOBJECTS
-			if (curline->polyseg) { // use REAL front and back floors please, so midtexture rendering isn't mucked up
+			if (curline->polyseg)
+			{ // use REAL front and back floors please, so midtexture rendering isn't mucked up
 				rw_midtextureslide = rw_midtexturebackslide = 0;
 				if (!!(linedef->flags & ML_DONTPEGBOTTOM) ^ !!(linedef->flags & ML_EFFECT3))
 					rw_midtexturemid = rw_midtextureback = max(curline->frontsector->floorheight, curline->backsector->floorheight) - viewz;
 				else
 					rw_midtexturemid = rw_midtextureback = min(curline->frontsector->ceilingheight, curline->backsector->ceilingheight) - viewz;
-			} else
-#endif
-			// Set midtexture starting height
-			if (linedef->flags & ML_EFFECT2) { // Ignore slopes when texturing
-				rw_midtextureslide = rw_midtexturebackslide = 0;
-				if (!!(linedef->flags & ML_DONTPEGBOTTOM) ^ !!(linedef->flags & ML_EFFECT3))
-					rw_midtexturemid = rw_midtextureback = max(frontsector->floorheight, backsector->floorheight) - viewz;
+			}
+			else
+			{
+				// Set midtexture starting height
+				if (linedef->flags & ML_EFFECT2)
+				{ // Ignore slopes when texturing
+					rw_midtextureslide = rw_midtexturebackslide = 0;
+					if (!!(linedef->flags & ML_DONTPEGBOTTOM) ^ !!(linedef->flags & ML_EFFECT3))
+						rw_midtexturemid = rw_midtextureback = max(frontsector->floorheight, backsector->floorheight) - viewz;
+					else
+						rw_midtexturemid = rw_midtextureback = min(frontsector->ceilingheight, backsector->ceilingheight) - viewz;
+
+				}
+				else if (!!(linedef->flags & ML_DONTPEGBOTTOM) ^ !!(linedef->flags & ML_EFFECT3))
+				{
+					rw_midtexturemid = worldbottom;
+					rw_midtextureslide = floorfrontslide;
+					rw_midtextureback = worldlow;
+					rw_midtexturebackslide = floorbackslide;
+				}
 				else
-					rw_midtexturemid = rw_midtextureback = min(frontsector->ceilingheight, backsector->ceilingheight) - viewz;
-
-			} else if (!!(linedef->flags & ML_DONTPEGBOTTOM) ^ !!(linedef->flags & ML_EFFECT3)) {
-				rw_midtexturemid = worldbottom;
-				rw_midtextureslide = floorfrontslide;
-				rw_midtextureback = worldlow;
-				rw_midtexturebackslide = floorbackslide;
-			} else {
-				rw_midtexturemid = worldtop;
-				rw_midtextureslide = ceilingfrontslide;
-				rw_midtextureback = worldhigh;
-				rw_midtexturebackslide = ceilingbackslide;
+				{
+					rw_midtexturemid = worldtop;
+					rw_midtextureslide = ceilingfrontslide;
+					rw_midtextureback = worldhigh;
+					rw_midtexturebackslide = ceilingbackslide;
+				}
 			}
 			rw_midtexturemid += sidedef->rowoffset;
 			rw_midtextureback += sidedef->rowoffset;
@@ -2586,8 +2586,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					if ((roverleft>>4 <= worldhigh || roverright>>4 <= worldhighslope) &&
 					    (roverleft>>4 >= worldlow || roverright>>4 >= worldlowslope) &&
-					    ((viewz < planevistest && !(rover->flags & FF_INVERTPLANES)) ||
-					     (viewz > planevistest && (rover->flags & FF_BOTHPLANES))))
+					    ((viewz < planevistest && (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES))) ||
+					     (viewz > planevistest && (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES))))
 					{
 						//ffloor[i].slope = *rover->b_slope;
 						ffloor[i].b_pos = roverleft;
@@ -2609,8 +2609,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					if ((roverleft>>4 <= worldhigh || roverright>>4 <= worldhighslope) &&
 					    (roverleft>>4 >= worldlow || roverright>>4 >= worldlowslope) &&
-					    ((viewz > planevistest && !(rover->flags & FF_INVERTPLANES)) ||
-					     (viewz < planevistest && (rover->flags & FF_BOTHPLANES))))
+					    ((viewz > planevistest && (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES))) ||
+					     (viewz < planevistest && (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES))))
 					{
 						//ffloor[i].slope = *rover->t_slope;
 						ffloor[i].b_pos = roverleft;
@@ -2643,8 +2643,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					if ((roverleft>>4 <= worldhigh || roverright>>4 <= worldhighslope) &&
 					    (roverleft>>4 >= worldlow || roverright>>4 >= worldlowslope) &&
-					    ((viewz < planevistest && !(rover->flags & FF_INVERTPLANES)) ||
-					     (viewz > planevistest && (rover->flags & FF_BOTHPLANES))))
+					    ((viewz < planevistest && (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES))) ||
+					     (viewz > planevistest && (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES))))
 					{
 						//ffloor[i].slope = *rover->b_slope;
 						ffloor[i].b_pos = roverleft;
@@ -2666,8 +2666,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					if ((roverleft>>4 <= worldhigh || roverright>>4 <= worldhighslope) &&
 					    (roverleft>>4 >= worldlow || roverright>>4 >= worldlowslope) &&
-					    ((viewz > planevistest && !(rover->flags & FF_INVERTPLANES)) ||
-					     (viewz < planevistest && (rover->flags & FF_BOTHPLANES))))
+					    ((viewz > planevistest && (rover->flags & FF_BOTHPLANES || !(rover->flags & FF_INVERTPLANES))) ||
+					     (viewz < planevistest && (rover->flags & FF_BOTHPLANES || rover->flags & FF_INVERTPLANES))))
 					{
 						//ffloor[i].slope = *rover->t_slope;
 						ffloor[i].b_pos = roverleft;
@@ -2681,7 +2681,6 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 					}
 				}
 			}
-#ifdef POLYOBJECTS_PLANES
 			if (curline->polyseg && frontsector && (curline->polyseg->flags & POF_RENDERPLANES))
 			{
 				while (i < numffloors && ffloor[i].polyobj != curline->polyseg) i++;
@@ -2720,7 +2719,6 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 					i++;
 				}
 			}
-#endif
 
 			numbackffloors = i;
 		}
@@ -2774,7 +2772,6 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			for (i = 0; i < numffloors; i++)
 				R_ExpandPlane(ffloor[i].plane, rw_x, rw_stopx - 1);
 		}
-#ifdef POLYOBJECTS_PLANES
 		// FIXME hack to fix planes disappearing when a seg goes behind the camera. This NEEDS to be changed to be done properly. -Red
 		if (curline->polyseg)
 		{
@@ -2789,7 +2786,6 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 					ffloor[i].plane->maxx = rw_stopx - 1;
 			}
 		}
-#endif
 	}
 
 #ifdef WALLSPLATS
diff --git a/src/r_skins.c b/src/r_skins.c
index 2e9548bd7cd0831469b3169f6618d726996ab4c6..caf1fb17299dc0cbad0352df4fa8ec5b71b79ef4 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -504,6 +504,7 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
 	GETFLAG(FASTEDGE)
 	GETFLAG(MULTIABILITY)
 	GETFLAG(NONIGHTSROTATION)
+	GETFLAG(NONIGHTSSUPER)
 #undef GETFLAG
 
 	else // let's check if it's a sound, otherwise error out
diff --git a/src/r_things.c b/src/r_things.c
index 00b7e5554fa32e75cdad1d2c7b1af7c190ec09aa..8a3c2e35f5457a6c5480941f90d430d03d2d3495 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -428,9 +428,9 @@ void R_AddSpriteDefs(UINT16 wadnum)
 	switch (wadfiles[wadnum]->type)
 	{
 	case RET_WAD:
-		start = W_CheckNumForNamePwad("S_START", wadnum, 0);
+		start = W_CheckNumForMarkerStartPwad("S_START", wadnum, 0);
 		if (start == INT16_MAX)
-			start = W_CheckNumForNamePwad("SS_START", wadnum, 0); //deutex compatib.
+			start = W_CheckNumForMarkerStartPwad("SS_START", wadnum, 0); //deutex compatib.
 
 		end = W_CheckNumForNamePwad("S_END",wadnum,start);
 		if (end == INT16_MAX)
@@ -452,8 +452,6 @@ void R_AddSpriteDefs(UINT16 wadnum)
 
 		start = 0; //let say S_START is lump 0
 	}
-	else
-		start++;   // just after S_START
 
 	if (end == INT16_MAX || start >= end)
 	{
@@ -641,10 +639,10 @@ void R_DrawMaskedColumn(column_t *column)
 			dc_yl = mceilingclip[dc_x]+1;
 		if (dc_yl < 0)
 			dc_yl = 0;
-		if (dc_yh >= vid.height)
+		if (dc_yh >= vid.height) // dc_yl must be < vid.height, so reduces number of checks in tight loop
 			dc_yh = vid.height - 1;
 
-		if (dc_yl <= dc_yh && dc_yl < vid.height && dc_yh > 0)
+		if (dc_yl <= dc_yh && dc_yh > 0)
 		{
 			dc_source = (UINT8 *)column + 3;
 			dc_texturemid = basetexturemid - (topdelta<<FRACBITS);
@@ -655,15 +653,10 @@ void R_DrawMaskedColumn(column_t *column)
 			// quick fix... something more proper should be done!!!
 			if (ylookup[dc_yl])
 				colfunc();
-			else if (colfunc == colfuncs[COLDRAWFUNC_BASE])
-			{
-				static INT32 first = 1;
-				if (first)
-				{
-					CONS_Debug(DBG_RENDER, "WARNING: avoiding a crash in %s %d\n", __FILE__, __LINE__);
-					first = 0;
-				}
-			}
+#ifdef PARANOIA
+			else
+				I_Error("R_DrawMaskedColumn: Invalid ylookup for dc_yl %d", dc_yl);
+#endif
 		}
 		column = (column_t *)((UINT8 *)column + column->length + 4);
 	}
@@ -671,7 +664,9 @@ void R_DrawMaskedColumn(column_t *column)
 	dc_texturemid = basetexturemid;
 }
 
-void R_DrawFlippedMaskedColumn(column_t *column, INT32 texheight)
+INT32 lengthcol; // column->length : for flipped column function pointers and multi-patch on 2sided wall = texture->height
+
+void R_DrawFlippedMaskedColumn(column_t *column)
 {
 	INT32 topscreen;
 	INT32 bottomscreen;
@@ -687,7 +682,7 @@ void R_DrawFlippedMaskedColumn(column_t *column, INT32 texheight)
 		if (topdelta <= prevdelta)
 			topdelta += prevdelta;
 		prevdelta = topdelta;
-		topdelta = texheight-column->length-topdelta;
+		topdelta = lengthcol-column->length-topdelta;
 		topscreen = sprtopscreen + spryscale*topdelta;
 		bottomscreen = sprbotscreen == INT32_MAX ? topscreen + spryscale*column->length
 		                                      : sprbotscreen + spryscale*column->length;
@@ -709,10 +704,10 @@ void R_DrawFlippedMaskedColumn(column_t *column, INT32 texheight)
 			dc_yl = mceilingclip[dc_x]+1;
 		if (dc_yl < 0)
 			dc_yl = 0;
-		if (dc_yh >= vid.height)
+		if (dc_yh >= vid.height) // dc_yl must be < vid.height, so reduces number of checks in tight loop
 			dc_yh = vid.height - 1;
 
-		if (dc_yl <= dc_yh && dc_yl < vid.height && dc_yh > 0)
+		if (dc_yl <= dc_yh && dc_yh > 0)
 		{
 			dc_source = ZZ_Alloc(column->length);
 			for (s = (UINT8 *)column+2+column->length, d = dc_source; d < dc_source+column->length; --s)
@@ -722,15 +717,10 @@ void R_DrawFlippedMaskedColumn(column_t *column, INT32 texheight)
 			// Still drawn by R_DrawColumn.
 			if (ylookup[dc_yl])
 				colfunc();
-			else if (colfunc == colfuncs[COLDRAWFUNC_BASE])
-			{
-				static INT32 first = 1;
-				if (first)
-				{
-					CONS_Debug(DBG_RENDER, "WARNING: avoiding a crash in %s %d\n", __FILE__, __LINE__);
-					first = 0;
-				}
-			}
+#ifdef PARANOIA
+			else
+				I_Error("R_DrawMaskedColumn: Invalid ylookup for dc_yl %d", dc_yl);
+#endif
 			Z_Free(dc_source);
 		}
 		column = (column_t *)((UINT8 *)column + column->length + 4);
@@ -746,7 +736,9 @@ void R_DrawFlippedMaskedColumn(column_t *column, INT32 texheight)
 static void R_DrawVisSprite(vissprite_t *vis)
 {
 	column_t *column;
+	void (*localcolfunc)(column_t *);
 	INT32 texturecolumn;
+	INT32 pwidth;
 	fixed_t frac;
 	patch_t *patch = vis->patch;
 	fixed_t this_scale = vis->mobj->scale;
@@ -895,50 +887,52 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	if (vis->x2 >= vid.width)
 		vis->x2 = vid.width-1;
 
+	localcolfunc = (vis->cut & SC_VFLIP) ? R_DrawFlippedMaskedColumn : R_DrawMaskedColumn;
+	lengthcol = SHORT(patch->height);
+
 	// Split drawing loops for paper and non-paper to reduce conditional checks per sprite
 	if (vis->scalestep)
 	{
-		// Papersprite drawing loop
+		pwidth = SHORT(patch->width);
 
+		// Papersprite drawing loop
 		for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, spryscale += vis->scalestep)
 		{
 			angle_t angle = ((vis->centerangle + xtoviewangle[dc_x]) >> ANGLETOFINESHIFT) & 0xFFF;
 			texturecolumn = (vis->paperoffset - FixedMul(FINETANGENT(angle), vis->paperdistance)) / this_scale;
 
-			if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width))
+			if (texturecolumn < 0 || texturecolumn >= pwidth)
 				continue;
 
 			if (vis->xiscale < 0) // Flipped sprite
-				texturecolumn = SHORT(patch->width) - 1 - texturecolumn;
+				texturecolumn = pwidth - 1 - texturecolumn;
 
 			sprtopscreen = (centeryfrac - FixedMul(dc_texturemid, spryscale));
 			dc_iscale = (0xffffffffu / (unsigned)spryscale);
 
 			column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
 
-			if (vis->cut & SC_VFLIP)
-				R_DrawFlippedMaskedColumn(column, patch->height);
-			else
-				R_DrawMaskedColumn(column);
+			localcolfunc (column);
 		}
 	}
 	else
 	{
+#ifdef RANGECHECK
+		pwidth = SHORT(patch->width);
+#endif
+
 		// Non-paper drawing loop
 		for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, frac += vis->xiscale, sprtopscreen += vis->shear.tan)
 		{
 #ifdef RANGECHECK
 			texturecolumn = frac>>FRACBITS;
-			if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width))
+			if (texturecolumn < 0 || texturecolumn >= pwidth)
 				I_Error("R_DrawSpriteRange: bad texturecolumn at %d from end", vis->x2 - dc_x);
 			column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
 #else
 			column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[frac>>FRACBITS]));
 #endif
-			if (vis->cut & SC_VFLIP)
-				R_DrawFlippedMaskedColumn(column, patch->height);
-			else
-				R_DrawMaskedColumn(column);
+			localcolfunc (column);
 		}
 	}
 
@@ -1147,7 +1141,6 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope)
 	}
 
 #if 0 // Unfortunately, this drops CEZ2 down to sub-17 FPS on my i7.
-//#ifdef POLYOBJECTS
 	// Check polyobjects and see if floorz needs to be altered, for rings only because they don't update floorz
 	if (thing->type == MT_RING)
 	{
@@ -1238,8 +1231,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	yscale = FixedDiv(projectiony, tz);
 	shadowxscale = FixedMul(thing->radius*2, scalemul);
 	shadowyscale = FixedMul(FixedMul(thing->radius*2, scalemul), FixedDiv(abs(floorz - viewz), tz));
-	shadowyscale = min(shadowyscale, shadowxscale) / patch->height;
-	shadowxscale /= patch->width;
+	shadowyscale = min(shadowyscale, shadowxscale) / SHORT(patch->height);
+	shadowxscale /= SHORT(patch->width);
 	shadowskew = 0;
 
 	if (floorslope)
@@ -1254,24 +1247,24 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 		//CONS_Printf("Shadow is sloped by %d %d\n", xslope, zslope);
 
 		if (viewz < floorz)
-			shadowyscale += FixedMul(FixedMul(thing->radius*2 / patch->height, scalemul), zslope);
+			shadowyscale += FixedMul(FixedMul(thing->radius*2 / SHORT(patch->height), scalemul), zslope);
 		else
-			shadowyscale -= FixedMul(FixedMul(thing->radius*2 / patch->height, scalemul), zslope);
+			shadowyscale -= FixedMul(FixedMul(thing->radius*2 / SHORT(patch->height), scalemul), zslope);
 
 		shadowyscale = abs(shadowyscale);
 
 		shadowskew = xslope;
 	}
 
-	tx -= patch->width * shadowxscale/2;
+	tx -= SHORT(patch->width) * shadowxscale/2;
 	x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS;
 	if (x1 >= viewwidth) return;
 
-	tx += patch->width * shadowxscale;
+	tx += SHORT(patch->width) * shadowxscale;
 	x2 = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS); x2--;
 	if (x2 < 0 || x2 <= x1) return;
 
-	if (shadowyscale < FRACUNIT/patch->height) return; // fix some crashes?
+	if (shadowyscale < FRACUNIT/SHORT(patch->height)) return; // fix some crashes?
 
 	shadow = R_NewVisSprite();
 	shadow->patch = patch;
@@ -1286,8 +1279,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	shadow->dispoffset = vis->dispoffset - 5;
 	shadow->gx = thing->x;
 	shadow->gy = thing->y;
-	shadow->gzt = shadow->pz + shadow->patch->height * shadowyscale / 2;
-	shadow->gz = shadow->gzt - shadow->patch->height * shadowyscale;
+	shadow->gzt = shadow->pz + SHORT(patch->height) * shadowyscale / 2;
+	shadow->gz = shadow->gzt - SHORT(patch->height) * shadowyscale;
 	shadow->texturemid = FixedMul(thing->scale, FixedDiv(shadow->gzt - viewz, shadowyscale));
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
 		shadow->texturemid = FixedMul(shadow->texturemid, ((skin_t *)thing->skin)->highresscale);
@@ -1308,7 +1301,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 
 	shadow->startfrac = 0;
 	//shadow->xiscale = 0x7ffffff0 / (shadow->xscale/2);
-	shadow->xiscale = (patch->width<<FRACBITS)/(x2-x1+1); // fuck it
+	shadow->xiscale = (SHORT(patch->width)<<FRACBITS)/(x2-x1+1); // fuck it
 
 	if (shadow->x1 > x1)
 		shadow->startfrac += shadow->xiscale*(shadow->x1-x1);
@@ -1534,10 +1527,10 @@ static void R_ProjectSprite(mobj_t *thing)
 		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
 		if (rotsprite != NULL)
 		{
-			spr_width = rotsprite->width << FRACBITS;
-			spr_height = rotsprite->height << FRACBITS;
-			spr_offset = rotsprite->leftoffset << FRACBITS;
-			spr_topoffset = rotsprite->topoffset << FRACBITS;
+			spr_width = SHORT(rotsprite->width) << FRACBITS;
+			spr_height = SHORT(rotsprite->height) << FRACBITS;
+			spr_offset = SHORT(rotsprite->leftoffset) << FRACBITS;
+			spr_topoffset = SHORT(rotsprite->topoffset) << FRACBITS;
 			// flip -> rotate, not rotate -> flip
 			flip = 0;
 		}
@@ -1695,7 +1688,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	// PORTAL SPRITE CLIPPING
 	if (portalrender && portalclipline)
 	{
-		if (x2 < portalclipstart || x1 > portalclipend)
+		if (x2 < portalclipstart || x1 >= portalclipend)
 			return;
 
 		if (P_PointOnLineSide(thing->x, thing->y, portalclipline) != 0)
@@ -1964,7 +1957,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 	// PORTAL SPRITE CLIPPING
 	if (portalrender && portalclipline)
 	{
-		if (x2 < portalclipstart || x1 > portalclipend)
+		if (x2 < portalclipstart || x1 >= portalclipend)
 			return;
 
 		if (P_PointOnLineSide(thing->x, thing->y, portalclipline) != 0)
@@ -2276,7 +2269,6 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 				entry->ffloor = ds->thicksides[i];
 			}
 		}
-#ifdef POLYOBJECTS_PLANES
 		// Check for a polyobject plane, but only if this is a front line
 		if (ds->curline->polyseg && ds->curline->polyseg->visplane && !ds->curline->side) {
 			plane = ds->curline->polyseg->visplane;
@@ -2292,7 +2284,6 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 			}
 			ds->curline->polyseg->visplane = NULL;
 		}
-#endif
 		if (ds->maskedtexturecol)
 		{
 			entry = R_CreateDrawNode(head);
@@ -2340,7 +2331,6 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 	if (tempskip)
 		return;
 
-#ifdef POLYOBJECTS_PLANES
 	// find all the remaining polyobject planes and add them on the end of the list
 	// probably this is a terrible idea if we wanted them to be sorted properly
 	// but it works getting them in for now
@@ -2361,7 +2351,6 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 		// note: no seg is set, for what should be obvious reasons
 		PolyObjects[i].visplane = NULL;
 	}
-#endif
 
 	// No vissprites in this mask?
 	if (mask->vissprites[1] - mask->vissprites[0] == 0)
diff --git a/src/r_things.h b/src/r_things.h
index 05d6fb27b7728e7f3a1103af3956468120a8ae9c..7a0fe3a60ee557bc358e95e75c49dc5f53d6ef3f 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -44,9 +44,10 @@ extern fixed_t sprtopscreen;
 extern fixed_t sprbotscreen;
 extern fixed_t windowtop;
 extern fixed_t windowbottom;
+extern INT32 lengthcol;
 
 void R_DrawMaskedColumn(column_t *column);
-void R_DrawFlippedMaskedColumn(column_t *column, INT32 texheight);
+void R_DrawFlippedMaskedColumn(column_t *column);
 
 // ----------------
 // SPRITE RENDERING
diff --git a/src/s_sound.c b/src/s_sound.c
index 0cc40a9cef5226b0b6ec275d71dac5a29823637f..5ed9fd83a22d8f475c7400878db264d329a42bd1 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -134,6 +134,7 @@ consvar_t cv_playmusicifunfocused = {"playmusicifunfocused", "No", CV_SAVE, CV_Y
 consvar_t cv_playsoundsifunfocused = {"playsoundsifunfocused", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 #ifdef HAVE_OPENMPT
+openmpt_module *openmpt_mhandle = NULL;
 static CV_PossibleValue_t interpolationfilter_cons_t[] = {{0, "Default"}, {1, "None"}, {2, "Linear"}, {4, "Cubic"}, {8, "Windowed sinc"}, {0, NULL}};
 consvar_t cv_modfilter = {"modfilter", "0", CV_SAVE|CV_CALL, interpolationfilter_cons_t, ModFilter_OnChange, 0, NULL, NULL, 0, 0, NULL};
 #endif
@@ -1456,6 +1457,7 @@ musicdef_t soundtestsfx = {
 	0, // with no conditions
 	0,
 	0,
+	0,
 	false,
 	NULL
 };
@@ -1651,6 +1653,8 @@ ReadMusicDefFields (UINT16 wadnum, int line, boolean fields, char *stoken,
 				fixed_t bpmf = FLOAT_TO_FIXED(bpm);
 				if (bpmf > 0)
 					def->bpm = FixedDiv((60*TICRATE)<<FRACBITS, bpmf);
+			} else if (!stricmp(stoken, "loopms")) {
+				def->loop_ms = atoi(textline);
 			} else {
 				CONS_Alert(CONS_WARNING,
 						"MUSICDEF: Invalid field '%s'. (file %s, line %d)\n",
@@ -1672,7 +1676,7 @@ void S_LoadMusicDefs(UINT16 wadnum)
 	char *lf;
 	char *stoken;
 
-	size_t nlf;
+	size_t nlf = 0xFFFFFFFF;
 	size_t ncr;
 
 	musicdef_t *def = NULL;
@@ -1907,6 +1911,10 @@ UINT32 S_GetMusicPosition(void)
 /// In this section: mazmazz doesn't know how to do dynamic arrays or struct pointers!
 /// ------------------------
 
+char music_stack_nextmusname[7];
+boolean music_stack_noposition = false;
+UINT32 music_stack_fadeout = 0;
+UINT32 music_stack_fadein = 0;
 static musicstack_t *music_stacks = NULL;
 static musicstack_t *last_music_stack = NULL;
 
@@ -2262,6 +2270,8 @@ static void S_UnloadMusic(void)
 
 static boolean S_PlayMusic(boolean looping, UINT32 fadeinms)
 {
+	musicdef_t *def;
+
 	if (S_MusicDisabled())
 		return false;
 
@@ -2273,6 +2283,17 @@ static boolean S_PlayMusic(boolean looping, UINT32 fadeinms)
 		return false;
 	}
 
+	/* set loop point from MUSICDEF */
+	for (def = musicdefstart; def; def = def->next)
+	{
+		if (strcasecmp(def->name, music_name) == 0)
+		{
+			if (def->loop_ms)
+				S_SetMusicLoopPoint(def->loop_ms);
+			break;
+		}
+	}
+
 	S_InitMusicVolume(); // switch between digi and sequence volume
 
 	if (S_MusicNotInFocus())
diff --git a/src/s_sound.h b/src/s_sound.h
index d7e0c46ab8dd1fbc683cea7b5ed246fc2ebc6dda..3334fcb69d7bf18820776130c627ba948b1ecb52 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -22,7 +22,7 @@
 
 #ifdef HAVE_OPENMPT
 #include "libopenmpt/libopenmpt.h"
-openmpt_module *openmpt_mhandle;
+extern openmpt_module *openmpt_mhandle;
 #endif
 
 // mask used to indicate sound origin is player item pickup
@@ -208,6 +208,7 @@ typedef struct musicdef_s
 	INT16 soundtestcond; // +ve for map, -ve for conditionset, 0 for already here
 	tic_t stoppingtics;
 	fixed_t bpm;
+	UINT32 loop_ms;/* override LOOPPOINT/LOOPMS */
 	boolean allowed; // question marks or listenable on sound test?
 	struct musicdef_s *next;
 } musicdef_t;
@@ -261,10 +262,10 @@ typedef struct musicstack_s
     struct musicstack_s *next;
 } musicstack_t;
 
-char music_stack_nextmusname[7];
-boolean music_stack_noposition;
-UINT32 music_stack_fadeout;
-UINT32 music_stack_fadein;
+extern char music_stack_nextmusname[7];
+extern boolean music_stack_noposition;
+extern UINT32 music_stack_fadeout;
+extern UINT32 music_stack_fadein;
 
 void S_SetStackAdjustmentStart(void);
 void S_AdjustMusicStackTics(void);
@@ -332,7 +333,7 @@ void S_StopSoundByNum(sfxenum_t sfxnum);
 #ifdef MUSICSLOT_COMPATIBILITY
 // For compatibility with code/scripts relying on older versions
 // This is a list of all the "special" slot names and their associated numbers
-const char *compat_special_music_slots[16];
+extern const char *compat_special_music_slots[16];
 #endif
 
 #endif
diff --git a/src/screen.c b/src/screen.c
index 6bdf91ed861cbd2e3ffda335b6dd76004e8cd591..e01d1a81a8160918010a61cc389646664a91cbb0 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -449,7 +449,7 @@ void SCR_ActuallyChangeRenderer(void)
 
 #ifdef HWRENDER
 	// Well, it didn't even load anyway.
-	if ((hwrenderloaded == -1) && (setrenderneeded == render_opengl))
+	if ((vid_opengl_state == -1) && (setrenderneeded == render_opengl))
 	{
 		if (M_CheckParm("-nogl"))
 			CONS_Alert(CONS_ERROR, "OpenGL rendering was disabled!\n");
@@ -474,7 +474,7 @@ void SCR_ChangeRenderer(void)
 	{
 		target_renderer = cv_renderer.value;
 #ifdef HWRENDER
-		if (M_CheckParm("-opengl") && (hwrenderloaded == 1))
+		if (M_CheckParm("-opengl") && (vid_opengl_state == 1))
 			target_renderer = rendermode = render_opengl;
 		else
 #endif
@@ -526,6 +526,9 @@ void SCR_DisplayTicRate(void)
 	INT32 ticcntcolor = 0;
 	const INT32 h = vid.height-(8*vid.dupy);
 
+	if (gamestate == GS_NULL)
+		return;
+
 	for (i = lasttic + 1; i < TICRATE+lasttic && i < ontic; ++i)
 		fpsgraph[i % TICRATE] = false;
 
@@ -538,10 +541,16 @@ void SCR_DisplayTicRate(void)
 	if (totaltics <= TICRATE/2) ticcntcolor = V_REDMAP;
 	else if (totaltics == TICRATE) ticcntcolor = V_GREENMAP;
 
-	V_DrawString(vid.width-(72*vid.dupx), h,
-		V_YELLOWMAP|V_NOSCALESTART|V_USERHUDTRANS, "FPS:");
-	V_DrawString(vid.width-(40*vid.dupx), h,
-		ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%02d/%02u", totaltics, TICRATE));
+	if (cv_ticrate.value == 2) // compact counter
+		V_DrawString(vid.width-(16*vid.dupx), h,
+			ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%02d", totaltics));
+	else if (cv_ticrate.value == 1) // full counter
+	{
+		V_DrawString(vid.width-(72*vid.dupx), h,
+			V_YELLOWMAP|V_NOSCALESTART|V_USERHUDTRANS, "FPS:");
+		V_DrawString(vid.width-(40*vid.dupx), h,
+			ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%02d/%02u", totaltics, TICRATE));
+	}
 
 	lasttic = ontic;
 }
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index 7e260f4c01ed5a83960bd8f65bdc1f50fbad5ee6..6335b3028a841997ddb38b92f70164060390fbf5 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -213,6 +213,7 @@
     <ClInclude Include="..\fastcmp.h" />
     <ClInclude Include="..\filesrch.h" />
     <ClInclude Include="..\f_finale.h" />
+    <ClInclude Include="..\g_demo.h" />
     <ClInclude Include="..\g_game.h" />
     <ClInclude Include="..\g_input.h" />
     <ClInclude Include="..\g_state.h" />
@@ -364,6 +365,7 @@
     <ClCompile Include="..\filesrch.c" />
     <ClCompile Include="..\f_finale.c" />
     <ClCompile Include="..\f_wipe.c" />
+    <ClCompile Include="..\g_demo.c" />
     <ClCompile Include="..\g_game.c" />
     <ClCompile Include="..\g_input.c" />
     <ClCompile Include="..\hardware\hw3sound.c" />
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index 21820551a4ad1bfcfe85765d7975fa482c646e55..a226e8397286000cc794a0172c03d983270e6d16 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -180,6 +180,9 @@
     <ClInclude Include="..\f_finale.h">
       <Filter>F_Frame</Filter>
     </ClInclude>
+    <ClInclude Include="..\g_demo.h">
+      <Filter>G_Game</Filter>
+    </ClInclude>
     <ClInclude Include="..\g_game.h">
       <Filter>G_Game</Filter>
     </ClInclude>
@@ -600,6 +603,9 @@
     <ClCompile Include="..\f_wipe.c">
       <Filter>F_Frame</Filter>
     </ClCompile>
+    <ClCompile Include="..\g_demo.c">
+      <Filter>G_Game</Filter>
+    </ClCompile>
     <ClCompile Include="..\g_game.c">
       <Filter>G_Game</Filter>
     </ClCompile>
diff --git a/src/sdl/i_main.c b/src/sdl/i_main.c
index 5d0009927f1f64579c538d2cc604ee30fa3aa007..3eded734f3d1adf4cbe620843754cffae259ed77 100644
--- a/src/sdl/i_main.c
+++ b/src/sdl/i_main.c
@@ -27,7 +27,7 @@
 #include <unistd.h>
 #endif
 
-#ifdef __unix__
+#if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)
 #include <errno.h>
 #endif
 
@@ -142,7 +142,7 @@ int main(int argc, char **argv)
 		const char *reldir;
 		int left;
 		boolean fileabs;
-#ifdef __unix__
+#if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)
 		const char *link;
 #endif
 
@@ -201,7 +201,7 @@ int main(int argc, char **argv)
 				M_PathParts(logdir) - 1,
 				M_PathParts(logfilename) - 1, 0755);
 
-#ifdef __unix__
+#if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)
 		logstream = fopen(logfilename, "w");
 #ifdef DEFAULTDIR
 		if (logdir)
@@ -214,9 +214,9 @@ int main(int argc, char **argv)
 		{
 			I_OutputMsg("Error symlinking latest-log.txt: %s\n", strerror(errno));
 		}
-#else/*__unix__*/
+#else/*defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)*/
 		logstream = fopen("latest-log.txt", "wt+");
-#endif/*__unix__*/
+#endif/*defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)*/
 	}
 
 	//I_OutputMsg("I_StartupSystem() ...\n");
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index a86af316e9e22460c734057960f39a8301fc67e9..d2ed62516c742cb1fcf4ba63d948202999998287 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2484,7 +2484,7 @@ void I_RemoveExitFunc(void (*func)())
 	}
 }
 
-#ifndef __unix__
+#if !(defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON))
 static void Shittycopyerror(const char *name)
 {
 	I_OutputMsg(
@@ -2524,7 +2524,7 @@ static void Shittylogcopy(void)
 		Shittycopyerror(logfilename);
 	}
 }
-#endif/*__unix__*/
+#endif/*!(defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON))*/
 
 //
 //  Closes down everything. This includes restoring the initial
@@ -2548,7 +2548,7 @@ void I_ShutdownSystem(void)
 	if (logstream)
 	{
 		I_OutputMsg("I_ShutdownSystem(): end of logstream.\n");
-#ifndef __unix__
+#if !(defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON))
 		Shittylogcopy();
 #endif
 		fclose(logstream);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index c2f492000326bdd6319cd8a2f68a3f81afa9960f..0abc9280c4012f8ce57bcba2c955ec879dab1ddf 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -104,7 +104,7 @@ static consvar_t cv_stretch = {"stretch", "Off", CV_SAVE|CV_NOSHOWHELP, CV_OnOff
 static consvar_t cv_alwaysgrabmouse = {"alwaysgrabmouse", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 UINT8 graphics_started = 0; // Is used in console.c and screen.c
-INT32 hwrenderloaded = 0;
+INT32 vid_opengl_state = 0;
 
 // To disable fullscreen at startup; is set in VID_PrepareModeList
 boolean allow_fullscreen = false;
@@ -217,7 +217,6 @@ static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen, SDL_bool
 	else
 	{
 		Impl_CreateWindow(fullscreen);
-		Impl_SetWindowIcon();
 		wasfullscreen = fullscreen;
 		SDL_SetWindowSize(window, width, height);
 		if (fullscreen)
@@ -372,7 +371,9 @@ static boolean IgnoreMouse(void)
 		return false;
 	if (menuactive)
 		return !M_MouseNeeded();
-	if (paused || con_destlines || chat_on || gamestate != GS_LEVEL)
+	if (paused || con_destlines || chat_on)
+		return true;
+	if (gamestate != GS_LEVEL && gamestate != GS_INTERMISSION && gamestate != GS_CUTSCENE)
 		return true;
 	return false;
 }
@@ -1440,7 +1441,7 @@ static SDL_bool Impl_CreateContext(void)
 {
 	// Renderer-specific stuff
 #ifdef HWRENDER
-	if ((rendermode == render_opengl) && (hwrenderloaded != -1))
+	if ((rendermode == render_opengl) && (vid_opengl_state != -1))
 	{
 		if (!sdlglcontext)
 			sdlglcontext = SDL_GL_CreateContext(window);
@@ -1473,10 +1474,10 @@ static SDL_bool Impl_CreateContext(void)
 	return SDL_TRUE;
 }
 
-#ifdef HWRENDER
-static void VID_CheckGLLoaded(rendermode_t oldrender)
+void VID_CheckGLLoaded(rendermode_t oldrender)
 {
-	if (hwrenderloaded == -1) // Well, it didn't work the first time anyway.
+#ifdef HWRENDER
+	if (vid_opengl_state == -1) // Well, it didn't work the first time anyway.
 	{
 		rendermode = oldrender;
 		if (chosenrendermode == render_opengl) // fallback to software
@@ -1488,40 +1489,66 @@ static void VID_CheckGLLoaded(rendermode_t oldrender)
 			setrenderneeded = 0;
 		}
 	}
-}
 #endif
+}
 
 void VID_CheckRenderer(void)
 {
-	SDL_bool rendererchanged = SDL_FALSE;
+	boolean rendererchanged = false;
+	boolean contextcreated = false;
 	rendermode_t oldrenderer = rendermode;
 
 	if (dedicated)
 		return;
 
-#ifdef HWRENDER
-	if (!graphics_started)
-		VID_CheckGLLoaded(oldrenderer);
-#endif
-
 	if (setrenderneeded)
 	{
 		rendermode = setrenderneeded;
-		rendererchanged = SDL_TRUE;
+		rendererchanged = true;
 
 #ifdef HWRENDER
 		if (rendermode == render_opengl)
 		{
 			VID_CheckGLLoaded(oldrenderer);
+
 			// Initialise OpenGL before calling SDLSetMode!!!
-			if (hwrenderloaded != 1)
-				I_StartupHardwareGraphics();
-			else if (hwrenderloaded == -1)
-				rendererchanged = SDL_FALSE;
+			// This is because SDLSetMode calls OglSdlSurface.
+			if (vid_opengl_state == 0)
+			{
+				VID_StartupOpenGL();
+				// Loaded successfully!
+				if (vid_opengl_state == 1)
+				{
+					// Destroy the current window, if it exists.
+					if (window)
+					{
+						SDL_DestroyWindow(window);
+						window = NULL;
+					}
+
+					// Destroy the current window rendering context, if that also exists.
+					if (renderer)
+					{
+						SDL_DestroyRenderer(renderer);
+						renderer = NULL;
+					}
+
+					// Create a new window.
+					Impl_CreateWindow(USE_FULLSCREEN);
+
+					// From there, the OpenGL context was already created.
+					contextcreated = true;
+				}
+			}
+			else if (vid_opengl_state == -1)
+				rendererchanged = false;
 		}
 #endif
 
-		Impl_CreateContext();
+		if (!contextcreated)
+			Impl_CreateContext();
+
+		setrenderneeded = 0;
 	}
 
 	SDLSetMode(vid.width, vid.height, USE_FULLSCREEN, (rendererchanged ? SDL_FALSE : SDL_TRUE));
@@ -1534,15 +1561,25 @@ void VID_CheckRenderer(void)
 			SDL_FreeSurface(bufSurface);
 			bufSurface = NULL;
 		}
+
+		if (rendererchanged)
+		{
 #ifdef HWRENDER
-		if (hwrenderloaded == 1) // Only if OpenGL ever loaded!
-			HWR_FreeTextureCache();
+			if (vid_opengl_state == 1) // Only if OpenGL ever loaded!
+				HWR_FreeTextureCache();
 #endif
-		SCR_SetDrawFuncs();
+			SCR_SetDrawFuncs();
+		}
 	}
 #ifdef HWRENDER
 	else if (rendermode == render_opengl)
-		R_InitHardwareMode();
+	{
+		if (rendererchanged)
+		{
+			R_InitHardwareMode();
+			V_SetPalette(0);
+		}
+	}
 #else
 	(void)oldrenderer;
 #endif
@@ -1586,7 +1623,7 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 		flags |= SDL_WINDOW_BORDERLESS;
 
 #ifdef HWRENDER
-	if (hwrenderloaded != -1)
+	if (vid_opengl_state == 1)
 		flags |= SDL_WINDOW_OPENGL;
 #endif
 
@@ -1594,12 +1631,15 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 	window = SDL_CreateWindow("SRB2 "VERSIONSTRING, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
 			realwidth, realheight, flags);
 
+
 	if (window == NULL)
 	{
 		CONS_Printf(M_GetText("Couldn't create window: %s\n"), SDL_GetError());
 		return SDL_FALSE;
 	}
 
+	Impl_SetWindowIcon();
+
 	return Impl_CreateContext();
 }
 
@@ -1616,12 +1656,8 @@ static void Impl_SetWindowName(const char *title)
 
 static void Impl_SetWindowIcon(void)
 {
-	if (window == NULL || icoSurface == NULL)
-	{
-		return;
-	}
-	//SDL2STUB(); // Monster Iestyn: why is this stubbed?
-	SDL_SetWindowIcon(window, icoSurface);
+	if (window && icoSurface)
+		SDL_SetWindowIcon(window, icoSurface);	
 }
 
 static void Impl_VideoSetupSDLBuffer(void)
@@ -1720,13 +1756,19 @@ void I_StartupGraphics(void)
 
 	//SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY>>1,SDL_DEFAULT_REPEAT_INTERVAL<<2);
 	VID_Command_ModeList_f();
+
 #ifdef HWRENDER
 	if (M_CheckParm("-nogl"))
-		hwrenderloaded = -1; // Don't call SDL_GL_LoadLibrary
-	else
-		I_StartupHardwareGraphics();
+		vid_opengl_state = -1; // Don't startup OpenGL
+	else if (chosenrendermode == render_opengl)
+		VID_StartupOpenGL();
 #endif
 
+	// Window icon
+#ifdef HAVE_IMAGE
+	icoSurface = IMG_ReadXPMFromArray(SDL_icon_xpm);
+#endif	
+
 	// Fury: we do window initialization after GL setup to allow
 	// SDL_GL_LoadLibrary to work well on Windows
 
@@ -1745,11 +1787,6 @@ void I_StartupGraphics(void)
 #ifdef HAVE_TTF
 	I_ShutdownTTF();
 #endif
-	// Window icon
-#ifdef HAVE_IMAGE
-	icoSurface = IMG_ReadXPMFromArray(SDL_icon_xpm);
-#endif
-	Impl_SetWindowIcon();
 
 	VID_SetMode(VID_GetModeForSize(BASEVIDWIDTH, BASEVIDHEIGHT));
 
@@ -1779,12 +1816,13 @@ void I_StartupGraphics(void)
 	graphics_started = true;
 }
 
-void I_StartupHardwareGraphics(void)
+void VID_StartupOpenGL(void)
 {
 #ifdef HWRENDER
 	static boolean glstartup = false;
 	if (!glstartup)
 	{
+		CONS_Printf("VID_StartupOpenGL()...\n");
 		HWD.pfnInit             = hwSym("Init",NULL);
 		HWD.pfnFinishUpdate     = NULL;
 		HWD.pfnDraw2DLine       = hwSym("Draw2DLine",NULL);
@@ -1816,13 +1854,13 @@ void I_StartupHardwareGraphics(void)
 		// check gl renderer lib
 		if (HWD.pfnGetRenderVersion() != VERSION)
 		{
-			CONS_Alert(CONS_ERROR, M_GetText("The version of the renderer doesn't match the version of the executable\nBe sure you have installed SRB2 properly.\n"));
-			hwrenderloaded = -1;
+			CONS_Alert(CONS_ERROR, M_GetText("The version of the renderer doesn't match the version of the executable!\nBe sure you have installed SRB2 properly.\n"));
+			vid_opengl_state = -1;
 		}
 		else
-			hwrenderloaded = HWD.pfnInit(I_Error) ? 1 : -1; // let load the OpenGL library
+			vid_opengl_state = HWD.pfnInit(I_Error) ? 1 : -1; // let load the OpenGL library
 
-		if (hwrenderloaded == -1)
+		if (vid_opengl_state == -1)
 		{
 			rendermode = render_soft;
 			setrenderneeded = 0;
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index 1822202651070b8330768516a26893a21b6384ca..745513eeb3542cbf68d6515c09cb8e6abc56db97 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1219,7 +1219,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.2.2;
+				CURRENT_PROJECT_VERSION = 2.2.4;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1231,7 +1231,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.2.2;
+				CURRENT_PROJECT_VERSION = 2.2.4;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/sounds.c b/src/sounds.c
index ca943c2d06f0fd9d86e0a3616a86c84b857f65e3..9894fd13ecb2509be3118b48dfa53eda04db8467 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -527,7 +527,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k8a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Boing"},
   {"s3k8b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powerful hit"},
   {"s3k8c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Humming power"},
-  {"s3k8d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k8d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "/"},
   {"s3k8e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Accelerating"},
   {"s3k8f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Opening"},
   {"s3k90",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Impact"},
diff --git a/src/st_stuff.c b/src/st_stuff.c
index ee7fc3f2166a148856aee09cb7febdfa655d6237..9d819b147ead4ab24e3abd29252a827f273c071b 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1325,12 +1325,21 @@ void ST_drawTitleCard(void)
 {
 	char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl;
 	char *subttl = mapheaderinfo[gamemap-1]->subttl;
-	INT32 actnum = mapheaderinfo[gamemap-1]->actnum;
+	UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
 	INT32 lvlttlxpos, ttlnumxpos, zonexpos;
 	INT32 subttlxpos = BASEVIDWIDTH/2;
 	INT32 ttlscroll = FixedInt(lt_scroll);
 	INT32 zzticker;
 	patch_t *actpat, *zigzag, *zztext;
+	UINT8 colornum;
+	const UINT8 *colormap;
+
+	if (players[consoleplayer].skincolor)
+		colornum = players[consoleplayer].skincolor;
+	else
+		colornum = cv_playercolor.value;
+
+	colormap = R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE);
 
 	if (!G_IsTitleCardAvailable())
 		return;
@@ -1364,16 +1373,21 @@ void ST_drawTitleCard(void)
 	if (!splitscreen || (splitscreen && stplyr == &players[displayplayer]))
 	{
 		zzticker = lt_ticker;
-		V_DrawScaledPatch(FixedInt(lt_zigzag), (-zzticker) % zigzag->height, V_SNAPTOTOP|V_SNAPTOLEFT, zigzag);
-		V_DrawScaledPatch(FixedInt(lt_zigzag), (zigzag->height-zzticker) % zigzag->height, V_SNAPTOTOP|V_SNAPTOLEFT, zigzag);
-		V_DrawScaledPatch(FixedInt(lt_zigzag), (-zigzag->height+zzticker) % zztext->height, V_SNAPTOTOP|V_SNAPTOLEFT, zztext);
-		V_DrawScaledPatch(FixedInt(lt_zigzag), (zzticker) % zztext->height, V_SNAPTOTOP|V_SNAPTOLEFT, zztext);
+		V_DrawMappedPatch(FixedInt(lt_zigzag), (-zzticker) % zigzag->height, V_SNAPTOTOP|V_SNAPTOLEFT, zigzag, colormap);
+		V_DrawMappedPatch(FixedInt(lt_zigzag), (zigzag->height-zzticker) % zigzag->height, V_SNAPTOTOP|V_SNAPTOLEFT, zigzag, colormap);
+		V_DrawMappedPatch(FixedInt(lt_zigzag), (-zigzag->height+zzticker) % zztext->height, V_SNAPTOTOP|V_SNAPTOLEFT, zztext, colormap);
+		V_DrawMappedPatch(FixedInt(lt_zigzag), (zzticker) % zztext->height, V_SNAPTOTOP|V_SNAPTOLEFT, zztext, colormap);
 	}
 
 	if (actnum)
 	{
 		if (!splitscreen)
-			V_DrawScaledPatch(ttlnumxpos + ttlscroll, 104 - ttlscroll, 0, actpat);
+		{
+			if (actnum > 9) // slightly offset the act diamond for two-digit act numbers
+				V_DrawMappedPatch(ttlnumxpos + (V_LevelActNumWidth(actnum)/4) + ttlscroll, 104 - ttlscroll, 0, actpat, colormap);
+			else
+				V_DrawMappedPatch(ttlnumxpos + ttlscroll, 104 - ttlscroll, 0, actpat, colormap);
+		}
 		V_DrawLevelActNum(ttlnumxpos + ttlscroll, 104, V_PERPLAYER, actnum);
 	}
 
diff --git a/src/v_video.c b/src/v_video.c
index 2d1014c23f3e44820a7106a11a05df2acc7be525..1e550fe9d4c488bb0f03456485956f06bfa03da0 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -42,7 +42,8 @@ UINT8 *screens[5];
 // screens[3] = fade screen start
 // screens[4] = fade screen end, postimage tempoarary buffer
 
-consvar_t cv_ticrate = {"showfps", "No", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t ticrate_cons_t[] = {{0, "No"}, {1, "Full"}, {2, "Compact"}, {0, NULL}};
+consvar_t cv_ticrate = {"showfps", "No", CV_SAVE, ticrate_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static void CV_palette_OnChange(void);
 
@@ -2951,13 +2952,19 @@ void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits)
 }
 
 // Draw an act number for a level title
-// Todo: actually draw two-digit numbers as two act num patches
-void V_DrawLevelActNum(INT32 x, INT32 y, INT32 flags, INT32 num)
+void V_DrawLevelActNum(INT32 x, INT32 y, INT32 flags, UINT8 num)
 {
-	if (num < 0 || num > 19)
+	if (num > 99)
 		return; // not supported
 
-	V_DrawScaledPatch(x, y, flags, ttlnum[num]);
+	while (num > 0)
+	{
+		if (num > 9) // if there are two digits, draw second digit first
+			V_DrawScaledPatch(x + (V_LevelActNumWidth(num) - V_LevelActNumWidth(num%10)), y, flags, ttlnum[num%10]);
+		else
+			V_DrawScaledPatch(x, y, flags, ttlnum[num]);
+		num = num/10;
+	}
 }
 
 // Write a string using the credit font
@@ -3338,13 +3345,21 @@ INT32 V_LevelNameHeight(const char *string)
 }
 
 // For ST_drawTitleCard
-// Returns the width of the act num patch
-INT32 V_LevelActNumWidth(INT32 num)
+// Returns the width of the act num patch(es)
+INT16 V_LevelActNumWidth(UINT8 num)
 {
-	if (num < 0 || num > 19)
-		return 0; // not a valid number
+	INT16 result = 0;
+
+	if (num == 0)
+		result = SHORT(ttlnum[num]->width);
+
+	while (num > 0 && num <= 99)
+	{
+		result = result + SHORT(ttlnum[num%10]->width);
+		num = num/10;
+	}
 
-	return SHORT(ttlnum[num]->width);
+	return result;
 }
 
 //
diff --git a/src/v_video.h b/src/v_video.h
index ed623a57f4e771781f45fb32c50c210dd804c0f4..664fa8995fa47b9cfc7720f1d67ff17c580b1be3 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -238,12 +238,12 @@ void V_DrawRightAlignedSmallThinStringAtFixed(fixed_t x, fixed_t y, INT32 option
 // Draw tall nums, used for menu, HUD, intermission
 void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num);
 void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits);
-void V_DrawLevelActNum(INT32 x, INT32 y, INT32 flags, INT32 num);
+void V_DrawLevelActNum(INT32 x, INT32 y, INT32 flags, UINT8 num);
 
 // Find string width from lt_font chars
 INT32 V_LevelNameWidth(const char *string);
 INT32 V_LevelNameHeight(const char *string);
-INT32 V_LevelActNumWidth(INT32 num); // act number width
+INT16 V_LevelActNumWidth(UINT8 num); // act number width
 
 void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string);
 INT32 V_CreditStringWidth(const char *string);
diff --git a/src/w_wad.c b/src/w_wad.c
index f7ccc175b87df030d492b1e9b6469ac42a99b070..041222479f0f8b8afee40672efe078583ee6c745 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -76,6 +76,10 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 //int	vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
 #endif
 
+#ifdef _DEBUG
+#include "console.h"
+#endif
+
 #ifndef O_BINARY
 #define O_BINARY 0
 #endif
@@ -92,7 +96,7 @@ typedef struct
 
 typedef struct lumpnum_cache_s
 {
-	char lumpname[8];
+	char lumpname[32];
 	lumpnum_t lumpnum;
 } lumpnum_cache_t;
 
@@ -114,13 +118,18 @@ void W_Shutdown(void)
 {
 	while (numwadfiles--)
 	{
-		fclose(wadfiles[numwadfiles]->handle);
-		Z_Free(wadfiles[numwadfiles]->filename);
-		while (wadfiles[numwadfiles]->numlumps--)
-			Z_Free(wadfiles[numwadfiles]->lumpinfo[wadfiles[numwadfiles]->numlumps].name2);
+		wadfile_t *wad = wadfiles[numwadfiles];
+
+		fclose(wad->handle);
+		Z_Free(wad->filename);
+		while (wad->numlumps--)
+		{
+			Z_Free(wad->lumpinfo[wad->numlumps].longname);
+			Z_Free(wad->lumpinfo[wad->numlumps].fullname);
+		}
 
-		Z_Free(wadfiles[numwadfiles]->lumpinfo);
-		Z_Free(wadfiles[numwadfiles]);
+		Z_Free(wad->lumpinfo);
+		Z_Free(wad);
 	}
 }
 
@@ -194,7 +203,6 @@ static inline void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
 	if (posStart != INT16_MAX)
 	{
 		posEnd = W_CheckNumForFolderEndPK3("Lua/", wadnum, posStart);
-		posStart++; // first "lump" will be "Lua/" folder itself, so ignore it
 		for (; posStart < posEnd; posStart++)
 			LUA_LoadLump(wadnum, posStart);
 	}
@@ -204,13 +212,12 @@ static inline void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
 	{
 		posEnd = W_CheckNumForFolderEndPK3("SOC/", wadnum, posStart);
 
-		posStart++; // first "lump" will be "SOC/" folder itself, so ignore it
 		for(; posStart < posEnd; posStart++)
 		{
 			lumpinfo_t *lump_p = &wadfiles[wadnum]->lumpinfo[posStart];
-			size_t length = strlen(wadfiles[wadnum]->filename) + 1 + strlen(lump_p->name2); // length of file name, '|', and lump name
+			size_t length = strlen(wadfiles[wadnum]->filename) + 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name
 			char *name = malloc(length + 1);
-			sprintf(name, "%s|%s", wadfiles[wadnum]->filename, lump_p->name2);
+			sprintf(name, "%s|%s", wadfiles[wadnum]->filename, lump_p->fullname);
 			name[length] = '\0';
 			CONS_Printf(M_GetText("Loading SOC from %s\n"), name);
 			DEH_LoadDehackedLumpPwad(wadnum, posStart, mainfile);
@@ -237,9 +244,9 @@ static inline void W_LoadDehackedLumps(UINT16 wadnum, boolean mainfile)
 		for (lump = 0; lump < wadfiles[wadnum]->numlumps; lump++, lump_p++)
 			if (memcmp(lump_p->name,"SOC_",4)==0) // Check for generic SOC lump
 			{	// shameless copy+paste of code from LUA_LoadLump
-				size_t length = strlen(wadfiles[wadnum]->filename) + 1 + strlen(lump_p->name2); // length of file name, '|', and lump name
+				size_t length = strlen(wadfiles[wadnum]->filename) + 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name
 				char *name = malloc(length + 1);
-				sprintf(name, "%s|%s", wadfiles[wadnum]->filename, lump_p->name2);
+				sprintf(name, "%s|%s", wadfiles[wadnum]->filename, lump_p->fullname);
 				name[length] = '\0';
 
 				CONS_Printf(M_GetText("Loading SOC from %s\n"), name);
@@ -341,10 +348,17 @@ static lumpinfo_t* ResGetLumpsStandalone (FILE* handle, UINT16* numlumps, const
 	lumpinfo->size = ftell(handle);
 	fseek(handle, 0, SEEK_SET);
 	strcpy(lumpinfo->name, lumpname);
+
+	// Allocate the lump's long name.
+	lumpinfo->longname = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
+	strcpy(lumpinfo->longname, lumpname);
+	lumpinfo->longname[8] = '\0';
+
 	// Allocate the lump's full name.
-	lumpinfo->name2 = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
-	strcpy(lumpinfo->name2, lumpname);
-	lumpinfo->name2[8] = '\0';
+	lumpinfo->fullname = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
+	strcpy(lumpinfo->fullname, lumpname);
+	lumpinfo->fullname[8] = '\0';
+
 	*numlumps = 1;
 	return lumpinfo;
 }
@@ -431,10 +445,16 @@ static lumpinfo_t* ResGetLumpsWad (FILE* handle, UINT16* nlmp, const char* filen
 			lump_p->compression = CM_NOCOMPRESSION;
 		memset(lump_p->name, 0x00, 9);
 		strncpy(lump_p->name, fileinfo->name, 8);
+
+		// Allocate the lump's long name.
+		lump_p->longname = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
+		strncpy(lump_p->longname, fileinfo->name, 8);
+		lump_p->longname[8] = '\0';
+
 		// Allocate the lump's full name.
-		lump_p->name2 = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
-		strncpy(lump_p->name2, fileinfo->name, 8);
-		lump_p->name2[8] = '\0';
+		lump_p->fullname = Z_Malloc(9 * sizeof(char), PU_STATIC, NULL);
+		strncpy(lump_p->fullname, fileinfo->name, 8);
+		lump_p->fullname[8] = '\0';
 	}
 	free(fileinfov);
 	*nlmp = numlumps;
@@ -525,8 +545,8 @@ typedef struct zlentry_s
 static lumpinfo_t* ResGetLumpsZip (FILE* handle, UINT16* nlmp)
 {
     zend_t zend;
-    zentry_t* zentries;
-    zentry_t* zentry;
+    zentry_t zentry;
+    zlentry_t zlentry;
 
 	UINT16 numlumps = *nlmp;
 	lumpinfo_t* lumpinfo;
@@ -554,40 +574,36 @@ static lumpinfo_t* ResGetLumpsZip (FILE* handle, UINT16* nlmp)
 	numlumps = zend.entries;
 
 	lump_p = lumpinfo = Z_Malloc(numlumps * sizeof (*lumpinfo), PU_STATIC, NULL);
-	zentry = zentries = malloc(numlumps * sizeof (*zentries));
 
 	fseek(handle, zend.cdiroffset, SEEK_SET);
-	for (i = 0; i < numlumps; i++, zentry++, lump_p++)
+	for (i = 0; i < numlumps; i++, lump_p++)
 	{
 		char* fullname;
 		char* trimname;
 		char* dotpos;
 
-		if (fread(zentry, 1, sizeof(zentry_t), handle) < sizeof(zentry_t))
+		if (fread(&zentry, 1, sizeof(zentry_t), handle) < sizeof(zentry_t))
 		{
 			CONS_Alert(CONS_ERROR, "Failed to read central directory (%s)\n", M_FileError(handle));
 			Z_Free(lumpinfo);
-			free(zentries);
 			return NULL;
 		}
-		if (memcmp(zentry->signature, pat_central, 4))
+		if (memcmp(zentry.signature, pat_central, 4))
 		{
 			CONS_Alert(CONS_ERROR, "Central directory is corrupt\n");
 			Z_Free(lumpinfo);
-			free(zentries);
 			return NULL;
 		}
 
-		lump_p->position = zentry->offset + zentry->namelen + zentry->xtralen + sizeof(zlentry_t);
-		lump_p->disksize = zentry->compsize;
-		lump_p->size = zentry->size;
+		lump_p->position = zentry.offset; // NOT ACCURATE YET: we still need to read the local entry to find our true position
+		lump_p->disksize = zentry.compsize;
+		lump_p->size = zentry.size;
 
-		fullname = malloc(zentry->namelen + 1);
-		if (fgets(fullname, zentry->namelen + 1, handle) != fullname)
+		fullname = malloc(zentry.namelen + 1);
+		if (fgets(fullname, zentry.namelen + 1, handle) != fullname)
 		{
 			CONS_Alert(CONS_ERROR, "Unable to read lumpname (%s)\n", M_FileError(handle));
 			Z_Free(lumpinfo);
-			free(zentries);
 			free(fullname);
 			return NULL;
 		}
@@ -604,12 +620,15 @@ static lumpinfo_t* ResGetLumpsZip (FILE* handle, UINT16* nlmp)
 		memset(lump_p->name, '\0', 9); // Making sure they're initialized to 0. Is it necessary?
 		strncpy(lump_p->name, trimname, min(8, dotpos - trimname));
 
-		lump_p->name2 = Z_Calloc(zentry->namelen + 1, PU_STATIC, NULL);
-		strncpy(lump_p->name2, fullname, zentry->namelen);
+		lump_p->longname = Z_Calloc(dotpos - trimname + 1, PU_STATIC, NULL);
+		strlcpy(lump_p->longname, trimname, dotpos - trimname + 1);
+
+		lump_p->fullname = Z_Calloc(zentry.namelen + 1, PU_STATIC, NULL);
+		strncpy(lump_p->fullname, fullname, zentry.namelen);
 
 		free(fullname);
 
-		switch(zentry->compression)
+		switch(zentry.compression)
 		{
 		case 0:
 			lump_p->compression = CM_NOCOMPRESSION;
@@ -627,9 +646,29 @@ static lumpinfo_t* ResGetLumpsZip (FILE* handle, UINT16* nlmp)
 			lump_p->compression = CM_UNSUPPORTED;
 			break;
 		}
+
+		// skip and ignore comments/extra fields
+		if (fseek(handle, zentry.xtralen + zentry.commlen, SEEK_CUR) != 0)
+		{
+			CONS_Alert(CONS_ERROR, "Central directory is corrupt\n");
+			Z_Free(lumpinfo);
+			return NULL;
+		}
 	}
 
-	free(zentries);
+	// Adjust lump position values properly
+	for (i = 0, lump_p = lumpinfo; i < numlumps; i++, lump_p++)
+	{
+		// skip and ignore comments/extra fields
+		if ((fseek(handle, lump_p->position, SEEK_SET) != 0) || (fread(&zlentry, 1, sizeof(zlentry_t), handle) < sizeof(zlentry_t)))
+		{
+			CONS_Alert(CONS_ERROR, "Local headers for lump %s are corrupt\n", lump_p->fullname);
+			Z_Free(lumpinfo);
+			return NULL;
+		}
+
+		lump_p->position += sizeof(zlentry_t) + zlentry.namelen + zlentry.xtralen;
+	}
 
 	*nlmp = numlumps;
 	return lumpinfo;
@@ -887,16 +926,48 @@ const char *W_CheckNameForNum(lumpnum_t lumpnum)
 UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump)
 {
 	UINT16 i;
-	static char uname[9];
+	static char uname[8 + 1];
+
+	if (!TestValidLump(wad,0))
+		return INT16_MAX;
 
-	memset(uname, 0x00, sizeof uname);
-	strncpy(uname, name, 8);
-	uname[8] = 0;
+	strlcpy(uname, name, sizeof uname);
 	strupr(uname);
 
+	//
+	// scan forward
+	// start at 'startlump', useful parameter when there are multiple
+	//                       resources with the same name
+	//
+	if (startlump < wadfiles[wad]->numlumps)
+	{
+		lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
+		for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
+			if (!strncmp(lump_p->name, uname, sizeof(uname) - 1))
+				return i;
+	}
+
+	// not found.
+	return INT16_MAX;
+}
+
+//
+// Like W_CheckNumForNamePwad, but can find entries with long names
+//
+// Should be the only version, but that's not possible until we fix
+// all the instances of non null-terminated strings in the codebase...
+//
+UINT16 W_CheckNumForLongNamePwad(const char *name, UINT16 wad, UINT16 startlump)
+{
+	UINT16 i;
+	static char uname[256 + 1];
+
 	if (!TestValidLump(wad,0))
 		return INT16_MAX;
 
+	strlcpy(uname, name, sizeof uname);
+	strupr(uname);
+
 	//
 	// scan forward
 	// start at 'startlump', useful parameter when there are multiple
@@ -906,7 +977,7 @@ UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump)
 	{
 		lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
 		for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
-			if (memcmp(lump_p->name,uname,8) == 0)
+			if (!strcmp(lump_p->longname, uname))
 				return i;
 	}
 
@@ -914,15 +985,32 @@ UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump)
 	return INT16_MAX;
 }
 
+UINT16
+W_CheckNumForMarkerStartPwad (const char *name, UINT16 wad, UINT16 startlump)
+{
+	UINT16 marker;
+	marker = W_CheckNumForNamePwad(name, wad, startlump);
+	if (marker != INT16_MAX)
+		marker++; // Do not count the first marker
+	return marker;
+}
+
 // Look for the first lump from a folder.
 UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlump)
 {
+	size_t name_length;
 	INT32 i;
 	lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
+	name_length = strlen(name);
 	for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
 	{
-		if (strnicmp(name, lump_p->name2, strlen(name)) == 0)
+		if (strnicmp(name, lump_p->fullname, name_length) == 0)
+		{
+			/* SLADE is special and puts a single directory entry. Skip that. */
+			if (strlen(lump_p->fullname) == name_length)
+				i++;
 			break;
+		}
 	}
 	return i;
 }
@@ -936,7 +1024,7 @@ UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump)
 	lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
 	for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
 	{
-		if (strnicmp(name, lump_p->name2, strlen(name)))
+		if (strnicmp(name, lump_p->fullname, strlen(name)))
 			break;
 	}
 	return i;
@@ -950,7 +1038,7 @@ UINT16 W_CheckNumForFullNamePK3(const char *name, UINT16 wad, UINT16 startlump)
 	lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
 	for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
 	{
-		if (!strnicmp(name, lump_p->name2, strlen(name)))
+		if (!strnicmp(name, lump_p->fullname, strlen(name)))
 		{
 			return i;
 		}
@@ -975,7 +1063,8 @@ lumpnum_t W_CheckNumForName(const char *name)
 	// most recent entries first
 	for (i = lumpnumcacheindex + LUMPNUMCACHESIZE; i > lumpnumcacheindex; i--)
 	{
-		if (strncmp(lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname, name, 8) == 0)
+		if (!lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname[8]
+			&& strncmp(lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname, name, 8) == 0)
 		{
 			lumpnumcacheindex = i & (LUMPNUMCACHESIZE - 1);
 			return lumpnumcache[lumpnumcacheindex].lumpnum;
@@ -995,6 +1084,7 @@ lumpnum_t W_CheckNumForName(const char *name)
 	{
 		// Update the cache.
 		lumpnumcacheindex = (lumpnumcacheindex + 1) & (LUMPNUMCACHESIZE - 1);
+		memset(lumpnumcache[lumpnumcacheindex].lumpname, '\0', 32);
 		strncpy(lumpnumcache[lumpnumcacheindex].lumpname, name, 8);
 		lumpnumcache[lumpnumcacheindex].lumpnum = (i<<16)+check;
 
@@ -1002,6 +1092,55 @@ lumpnum_t W_CheckNumForName(const char *name)
 	}
 }
 
+//
+// Like W_CheckNumForName, but can find entries with long names
+//
+// Should be the only version, but that's not possible until we fix
+// all the instances of non null-terminated strings in the codebase...
+//
+lumpnum_t W_CheckNumForLongName(const char *name)
+{
+	INT32 i;
+	lumpnum_t check = INT16_MAX;
+
+	if (!*name) // some doofus gave us an empty string?
+		return LUMPERROR;
+
+	// Check the lumpnumcache first. Loop backwards so that we check
+	// most recent entries first
+	for (i = lumpnumcacheindex + LUMPNUMCACHESIZE; i > lumpnumcacheindex; i--)
+	{
+		if (strcmp(lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname, name) == 0)
+		{
+			lumpnumcacheindex = i & (LUMPNUMCACHESIZE - 1);
+			return lumpnumcache[lumpnumcacheindex].lumpnum;
+		}
+	}
+
+	// scan wad files backwards so patch lump files take precedence
+	for (i = numwadfiles - 1; i >= 0; i--)
+	{
+		check = W_CheckNumForLongNamePwad(name,(UINT16)i,0);
+		if (check != INT16_MAX)
+			break; //found it
+	}
+
+	if (check == INT16_MAX) return LUMPERROR;
+	else
+	{
+		if (strlen(name) < 32)
+		{
+			// Update the cache.
+			lumpnumcacheindex = (lumpnumcacheindex + 1) & (LUMPNUMCACHESIZE - 1);
+			memset(lumpnumcache[lumpnumcacheindex].lumpname, '\0', 32);
+			strlcpy(lumpnumcache[lumpnumcacheindex].lumpname, name, 32);
+			lumpnumcache[lumpnumcacheindex].lumpnum = (i << 16) + check;
+		}
+
+		return (i << 16) + check;
+	}
+}
+
 // Look for valid map data through all added files in descendant order.
 // Get a map marker for WADs, and a standalone WAD file lump inside PK3s.
 // TODO: Make it search through cache first, maybe...?
@@ -1025,7 +1164,7 @@ lumpnum_t W_CheckNumForMap(const char *name)
 			else
 				continue;
 			// Now look for the specified map.
-			for (++lumpNum; lumpNum < end; lumpNum++)
+			for (; lumpNum < end; lumpNum++)
 				if (!strnicmp(name, (wadfiles[i]->lumpinfo + lumpNum)->name, 8))
 					return (i<<16) + lumpNum;
 		}
@@ -1050,6 +1189,24 @@ lumpnum_t W_GetNumForName(const char *name)
 	return i;
 }
 
+//
+// Like W_GetNumForName, but can find entries with long names
+//
+// Should be the only version, but that's not possible until we fix
+// all the instances of non null-terminated strings in the codebase...
+//
+lumpnum_t W_GetNumForLongName(const char *name)
+{
+	lumpnum_t i;
+
+	i = W_CheckNumForLongName(name);
+
+	if (i == LUMPERROR)
+		I_Error("W_GetNumForLongName: %s not found!\n", name);
+
+	return i;
+}
+
 //
 // W_CheckNumForNameInBlock
 // Checks only in blocks from blockstart lump to blockend lump
@@ -1089,7 +1246,7 @@ UINT8 W_LumpExists(const char *name)
 	{
 		lumpinfo_t *lump_p = wadfiles[i]->lumpinfo;
 		for (j = 0; j < wadfiles[i]->numlumps; ++j, ++lump_p)
-			if (fastcmp(lump_p->name,name))
+			if (fastcmp(lump_p->longname, name))
 				return true;
 	}
 	return false;
@@ -1120,7 +1277,7 @@ boolean W_IsLumpWad(lumpnum_t lumpnum)
 {
 	if (wadfiles[WADFILENUM(lumpnum)]->type == RET_PK3)
 	{
-		const char *lumpfullName = (wadfiles[WADFILENUM(lumpnum)]->lumpinfo + LUMPNUM(lumpnum))->name2;
+		const char *lumpfullName = (wadfiles[WADFILENUM(lumpnum)]->lumpinfo + LUMPNUM(lumpnum))->fullname;
 
 		if (strlen(lumpfullName) < 4)
 			return false; // can't possibly be a WAD can it?
@@ -1138,7 +1295,7 @@ boolean W_IsLumpFolder(UINT16 wad, UINT16 lump)
 {
 	if (wadfiles[wad]->type == RET_PK3)
 	{
-		const char *name = wadfiles[wad]->lumpinfo[lump].name2;
+		const char *name = wadfiles[wad]->lumpinfo[lump].fullname;
 
 		return (name[strlen(name)-1] == '/'); // folders end in '/'
 	}
@@ -1216,7 +1373,7 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 		{
 			size_t bytesread = fread(dest, 1, size, handle);
 			if (R_IsLumpPNG((UINT8 *)dest, bytesread))
-				W_ThrowPNGError(l->name2, wadfiles[wad]->filename);
+				W_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
 			return bytesread;
 		}
 #else
@@ -1258,7 +1415,7 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 			Z_Free(decData);
 #ifdef NO_PNG_LUMPS
 			if (R_IsLumpPNG((UINT8 *)dest, size))
-				W_ThrowPNGError(l->name2, wadfiles[wad]->filename);
+				W_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
 #endif
 			return size;
 #else
@@ -1321,7 +1478,7 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 
 #ifdef NO_PNG_LUMPS
 			if (R_IsLumpPNG((UINT8 *)dest, size))
-				W_ThrowPNGError(l->name2, wadfiles[wad]->filename);
+				W_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
 #endif
 			return size;
 		}
@@ -1505,11 +1662,9 @@ void *W_CacheLumpName(const char *name, INT32 tag)
 // Cache a patch into heap memory, convert the patch format as necessary
 //
 
-void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
+void *W_CacheSoftwarePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 {
-#ifdef HWRENDER
-	GLPatch_t *grPatch;
-#endif
+	lumpcache_t *lumpcache = NULL;
 
 	if (needpatchflush)
 		W_FlushCachedPatches();
@@ -1517,44 +1672,65 @@ void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 	if (!TestValidLump(wad, lump))
 		return NULL;
 
-#ifdef HWRENDER
-	// Software-only compile cache the data without conversion
-	if (rendermode == render_soft || rendermode == render_none)
-#endif
+	lumpcache = wadfiles[wad]->patchcache;
+
+	if (!lumpcache[lump])
 	{
-		lumpcache_t *lumpcache = wadfiles[wad]->patchcache;
-		if (!lumpcache[lump])
-		{
-			size_t len = W_LumpLengthPwad(wad, lump);
-			void *ptr, *lumpdata;
+		size_t len = W_LumpLengthPwad(wad, lump);
+		void *ptr, *lumpdata;
 #ifndef NO_PNG_LUMPS
-			void *srcdata = NULL;
+		void *srcdata = NULL;
 #endif
 
-			ptr = Z_Malloc(len, tag, &lumpcache[lump]);
-			lumpdata = Z_Malloc(len, tag, NULL);
+		ptr = Z_Malloc(len, tag, &lumpcache[lump]);
+		lumpdata = Z_Malloc(len, tag, NULL);
 
-			// read the lump in full
-			W_ReadLumpHeaderPwad(wad, lump, lumpdata, 0, 0);
+		// read the lump in full
+		W_ReadLumpHeaderPwad(wad, lump, lumpdata, 0, 0);
 
 #ifndef NO_PNG_LUMPS
-			// lump is a png so convert it
-			if (R_IsLumpPNG((UINT8 *)lumpdata, len))
-			{
-				size_t newlen;
-				srcdata = R_PNGToPatch((UINT8 *)lumpdata, len, &newlen);
-				ptr = Z_Realloc(ptr, newlen, tag, &lumpcache[lump]);
-				M_Memcpy(ptr, srcdata, newlen);
-				Z_Free(srcdata);
-			}
-			else // just copy it into the patch cache
-#endif
-				M_Memcpy(ptr, lumpdata, len);
+		// lump is a png so convert it
+		if (R_IsLumpPNG((UINT8 *)lumpdata, len))
+		{
+			size_t newlen;
+			srcdata = R_PNGToPatch((UINT8 *)lumpdata, len, &newlen);
+			ptr = Z_Realloc(ptr, newlen, tag, &lumpcache[lump]);
+			M_Memcpy(ptr, srcdata, newlen);
+			Z_Free(srcdata);
 		}
-		else
-			Z_ChangeTag(lumpcache[lump], tag);
+		else // just copy it into the patch cache
+#endif
+			M_Memcpy(ptr, lumpdata, len);
+	}
+	else
+		Z_ChangeTag(lumpcache[lump], tag);
+
+	return lumpcache[lump];
+}
+
+void *W_CacheSoftwarePatchNum(lumpnum_t lumpnum, INT32 tag)
+{
+	return W_CacheSoftwarePatchNumPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum),tag);
+}
+
+void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
+{
+#ifdef HWRENDER
+	GLPatch_t *grPatch;
+#endif
+
+	if (needpatchflush)
+		W_FlushCachedPatches();
+
+	if (!TestValidLump(wad, lump))
+		return NULL;
 
-		return lumpcache[lump];
+#ifdef HWRENDER
+	// Software-only compile cache the data without conversion
+	if (rendermode == render_soft || rendermode == render_none)
+#endif
+	{
+		return W_CacheSoftwarePatchNumPwad(wad, lump, tag);
 	}
 #ifdef HWRENDER
 
@@ -1611,6 +1787,17 @@ void *W_CachePatchName(const char *name, INT32 tag)
 		return W_CachePatchNum(W_GetNumForName("MISSING"), tag);
 	return W_CachePatchNum(num, tag);
 }
+
+void *W_CachePatchLongName(const char *name, INT32 tag)
+{
+	lumpnum_t num;
+
+	num = W_CheckNumForLongName(name);
+
+	if (num == LUMPERROR)
+		return W_CachePatchNum(W_GetNumForLongName("MISSING"), tag);
+	return W_CachePatchNum(num, tag);
+}
 #ifndef NOMD5
 #define MD5_LEN 16
 
@@ -1829,6 +2016,10 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 		}
 
 		free(fullname);
+
+		// skip and ignore comments/extra fields
+		if (fseek(fp, zentry.xtralen + zentry.commlen, SEEK_CUR) != 0)
+			return true;
 	}
 
 	return true;
diff --git a/src/w_wad.h b/src/w_wad.h
index d598d9b391833d32b7d4f528b8b9fe035f9c341e..88b542fca824ef0915f86e380c9f469d933c4638 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -66,9 +66,10 @@ typedef struct
 {
 	unsigned long position; // filelump_t filepos
 	unsigned long disksize; // filelump_t size
-	char name[9]; // filelump_t name[]
-	char *name2; // Used by PK3s. Dynamically allocated name.
-	size_t size; // real (uncompressed) size
+	char name[9];           // filelump_t name[] e.g. "LongEntr"
+	char *longname;         //                   e.g. "LongEntryName"
+	char *fullname;         //                   e.g. "Folder/Subfolder/LongEntryName.extension"
+	size_t size;            // real (uncompressed) size
 	compmethod compression; // lump compression method
 } lumpinfo_t;
 
@@ -155,6 +156,10 @@ const char *W_CheckNameForNumPwad(UINT16 wad, UINT16 lump);
 const char *W_CheckNameForNum(lumpnum_t lumpnum);
 
 UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump); // checks only in one pwad
+UINT16 W_CheckNumForLongNamePwad(const char *name, UINT16 wad, UINT16 startlump);
+
+/* Find the first lump after F_START for instance. */
+UINT16 W_CheckNumForMarkerStartPwad(const char *name, UINT16 wad, UINT16 startlump);
 
 UINT16 W_CheckNumForFullNamePK3(const char *name, UINT16 wad, UINT16 startlump);
 UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlump);
@@ -162,7 +167,9 @@ UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump)
 
 lumpnum_t W_CheckNumForMap(const char *name);
 lumpnum_t W_CheckNumForName(const char *name);
+lumpnum_t W_CheckNumForLongName(const char *name);
 lumpnum_t W_GetNumForName(const char *name); // like W_CheckNumForName but I_Error on LUMPERROR
+lumpnum_t W_GetNumForLongName(const char *name);
 lumpnum_t W_CheckNumForNameInBlock(const char *name, const char *blockstart, const char *blockend);
 UINT8 W_LumpExists(const char *name); // Lua uses this.
 
@@ -190,9 +197,17 @@ boolean W_IsPatchCached(lumpnum_t lump, void *ptr);
 
 void *W_CacheLumpName(const char *name, INT32 tag);
 void *W_CachePatchName(const char *name, INT32 tag);
+void *W_CachePatchLongName(const char *name, INT32 tag);
+
+// Returns either a Software patch, or an OpenGL patch.
+// Performs any necessary conversions from PNG images.
+void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag);
+void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag);
 
-void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag); // return a patch_t
-void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag); // return a patch_t
+// Returns a Software patch.
+// Performs any necessary conversions from PNG images.
+void *W_CacheSoftwarePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag);
+void *W_CacheSoftwarePatchNum(lumpnum_t lumpnum, INT32 tag);
 
 void W_UnlockCachedPatch(void *patch);
 void W_FlushCachedPatches(void);
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index 42733c30909897d2437387dbe3b6365a096b40d8..f9d66be7ffc57c7f613b85b6a60bf28297b20781 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -3199,7 +3199,7 @@ INT32 I_GetKey(void)
 // -----------------
 #define DI_KEYBOARD_BUFFERSIZE 32 // number of data elements in keyboard buffer
 
-void I_StartupKeyboard(void)
+static void I_StartupKeyboard(void)
 {
 	DIPROPDWORD dip;
 
@@ -3435,6 +3435,8 @@ INT32 I_StartupSystem(void)
 	// some 'more global than globals' things to initialize here ?
 	graphics_started = keyboard_started = sound_started = cdaudio_started = false;
 
+	I_StartupKeyboard();
+
 #ifdef NDEBUG
 
 #ifdef BUGTRAP
diff --git a/src/win32/win_vid.c b/src/win32/win_vid.c
index d0aab92b3e16a4d13fb4cb57039869a732bfaef1..5fa2195866b08329f02c0057b1aaa6d9f384a977 100644
--- a/src/win32/win_vid.c
+++ b/src/win32/win_vid.c
@@ -56,6 +56,7 @@ static consvar_t cv_stretch = {"stretch", "On", CV_SAVE|CV_NOSHOWHELP, CV_OnOff,
 static consvar_t cv_ontop = {"ontop", "Never", 0, CV_NeverOnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 boolean highcolor;
+int vid_opengl_state = 0;
 
 static BOOL bDIBMode; // means we are using DIB instead of DirectDraw surfaces
 static LPBITMAPINFO bmiMain = NULL;
@@ -239,10 +240,7 @@ void I_StartupGraphics(void)
 	if (!dedicated) graphics_started = true;
 }
 
-void I_StartupHardwareGraphics(void)
-{
-	// oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo y
-}
+void VID_StartupOpenGL(void){}
 
 // ------------------
 // I_ShutdownGraphics
@@ -951,9 +949,10 @@ INT32 VID_SetMode(INT32 modenum)
 	return 1;
 }
 
-void VID_CheckRenderer(void)
+void VID_CheckRenderer(void) {}
+void VID_CheckGLLoaded(rendermode_t oldrender)
 {
-	// ..............
+	(void)oldrender;
 }
 
 // ========================================================================
diff --git a/src/y_inter.c b/src/y_inter.c
index 94a289817192f89e8da0007006638086c1aa5be4..a2628832fe09074bc77817604e3d72f16f49ea6d 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -73,7 +73,7 @@ typedef union
 		UINT32 score, total; // fake score, total
 		UINT32 tics; // time
 
-		INT32 actnum; // act number being displayed
+		UINT8 actnum; // act number being displayed
 		patch_t *ptotal; // TOTAL
 		UINT8 gotlife; // Number of extra lives obtained
 	} coop;
@@ -564,7 +564,7 @@ dontdrawbg:
 			V_DrawTallNum(BASEVIDWIDTH + xoffset4 - 68, 125+yoffset, 0, data.spec.score);
 
 			// Draw continues!
-			if (!multiplayer /* && (data.spec.continues & 0x80) */) // Always draw outside of netplay
+			if (continuesInSession /* && (data.spec.continues & 0x80) */) // Always draw when continues are a thing
 			{
 				UINT8 continues = data.spec.continues & 0x7F;