diff --git a/extras/conf/SRB2-22.cfg b/extras/conf/SRB2-22.cfg
index 5580140737716a175c69bba08b8a51f3422b544c..969f645f3f23805d8085199eed5cbc5578f89ce2 100644
--- a/extras/conf/SRB2-22.cfg
+++ b/extras/conf/SRB2-22.cfg
@@ -425,20 +425,18 @@ sectortypes
 	12 = "Space Countdown";
 	13 = "Ramp Sector (double step-up/down)";
 	14 = "Non-Ramp Sector (no step-down)";
-	15 = "Bouncy FOF";
+	15 = "Bouncy FOF <deprecated>";
 	16 = "Trigger Line Ex. (Pushable Objects)";
 	32 = "Trigger Line Ex. (Anywhere, All Players)";
 	48 = "Trigger Line Ex. (Floor Touch, All Players)";
 	64 = "Trigger Line Ex. (Anywhere in Sector)";
 	80 = "Trigger Line Ex. (Floor Touch)";
-	96 = "Trigger Line Ex. (Emerald Check)";
-	112 = "Trigger Line Ex. (NiGHTS Mare)";
+	96 = "Trigger Line Ex. (Emerald Check) <deprecated>";
+	112 = "Trigger Line Ex. (NiGHTS Mare) <deprecated>";
 	128 = "Check for Linedef Executor on FOFs";
 	144 = "Egg Capsule";
-	160 = "Special Stage Time/Spheres Parameters";
-	176 = "Custom Global Gravity";
-	512 = "Wind/Current";
-	1024 = "Conveyor Belt";
+	160 = "Special Stage Time/Spheres Parameters <deprecated>";
+	176 = "Custom Global Gravity <deprecated>";
 	1280 = "Speed Pad";
 	4096 = "Star Post Activator";
 	8192 = "Exit/Special Stage Pit/Return Flag";
@@ -475,7 +473,7 @@ gen_sectortypes
 		12 = "Space Countdown";
 		13 = "Ramp Sector (double step-up/down)";
 		14 = "Non-Ramp Sector (no step-down)";
-		15 = "Bouncy FOF";
+		15 = "Bouncy FOF <deprecated>";
 	}
 
 	second
@@ -486,19 +484,17 @@ gen_sectortypes
 		48 = "Trigger Line Ex. (Floor Touch, All Players)";
 		64 = "Trigger Line Ex. (Anywhere in Sector)";
 		80 = "Trigger Line Ex. (Floor Touch)";
-		96 = "Trigger Line Ex. (Emerald Check)";
-		112 = "Trigger Line Ex. (NiGHTS Mare)";
+		96 = "Trigger Line Ex. (Emerald Check) <deprecated>";
+		112 = "Trigger Line Ex. (NiGHTS Mare) <deprecated>";
 		128 = "Check for Linedef Executor on FOFs";
 		144 = "Egg Capsule";
-		160 = "Special Stage Time/Spheres Parameters";
-		176 = "Custom Global Gravity";
+		160 = "Special Stage Time/Spheres Parameters <deprecated>";
+		176 = "Custom Global Gravity <deprecated>";
 	}
 
 	third
 	{
 		0 = "Normal";
-		512 = "Wind/Current";
-		1024 = "Conveyor Belt";
 		1280 = "Speed Pad";
 	}
 
@@ -771,6 +767,12 @@ linedeftypes
 			flags2text = "[1] Use control sector tag";
 			flags64text = "[6] No sound effect";
 		}
+
+		76
+		{
+			title = "Make FOF Bouncy";
+			prefix = "(76)";
+		}
 	}
 
 	polyobject
@@ -1279,7 +1281,7 @@ linedeftypes
 
 		160
 		{
-			title = "Floating, Bobbing";
+			title = "Water Bobbing";
 			prefix = "(160)";
 			flags8text = "[3] Slope skew sides";
 			flags32text = "[5] Only block player";
@@ -2018,6 +2020,48 @@ linedeftypes
 			flags1024text = "[10] Use faster, unordered execution";
 		}
 
+		337
+		{
+			title = "Emerald Check - Continuous";
+			prefix = "(337)";
+		}
+
+		338
+		{
+			title = "Emerald Check - Each Time";
+			prefix = "(338)";
+		}
+
+		339
+		{
+			title = "Emerald Check - Once";
+			prefix = "(339)";
+		}
+
+		340
+		{
+			title = "NiGHTS Mare - Continuous";
+			flags2text = "[1] Mare greater or equal";
+			flags64text = "[6] Mare less or equal";
+			prefix = "(340)";
+		}
+
+		341
+		{
+			title = "NiGHTS Mare - Each Time";
+			flags2text = "[1] Mare greater or equal";
+			flags64text = "[6] Mare less or equal";
+			prefix = "(341)";
+		}
+
+		342
+		{
+			title = "NiGHTS Mare - Once";
+			flags2text = "[1] Mare greater or equal";
+			flags64text = "[6] Mare less or equal";
+			prefix = "(342)";
+		}
+
 		399
 		{
 			title = "Level Load";
@@ -2048,23 +2092,35 @@ linedeftypes
 
 		402
 		{
-			title = "Set Tagged Sector's Light Level";
+			title = "Copy Light Level to Tagged Sectors";
 			prefix = "(402)";
 			flags8text = "[3] Set delay by backside sector";
 		}
 
+		408
+		{
+			title = "Set Tagged Sector's Flats";
+			prefix = "(408)";
+			flags64text = "[6] Don't set floor flat";
+			flags512text = "[9] Don't set ceiling flat";
+		}
+
 		409
 		{
 			title = "Change Tagged Sector's Tag";
 			prefix = "(409)";
+			flags2text = "[1] Remove tag";
 			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Add tag";
 		}
 
 		410
 		{
 			title = "Change Front Sector's Tag";
 			prefix = "(410)";
+			flags2text = "[1] Remove tag";
 			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Add tag";
 		}
 
 		416
@@ -2122,6 +2178,14 @@ linedeftypes
 			prefix = "(435)";
 			flags8text = "[3] Set delay by backside sector";
 		}
+
+		467
+		{
+			title = "Set Tagged Sector's Light Level";
+			prefix = "(467)";
+			flags8text = "[3] Set delay by backside sector";
+			flags256text = "[8] Set relative to current";
+		}
 	}
 
 	linedefexecplane
@@ -3228,6 +3292,7 @@ linedeftypes
 		{
 			title = "Set Tagged Dynamic Slope Vertex to Front Sector Height";
 			prefix = "(799)";
+			flags64text = "[6] Use relative heights";
 		}
 	}
 
diff --git a/extras/conf/udb/Includes/SRB222_common.cfg b/extras/conf/udb/Includes/SRB222_common.cfg
index b752e36544590c7a0a2a5fd7af9e33cc97acda30..0ff044a6d382cc102c5b5dd98681e04ff3c81b31 100644
--- a/extras/conf/udb/Includes/SRB222_common.cfg
+++ b/extras/conf/udb/Includes/SRB222_common.cfg
@@ -191,6 +191,12 @@ mapformat_doom
 	// that make the same thing appear in the same modes
 	thingflagsmask1 = 7;	// 1 + 2 + 4
 	thingflagsmask2 = 0;
+
+	// THING TYPES
+	thingtypes
+	{
+		include("SRB222_things.cfg", "doom");
+	}
 }
 
 mapformat_udmf
@@ -240,17 +246,7 @@ mapformat_udmf
 		include("SRB222_misc.cfg", "sectorbrightness");
 	}
 
-	// SECTOR TYPES
-	sectortypes
-	{
-		include("SRB222_sectors.cfg", "sectortypes");
-	}
-
-	// GENERALISED SECTOR TYPES
-	gen_sectortypes
-	{
-		include("SRB222_sectors.cfg", "gen_sectortypes");
-	}
+	damagetypes = "Generic Water Fire Lava Electric Spike DeathPitTilt DeathPitNoTilt Instakill SpecialStage";
 
 	// LINEDEF FLAGS
 	linedefflags
@@ -289,6 +285,12 @@ mapformat_udmf
 		include("UDMF_misc.cfg", "thingflagscompare");
 	}
 
+	// THING TYPES
+	thingtypes
+	{
+		include("SRB222_things.cfg", "udmf");
+	}
+
 	// LINEDEF TYPES
 	linedeftypes
 	{
diff --git a/extras/conf/udb/Includes/SRB222_linedefs.cfg b/extras/conf/udb/Includes/SRB222_linedefs.cfg
index c6b0cb1c80ccc27d02d46a82c498f3c0994b2e9d..0aa7ea6291d899336f0b7935a456f262d62d63b2 100644
--- a/extras/conf/udb/Includes/SRB222_linedefs.cfg
+++ b/extras/conf/udb/Includes/SRB222_linedefs.cfg
@@ -16,7 +16,7 @@ doom
 		}
 		5
 		{
-			title = "Camera Scanner";
+			title = "Camera Scanner <deprecated>";
 			prefix = "(5)";
 		}
 		7
@@ -125,10 +125,10 @@ doom
 			title = "Continuously Appearing/Disappearing FOF";
 			prefix = "(64)";
 		}
-		65
+		76
 		{
-			title = "Bridge Thinker <disabled>";
-			prefix = "(65)";
+			title = "Make FOF Bouncy";
+			prefix = "(76)";
 		}
 	}
 
@@ -397,7 +397,7 @@ doom
 		}
 		160
 		{
-			title = "Floating, Bobbing";
+			title = "Water Bobbing";
 			prefix = "(160)";
 		}
 		190
@@ -734,6 +734,51 @@ doom
 			title = "Player Skin - Once";
 			prefix = "(333)";
 		}
+		334
+		{
+			title = "Object Dye - Continuous";
+			prefix = "(334)";
+		}
+		335
+		{
+			title = "Object Dye - Each Time";
+			prefix = "(335)";
+		}
+		336
+		{
+			title = "Object Dye - Once";
+			prefix = "(336)";
+		}
+		337
+		{
+			title = "Emerald Check - Continuous";
+			prefix = "(337)";
+		}
+		338
+		{
+			title = "Emerald Check - Each Time";
+			prefix = "(338)";
+		}
+		339
+		{
+			title = "Emerald Check - Once";
+			prefix = "(339)";
+		}
+		340
+		{
+			title = "NiGHTS Mare - Continuous";
+			prefix = "(340)";
+		}
+		341
+		{
+			title = "NiGHTS Mare - Each Time";
+			prefix = "(341)";
+		}
+		342
+		{
+			title = "NiGHTS Mare - Once";
+			prefix = "(342)";
+		}
 		399
 		{
 			title = "Level Load";
@@ -757,9 +802,14 @@ doom
 		}
 		402
 		{
-			title = "Set Tagged Sector's Light Level";
+			title = "Copy Light Level to Tagged Sectors";
 			prefix = "(402)";
 		}
+		408
+		{
+			title = "Set Tagged Sector's Flats";
+			prefix = "(408)";
+		}
 		409
 		{
 			title = "Change Tagged Sector's Tag";
@@ -805,6 +855,11 @@ doom
 			title = "Change Plane Scroller Direction";
 			prefix = "(435)";
 		}
+		467
+		{
+			title = "Set Tagged Sector's Light Level";
+			prefix = "(467)";
+		}
 	}
 
 	linedefexecplane
@@ -937,6 +992,21 @@ doom
 			title = "Stop Timer/Exit Stage in Record Attack";
 			prefix = "(462)";
 		}
+		463
+		{
+			title = "Dye Object";
+			prefix = "(463)";
+		}
+		464
+		{
+			title = "Trigger Egg Capsule";
+			prefix = "(464)";
+		}
+		466
+		{
+			title = "Set Level Failure State";
+			prefix = "(466)";
+		}
 	}
 
 	linedefexecmisc
@@ -1669,109 +1739,125 @@ udmf
 			title = "None";
 			prefix = "(0)";
 		}
-	}
-
-	polyobject
-	{
-		title = "PolyObject";
 
-		20
+		7
 		{
-			title = "First Line";
-			prefix = "(20)";
+			title = "Sector Flat Alignment";
+			prefix = "(7)";
 			arg0
 			{
-				title = "PolyObject ID";
-				type = 14;
+				title = "Target sector tag";
+				type = 13;
 			}
 			arg1
 			{
-				title = "Parent ID";
-				type = 14;
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+				default = 2;
 			}
-			arg2
+		}
+
+		10
+		{
+			title = "Culling Plane";
+			prefix = "(10)";
+			arg0
 			{
-				title = "Translucency";
+				title = "Target sector tag";
+				type = 13;
 			}
-			arg3
+			arg1
 			{
-				title = "Flags";
-				type = 12;
+				title = "Culling behavior";
+				type = 11;
 				enum
 				{
-					1 = "Don't render insides";
-					2 = "Intangible";
-					4 = "Stopped by pushables";
-					8 = "Don't render planes";
-					16 = "Trigger linedef executor on touch";
-					32 = "Crush player";
-					64 = "Cut cyan flat pixels";
+					0 = "Always";
+					1 = "Only while in sector";
 				}
 			}
-			arg4
+		}
+
+		40
+		{
+			title = "Visual Portal Between Tagged Linedefs";
+			prefix = "(40)";
+		}
+
+		41
+		{
+			title = "Horizon Effect";
+			prefix = "(41)";
+		}
+
+		63
+		{
+			title = "Fake Floor/Ceiling Planes";
+			prefix = "(63)";
+			arg0
 			{
-				title = "Trigger linedef tag";
-				type = 15;
+				title = "Target sector tag";
+				type = 13;
 			}
 		}
 	}
 
-	fof
+	parameters
 	{
-		title = "FOF";
+		title = "Parameters";
 
-		100
+		2
 		{
-			title = "Solid";
-			prefix = "(100)";
+			title = "Custom Exit";
+			prefix = "(2)";
 			arg0
 			{
-				title = "Target sector tag";
-				type = 13;
+				title = "Next map";
 			}
 			arg1
 			{
-				title = "Visibility";
+				title = "Flags";
 				type = 12;
 				enum
 				{
-					1 = "Don't render planes";
-					2 = "Don't render sides";
+					1 = "Skip score tally";
+					2 = "Check emeralds";
 				}
 			}
 			arg2
 			{
-				title = "Translucency";
-				type = 11;
-				enum
-				{
-					0 = "Opaque";
-					1 = "Translucent, no insides";
-					2 = "Translucent, render insides";
-				}
+				title = "Next map (all emeralds)";
 			}
-			arg3
+		}
+
+		3
+		{
+			title = "Zoom Tube Parameters";
+			prefix = "(3)";
+			arg0
 			{
-				title = "Tangibility";
-				type = 12;
-				enum = "tangibility";
+				title = "Speed";
 			}
-			arg4
+			arg1
+			{
+				title = "Sequence";
+			}
+			arg2
 			{
-				title = "Cast shadow?";
+				title = "Check player direction?";
 				type = 11;
 				enum = "yesno";
 			}
 		}
 
-		120
+		4
 		{
-			title = "Water";
-			prefix = "(120)";
+			title = "Speed Pad Parameters";
+			prefix = "(4)";
 			arg0
 			{
-				title = "Target sector tag";
-				type = 13;
+				title = "Speed";
 			}
 			arg1
 			{
@@ -1779,151 +1865,3653 @@ udmf
 				type = 12;
 				enum
 				{
-				    1 = "Opaque";
-					2 = "Don't render sides";
-					4 = "Render separate light level";
-					8 = "Use target light level";
-					16 = "No ripple effect";
-					32 = "Goo physics";
+					1 = "No teleport to center";
+					2 = "Force spinning frames";
 				}
 			}
-		}
-	}
-
-	linedefexecmisc
-	{
-		title = "Linedef Executor (misc.)";
-
-		443
-		{
-			title = "Call Lua Function";
-			prefix = "(443)";
 			stringarg0
 			{
-				title = "Function name";
+				title = "Sound";
 				type = 2;
 			}
 		}
 
-		447
+		8
 		{
-			title = "Change Tagged Sector's Colormap";
-			prefix = "(447)";
+			title = "Set Camera Collision Planes";
+			prefix = "(8)";
 			arg0
 			{
 				title = "Target sector tag";
 				type = 13;
 			}
+		}
+
+		11
+		{
+			title = "Rope Hang Parameters";
+			prefix = "(11)";
+			arg0
+			{
+				title = "Speed";
+			}
 			arg1
 			{
-				title = "Colormap sector tag";
-				type = 13;
+				title = "Sequence";
 			}
 			arg2
 			{
-				title = "Flags";
-				type = 12;
-				enum
-				{
-					1 = "Add to existing colormap";
-					2 = "Subtract light R";
-					4 = "Subtract light G";
-					8 = "Subtract light B";
-					16 = "Subtract light A";
-					32 = "Subtract fade R";
-					64 = "Subtract fade G";
-					128 = "Subtract fade B";
-					256 = "Subtract fade A";
-					512 = "Subtract fadestart";
-					1024 = "Subtract fadeend";
-					2048 = "Ignore flags";
-				}
+				title = "Loop?";
+				type = 11;
+				enum = "yesno";
 			}
 		}
 
-		455
+		14
 		{
-			title = "Fade Tagged Sector's Colormap";
-			prefix = "(455)";
+			title = "Bustable Block Parameters";
+			prefix = "(14)";
 			arg0
 			{
-				title = "Target sector tag";
-				type = 13;
+				title = "Debris spacing";
 			}
 			arg1
 			{
-				title = "Colormap sector tag";
-				type = 13;
+				title = "Debris lifetime";
 			}
 			arg2
 			{
-				title = "Fade duration";
+				title = "Launch from center?";
+				type = 11;
+				enum = "noyes";
 			}
-			arg3
+			stringarg0
 			{
-				title = "Flags";
-				type = 12;
-				enum
-				{
-					1 = "Add to existing colormap";
-					2 = "Subtract light R";
-					4 = "Subtract light G";
-					8 = "Subtract light B";
-					16 = "Subtract light A";
-					32 = "Subtract fade R";
-					64 = "Subtract fade G";
-					128 = "Subtract fade B";
-					256 = "Subtract fade A";
-					512 = "Subtract fadestart";
-					1024 = "Subtract fadeend";
-					2048 = "Ignore flags";
-					4096 = "Fade from invisible black";
-					8192 = "Interrupt ongoing fades";
-				}
+				title = "Debris object type";
+				type = 2;
 			}
 		}
 
-		456
+		15
 		{
-			title = "Stop Fading Tagged Sector's Colormap";
-			prefix = "(456)";
+			title = "Fan Particle Generator Heights";
+			prefix = "(15)";
+		}
+
+		16
+		{
+			title = "Minecart Parameters";
+			prefix = "(16)";
 			arg0
 			{
-				title = "Target sector tag";
-				type = 13;
+				title = "Order";
 			}
 		}
 
-		465
+		64
 		{
-			title = "Set Linedef Executor Delay";
-			prefix = "(465)";
+			title = "Continuously Appearing/Disappearing FOF";
+			prefix = "(64)";
 			arg0
 			{
-				title = "Linedef tag";
+				title = "Control linedef tag";
 				type = 15;
 			}
 			arg1
 			{
-				title = "Value";
+				title = "Control sector tag";
+				type = 13;
 			}
 			arg2
 			{
-				title = "Set/add?";
-				type = 11;
-				enum
-				{
-					0 = "Set";
-					1 = "Add";
-				}
+				title = "On time";
 			}
-		}
-	}
-
-	light
-	{
-		606
-		{
+			arg3
+			{
+				title = "Off time";
+			}
+			arg4
+			{
+				title = "Initial delay";
+			}
+			arg5
+			{
+				title = "Play sound?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+	}
+
+	polyobject
+	{
+		title = "PolyObject";
+
+		20
+		{
+			title = "First Line";
+			prefix = "(20)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Parent ID";
+				type = 14;
+			}
+			arg2
+			{
+				title = "Translucency";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Don't render insides";
+					2 = "Intangible";
+					4 = "Stopped by pushables";
+					8 = "Don't render planes";
+					16 = "Trigger linedef executor on touch";
+					32 = "Crush player";
+					64 = "Cut cyan flat pixels";
+				}
+			}
+			arg4
+			{
+				title = "Trigger linedef tag";
+				type = 15;
+			}
+		}
+
+		30
+		{
+			title = "Waving Flag";
+			prefix = "(30)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Distance";
+			}
+		}
+
+		31
+		{
+			title = "Displacement by Front Sector";
+			prefix = "(31)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Base speed";
+			}
+		}
+
+		32
+		{
+			title = "Angular Displacement by Front Sector";
+			prefix = "(32)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Plane factor";
+				default = 128;
+			}
+			arg2
+			{
+				title = "Rotation factor";
+				default = 90;
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Don't turn others";
+					2 = "Turn players";
+				}
+			}
+		}
+	}
+
+	planemove
+	{
+		title = "Plane Movement";
+
+		52
+		{
+			title = "Continuously Falling Sector";
+			prefix = "(52)";
+			arg0
+			{
+				title = "Speed";
+			}
+			arg1
+			{
+				title = "Direction";
+				type = 11;
+				enum
+				{
+					0 = "Fall";
+					1 = "Rise";
+				}
+			}
+		}
+
+		53
+		{
+			title = "Continuous Plane Mover (Slowdown)";
+			prefix = "(53)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+			arg2
+			{
+				title = "Forward speed";
+			}
+			arg3
+			{
+				title = "Return speed";
+			}
+			arg4
+			{
+				title = "Starting delay";
+			}
+			arg5
+			{
+				title = "Delay before flip";
+			}
+		}
+
+		56
+		{
+			title = "Continuous Plane Mover (Constant)";
+			prefix = "(56)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+			arg2
+			{
+				title = "Forward speed";
+			}
+			arg3
+			{
+				title = "Return speed";
+			}
+			arg4
+			{
+				title = "Starting delay";
+			}
+			arg5
+			{
+				title = "Delay before flip";
+			}
+		}
+
+		60
+		{
+			title = "Activate Moving Platform";
+			prefix = "(60)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Starting delay";
+			}
+			arg3
+			{
+				title = "Delay before flip";
+			}
+			arg4
+			{
+				title = "Starting direction";
+				type = 11;
+				enum = "downup";
+			}
+		}
+
+		61
+		{
+			title = "Ceiling Crusher";
+			prefix = "(61)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Starting direction";
+				type = 11;
+				enum
+				{
+					0 = "Crush";
+					1 = "Retract";
+				}
+			}
+			arg2
+			{
+				title = "Crush speed";
+			}
+			arg3
+			{
+				title = "Retract speed";
+			}
+		}
+
+		66
+		{
+			title = "Move Planes by Displacement";
+			prefix = "(66)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+			arg2
+			{
+				title = "Translation factor";
+				default = 256;
+			}
+		}
+	}
+
+	fofmodifiers
+	{
+		title = "FOF Modifiers";
+
+		70
+		{
+			title = "Add Raise Thinker";
+			prefix = "(70)";
+			arg0
+			{
+				title = "Control linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Destination height";
+			}
+			arg3
+			{
+				title = "Require spindash?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		71
+		{
+			title = "Add Air Bobbing Thinker";
+			prefix = "(71)";
+			arg0
+			{
+				title = "Control linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Bobbing distance";
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Raise";
+					2 = "Require spindash";
+					4 = "Dynamic";
+				}
+			}
+		}
+
+		72
+		{
+			title = "Add Thwomp Thinker";
+			prefix = "(72)";
+			arg0
+			{
+				title = "Control linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Falling speed";
+			}
+			arg2
+			{
+				title = "Rising speed";
+			}
+			stringarg0
+			{
+				title = "Crushing sound";
+				type = 2;
+			}
+		}
+
+		73
+		{
+			title = "Add Laser Thinker";
+			prefix = "(73)";
+			arg0
+			{
+				title = "Control linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Damage bosses?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+
+		74
+		{
+			title = "Make FOF Bustable";
+			prefix = "(74)";
+			arg0
+			{
+				title = "Control linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Bustable type";
+				type = 11;
+				enum
+				{
+					0 = "Touch";
+					1 = "Spin";
+					2 = "Regular";
+					3 = "Strong";
+				}
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Bustable by pushables";
+					2 = "Trigger linedef executor";
+					4 = "Only bustable from below";
+				}
+			}
+			arg3
+			{
+				title = "Linedef executor tag";
+				type = 15;
+			}
+		}
+
+		75
+		{
+			title = "Make FOF Quicksand";
+			prefix = "(75)";
+			arg0
+			{
+				title = "Control linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Sinking speed";
+			}
+			arg2
+			{
+				title = "Friction";
+			}
+		}
+
+		76
+		{
+			title = "Make FOF Bouncy";
+			prefix = "(76)";
+			arg0
+			{
+				title = "Control linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Bounce strength";
+			}
+		}
+	}
+
+	fof
+	{
+		title = "FOF";
+
+		100
+		{
+			title = "Solid";
+			prefix = "(100)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Alpha";
+				default = 255;
+			}
+			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
+			{
+				title = "Appearance";
+				type = 12;
+				enum
+				{
+					1 = "Don't render planes";
+					2 = "Don't render sides";
+					4 = "Render insides";
+					8 = "Render only insides";
+					16 = "No shadow";
+					32 = "Cut cyan flat pixels";
+				}
+			}
+			arg4
+			{
+				title = "Tangibility";
+				type = 12;
+				enum = "tangibility";
+			}
+		}
+
+		120
+		{
+			title = "Water";
+			prefix = "(120)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Alpha";
+				default = 128;
+			}
+			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Don't render sides";
+					2 = "Render separate light level";
+					4 = "Use target light level";
+					8 = "No ripple effect";
+					16 = "Goo physics";
+					32 = "Cut cyan flat pixels";
+				}
+			}
+		}
+
+		150
+		{
+			title = "Air Bobbing";
+			prefix = "(150)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Bobbing distance";
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Raise";
+					2 = "Require spindash";
+					4 = "Dynamic";
+				}
+			}
+		}
+
+		160
+		{
+			title = "Water Bobbing";
+			prefix = "(160)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+		}
+
+		170
+		{
+			title = "Crumbling";
+			prefix = "(170)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Alpha";
+				default = 255;
+			}
+			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
+			{
+				title = "Tangibility";
+				type = 12;
+				enum = "tangibility";
+			}
+			arg4
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "No shadow";
+					2 = "No respawn";
+					4 = "Air bobbing";
+					8 = "Float on water";
+					16 = "Cut cyan flat pixels";
+				}
+			}
+		}
+		
+		190
+		{
+			title = "Rising";
+			prefix = "(190)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Alpha";
+				default = 255;
+			}
+			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
+			{
+				title = "Appearance";
+				type = 12;
+				enum
+				{
+					1 = "Don't render planes";
+					2 = "Don't render sides";
+					4 = "Render insides";
+					8 = "Render only insides";
+					16 = "No shadow";
+					32 = "Cut cyan flat pixels";
+				}
+			}
+			arg4
+			{
+				title = "Tangibility";
+				type = 12;
+				enum = "tangibility";
+			}
+			arg5
+			{
+				title = "Speed";
+			}
+			arg6
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Lower";
+					2 = "Require spindash";
+				}
+			}
+		}
+
+		200
+		{
+			title = "Light Block";
+			prefix = "(200)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Expand to bottom?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		202
+		{
+			title = "Fog Block";
+			prefix = "(202)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+		}
+
+		220
+		{
+			title = "Intangible";
+			prefix = "(220)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Alpha";
+				default = 255;
+			}
+			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
+			{
+				title = "Appearance";
+				type = 12;
+				enum
+				{
+					1 = "Don't render planes";
+					2 = "Don't render sides";
+					4 = "Don't render insides";
+					8 = "Render only insides";
+					16 = "No shadow";
+					32 = "Cut cyan flat pixels";
+				}
+			}
+		}
+
+		223
+		{
+			title = "Intangible, Invisible";
+			prefix = "(223)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+		}
+
+		250
+		{
+			title = "Mario Block";
+			prefix = "(250)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Block type";
+				type = 12;
+				enum
+				{
+					1 = "Brick";
+					2 = "Invisible";
+				}
+			}
+		}
+
+		251
+		{
+			title = "Thwomp Block";
+			prefix = "(251)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Falling speed";
+			}
+			arg2
+			{
+				title = "Rising speed";
+			}
+			stringarg0
+			{
+				title = "Crushing sound";
+				type = 2;
+			}
+		}
+
+		254
+		{
+			title = "Bustable Block";
+			prefix = "(254)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Alpha";
+				default = 255;
+			}
+			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
+			{
+				title = "Bustable type";
+				type = 11;
+				enum
+				{
+					0 = "Touch";
+					1 = "Spin";
+					2 = "Regular";
+					3 = "Strong";
+				}
+			}
+			arg4
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Bustable by pushables";
+					2 = "Trigger linedef executor";
+					4 = "Only bustable from below";
+					8 = "Cut cyan flat pixels";
+				}
+			}
+			arg5
+			{
+				title = "Linedef executor tag";
+				type = 15;
+			}
+		}
+
+		257
+		{
+			title = "Quicksand";
+			prefix = "(257)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Ripple effect?";
+				type = 11;
+				enum = "yesno";
+			}
+			arg2
+			{
+				title = "Sinking speed";
+			}
+			arg3
+			{
+				title = "Friction";
+			}
+		}
+
+		258
+		{
+			title = "Laser";
+			prefix = "(258)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Alpha";
+				default = 128;
+			}
+			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Don't damage bosses";
+					2 = "Cut cyan flat pixels";
+				}
+			}
+		}
+
+		259
+		{
+			title = "Custom";
+			prefix = "(259)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Alpha";
+				default = 255;
+			}
+			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Exists";
+					2 = "Block player";
+					4 = "Block others";
+					8 = "Render sides";
+					16 = "Render planes";
+					32 = "Water";
+					64 = "No shadow";
+					128 = "Cut solid walls";
+					256 = "Cut extra walls";
+					512 = "Split sprites";
+					1024 = "Render inside planes";
+					2048 = "Extra";
+					8192 = "Fog";
+					16384 = "Only render inside planes";
+					32768 = "Render inside walls";
+					65536 = "Only render inside walls";
+					131072 = "Double shadow";
+					262144 = "Water bobbing";
+					524288 = "Don't respawn";
+					1048576 = "Crumbling";
+					2097152 = "Goo water";
+					4194304 = "Mario block";
+					33554432 = "Intangible from below";
+					67108864 = "Intangible from above";
+					134217728 = "Ripple effect";
+					268435456 = "Don't copy light level";
+					536870912 = "Bouncy";
+					1073741824 = "Cut cyan flat pixels";
+				}
+			}
+		}
+		260
+		{
+			title = "Generalized 3D Floor";
+			prefix = "(260)";
+			id = "Sector_Set3dFloor";
+			requiresactivation = false;
+
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Type";
+				type = 26;
+				default = 1;
+				enum
+				{
+					1 = "Solid";
+					2 = "Water";
+					3 = "Intangible";
+				}
+				flags
+				{
+					4 = "Render insides";
+					16 = "Only render insides";
+				}
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "No shadow";
+					2 = "Double shadow";
+					4 = "Fog";
+				}
+			}
+			arg3
+			{
+				title = "Alpha";
+				default = 255;
+			}
+		}
+	}
+
+	linedeftrigger
+	{
+		title = "Linedef Executor Trigger";
+
+		300
+		{
+			title = "Basic";
+			prefix = "(300)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+		}
+
+		303
+		{
+			title = "Ring Count";
+			prefix = "(303)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Rings";
+			}
+			arg2
+			{
+				title = "Comparison";
+				type = 11;
+				enum = "comparison";
+			}
+			arg3
+			{
+				title = "Count all players?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		305
+		{
+			title = "Character Ability";
+			prefix = "(305)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Ability";
+				type = 11;
+				enum
+				{
+					0 = "None";
+					1 = "Thok";
+					2 = "Fly";
+					3 = "Glide and climb";
+					4 = "Homing attack";
+					5 = "Swim";
+					6 = "Double jump";
+					7 = "Float";
+					8 = "Float with slow descent";
+					9 = "Telekinesis";
+					10 = "Fall switch";
+					11 = "Jump boost";
+					12 = "Air drill";
+					13 = "Jump-thok";
+					14 = "Pogo bounce";
+					15 = "Twin spin";
+				}
+			}
+		}
+
+		308
+		{
+			title = "Gametype";
+			prefix = "(308)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Rules";
+				type = 12;
+				enum
+				{
+					1 = "Campaign";
+					2 = "Ringslinger";
+					4 = "Spectators";
+					8 = "Lives";
+					16 = "Teams";
+					32 = "First person";
+					64 = "Match emeralds";
+					128 = "Team flags";
+					256 = "Coop";
+					512 = "Allow special stages";
+					1024 = "Spawn emerald tokens";
+					2048 = "Emerald hunt";
+					4096 = "Race";
+					8192 = "Tag";
+					16384 = "Point limit";
+					32768 = "Time limit";
+					65536 = "Overtime";
+					131072 = "Hurt messages";
+					262144 = "Friendly fire";
+					524288 = "Hide time countdown";
+					1048576 = "Frozen after hide time";
+					2097152 = "Blindfolded view";
+					4194304 = "Respawn delay";
+					8388608 = "Award pity shield";
+					16777216 = "Death score penalty";
+					33554432 = "No spectator spawn";
+					67108864 = "Use match starts";
+					134217728 = "Spawn invincibility";
+					268435456 = "Allow enemies";
+					536870912 = "Allow exit sectors";
+					1073741824 = "No title card";
+					2147483648 = "Allow cutscenes";
+				}
+			}
+			arg2
+			{
+				title = "Check if";
+				type = 11;
+				enum = "flagcheck";
+			}
+		}
+
+		309
+		{
+			title = "CTF Team";
+			prefix = "(309)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Team";
+				type = 11;
+				enum = "team";
+			}
+		}
+
+		313
+		{
+			title = "No More Enemies";
+			prefix = "(313)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+		}
+
+		314
+		{
+			title = "Number of Pushables";
+			prefix = "(314)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Pushables";
+			}
+			arg2
+			{
+				title = "Comparison";
+				type = 11;
+				enum = "comparison";
+			}
+		}
+
+		317
+		{
+			title = "Condition Set Trigger";
+			prefix = "(317)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Trigger ID";
+			}
+		}
+
+		319
+		{
+			title = "Unlockable";
+			prefix = "(319)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Unlockable ID";
+			}
+		}
+
+		321
+		{
+			title = "Trigger After X Calls";
+			prefix = "(321)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "xtriggertype";
+			}
+			arg1
+			{
+				title = "Calls";
+			}
+			arg2
+			{
+				title = "Can retrigger?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg3
+			{
+				title = "Starting calls";
+			}
+		}
+
+		323
+		{
+			title = "NiGHTSerize";
+			prefix = "(323)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum
+				{
+					0 = "Each time";
+					1 = "Once";
+				}
+			}
+			arg1
+			{
+				title = "Mare number";
+			}
+			arg2
+			{
+				title = "Lap number";
+			}
+			arg3
+			{
+				title = "Mare comparison";
+				type = 11;
+				enum = "comparison";
+			}
+			arg4
+			{
+				title = "Lap comparison";
+				type = 11;
+				enum = "comparison";
+			}
+			arg5
+			{
+				title = "Compared player";
+				type = 11;
+				enum
+				{
+					0 = "Fastest";
+					1 = "Slowest";
+					2 = "Triggerer";
+				}
+			}
+			arg6
+			{
+				title = "NiGHTS check";
+				type = 11;
+				enum
+				{
+					0 = "No check";
+					1 = "Trigger if player was not NiGHTS";
+					2 = "Trigger if player was already NiGHTS";
+				}
+			}
+			arg7
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Only count bonus time laps";
+					2 = "Only trigger if final mare completed";
+				}
+			}
+		}
+		325
+		{
+			title = "De-NiGHTSerize";
+			prefix = "(325)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum
+				{
+					0 = "Each time";
+					1 = "Once";
+				}
+			}
+			arg1
+			{
+				title = "Mare number";
+			}
+			arg2
+			{
+				title = "Lap number";
+			}
+			arg3
+			{
+				title = "Mare comparison";
+				type = 11;
+				enum = "comparison";
+			}
+			arg4
+			{
+				title = "Lap comparison";
+				type = 11;
+				enum = "comparison";
+			}
+			arg5
+			{
+				title = "Compared player";
+				type = 11;
+				enum
+				{
+					0 = "Fastest";
+					1 = "Slowest";
+					2 = "Triggerer";
+				}
+			}
+			arg6
+			{
+				title = "NiGHTS check";
+				type = 11;
+				enum
+				{
+					0 = "No check";
+					1 = "Trigger if nobody is now NiGHTS";
+					2 = "Trigger if somebody is still NiGHTS";
+				}
+			}
+			arg7
+			{
+				title = "Only bonus laps?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		327
+		{
+			title = "NiGHTS Lap";
+			prefix = "(327)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum
+				{
+					0 = "Each time";
+					1 = "Once";
+				}
+			}
+			arg1
+			{
+				title = "Mare number";
+			}
+			arg2
+			{
+				title = "Lap number";
+			}
+			arg3
+			{
+				title = "Mare comparison";
+				type = 11;
+				enum = "comparison";
+			}
+			arg4
+			{
+				title = "Lap comparison";
+				type = 11;
+				enum = "comparison";
+			}
+			arg5
+			{
+				title = "Compared player";
+				type = 11;
+				enum
+				{
+					0 = "Fastest";
+					1 = "Slowest";
+					2 = "Triggerer";
+				}
+			}
+			arg6
+			{
+				title = "Only bonus laps?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		329
+		{
+			title = "Ideya Capture Touch";
+			prefix = "(329)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum
+				{
+					0 = "Each time";
+					1 = "Once";
+				}
+			}
+			arg1
+			{
+				title = "Mare number";
+			}
+			arg2
+			{
+				title = "Lap number";
+			}
+			arg3
+			{
+				title = "Mare comparison";
+				type = 11;
+				enum = "comparison";
+			}
+			arg4
+			{
+				title = "Lap comparison";
+				type = 11;
+				enum = "comparison";
+			}
+			arg5
+			{
+				title = "Compared player";
+				type = 11;
+				enum
+				{
+					0 = "Fastest";
+					1 = "Slowest";
+					2 = "Triggerer";
+				}
+			}
+			arg6
+			{
+				title = "Spheres check";
+				type = 11;
+				enum
+				{
+					0 = "Trigger if enough spheres";
+					1 = "Trigger if not enough spheres";
+					2 = "Trigger regardless of spheres";
+				}
+			}
+			arg7
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Only count bonus time laps";
+					2 = "Trigger upon entering Ideya Capture";
+				}
+			}
+		}
+
+		331
+		{
+			title = "Player Skin";
+			prefix = "(331)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Invert choice?";
+				type = 11;
+				enum = "noyes";
+			}
+			stringarg0
+			{
+				title = "Skin name";
+				type = 2;
+			}
+		}
+
+		334
+		{
+			title = "Object Dye";
+			prefix = "(334)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Invert choice?";
+				type = 11;
+				enum = "noyes";
+			}
+			stringarg0
+			{
+				title = "Color";
+				type = 2;
+			}
+		}
+
+		337
+		{
+			title = "Emerald Check";
+			prefix = "(337)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Emeralds";
+				type = 12;
+				enum
+				{
+					1 = "Emerald 1";
+					2 = "Emerald 2";
+					4 = "Emerald 3";
+					8 = "Emerald 4";
+					16 = "Emerald 5";
+					32 = "Emerald 6";
+					64 = "Emerald 7";
+				}
+			}
+			arg2
+			{
+				title = "Check if";
+				type = 11;
+				enum = "flagcheck";
+			}
+		}
+
+		340
+		{
+			title = "NiGHTS Mare";
+			prefix = "(340)";
+			arg0
+			{
+				title = "Trigger type";
+				type = 11;
+				enum = "triggertype";
+			}
+			arg1
+			{
+				title = "Mare";
+			}
+			arg2
+			{
+				title = "Comparison";
+				type = 11;
+				enum = "comparison";
+			}
+		}
+
+		399
+		{
+			title = "Level Load";
+			prefix = "(399)";
+		}
+	}
+
+	linedefexecsector
+	{
+		title = "Linedef Executor (sector)";
+
+		400
+		{
+			title = "Set Tagged Sector's Heights/Textures";
+			prefix = "(400)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+			arg2
+			{
+				title = "Set flats?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		402
+		{
+			title = "Copy Light Level to Tagged Sectors";
+			prefix = "(402)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Don't copy main light level";
+					2 = "Don't copy floor light level";
+					4 = "Don't copy ceiling light level";
+				}
+			}
+		}
+
+		408
+		{
+			title = "Set Tagged Sector's Flats";
+			prefix = "(408)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+		}
+
+		409
+		{
+			title = "Change Tagged Sector's Tag";
+			prefix = "(409)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Behavior";
+				type = 11;
+				enum
+				{
+					0 = "Add tag";
+					1 = "Remove tag";
+					2 = "Replace first tag";
+				}
+			}
+		}
+
+		410
+		{
+			title = "Change Front Sector's Tag";
+			prefix = "(410)";
+			arg0
+			{
+				title = "Tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Behavior";
+				type = 11;
+				enum
+				{
+					0 = "Add tag";
+					1 = "Remove tag";
+					2 = "Replace first tag";
+				}
+			}
+		}
+
+		416
+		{
+			title = "Start Adjustable Flickering Light";
+			prefix = "(416)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Brightness 1";
+			}
+			arg3
+			{
+				title = "Use target brightness?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg4
+			{
+				title = "Brightness 2";
+			}
+		}
+
+		417
+		{
+			title = "Start Adjustable Pulsating Light";
+			prefix = "(417)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Brightness 1";
+			}
+			arg3
+			{
+				title = "Use target brightness?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg4
+			{
+				title = "Brightness 2";
+			}
+		}
+
+		418
+		{
+			title = "Start Adjustable Blinking Light";
+			prefix = "(418)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Brightness 1 tics";
+			}
+			arg2
+			{
+				title = "Brightness 2 tics";
+			}
+			arg3
+			{
+				title = "Brightness 1";
+			}
+			arg4
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Use target brightness";
+					2 = "Synchronized";
+				}
+			}
+			arg5
+			{
+				title = "Brightness 2";
+			}
+		}
+
+		420
+		{
+			title = "Fade Light Level";
+			prefix = "(420)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Destination light level";
+			}
+			arg2
+			{
+				title = "Fading speed";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Add to current light level";
+					2 = "Interrupt ongoing fades";
+					4 = "Speed is duration";
+				}
+			}
+		}
+
+		421
+		{
+			title = "Stop Lighting Effect";
+			prefix = "(421)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+		}
+
+		435
+		{
+			title = "Change Plane Scroller Direction";
+			prefix = "(435)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+		}
+
+		467
+		{
+			title = "Set Tagged Sector's Light Level";
+			prefix = "(467)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Light level";
+			}
+			arg2
+			{
+				title = "Affected area";
+				type = 11;
+				enum
+				{
+					0 = "Sector";
+					1 = "Floor";
+					2 = "Ceiling";
+				}
+			}
+			arg3
+			{
+				title = "Set/Add?";
+				type = 11;
+				enum = "setadd";
+			}
+		}
+
+		469
+		{
+			title = "Change Tagged Sector's Gravity";
+			prefix = "(469)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Set/Multiply?";
+				type = 11;
+				enum
+				{
+					0 = "Set";
+					1 = "Multiply";
+				}
+			}
+			arg2
+			{
+				title = "Flip flag";
+				type = 11;
+				enum
+				{
+					0 = "Don't change";
+					1 = "Set";
+					2 = "Remove";
+				}
+			}
+			stringarg0
+			{
+				title = "Gravity value";
+				type = 2;
+			}
+		}
+	}
+
+	linedefexecplane
+	{
+		title = "Linedef Executor (plane movement)";
+
+		403
+		{
+			title = "Move Tagged Sector's Planes";
+			prefix = "(403)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+			arg2
+			{
+				title = "Speed";
+			}
+			arg3
+			{
+				title = "Linedef executor tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Set flats?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		405
+		{
+			title = "Move Planes by Distance";
+			prefix = "(405)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+			arg2
+			{
+				title = "Distance";
+			}
+			arg3
+			{
+				title = "Speed";
+			}
+			arg4
+			{
+				title = "Instant?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		411
+		{
+			title = "Stop Plane Movement";
+			prefix = "(411)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+		}
+
+		428
+		{
+			title = "Start Platform Movement";
+			prefix = "(428)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Starting delay";
+			}
+			arg3
+			{
+				title = "Delay before flip";
+			}
+			arg4
+			{
+				title = "Starting direction";
+				type = 11;
+				enum
+				{
+					0 = "Down";
+					1 = "Up";
+				}
+			}
+		}
+
+		429
+		{
+			title = "Crush Planes Once";
+			prefix = "(429)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+			arg2
+			{
+				title = "Crush speed";
+			}
+			arg3
+			{
+				title = "Retract speed";
+			}
+		}
+	}
+
+	linedefexecplayer
+	{
+		title = "Linedef Executor (player/object)";
+
+		412
+		{
+			title = "Teleporter";
+			prefix = "(412)";
+			arg0
+			{
+				title = "Destination tag";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Silent";
+					2 = "Keep angle";
+					4 = "Keep momentum";
+					8 = "Relative silent";
+				}
+			}
+			arg2
+			{
+				title = "X offset";
+			}
+			arg3
+			{
+				title = "Y offset";
+			}
+			arg4
+			{
+				title = "Z offset";
+			}
+		}
+
+		425
+		{
+			title = "Change Object State";
+			prefix = "(425)";
+			stringarg0
+			{
+				title = "State";
+				type = 2;
+			}
+		}
+
+		426
+		{
+			title = "Stop Object";
+			prefix = "(426)";
+			arg0
+			{
+				title = "Move to center?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		427
+		{
+			title = "Award Score";
+			prefix = "(427)";
+			arg0
+			{
+				title = "Score";
+			}
+		}
+
+		432
+		{
+			title = "Enable/Disable 2D Mode";
+			prefix = "(432)";
+			arg0
+			{
+				title = "Mode";
+				type = 11;
+				enum
+				{
+					0 = "2D";
+					1 = "3D";
+				}
+			}
+		}
+
+		433
+		{
+			title = "Enable/Disable Gravity Flip";
+			prefix = "(433)";
+			arg0
+			{
+				title = "Gravity";
+				type = 11;
+				enum
+				{
+					0 = "Reverse";
+					1 = "Normal";
+				}
+			}
+		}
+
+		434
+		{
+			title = "Award Power-Up";
+			prefix = "(434)";
+			stringarg0
+			{
+				title = "Power";
+				type = 2;
+			}
+			stringarg1
+			{
+				title = "Duration/Amount";
+				type = 2;
+			}
+		}
+
+		437
+		{
+			title = "Disable Player Control";
+			prefix = "(437)";
+			arg0
+			{
+				title = "Time";
+			}
+			arg1
+			{
+				title = "Allow jumping?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		438
+		{
+			title = "Change Object Size";
+			prefix = "(438)";
+			arg0
+			{
+				title = "Size (%)";
+				default = 100;
+			}
+		}
+
+		442
+		{
+			title = "Change Object Type State";
+			prefix = "(442)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Change to";
+				type = 11;
+				enum
+				{
+					0 = "Specified state";
+					1 = "Next state";
+				}
+			}
+			stringarg0
+			{
+				title = "Object type";
+				type = 2;
+			}
+			stringarg1
+			{
+				title = "State";
+				type = 2;
+			}
+		}
+
+		457
+		{
+			title = "Track Object's Angle";
+			prefix = "(457)";
+			arg0
+			{
+				title = "Anchor tag";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Angle tolerance";
+				type = 8;
+			}
+			arg2
+			{
+				title = "Time tolerance";
+			}
+			arg3
+			{
+				title = "Trigger linedef tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Track after failure?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		458
+		{
+			title = "Stop Tracking Object's Angle";
+			prefix = "(458)";
+		}
+
+		460
+		{
+			title = "Award Rings";
+			prefix = "(460)";
+			arg0
+			{
+				title = "Rings";
+			}
+			arg1
+			{
+				title = "Periodicity";
+			}
+		}
+
+		461
+		{
+			title = "Spawn Object";
+			prefix = "(461)";
+			arg0
+			{
+				title = "X position";
+			}
+			arg1
+			{
+				title = "Y position";
+			}
+			arg2
+			{
+				title = "Z position";
+			}
+			arg3
+			{
+				title = "Angle";
+				type = 8;
+			}
+			arg4
+			{
+				title = "Randomize position?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg5
+			{
+				title = "Max X position";
+			}
+			arg6
+			{
+				title = "Max Y position";
+			}
+			arg7
+			{
+				title = "Max Z position";
+			}
+			stringarg0
+			{
+				title = "Object type";
+				type = 2;
+			}
+		}
+
+		462
+		{
+			title = "Stop Timer/Exit Stage in Record Attack";
+			prefix = "(462)";
+		}
+
+		463
+		{
+			title = "Dye Object";
+			prefix = "(463)";
+			stringarg0
+			{
+				title = "Skin color";
+				type = 2;
+			}
+		}
+
+		464
+		{
+			title = "Trigger Egg Capsule";
+			prefix = "(464)";
+			arg0
+			{
+				title = "Egg Capsule tag";
+				type = 14;
+			}
+			arg1
+			{
+				title = "End level?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+
+		466
+		{
+			title = "Set Level Failure State";
+			prefix = "(466)";
+			arg0
+			{
+				title = "State";
+				type = 11;
+				enum
+				{
+					0 = "Failure";
+					1 = "Success";
+				}
+			}
+		}
+	}
+
+	linedefexecmisc
+	{
+		title = "Linedef Executor (misc.)";
+
+		413
+		{
+			title = "Change Music";
+			prefix = "(413)";
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "For all players";
+					2 = "Seek offset from current position";
+					4 = "Fade to custom volume";
+					8 = "Don't reload after death";
+					16 = "Force music reload";
+					32 = "Don't loop";
+				}
+			}
+			arg1
+			{
+				title = "Position";
+			}
+			arg2
+			{
+				title = "Fade out time";
+			}
+			arg3
+			{
+				title = "Fade in time";
+			}
+			arg4
+			{
+				title = "Fade destination volume";
+			}
+			arg5
+			{
+				title = "Fade start volume";
+				default = -1;
+			}
+			arg6
+			{
+				title = "Track number";
+			}
+			stringarg0
+			{
+				title = "Music name";
+				type = 2;
+			}
+		}
+
+		414
+		{
+			title = "Play Sound Effect";
+			prefix = "(414)";
+			arg0
+			{
+				title = "Source";
+				type = 11;
+				enum
+				{
+					0 = "Triggering object";
+					1 = "Trigger sector";
+					2 = "Nowhere";
+					3 = "Tagged sectors";
+				}
+			}
+			arg1
+			{
+				title = "Listener";
+				type = 11;
+				enum
+				{
+					0 = "Triggering player";
+					1 = "Everyone";
+					2 = "Everyone touching tagged sectors";
+				}
+			}
+			arg2
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			stringarg0
+			{
+				title = "Sound name";
+				type = 2;
+			}
+		}
+
+		415
+		{
+			title = "Run Script";
+			prefix = "(415)";
+			stringarg0
+			{
+				title = "Lump name";
+				type = 2;
+			}
+		}
+
+		422
+		{
+			title = "Switch to Cut-Away View";
+			prefix = "(422)";
+			arg0
+			{
+				title = "Viewpoint tag";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Time";
+			}
+		}
+
+		423
+		{
+			title = "Change Sky";
+			prefix = "(423)";
+			arg0
+			{
+				title = "Sky number";
+			}
+			arg1
+			{
+				title = "For all players?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		424
+		{
+			title = "Change Weather";
+			prefix = "(424)";
+			arg0
+			{
+				title = "Weather";
+				type = 11;
+				enum
+				{
+					0 = "None";
+					1 = "Storm (thunder, lightning and rain)";
+					2 = "Snow";
+					3 = "Rain";
+					4 = "Preloaded";
+					5 = "Storm (no rain)";
+					6 = "Storm (no lightning)";
+				}
+			}
+			arg1
+			{
+				title = "For all players?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		436
+		{
+			title = "Shatter FOF";
+			prefix = "(436)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Control sector tag";
+				type = 13;
+			}
+		}
+
+		439
+		{
+			title = "Change Tagged Linedef's Textures";
+			prefix = "(439)";
+			arg0
+			{
+				title = "Target linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Affected sides";
+				type = 11;
+				enum = "frontbackboth";
+			}
+			arg2
+			{
+				title = "Change unset textures?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+
+		440
+		{
+			title = "Start Metal Sonic Race";
+			prefix = "(440)";
+		}
+
+		441
+		{
+			title = "Condition Set Trigger";
+			prefix = "(441)";
+			arg0
+			{
+				title = "Trigger number";
+			}
+		}
+
+		443
+		{
+			title = "Call Lua Function";
+			prefix = "(443)";
+			stringarg0
+			{
+				title = "Function name";
+				type = 2;
+			}
+		}
+
+		444
+		{
+			title = "Earthquake";
+			prefix = "(444)";
+			arg0
+			{
+				title = "Duration";
+			}
+			arg1
+			{
+				title = "Intensity";
+			}
+		}
+
+		445
+		{
+			title = "Make FOF Disappear/Reappear";
+			prefix = "(445)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Control sector tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Effect";
+				type = 11;
+				enum
+				{
+					0 = "Disappear";
+					1 = "Reappear";
+				}
+			}
+		}
+
+		446
+		{
+			title = "Make FOF Crumble";
+			prefix = "(446)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Control sector tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Respawn?";
+				type = 11;
+				enum
+				{
+					0 = "Yes";
+					1 = "No";
+					2 = "Unless FF_NORETURN";
+					3 = "Only if FF_NORETURN";
+				}
+			}
+		}
+
+		447
+		{
+			title = "Change Tagged Sector's Colormap";
+			prefix = "(447)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Colormap sector tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Add to existing colormap";
+					2 = "Subtract light R";
+					4 = "Subtract light G";
+					8 = "Subtract light B";
+					16 = "Subtract light A";
+					32 = "Subtract fade R";
+					64 = "Subtract fade G";
+					128 = "Subtract fade B";
+					256 = "Subtract fade A";
+					512 = "Subtract fadestart";
+					1024 = "Subtract fadeend";
+					2048 = "Ignore flags";
+				}
+			}
+		}
+
+		448
+		{
+			title = "Change Skybox";
+			prefix = "(448)";
+			arg0
+			{
+				title = "Viewpoint ID";
+			}
+			arg1
+			{
+				title = "Centerpoint ID";
+			}
+			arg2
+			{
+				title = "Change?";
+				type = 11;
+				enum
+				{
+					0 = "Viewpoint";
+					1 = "Centerpoint";
+					2 = "Both";
+				}
+			}
+			arg3
+			{
+				title = "For all players?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		449
+		{
+			title = "Enable Bosses with Parameter";
+			prefix = "(449)";
+			arg0
+			{
+				title = "Boss ID";
+			}
+			arg1
+			{
+				title = "Effect";
+				type = 11;
+				enum
+				{
+					0 = "Enable";
+					1 = "Disable";
+				}
+			}
+		}
+
+		450
+		{
+			title = "Execute Linedef Executor (specific tag)";
+			prefix = "(450)";
+			arg0
+			{
+				title = "Trigger linedef tag";
+				type = 15;
+			}
+		}
+
+		451
+		{
+			title = "Execute Linedef Executor (random tag in range)";
+			prefix = "(451)";
+			arg0
+			{
+				title = "Start of tag range";
+				type = 15;
+			}
+			arg1
+			{
+				title = "End of tag range";
+				type = 15;
+			}
+		}
+
+		452
+		{
+			title = "Set FOF Translucency";
+			prefix = "(452)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Control sector tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Alpha";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Add to current translucency";
+					2 = "Don't handle FF_TRANSLUCENT";
+				}
+			}
+		}
+
+		453
+		{
+			title = "Fade FOF";
+			prefix = "(453)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Control sector tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Alpha";
+			}
+			arg3
+			{
+				title = "Fading speed";
+			}
+			arg4
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Add to current translucency";
+					2 = "Interrupt ongoing fades";
+					4 = "Speed is duration";
+					8 = "Don't change collision";
+					16 = "No collision during fade";
+					32 = "Don't handle FF_TRANSLUCENT";
+					64 = "Don't handle FF_EXISTS";
+					128 = "Don't fade lighting";
+					256 = "Don't fade colormap";
+					512 = "Use exact alpha in OpenGL";
+				}
+			}
+		}
+
+		454
+		{
+			title = "Stop Fading FOF";
+			prefix = "(454)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Control sector tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Finalize collision?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+
+		455
+		{
+			title = "Fade Tagged Sector's Colormap";
+			prefix = "(455)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Colormap sector tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Fade duration";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Add to existing colormap";
+					2 = "Subtract light R";
+					4 = "Subtract light G";
+					8 = "Subtract light B";
+					16 = "Subtract light A";
+					32 = "Subtract fade R";
+					64 = "Subtract fade G";
+					128 = "Subtract fade B";
+					256 = "Subtract fade A";
+					512 = "Subtract fadestart";
+					1024 = "Subtract fadeend";
+					2048 = "Ignore flags";
+					4096 = "Fade from invisible black";
+					8192 = "Interrupt ongoing fades";
+				}
+			}
+		}
+
+		456
+		{
+			title = "Stop Fading Tagged Sector's Colormap";
+			prefix = "(456)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+		}
+
+		459
+		{
+			title = "Control Text Prompt";
+			prefix = "(459)";
+			arg0
+			{
+				title = "Prompt number";
+			}
+			arg1
+			{
+				title = "Page number";
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 11;
+				enum
+				{
+					1 = "Close current text prompt";
+					2 = "Trigger linedef executor on close";
+					4 = "Find prompt by name";
+					8 = "Don't disable controls";
+				}
+			}
+			arg3
+			{
+				title = "Trigger linedef tag";
+				type = 15;
+			}
+			stringarg0
+			{
+				title = "Prompt name";
+				type = 2;
+			}
+		}
+
+		465
+		{
+			title = "Set Linedef Executor Delay";
+			prefix = "(465)";
+			arg0
+			{
+				title = "Linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Value";
+			}
+			arg2
+			{
+				title = "Set/Add?";
+				type = 11;
+				enum = "setadd";
+			}
+		}
+
+		468
+		{
+			title = "Change Linedef Argument";
+			prefix = "(468)";
+			arg0
+			{
+				title = "Linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Argument";
+			}
+			arg2
+			{
+				title = "Value";
+			}
+			arg3
+			{
+				title = "Set/Add?";
+				type = 11;
+				enum = "setadd";
+			}
+		}
+	}
+
+	linedefexecpoly
+	{
+		title = "Linedef Executor (polyobject)";
+
+		480
+		{
+			title = "Door Slide";
+			prefix = "(480)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Distance";
+			}
+			arg3
+			{
+				title = "Return delay";
+			}
+		}
+
+		481
+		{
+			title = "Door Swing";
+			prefix = "(481)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Rotation";
+				type = 8;
+			}
+			arg3
+			{
+				title = "Return delay";
+			}
+		}
+
+		482
+		{
+			title = "Move";
+			prefix = "(482)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Distance";
+			}
+			arg3
+			{
+				title = "Override?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		484
+		{
+			title = "Rotate";
+			prefix = "(484)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Rotation";
+				type = 8;
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Don't turn others";
+					2 = "Turn players";
+					4 = "Continuous rotation";
+					8 = "Override";
+				}
+			}
+		}
+
+		488
+		{
+			title = "Move by Waypoints";
+			prefix = "(488)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Waypoint sequence";
+			}
+			arg3
+			{
+				title = "Return behavior";
+				type = 11;
+				enum
+				{
+					0 = "Don't return";
+					1 = "Return to first waypoint";
+					2 = "Repeat sequence in reverse";
+				}
+			}
+			arg4
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move in reverse";
+					2 = "Loop movement";
+				}
+			}
+		}
+
+		489
+		{
+			title = "Set Visibility, Tangibility";
+			prefix = "(489)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Visibility";
+				type = 11;
+				enum
+				{
+					0 = "No change";
+					1 = "Visible";
+					2 = "Invisible";
+				}
+			}
+			arg2
+			{
+				title = "Tangibility";
+				type = 11;
+				enum
+				{
+					0 = "No change";
+					1 = "Tangible";
+					2 = "Intangible";
+				}
+			}
+		}
+
+		491
+		{
+			title = "Set Translucency";
+			prefix = "(491)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Translucency level";
+			}
+			arg2
+			{
+				title = "Set/Add?";
+				type = 11;
+				enum = "setadd";
+			}
+		}
+
+		492
+		{
+			title = "Fade Translucency";
+			prefix = "(492)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Translucency level";
+			}
+			arg2
+			{
+				title = "Fading speed";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Add to current translucency";
+					2 = "Interrupt ongoing fades";
+					4 = "Speed is duration";
+					8 = "Don't change collision";
+					16 = "No collision during fade";
+				}
+			}
+		}
+	}
+
+	scrollpush
+	{
+		title = "Scrollers and Pushers";
+
+		500
+		{
+			title = "Scroll Walls";
+			prefix = "(500)";
+			arg0
+			{
+				title = "Side";
+				type = 11;
+				enum = "frontbackboth";
+			}
+			arg1
+			{
+				title = "Horizontal speed";
+			}
+			arg2
+			{
+				title = "Vertical speed";
+			}
+		}
+
+		502
+		{
+			title = "Scroll Walls Remotely";
+			prefix = "(502)";
+			arg0
+			{
+				title = "Linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Side";
+				type = 11;
+				enum = "frontbackboth";
+			}
+			arg2
+			{
+				title = "Horizontal speed";
+			}
+			arg3
+			{
+				title = "Vertical speed";
+			}
+			arg4
+			{
+				title = "Type";
+				type = 11;
+				enum = "scrolltype";
+			}
+		}
+
+		510
+		{
+			title = "Scroll Planes";
+			prefix = "(510)";
+			arg0
+			{
+				title = "Sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+			arg2
+			{
+				title = "Scroll/Carry?";
+				type = 11;
+				enum = "scrollcarry";
+			}
+			arg3
+			{
+				title = "Base speed";
+			}
+			arg4
+			{
+				title = "Type";
+				type = 26;
+				enum = "scrolltype";
+				flags
+				{
+					4 = "Non-exclusive";
+				}
+			}
+		}
+
+		541
+		{
+			title = "Wind/Current";
+			prefix = "(541)";
+			arg0
+			{
+				title = "Sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Horizontal speed";
+			}
+			arg2
+			{
+				title = "Vertical speed";
+			}
+			arg3
+			{
+				title = "Type";
+				type = 11;
+				enum
+				{
+					0 = "Wind";
+					1 = "Current";
+				}
+			}
+			arg4
+			{
+				title = "Flags";
+				type = 12;
+				flags
+				{
+					1 = "Slide";
+					2 = "Non-exclusive";
+				}
+			}
+		}
+	}
+
+	light
+	{
+		title = "Lighting";
+
+		600
+		{
+			title = "Copy Light Level to Tagged Sector's Planes";
+			prefix = "(600)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Affected planes";
+				type = 11;
+				enum = "floorceiling";
+			}
+		}
+
+		602
+		{
+			title = "Adjustable Pulsating Light";
+			prefix = "(602)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Brightness 1";
+			}
+			arg3
+			{
+				title = "Use target brightness?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg4
+			{
+				title = "Brightness 2";
+			}
+		}
+
+		603
+		{
+			title = "Adjustable Flickering Light";
+			prefix = "(603)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Speed";
+			}
+			arg2
+			{
+				title = "Brightness 1";
+			}
+			arg3
+			{
+				title = "Use target brightness?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg4
+			{
+				title = "Brightness 2";
+			}
+		}
+
+		604
+		{
+			title = "Adjustable Blinking Light";
+			prefix = "(604)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Brightness 1 tics";
+			}
+			arg2
+			{
+				title = "Brightness 2 tics";
+			}
+			arg3
+			{
+				title = "Brightness 1";
+			}
+			arg4
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Use target brightness";
+					2 = "Synchronized";
+				}
+			}
+			arg5
+			{
+				title = "Brightness 2";
+			}
+		}
+
+		606
+		{
 			title = "Copy Colormap";
 			prefix = "(606)";
 			arg0
@@ -1968,6 +5556,7 @@ udmf
 				{
 					1 = "No physics";
 					2 = "Dynamic";
+					4 = "Copy to other side";
 				}
 			}
 		}
@@ -2052,5 +5641,21 @@ udmf
 				}
 			}
 		}
+
+		799
+		{
+			title = "Set Tagged Dynamic Slope Vertex to Front Sector Height";
+			prefix = "(799)";
+			arg0
+			{
+				title = "Apply height";
+				type = 11;
+				enum
+				{
+					0 = "Absolute";
+					1 = "Relative";
+				}
+			}
+		}
 	}
-}
\ No newline at end of file
+}
diff --git a/extras/conf/udb/Includes/SRB222_misc.cfg b/extras/conf/udb/Includes/SRB222_misc.cfg
index 50c83e456ef92f92b40fb9a858f1cb8eca0c6dc5..fcc24741e3b7f7ff9a2172e6cff702964f6dd71a 100644
--- a/extras/conf/udb/Includes/SRB222_misc.cfg
+++ b/extras/conf/udb/Includes/SRB222_misc.cfg
@@ -58,7 +58,6 @@ linedefflags_udmf
 	wrapmidtex = "Repeat Midtexture";
 	netonly = "Netgame Only";
 	nonet = "No Netgame";
-	effect6 = "Effect 6";
 	bouncy = "Bouncy Wall";
 	transfer = "Transfer Line";
 }
@@ -78,6 +77,33 @@ sectorflags
 	colormapfog = "Fog Planes in Colormap";
 	colormapfadesprites = "Fade Fullbright in Colormap";
 	colormapprotected = "Protected Colormap";
+	flipspecial_nofloor = "No Trigger on Floor Touch";
+	flipspecial_ceiling = "Trigger on Ceiling Touch";
+	triggerspecial_touch = "Trigger on Edge Touch";
+	triggerspecial_headbump = "Trigger on Headbump";
+	triggerline_plane = "Linedef Trigger Requires Plane Touch";
+	triggerline_mobj = "Non-Pushables Can Trigger Linedef";
+	invertprecip = "Invert Precipitation";
+	gravityflip = "Flip Objects in Reverse Gravity";
+	heatwave = "Heat Wave";
+	noclipcamera = "Intangible to the Camera";
+	outerspace = "Space Countdown";
+	doublestepup = "Ramp Sector (double step-up/down)";
+	nostepdown = "Non-Ramp Sector (No step-down)";
+	speedpad = "Speed Pad";
+	starpostactivator = "Star Post Activator";
+	exit = "Exit";
+	specialstagepit = "Special Stage Pit";
+	returnflag = "Return Flag";
+	redteambase = "Red Team Base";
+	blueteambase = "Blue Team Base";
+	fan = "Fan Sector";
+	supertransform = "Super Sonic Transform";
+	forcespin = "Force Spin";
+	zoomtubestart = "Zoom Tube Start";
+	zoomtubeend = "Zoom Tube End";
+	finishline = "Circuit Finish Line";
+	ropehang = "Rope Hang";
 }
 
 thingflags
@@ -91,10 +117,7 @@ thingflags
 // THING FLAGS
 thingflags_udmf
 {
-	extra = "Extra";
 	flip = "Flip";
-	special = "Special";
-	ambush = "Ambush";
 }
 
 
@@ -227,6 +250,30 @@ universalfields
 			type = 3;
 			default = false;
 		}
+
+		friction
+		{
+			type = 1;
+			default = 0.90625;
+		}
+
+		triggertag
+		{
+			type = 15;
+			default = 0;
+		}
+
+		triggerer
+		{
+			type = 0;
+			default = 0;
+			enum
+			{
+				0 = "Player";
+				1 = "All players";
+				2 = "Object";
+			}
+		}
 	}
 
 	linedef
@@ -236,6 +283,26 @@ universalfields
 			type = 0;
 			default = 0;
 		}
+		arg6
+		{
+			type = 0;
+			default = 0;
+		}
+		arg7
+		{
+			type = 0;
+			default = 0;
+		}
+		arg8
+		{
+			type = 0;
+			default = 0;
+		}
+		arg9
+		{
+			type = 0;
+			default = 0;
+		}
 		stringarg0
 		{
 			type = 2;
@@ -264,6 +331,41 @@ universalfields
 
 	thing
 	{
+		arg5
+		{
+			type = 0;
+			default = 0;
+		}
+		arg6
+		{
+			type = 0;
+			default = 0;
+		}
+		arg7
+		{
+			type = 0;
+			default = 0;
+		}
+		arg8
+		{
+			type = 0;
+			default = 0;
+		}
+		arg9
+		{
+			type = 0;
+			default = 0;
+		}
+		stringarg0
+		{
+			type = 2;
+			default = "";
+		}
+		stringarg1
+		{
+			type = 2;
+			default = "";
+		}
 	}
 }
 
@@ -410,6 +512,12 @@ enums
 		1 = "Yes";
 	}
 
+	setadd
+	{
+		0 = "Set";
+		1 = "Add";
+	}
+
 	onoff
 	{
 		0 = "On";
@@ -441,6 +549,13 @@ enums
 		2 = "Back";
 	}
 
+	frontbackboth
+	{
+		0 = "Front";
+		1 = "Back";
+		2 = "Front and back";
+	}
+
 	tangibility
 	{
 		1 = "Intangible from top";
@@ -448,6 +563,100 @@ enums
 		4 = "Don't block players";
 		8 = "Don't block non-players";
 	}
+
+	floorceiling
+	{
+		0 = "Floor";
+		1 = "Ceiling";
+		2 = "Both";
+	}
+
+	scrollcarry
+	{
+		0 = "Scroll and carry";
+		1 = "Scroll";
+		2 = "Carry";
+	}
+
+	scrolltype
+	{
+		0 = "Regular";
+		1 = "Accelerative";
+		2 = "Displacement";
+	}
+
+	comparison
+	{
+		0 = "Equal";
+		1 = "Less than or equal";
+		2 = "Greater than or equal";
+	}
+
+	triggertype
+	{
+		0 = "Continuous";
+		1 = "Once";
+		2 = "Each time on entry";
+		3 = "Each time on entry/exit";
+	}
+
+	xtriggertype
+	{
+		0 = "Continuous";
+		1 = "Each time on entry";
+		2 = "Each time on entry/exit";
+	}
+
+	team
+	{
+		0 = "Red";
+		1 = "Blue";
+	}
+
+	flagcheck
+	{
+		0 = "Has all";
+		1 = "Has any";
+		2 = "Has exactly";
+		3 = "Doesn't have all";
+		4 = "Doesn't have any";
+	}
+
+	maceflags
+	{
+		1 = "Double size";
+		2 = "No sounds";
+		4 = "Player-turnable chain";
+		8 = "Swing instead of spin";
+		16 = "Make chain from end item";
+		32 = "Spawn link at origin";
+		64 = "Clip inside ground";
+		128 = "No distance check";
+	}
+
+	pushablebehavior
+	{
+		0 = "Normal";
+		1 = "Slide";
+		2 = "Immovable";
+		3 = "Classic";
+	}
+
+	monitorrespawn
+	{
+		0 = "Same item";
+		1 = "Random (Weak)";
+		2 = "Random (Strong)";
+	}
+
+	blendmodes
+	{
+		0 = "Translucent";
+		1 = "Add";
+		2 = "Subtract";
+		3 = "Reverse subtract";
+		4 = "Modulate";
+	}
 }
 
 //Default things filters
@@ -626,4 +835,4 @@ flats
 		start = "F_START";
 		end = "FF_END";
 	}
-}
\ No newline at end of file
+}
diff --git a/extras/conf/udb/Includes/SRB222_sectors.cfg b/extras/conf/udb/Includes/SRB222_sectors.cfg
index 5cc14ad0fb1a6b58c7d9ab29e23045b4a7ff8b1e..f9df297e76d06a3ed122156379066d3b38136c84 100644
--- a/extras/conf/udb/Includes/SRB222_sectors.cfg
+++ b/extras/conf/udb/Includes/SRB222_sectors.cfg
@@ -15,20 +15,18 @@ sectortypes
 	12 = "Space Countdown";
 	13 = "Ramp Sector (double step-up/down)";
 	14 = "Non-Ramp Sector (no step-down)";
-	15 = "Bouncy FOF";
+	15 = "Bouncy FOF <deprecated>";
 	16 = "Trigger Line Ex. (Pushable Objects)";
 	32 = "Trigger Line Ex. (Anywhere, All Players)";
 	48 = "Trigger Line Ex. (Floor Touch, All Players)";
 	64 = "Trigger Line Ex. (Anywhere in Sector)";
 	80 = "Trigger Line Ex. (Floor Touch)";
-	96 = "Trigger Line Ex. (Emerald Check)";
-	112 = "Trigger Line Ex. (NiGHTS Mare)";
+	96 = "Trigger Line Ex. (Emerald Check) <deprecated>";
+	112 = "Trigger Line Ex. (NiGHTS Mare) <deprecated>";
 	128 = "Check for Linedef Executor on FOFs";
 	144 = "Egg Capsule";
-	160 = "Special Stage Time/Spheres Parameters";
-	176 = "Custom Global Gravity";
-	512 = "Wind/Current";
-	1024 = "Conveyor Belt";
+	160 = "Special Stage Time/Spheres Parameters <deprecated>";
+	176 = "Custom Global Gravity <deprecated>";
 	1280 = "Speed Pad";
 	4096 = "Star Post Activator";
 	8192 = "Exit/Special Stage Pit/Return Flag";
@@ -63,7 +61,7 @@ gen_sectortypes
 		12 = "Space Countdown";
 		13 = "Ramp Sector (double step-up/down)";
 		14 = "Non-Ramp Sector (no step-down)";
-		15 = "Bouncy FOF";
+		15 = "Bouncy FOF <deprecated>";
 	}
 
 	second
@@ -74,19 +72,17 @@ gen_sectortypes
 		48 = "Trigger Line Ex. (Floor Touch, All Players)";
 		64 = "Trigger Line Ex. (Anywhere in Sector)";
 		80 = "Trigger Line Ex. (Floor Touch)";
-		96 = "Trigger Line Ex. (Emerald Check)";
-		112 = "Trigger Line Ex. (NiGHTS Mare)";
+		96 = "Trigger Line Ex. (Emerald Check) <deprecated>";
+		112 = "Trigger Line Ex. (NiGHTS Mare) <deprecated>";
 		128 = "Check for Linedef Executor on FOFs";
 		144 = "Egg Capsule";
-		160 = "Special Stage Time/Spheres Parameters";
-		176 = "Custom Global Gravity";
+		160 = "Special Stage Time/Spheres Parameters <deprecated>";
+		176 = "Custom Global Gravity <deprecated>";
 	}
 
 	third
 	{
 		0 = "Normal";
-		512 = "Wind/Current";
-		1024 = "Conveyor Belt";
 		1280 = "Speed Pad";
 	}
 
diff --git a/extras/conf/udb/Includes/SRB222_things.cfg b/extras/conf/udb/Includes/SRB222_things.cfg
index 0407741fc9871c421340e19aa4a1b8ecd0a76476..1de661e29643d303acb08b961fc564f083aae2b5 100644
--- a/extras/conf/udb/Includes/SRB222_things.cfg
+++ b/extras/conf/udb/Includes/SRB222_things.cfg
@@ -3,3140 +3,8037 @@
 // 8-Dark_Gray 9-Blue 10-Green 11-Cyan 12-Red 13-Magenta
 // 14-Yellow 15-White 16-Pink 17-Orange 18-Gold 19-Cream
 
-editor
+doom
 {
-	color = 15; // White
-	arrow = 1;
-	title = "<Editor Things>";
-	error = -1;
-	width = 8;
-	height = 16;
-	sort = 1;
-
-	3328 = "3D Mode Start";
-}
-
-starts
-{
-	color = 1; // Blue
-	arrow = 1;
-	title = "Player Starts";
-	width = 16;
-	height = 48;
-	sprite = "PLAYA0";
-
-	1
-	{
-		title = "Player 01 Start";
-		sprite = "PLAYA0";
-	}
-	2
-	{
-		title = "Player 02 Start";
-		sprite = "PLAYA0";
-	}
-	3
-	{
-		title = "Player 03 Start";
-		sprite = "PLAYA0";
-	}
-	4
-	{
-		title = "Player 04 Start";
-		sprite = "PLAYA0";
-	}
-	5
-	{
-		title = "Player 05 Start";
-		sprite = "PLAYA0";
-	}
-	6
-	{
-		title = "Player 06 Start";
-		sprite = "PLAYA0";
-	}
-	7
-	{
-		title = "Player 07 Start";
-		sprite = "PLAYA0";
-	}
-	8
-	{
-		title = "Player 08 Start";
-		sprite = "PLAYA0";
-	}
-	9
-	{
-		title = "Player 09 Start";
-		sprite = "PLAYA0";
-	}
-	10
-	{
-		title = "Player 10 Start";
-		sprite = "PLAYA0";
-	}
-	11
-	{
-		title = "Player 11 Start";
-		sprite = "PLAYA0";
-	}
-	12
-	{
-		title = "Player 12 Start";
-		sprite = "PLAYA0";
-	}
-	13
-	{
-		title = "Player 13 Start";
-		sprite = "PLAYA0";
-	}
-	14
-	{
-		title = "Player 14 Start";
-		sprite = "PLAYA0";
-	}
-	15
-	{
-		title = "Player 15 Start";
-		sprite = "PLAYA0";
-	}
-	16
-	{
-		title = "Player 16 Start";
-		sprite = "PLAYA0";
-	}
-	17
-	{
-		title = "Player 17 Start";
-		sprite = "PLAYA0";
-	}
-	18
-	{
-		title = "Player 18 Start";
-		sprite = "PLAYA0";
-	}
-	19
-	{
-		title = "Player 19 Start";
-		sprite = "PLAYA0";
-	}
-	20
-	{
-		title = "Player 20 Start";
-		sprite = "PLAYA0";
-	}
-	21
-	{
-		title = "Player 21 Start";
-		sprite = "PLAYA0";
-	}
-	22
-	{
-		title = "Player 22 Start";
-		sprite = "PLAYA0";
-	}
-	23
-	{
-		title = "Player 23 Start";
-		sprite = "PLAYA0";
-	}
-	24
-	{
-		title = "Player 24 Start";
-		sprite = "PLAYA0";
-	}
-	25
-	{
-		title = "Player 25 Start";
-		sprite = "PLAYA0";
-	}
-	26
-	{
-		title = "Player 26 Start";
-		sprite = "PLAYA0";
-	}
-	27
-	{
-		title = "Player 27 Start";
-		sprite = "PLAYA0";
-	}
-	28
-	{
-		title = "Player 28 Start";
-		sprite = "PLAYA0";
-	}
-	29
-	{
-		title = "Player 29 Start";
-		sprite = "PLAYA0";
-	}
-	30
-	{
-		title = "Player 30 Start";
-		sprite = "PLAYA0";
-	}
-	31
-	{
-		title = "Player 31 Start";
-		sprite = "PLAYA0";
-	}
-	32
-	{
-		title = "Player 32 Start";
-		sprite = "PLAYA0";
-	}
-	33
-	{
-		title = "Match Start";
-		sprite = "NDRNA2A8";
-	}
-	34
-	{
-		title = "CTF Red Team Start";
-		sprite = "SIGNG0";
-	}
-	35
-	{
-		title = "CTF Blue Team Start";
-		sprite = "SIGNE0";
-	}
-}
-
-enemies
-{
-	color = 9; // Light_Blue
-	arrow = 1;
-	title = "Enemies";
-
-	100
-	{
-		title = "Crawla (Blue)";
-		sprite = "POSSA1";
-		width = 24;
-		height = 32;
-	}
-	101
-	{
-		title = "Crawla (Red)";
-		sprite = "SPOSA1";
-		width = 24;
-		height = 32;
-	}
-	102
-	{
-		title = "Stupid Dumb Unnamed RoboFish";
-		sprite = "FISHA0";
-		width = 8;
-		height = 28;
-	}
-	103
-	{
-		title = "Buzz (Gold)";
-		sprite = "BUZZA1";
-		width = 28;
-		height = 40;
-	}
-	104
-	{
-		title = "Buzz (Red)";
-		sprite = "RBUZA1";
-		width = 28;
-		height = 40;
-	}
-	108
-	{
-		title = "Deton";
-		sprite = "DETNA1";
-		width = 20;
-		height = 32;
-	}
-	110
-	{
-		title = "Turret";
-		sprite = "TRETA1";
-		width = 16;
-		height = 32;
-	}
-	111
-	{
-		title = "Pop-up Turret";
-		sprite = "TURRI1";
-		width = 12;
-		height = 64;
-	}
-	122
-	{
-		title = "Spring Shell (Green)";
-		sprite = "SSHLA1";
-		width = 24;
-		height = 40;
-	}
-	125
-	{
-		title = "Spring Shell (Yellow)";
-		sprite = "SSHLI1";
-		width = 24;
-		height = 40;
-	}
-	109
-	{
-		title = "Skim";
-		sprite = "SKIMA1";
-		width = 16;
-		height = 24;
-	}
-	113
-	{
-		title = "Jet Jaw";
-		sprite = "JJAWA3A7";
-		width = 12;
-		height = 20;
-	}
-	126
-	{
-		title = "Crushstacean";
-		sprite = "CRABA0";
-		width = 24;
-		height = 32;
-	}
-	138
-	{
-		title = "Banpyura";
-		sprite = "CR2BA0";
-		width = 24;
-		height = 32;
-	}
-	117
-	{
-		title = "Robo-Hood";
-		sprite = "ARCHA1";
-		width = 24;
-		height = 32;
-	}
-	118
-	{
-		title = "Lance-a-Bot";
-		sprite = "CBFSA1";
-		width = 32;
-		height = 72;
-	}
-	1113
-	{
-		title = "Suspicious Lance-a-Bot Statue";
-		sprite = "CBBSA1";
-		width = 32;
-		height = 72;
-	}
-	119
-	{
-		title = "Egg Guard";
-		sprite = "ESHIA1";
-		width = 16;
-		height = 48;
-	}
-	115
-	{
-		title = "Bird Aircraft Strike Hazard";
-		sprite = "VLTRF1";
-		width = 12;
-		height = 24;
-	}
-	120
-	{
-		title = "Green Snapper";
-		sprite = "GSNPA1";
-		width = 24;
-		height = 24;
-	}
-	121
-	{
-		title = "Minus";
-		sprite = "MNUSA0";
-		width = 24;
-		height = 32;
-	}
-	134
-	{
-		title = "Canarivore";
-		sprite = "CANAA0";
-		width = 12;
-		height = 80;
-		hangs = 1;
-	}
-	123
-	{
-		title = "Unidus";
-		sprite = "UNIDA1";
-		width = 18;
-		height = 36;
-	}
-	135
-	{
-		title = "Pterabyte Spawner";
-		sprite = "PTERA2A8";
-		width = 16;
-		height = 16;
-	}
-	136
-	{
-		title = "Pyre Fly";
-		sprite = "PYREA0";
-		width = 24;
-		height = 34;
-	}
-	137
-	{
-		title = "Dragonbomber";
-		sprite = "DRABA1";
-		width = 28;
-		height = 48;
-	}
-	105
-	{
-		title = "Jetty-Syn Bomber";
-		sprite = "JETBB1";
-		width = 20;
-		height = 50;
-	}
-	106
-	{
-		title = "Jetty-Syn Gunner";
-		sprite = "JETGB1";
-		width = 20;
-		height = 48;
-	}
-	112
-	{
-		title = "Spincushion";
-		sprite = "SHRPA1";
-		width = 16;
-		height = 24;
-	}
-	114
-	{
-		title = "Snailer";
-		sprite = "SNLRA3A7";
-		width = 24;
-		height = 48;
-	}
-	129
-	{
-		title = "Penguinator";
-		sprite = "PENGA1";
-		width = 24;
-		height = 32;
-	}
-	130
-	{
-		title = "Pophat";
-		sprite = "POPHA1";
-		width = 24;
-		height = 32;
-	}
-	107
-	{
-		title = "Crawla Commander";
-		sprite = "CCOMA1";
-		width = 16;
-		height = 32;
-	}
-	131
-	{
-		title = "Spinbobert";
-		sprite = "SBOBB0";
-		width = 32;
-		height = 32;
-	}
-	132
-	{
-		title = "Cacolantern";
-		sprite = "CACOA0";
-		width = 32;
-		height = 32;
-	}
-	133
-	{
-		title = "Hangster";
-		sprite = "HBATC1";
-		width = 24;
-		height = 24;
-		hangs = 1;
-	}
-	127
-	{
-		title = "Hive Elemental";
-		sprite = "HIVEA0";
-		width = 32;
-		height = 80;
-	}
-	128
-	{
-		title = "Bumblebore";
-		sprite = "BUMBA1";
-		width = 16;
-		height = 32;
-	}
-	124
-	{
-		title = "Buggle";
-		sprite = "BBUZA1";
-		width = 20;
-		height = 24;
-	}
-	116
-	{
-		title = "Pointy";
-		sprite = "PNTYA1";
-		width = 8;
-		height = 16;
-	}
-}
-
-bosses
-{
-	color = 8; // Dark_Gray
-	arrow = 1;
-	title = "Bosses";
-
-	200
-	{
-		title = "Egg Mobile";
-		sprite = "EGGMA1";
-		width = 24;
-		height = 76;
-	}
-	201
-	{
-		title = "Egg Slimer";
-		sprite = "EGGNA1";
-		width = 24;
-		height = 76;
-	}
-	202
-	{
-		title = "Sea Egg";
-		sprite = "EGGOA1";
-		width = 32;
-		height = 116;
-	}
-	203
-	{
-		title = "Egg Colosseum";
-		sprite = "EGGPA1";
-		width = 24;
-		height = 76;
-	}
-	204
-	{
-		title = "Fang";
-		sprite = "FANGA1";
-		width = 24;
-		height = 60;
-	}
-	206
-	{
-		title = "Brak Eggman (Old)";
-		sprite = "BRAKB1";
-		width = 48;
-		height = 160;
-	}
-	207
-	{
-		title = "Metal Sonic (Race)";
-		sprite = "METLI1";
-		width = 16;
-		height = 48;
-	}
-	208
-	{
-		title = "Metal Sonic (Battle)";
-		sprite = "METLC1";
-		width = 16;
-		height = 48;
-	}
-	209
-	{
-		title = "Brak Eggman";
-		sprite = "BRAK01";
-		width = 48;
-		height = 160;
-	}
-	290
-	{
-		arrow = 0;
-		title = "Boss Escape Point";
-		width = 8;
-		height = 16;
-		sprite = "internal:eggmanend";
-	}
-	291
-	{
-		arrow = 0;
-		title = "Egg Capsule Center";
-		width = 8;
-		height = 16;
-		sprite = "internal:capsule";
-	}
-	292
-	{
-		arrow = 0;
-		title = "Boss Waypoint";
-		width = 8;
-		height = 16;
-		sprite = "internal:eggmanway";
-	}
-	293
-	{
-		title = "Metal Sonic Gather Point";
-		sprite = "internal:metal";
-		width = 8;
-		height = 16;
-	}
-	294
-	{
-		title = "Fang Waypoint";
-		sprite = "internal:eggmanway";
-		width = 8;
-		height = 16;
-	}
-}
-
-rings
-{
-	color = 14; // Yellow
-	title = "Rings and Weapon Panels";
-	width = 24;
-	height = 24;
-	sprite = "RINGA0";
-
-	300
-	{
-		title = "Ring";
-		sprite = "RINGA0";
-		width = 16;
-	}
-	301
-	{
-		title = "Bounce Ring";
-		sprite = "internal:RNGBA0";
-	}
-	302
-	{
-		title = "Rail Ring";
-		sprite = "internal:RNGRA0";
-	}
-	303
-	{
-		title = "Infinity Ring";
-		sprite = "internal:RNGIA0";
-	}
-	304
-	{
-		title = "Automatic Ring";
-		sprite = "internal:RNGAA0";
-	}
-	305
-	{
-		title = "Explosion Ring";
-		sprite = "internal:RNGEA0";
-	}
-	306
-	{
-		title = "Scatter Ring";
-		sprite = "internal:RNGSA0";
-	}
-	307
-	{
-		title = "Grenade Ring";
-		sprite = "internal:RNGGA0";
-	}
-	308
-	{
-		title = "CTF Team Ring (Red)";
-		sprite = "internal:RRNGA0";
-		width = 16;
-	}
-	309
-	{
-		title = "CTF Team Ring (Blue)";
-		sprite = "internal:BRNGA0";
-		width = 16;
-	}
-	330
-	{
-		title = "Bounce Ring Panel";
-		sprite = "internal:PIKBA0";
-	}
-	331
-	{
-		title = "Rail Ring Panel";
-		sprite = "internal:PIKRA0";
-	}
-	332
-	{
-		title = "Automatic Ring Panel";
-		sprite = "internal:PIKAA0";
-	}
-	333
-	{
-		title = "Explosion Ring Panel";
-		sprite = "internal:PIKEA0";
-	}
-	334
-	{
-		title = "Scatter Ring Panel";
-		sprite = "internal:PIKSA0";
-	}
-	335
-	{
-		title = "Grenade Ring Panel";
-		sprite = "internal:PIKGA0";
-	}
-}
-
-collectibles
-{
-	color = 10; // Light_Green
-	title = "Other Collectibles";
-	width = 16;
-	height = 32;
-	sort = 1;
-	sprite = "CEMGA0";
-
-	310
-	{
-		title = "CTF Red Flag";
-		sprite = "RFLGA0";
-		width = 24;
-		height = 64;
-	}
-	311
-	{
-		title = "CTF Blue Flag";
-		sprite = "BFLGA0";
-		width = 24;
-		height = 64;
-	}
-	312
-	{
-		title = "Emerald Token";
-		sprite = "TOKEA0";
-		width = 16;
-		height = 32;
-	}
-	313
-	{
-		title = "Chaos Emerald 1 (Green)";
-		sprite = "CEMGA0";
-	}
-	314
-	{
-		title = "Chaos Emerald 2 (Purple)";
-		sprite = "CEMGB0";
-	}
-	315
-	{
-		title = "Chaos Emerald 3 (Blue)";
-		sprite = "CEMGC0";
-	}
-	316
-	{
-		title = "Chaos Emerald 4 (Cyan)";
-		sprite = "CEMGD0";
-	}
-	317
-	{
-		title = "Chaos Emerald 5 (Orange)";
-		sprite = "CEMGE0";
-	}
-	318
-	{
-		title = "Chaos Emerald 6 (Red)";
-		sprite = "CEMGF0";
-	}
-	319
-	{
-		title = "Chaos Emerald 7 (Gray)";
-		sprite = "CEMGG0";
-	}
-	320
-	{
-		title = "Emerald Hunt Location";
-		sprite = "SHRDA0";
-	}
-	321
-	{
-		title = "Match Chaos Emerald Spawn";
-		sprite = "CEMGA0";
-	}
-	322
-	{
-		title = "Emblem";
-		sprite = "EMBMA0";
-		width = 16;
-		height = 30;
-	}
-}
-
-boxes
-{
-	color = 7; // Gray
-	blocking = 2;
-	title = "Monitors";
-	width = 18;
-	height = 40;
-
-	400
-	{
-		title = "Super Ring (10 Rings)";
-		sprite = "TVRIA0";
-	}
-	401
-	{
-		title = "Pity Shield";
-		sprite = "TVPIA0";
-	}
-	402
-	{
-		title = "Attraction Shield";
-		sprite = "TVATA0";
-	}
-	403
-	{
-		title = "Force Shield";
-		sprite = "TVFOA0";
-	}
-	404
-	{
-		title = "Armageddon Shield";
-		sprite = "TVARA0";
-	}
-	405
-	{
-		title = "Whirlwind Shield";
-		sprite = "TVWWA0";
-	}
-	406
-	{
-		title = "Elemental Shield";
-		sprite = "TVELA0";
-	}
-	407
-	{
-		title = "Super Sneakers";
-		sprite = "TVSSA0";
-	}
-	408
-	{
-		title = "Invincibility";
-		sprite = "TVIVA0";
-	}
-	409
-	{
-		title = "Extra Life";
-		sprite = "TV1UA0";
-	}
-	410
-	{
-		title = "Eggman";
-		sprite = "TVEGA0";
-	}
-	411
-	{
-		title = "Teleporter";
-		sprite = "TVMXA0";
-	}
-	413
-	{
-		title = "Gravity Boots";
-		sprite = "TVGVA0";
-	}
-	414
-	{
-		title = "CTF Team Ring Monitor (Red)";
-		sprite = "TRRIA0";
-	}
-	415
-	{
-		title = "CTF Team Ring Monitor (Blue)";
-		sprite = "TBRIA0";
-	}
-	416
-	{
-		title = "Recycler";
-		sprite = "TVRCA0";
-	}
-	418
-	{
-		title = "Score (1,000 Points)";
-		sprite = "TV1KA0";
-	}
-	419
-	{
-		title = "Score (10,000 Points)";
-		sprite = "TVTKA0";
-	}
-	420
-	{
-		title = "Flame Shield";
-		sprite = "TVFLA0";
-	}
-	421
-	{
-		title = "Water Shield";
-		sprite = "TVBBA0";
-	}
-	422
-	{
-		title = "Lightning Shield";
-		sprite = "TVZPA0";
-	}
-}
-
-boxes2
-{
-	color = 18; // Gold
-	blocking = 2;
-	title = "Monitors (Respawning)";
-	width = 20;
-	height = 44;
-
-	431
-	{
-		title = "Pity Shield (Respawn)";
-		sprite = "TVPIB0";
-	}
-	432
-	{
-		title = "Attraction Shield (Respawn)";
-		sprite = "TVATB0";
-	}
-	433
-	{
-		title = "Force Shield (Respawn)";
-		sprite = "TVFOB0";
-	}
-	434
-	{
-		title = "Armageddon Shield (Respawn)";
-		sprite = "TVARB0";
-	}
-	435
-	{
-		title = "Whirlwind Shield (Respawn)";
-		sprite = "TVWWB0";
-	}
-	436
-	{
-		title = "Elemental Shield (Respawn)";
-		sprite = "TVELB0";
-	}
-	437
-	{
-		title = "Super Sneakers (Respawn)";
-		sprite = "TVSSB0";
-	}
-	438
-	{
-		title = "Invincibility (Respawn)";
-		sprite = "TVIVB0";
-	}
-	440
-	{
-		title = "Eggman (Respawn)";
-		sprite = "TVEGB0";
-	}
-	443
-	{
-		title = "Gravity Boots (Respawn)";
-		sprite = "TVGVB0";
-	}
-	450
-	{
-		title = "Flame Shield (Respawn)";
-		sprite = "TVFLB0";
-	}
-	451
-	{
-		title = "Water Shield (Respawn)";
-		sprite = "TVBBB0";
-	}
-	452
-	{
-		title = "Lightning Shield (Respawn)";
-		sprite = "TVZPB0";
-	}
-}
-
-generic
-{
-	color = 11; // Light_Cyan
-	title = "Generic Items & Hazards";
-
-	500
-	{
-		title = "Air Bubble Patch";
-		sprite = "BUBLE0";
-		width = 8;
-		height = 16;
-	}
-	501
-	{
-		title = "Signpost";
-		sprite = "SIGND0";
-		width = 8;
-		height = 32;
-	}
-	502
-	{
-		arrow = 1;
-		title = "Star Post";
-		sprite = "STPTA0M0";
-		width = 64;
-		height = 128;
-	}
-	520
-	{
-		title = "Bomb Sphere";
-		sprite = "SPHRD0";
-		width = 16;
-		height = 24;
-	}
-	521
-	{
-		title = "Spikeball";
-		sprite = "SPIKA0";
-		width = 12;
-		height = 8;
-	}
-	522
-	{
-		title = "Wall Spike";
-		sprite = "WSPKALAR";
-		width = 16;
-		height = 14;
-		arrow = 1;
-	}
-	523
-	{
-		title = "Spike";
-		sprite = "USPKA0";
-		width = 8;
-		height = 32;
-	}
-	1130
-	{
-		title = "Small Mace";
-		sprite = "SMCEA0";
-		width = 17;
-		height = 34;
-	}
-	1131
-	{
-		title = "Big Mace";
-		sprite = "BMCEA0";
-		width = 34;
-		height = 68;
-	}
-	1136
-	{
-		title = "Small Fireball";
-		sprite = "SFBRA0";
-		width = 17;
-		height = 34;
-	}
-	1137
-	{
-		title = "Large Fireball";
-		sprite = "BFBRA0";
-		width = 34;
-		height = 68;
-	}
-}
-
-springs
-{
-	color = 12; // Light_Red
-	title = "Springs and Fans";
-	width = 20;
-	height = 16;
-	sprite = "RSPRD2";
-
-	540
-	{
-		title = "Fan";
-		sprite = "FANSA0D0";
-		width = 16;
-		height = 8;
-	}
-	541
-	{
-		title = "Gas Jet";
-		sprite = "STEMD0";
-		width = 32;
-	}
-	542
-	{
-		title = "Bumper";
-		sprite = "BUMPA0";
-		width = 32;
-		height = 64;
-	}
-	543
-	{
-		title = "Balloon";
-		sprite = "BLONA0";
-		width = 32;
-		height = 64;
-	}
-	550
-	{
-		title = "Yellow Spring";
-		sprite = "SPRYA0";
-	}
-	551
-	{
-		title = "Red Spring";
-		sprite = "SPRRA0";
-	}
-	552
-	{
-		title = "Blue Spring";
-		sprite = "SPRBA0";
-	}
-	555
-	{
-		arrow = 1;
-		title = "Diagonal Yellow Spring";
-		sprite = "YSPRD2";
-		width = 16;
-	}
-	556
-	{
-		arrow = 1;
-		title = "Diagonal Red Spring";
-		sprite = "RSPRD2";
-		width = 16;
-	}
-	557
-	{
-		arrow = 1;
-		title = "Diagonal Blue Spring";
-		sprite = "BSPRD2";
-		width = 16;
-	}
-	558
-	{
-		arrow = 1;
-		title = "Horizontal Yellow Spring";
-		sprite = "SSWYD2D8";
-		width = 16;
-		height = 32;
-	}
-	559
-	{
-		arrow = 1;
-		title = "Horizontal Red Spring";
-		sprite = "SSWRD2D8";
-		width = 16;
-		height = 32;
-	}
-	560
-	{
-		arrow = 1;
-		title = "Horizontal Blue Spring";
-		sprite = "SSWBD2D8";
-		width = 16;
-		height = 32;
-	}
-	1134
-	{
-		title = "Yellow Spring Ball";
-		sprite = "YSPBA0";
-		width = 17;
-		height = 34;
-	}
-	1135
-	{
-		title = "Red Spring Ball";
-		sprite = "RSPBA0";
-		width = 17;
-		height = 34;
-	}
-	544
-	{
-		arrow = 1;
-		title = "Yellow Boost Panel";
-		sprite = "BSTYA0";
-		width = 28;
-		height = 2;
-	}
-	545
-	{
-		arrow = 1;
-		title = "Red Boost Panel";
-		sprite = "BSTRA0";
-		width = 28;
-		height = 2;
-	}
-}
-
-patterns
-{
-	color = 5; // Magenta
-	arrow = 1;
-	title = "Special Placement Patterns";
-	width = 16;
-	height = 384;
-	sprite = "RINGA0";
-
-	600
-	{
-		arrow = 0;
-		title = "5 Vertical Rings (Yellow Spring)";
-		sprite = "RINGA0";
-	}
-	601
-	{
-		arrow = 0;
-		title = "5 Vertical Rings (Red Spring)";
-		sprite = "RINGA0";
-		height = 1024;
-	}
-	602
-	{
-		title = "5 Diagonal Rings (Yellow Spring)";
-		sprite = "RINGA0";
-		height = 32;
-	}
-	603
-	{
-		title = "10 Diagonal Rings (Red Spring)";
-		sprite = "RINGA0";
-		height = 32;
-	}
-	604
-	{
-		title = "Circle of Rings";
-		sprite = "RINGA0";
-		width = 96;
-		height = 192;
-	}
-	605
-	{
-		title = "Circle of Rings (Big)";
-		sprite = "RINGA0";
-		width = 192;
-	}
-	606
-	{
-		title = "Circle of Blue Spheres";
-		sprite = "SPHRA0";
-		width = 96;
-		height = 192;
-	}
-	607
-	{
-		title = "Circle of Blue Spheres (Big)";
-		sprite = "SPHRA0";
-		width = 192;
-	}
-	608
-	{
-		title = "Circle of Rings and Spheres";
-		sprite = "SPHRA0";
-		width = 96;
-		height = 192;
-	}
-	609
-	{
-		title = "Circle of Rings and Spheres (Big)";
-		sprite = "SPHRA0";
-		width = 192;
-	}
-}
-
-invisible
-{
-	color = 15; // White
-	title = "Misc. Invisible";
-	width = 0;
-	height = 0;
-	sprite = "UNKNA0";
-	sort = 1;
-	fixedsize = true;
-	blocking = 0;
-
-	700
-	{
-		title = "Water Ambience A (Large)";
-		sprite = "internal:ambiance";
-	}
-
-	701
-	{
-		title = "Water Ambience B (Large)";
-		sprite = "internal:ambiance";
-	}
-
-	702
-	{
-		title = "Water Ambience C (Medium)";
-		sprite = "internal:ambiance";
-	}
-
-	703
-	{
-		title = "Water Ambience D (Medium)";
-		sprite = "internal:ambiance";
-	}
-
-	704
-	{
-		title = "Water Ambience E (Small)";
-		sprite = "internal:ambiance";
-	}
-
-	705
-	{
-		title = "Water Ambience F (Small)";
-		sprite = "internal:ambiance";
-	}
-
-	706
-	{
-		title = "Water Ambience G (Extra Large)";
-		sprite = "internal:ambiance";
-	}
-
-	707
-	{
-		title = "Water Ambience H (Extra Large)";
-		sprite = "internal:ambiance";
-	}
-
-	708
-	{
-		title = "Disco Ambience";
-		sprite = "internal:ambiance";
-	}
-
-	709
-	{
-		title = "Volcano Ambience";
-		sprite = "internal:ambiance";
-	}
-
-	710
-	{
-		title = "Machine Ambience";
-		sprite = "internal:ambiance";
-	}
-
-	750
-	{
-		title = "Slope Vertex";
-		sprite = "internal:vertexslope";
-	}
-
-	751
-	{
-		arrow = 1;
-		title = "Teleport Destination";
-		sprite = "internal:tele";
-	}
-
-	752
-	{
-		arrow = 1;
-		title = "Alternate View Point";
-		sprite = "internal:view";
-	}
-
-	753
-	{
-		title = "Zoom Tube Waypoint";
-		sprite = "internal:zoom";
-	}
-
-	754
-	{
-		title = "Push Point";
-		sprite = "GWLGA0";
-	}
-	755
-	{
-		title = "Pull Point";
-		sprite = "GWLRA0";
-	}
-	756
-	{
-		title = "Blast Linedef Executor";
-		sprite = "TOADA0";
-		width = 32;
-		height = 16;
-	}
-	757
-	{
-		title = "Fan Particle Generator";
-		sprite = "PRTLA0";
-		width = 8;
-		height = 16;
-	}
-	758
-	{
-		title = "Object Angle Anchor";
-		sprite = "internal:view";
-	}
-	760
-	{
-		title = "PolyObject Anchor";
-		sprite = "internal:polyanchor";
-	}
-	761
-	{
-		title = "PolyObject Spawn Point";
-		sprite = "internal:polycenter";
-	}
-	762
-	{
-		title = "PolyObject Spawn Point (Crush)";
-		sprite = "internal:polycentercrush";
-	}
-	780
-	{
-		title = "Skybox View Point";
-		sprite = "internal:skyb";
-	}
-}
-
-greenflower
-{
-	color = 10; // Green
-	title = "Greenflower";
-
-	800
-	{
-		title = "GFZ Flower";
-		sprite = "FWR1A0";
-		width = 16;
-		height = 40;
-	}
-	801
-	{
-		title = "Sunflower";
-		sprite = "FWR2A0";
-		width = 16;
-		height = 96;
-	}
-	802
-	{
-		title = "Budding Flower";
-		sprite = "FWR3A0";
-		width = 8;
-		height = 32;
-	}
-	803
-	{
-		title = "Blueberry Bush";
-		sprite = "BUS3A0";
-		width = 16;
-		height = 32;
-	}
-	804
-	{
-		title = "Berry Bush";
-		sprite = "BUS1A0";
-		width = 16;
-		height = 32;
-	}
-	805
-	{
-		title = "Bush";
-		sprite = "BUS2A0";
-		width = 16;
-		height = 32;
-	}
-	806
-	{
-		title = "GFZ Tree";
-		sprite = "TRE1A0";
-		width = 20;
-		height = 128;
-	}
-	807
-	{
-		title = "GFZ Berry Tree";
-		sprite = "TRE1B0";
-		width = 20;
-		height = 128;
-	}
-	808
-	{
-		title = "GFZ Cherry Tree";
-		sprite = "TRE1C0";
-		width = 20;
-		height = 128;
-	}
-	809
-	{
-		title = "Checkered Tree";
-		sprite = "TRE2A0";
-		width = 20;
-		height = 200;
-	}
-	810
-	{
-		title = "Checkered Tree (Sunset)";
-		sprite = "TRE2B0";
-		width = 20;
-		height = 200;
-	}
-	811
-	{
-		title = "Polygon Tree";
-		sprite = "TRE4A0";
-		width = 20;
-		height = 200;
-	}
-	812
-	{
-		title = "Bush Tree";
-		sprite = "TRE5A0";
-		width = 20;
-		height = 200;
-	}
-	813
-	{
-		title = "Red Bush Tree";
-		sprite = "TRE5B0";
-		width = 20;
-		height = 200;
-	}
-}
-
-technohill
-{
-	color = 10; // Green
-	title = "Techno Hill";
-
-	900
-	{
-		title = "THZ Steam Flower";
-		sprite = "THZPA0";
-		width = 8;
-		height = 32;
-	}
-	901
-	{
-		title = "Alarm";
-		sprite = "ALRMA0";
-		width = 8;
-		height = 16;
-		hangs = 1;
-	}
-	902
-	{
-		title = "THZ Spin Flower (Red)";
-		sprite = "FWR5A0";
-		width = 16;
-		height = 64;
-	}
-	903
-	{
-		title = "THZ Spin Flower (Yellow)";
-		sprite = "FWR6A0";
-		width = 16;
-		height = 64;
-	}
-	904
-	{
-		arrow = 1;
-		title = "Whistlebush";
-		sprite = "THZTA0";
-		width = 16;
-		height = 64;
-	}
-}
-
-deepsea
-{
-	color = 10; // Green
-	title = "Deep Sea";
-
-	1000
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Gargoyle";
-		sprite = "GARGA1";
-		width = 16;
-		height = 40;
-	}
-	1009
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Gargoyle (Big)";
-		sprite = "GARGB1";
-		width = 32;
-		height = 80;
-	}
-	1001
-	{
-		title = "Seaweed";
-		sprite = "SEWEA0";
-		width = 24;
-		height = 56;
-	}
-	1002
-	{
-		title = "Dripping Water";
-		sprite = "DRIPD0";
-		width = 8;
-		height = 16;
-		hangs = 1;
-	}
-	1003
-	{
-		title = "Coral (Green)";
-		sprite = "CORLA0";
-		width = 29;
-		height = 40;
-	}
-	1004
-	{
-		title = "Coral (Red)";
-		sprite = "CORLB0";
-		width = 30;
-		height = 53;
-	}
-	1005
-	{
-		title = "Coral (Orange)";
-		sprite = "CORLC0";
-		width = 28;
-		height = 41;
-	}
-	1006
-	{
-		title = "Blue Crystal";
-		sprite = "BCRYA1";
-		width = 8;
-		height = 16;
-	}
-	1007
-	{
-		title = "Kelp";
-		sprite = "KELPA0";
-		width = 16;
-		height = 292;
-	}
-	1008
-	{
-		title = "Stalagmite (DSZ1)";
-		sprite = "DSTGA0";
-		width = 8;
-		height = 116;
-	}
-	1010
-	{
-		arrow = 1;
-		title = "Light Beam";
-		sprite = "LIBEARAL";
-		width = 16;
-		height = 16;
-	}
-	1011
-	{
-		title = "Stalagmite (DSZ2)";
-		sprite = "DSTGA0";
-		width = 8;
-		height = 116;
-	}
-	1012
-	{
-		arrow = 1;
-		title = "Big Floating Mine";
-		width = 28;
-		height = 56;
-		sprite = "BMNEA1";
-	}
-	1013
-	{
-		title = "Animated Kelp";
-		sprite = "ALGAA0";
-		width = 48;
-		height = 120;
-	}
-	1014
-	{
-		title = "Large Coral (Brown)";
-		sprite = "CORLD0";
-		width = 56;
-		height = 112;
-	}
-	1015
-	{
-		title = "Large Coral (Beige)";
-		sprite = "CORLE0";
-		width = 56;
-		height = 112;
-	}
-}
-
-castleeggman
-{
-	color = 10; // Green
-	title = "Castle Eggman";
-
-	1100
-	{
-		title = "Chain (Decorative)";
-		sprite = "CHANA0";
-		width = 4;
-		height = 128;
-		hangs = 1;
-	}
-	1101
-	{
-		title = "Torch";
-		sprite = "FLAMA0E0";
-		width = 8;
-		height = 32;
-	}
-	1102
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Eggman Statue";
-		sprite = "ESTAA1";
-		width = 32;
-		height = 240;
-	}
-	1103
-	{
-		title = "CEZ Flower";
-		sprite = "FWR4A0";
-		width = 16;
-		height = 40;
-	}
-	1104
-	{
-		title = "Mace Spawnpoint";
-		sprite = "SMCEA0";
-		width = 17;
-		height = 34;
-	}
-	1105
-	{
-		title = "Chain with Maces Spawnpoint";
-		sprite = "SMCEA0";
-		width = 17;
-		height = 34;
-	}
-	1106
-	{
-		title = "Chained Spring Spawnpoint";
-		sprite = "YSPBA0";
-		width = 17;
-		height = 34;
-	}
-	1107
-	{
-		title = "Chain Spawnpoint";
-		sprite = "BMCHA0";
-		width = 17;
-		height = 34;
-	}
-	1108
-	{
-		arrow = 1;
-		title = "Hidden Chain Spawnpoint";
-		sprite = "internal:chain3";
-		width = 17;
-		height = 34;
-	}
-	1109
-	{
-		title = "Firebar Spawnpoint";
-		sprite = "BFBRA0";
-		width = 17;
-		height = 34;
-	}
-	1110
-	{
-		title = "Custom Mace Spawnpoint";
-		sprite = "SMCEA0";
-		width = 17;
-		height = 34;
-	}
-	1111
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Crawla Statue";
-		sprite = "CSTAA1";
-		width = 16;
-		height = 40;
-	}
-	1112
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Lance-a-Bot Statue";
-		sprite = "CBBSA1";
-		width = 32;
-		height = 72;
-	}
-	1114
-	{
-		title = "Pine Tree";
-		sprite = "PINEA0";
-		width = 16;
-		height = 628;
-	}
-	1115
-	{
-		title = "CEZ Shrub (Small)";
-		sprite = "CEZBA0";
-		width = 16;
-		height = 24;
-	}
-	1116
-	{
-		title = "CEZ Shrub (Large)";
-		sprite = "CEZBB0";
-		width = 32;
-		height = 48;
-	}
-	1117
-	{
-		arrow = 1;
-		title = "Pole Banner (Red)";
-		sprite = "BANRA0";
-		width = 40;
-		height = 224;
-	}
-	1118
-	{
-		arrow = 1;
-		title = "Pole Banner (Blue)";
-		sprite = "BANRA0";
-		width = 40;
-		height = 224;
-	}
-	1119
-	{
-		title = "Candle";
-		sprite = "CNDLA0";
-		width = 8;
-		height = 48;
-	}
-	1120
-	{
-		title = "Candle Pricket";
-		sprite = "CNDLB0";
-		width = 8;
-		height = 176;
-	}
-	1121
-	{
-		title = "Flame Holder";
-		sprite = "FLMHA0";
-		width = 24;
-		height = 80;
-	}
-	1122
-	{
-		title = "Fire Torch";
-		sprite = "CTRCA0";
-		width = 16;
-		height = 80;
-	}
-	1123
-	{
-		title = "Cannonball Launcher";
-		sprite = "internal:cannonball";
-		width = 8;
-		height = 16;
-	}
-	1124
-	{
-		blocking = 2;
-		title = "Cannonball";
-		sprite = "CBLLA0";
-		width = 20;
-		height = 40;
-	}
-	1125
-	{
-		title = "Brambles";
-		sprite = "CABRALAR";
-		width = 48;
-		height = 32;
-	}
-	1126
-	{
-		title = "Invisible Lockon Object";
-		sprite = "LCKNC0";
-		width = 16;
-		height = 32;
-	}
-	1127
-	{
-		title = "Spectator Eggrobo";
-		sprite = "EGR1A1";
-		width = 20;
-		height = 72;
-	}
-	1128
-	{
-		arrow = 1;
-		title = "Waving Flag (Red)";
-		sprite = "CFLGA0";
-		width = 8;
-		height = 208;
-	}
-	1129
-	{
-		arrow = 1;
-		title = "Waving Flag (Blue)";
-		sprite = "CFLGA0";
-		width = 8;
-		height = 208;
-	}
-}
-
-aridcanyon
-{
-	color = 10; // Green
-	title = "Arid Canyon";
-
-	1200
-	{
-		title = "Tumbleweed (Big)";
-		sprite = "BTBLA0";
-		width = 24;
-		height = 48;
-	}
-	1201
-	{
-		title = "Tumbleweed (Small)";
-		sprite = "STBLA0";
-		width = 12;
-		height = 24;
-	}
-	1202
-	{
-		arrow = 1;
-		title = "Rock Spawner";
-		sprite = "ROIAA0";
-		width = 8;
-		height = 16;
-	}
-	1203
-	{
-		title = "Tiny Red Flower Cactus";
-		sprite = "CACTA0";
-		width = 13;
-		height = 24;
-	}
-	1204
-	{
-		title = "Small Red Flower Cactus";
-		sprite = "CACTB0";
-		width = 15;
-		height = 52;
-	}
-	1205
-	{
-		title = "Tiny Blue Flower Cactus";
-		sprite = "CACTC0";
-		width = 13;
-		height = 24;
-	}
-	1206
-	{
-		title = "Small Blue Flower Cactus";
-		sprite = "CACTD0";
-		width = 15;
-		height = 52;
-	}
-	1207
-	{
-		title = "Prickly Pear";
-		sprite = "CACTE0";
-		width = 32;
-		height = 96;
-	}
-	1208
-	{
-		title = "Barrel Cactus";
-		sprite = "CACTF0";
-		width = 20;
-		height = 128;
-	}
-	1209
-	{
-		title = "Tall Barrel Cactus";
-		sprite = "CACTG0";
-		width = 24;
-		height = 224;
-	}
-	1210
-	{
-		title = "Armed Cactus";
-		sprite = "CACTH0";
-		width = 24;
-		height = 256;
-	}
-	1211
-	{
-		title = "Ball Cactus";
-		sprite = "CACTI0";
-		width = 48;
-		height = 96;
-	}
-	1212
-	{
-		title = "Caution Sign";
-		sprite = "WWSGAR";
-		width = 22;
-		height = 64;
-	}
-	1213
-	{
-		title = "Cacti Sign";
-		sprite = "WWS2AR";
-		width = 22;
-		height = 64;
-	}
-	1214
-	{
-		title = "Sharp Turn Sign";
-		sprite = "WWS3ALAR";
-		width = 16;
-		height = 192;
-	}
-	1215
-	{
-		title = "Mine Oil Lamp";
-		sprite = "OILLA0";
-		width = 22;
-		height = 64;
-		hangs = 1;
-	}
-	1216
-	{
-		title = "TNT Barrel";
-		sprite = "BARRA1";
-		width = 24;
-		height = 63;
-	}
-	1217
-	{
-		title = "TNT Proximity Shell";
-		sprite = "REMTA0";
-		width = 64;
-		height = 40;
-	}
-	1218
-	{
-		title = "Dust Devil";
-		sprite = "TAZDCR";
-		width = 80;
-		height = 416;
-	}
-	1219
-	{
-		title = "Minecart Spawner";
-		sprite = "MCRTCLFR";
-		width = 22;
-		height = 32;
-	}
-	1220
-	{
-		title = "Minecart Stopper";
-		sprite = "MCRTIR";
-		width = 32;
-		height = 32;
-	}
-	1221
-	{
-		title = "Minecart Saloon Door";
-		sprite = "SALDARAL";
-		width = 96;
-		height = 160;
-	}
-	1222
-	{
-		title = "Train Cameo Spawner";
-		sprite = "TRAEBRBL";
-		width = 28;
-		height = 32;
-	}
-	1223
-	{
-		title = "Train Dust Spawner";
-		sprite = "ADSTA0";
-		width = 4;
-		height = 4;
-	}
-	1224
-	{
-		title = "Train Steam Spawner";
-		sprite = "STEAA0";
-		width = 4;
-		height = 4;
-	}
-	1229
-	{
-		title = "Minecart Switch Point";
-		sprite = "internal:zoom";
-		width = 8;
-		height = 16;
-	}
-	1230
-	{
-		title = "Tiny Cactus";
-		sprite = "CACTJ0";
-		width = 13;
-		height = 28;
-	}
-	1231
-	{
-		title = "Small Cactus";
-		sprite = "CACTK0";
-		width = 15;
-		height = 60;
-	}
-}
-
-redvolcano
-{
-	color = 10; // Green
-	title = "Red Volcano";
-
-	1300
-	{
-		arrow = 1;
-		title = "Flame Jet (Horizontal)";
-		sprite = "internal:flameh";
-		width = 16;
-		height = 40;
-	}
-	1301
-	{
-		title = "Flame Jet (Vertical)";
-		sprite = "internal:flamev";
-		width = 16;
-		height = 40;
-	}
-	1302
-	{
-		title = "Spinning Flame Jet (Counter-Clockwise)";
-		sprite = "internal:flame2";
-		width = 16;
-		height = 24;
-	}
-	1303
-	{
-		title = "Spinning Flame Jet (Clockwise)";
-		sprite = "internal:flame1";
-		width = 16;
-		height = 24;
-	}
-	1304
-	{
-		title = "Lavafall";
-		sprite = "LFALF0";
-		width = 30;
-		height = 32;
-	}
-	1305
-	{
-		title = "Rollout Rock";
-		sprite = "PUMIA1A5";
-		width = 30;
-		height = 60;
-	}
-	1306
-	{
-		title = "Big Fern";
-		sprite = "JPLAB0";
-		width = 32;
-		height = 48;
-	}
-	1307
-	{
-		title = "Jungle Palm";
-		sprite = "JPLAC0";
-		width = 32;
-		height = 48;
-	}
-	1308
-	{
-		title = "Torch Flower";
-		sprite = "TFLOA0";
-		width = 14;
-		height = 110;
-	}
-	1309
-	{
-		title = "RVZ1 Wall Vine (Long)";
-		sprite = "WVINALAR";
-		width = 1;
-		height = 288;
-	}
-	1310
-	{
-		title = "RVZ1 Wall Vine (Short)";
-		sprite = "WVINBLBR";
-		width = 1;
-		height = 288;
-	}
-}
-
-botanicserenity
-{
-	color = 10; // Green
-	title = "Botanic Serenity";
-	width = 16;
-	height = 32;
-	sprite = "BSZ1A0";
-	1400
-	{
-		title = "Tall Flower (Red)";
-		sprite = "BSZ1A0";
-	}
-	1401
-	{
-		title = "Tall Flower (Purple)";
-		sprite = "BSZ1B0";
-	}
-	1402
-	{
-		title = "Tall Flower (Blue)";
-		sprite = "BSZ1C0";
-	}
-	1403
-	{
-		title = "Tall Flower (Cyan)";
-		sprite = "BSZ1D0";
-	}
-	1404
-	{
-		title = "Tall Flower (Yellow)";
-		sprite = "BSZ1E0";
-	}
-	1405
-	{
-		title = "Tall Flower (Orange)";
-		sprite = "BSZ1F0";
-	}
-	1410
-	{
-		title = "Medium Flower (Red)";
-		sprite = "BSZ2A0";
-	}
-	1411
-	{
-		title = "Medium Flower (Purple)";
-		sprite = "BSZ2B0";
-	}
-	1412
-	{
-		title = "Medium Flower (Blue)";
-		sprite = "BSZ2C0";
-	}
-	1413
-	{
-		title = "Medium Flower (Cyan)";
-		sprite = "BSZ2D0";
-	}
-	1414
-	{
-		title = "Medium Flower (Yellow)";
-		sprite = "BSZ2E0";
-	}
-	1415
-	{
-		title = "Medium Flower (Orange)";
-		sprite = "BSZ2F0";
-	}
-	1420
-	{
-		title = "Short Flower (Red)";
-		sprite = "BSZ3A0";
-	}
-	1421
-	{
-		title = "Short Flower (Purple)";
-		sprite = "BSZ3B0";
-	}
-	1422
-	{
-		title = "Short Flower (Blue)";
-		sprite = "BSZ3C0";
-	}
-	1423
-	{
-		title = "Short Flower (Cyan)";
-		sprite = "BSZ3D0";
-	}
-	1424
-	{
-		title = "Short Flower (Yellow)";
-		sprite = "BSZ3E0";
-	}
-	1425
-	{
-		title = "Short Flower (Orange)";
-		sprite = "BSZ3F0";
-	}
-	1430
-	{
-		title = "Tulip (Red)";
-		sprite = "BST1A0";
-	}
-	1431
-	{
-		title = "Tulip (Purple)";
-		sprite = "BST2A0";
-	}
-	1432
-	{
-		title = "Tulip (Blue)";
-		sprite = "BST3A0";
-	}
-	1433
-	{
-		title = "Tulip (Cyan)";
-		sprite = "BST4A0";
-	}
-	1434
-	{
-		title = "Tulip (Yellow)";
-		sprite = "BST5A0";
-	}
-	1435
-	{
-		title = "Tulip (Orange)";
-		sprite = "BST6A0";
-	}
-	1440
-	{
-		title = "Cluster (Red)";
-		sprite = "BSZ5A0";
-	}
-	1441
-	{
-		title = "Cluster (Purple)";
-		sprite = "BSZ5B0";
-	}
-	1442
-	{
-		title = "Cluster (Blue)";
-		sprite = "BSZ5C0";
-	}
-	1443
-	{
-		title = "Cluster (Cyan)";
-		sprite = "BSZ5D0";
-	}
-	1444
-	{
-		title = "Cluster (Yellow)";
-		sprite = "BSZ5E0";
-	}
-	1445
-	{
-		title = "Cluster (Orange)";
-		sprite = "BSZ5F0";
-	}
-	1450
-	{
-		title = "Bush (Red)";
-		sprite = "BSZ6A0";
-	}
-	1451
-	{
-		title = "Bush (Purple)";
-		sprite = "BSZ6B0";
-	}
-	1452
-	{
-		title = "Bush (Blue)";
-		sprite = "BSZ6C0";
-	}
-	1453
-	{
-		title = "Bush (Cyan)";
-		sprite = "BSZ6D0";
-	}
-	1454
-	{
-		title = "Bush (Yellow)";
-		sprite = "BSZ6E0";
-	}
-	1455
-	{
-		title = "Bush (Orange)";
-		sprite = "BSZ6F0";
-	}
-	1460
-	{
-		title = "Vine (Red)";
-		sprite = "BSZ7A0";
-	}
-	1461
-	{
-		title = "Vine (Purple)";
-		sprite = "BSZ7B0";
-	}
-	1462
-	{
-		title = "Vine (Blue)";
-		sprite = "BSZ7C0";
-	}
-	1463
-	{
-		title = "Vine (Cyan)";
-		sprite = "BSZ7D0";
-	}
-	1464
-	{
-		title = "Vine (Yellow)";
-		sprite = "BSZ7E0";
-	}
-	1465
-	{
-		title = "Vine (Orange)";
-		sprite = "BSZ7F0";
-	}
-	1470
-	{
-		title = "BSZ Shrub";
-		sprite = "BSZ8A0";
-	}
-	1471
-	{
-		title = "BSZ Clover";
-		sprite = "BSZ8B0";
-	}
-	1473
-	{
-		title = "Palm Tree (Big)";
-		width = 16;
-		height = 160;
-		sprite = "BSZ8D0";
-	}
-	1475
-	{
-		title = "Palm Tree (Small)";
-		width = 16;
-		height = 80;
-		sprite = "BSZ8F0";
-	}
-}
-
-azuretemple
-{
-	color = 10; // Green
-	title = "Azure Temple";
-
-	1500
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Glaregoyle";
-		sprite = "BGARA1";
-		width = 16;
-		height = 40;
-	}
-	1501
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Glaregoyle (Up)";
-		sprite = "BGARA1";
-		width = 16;
-		height = 40;
-	}
-	1502
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Glaregoyle (Down)";
-		sprite = "BGARA1";
-		width = 16;
-		height = 40;
-	}
-	1503
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Glaregoyle (Long)";
-		sprite = "BGARA1";
-		width = 16;
-		height = 40;
-	}
-	1504
-	{
-		title = "ATZ Target";
-		sprite = "RCRYB0";
-		width = 24;
-		height = 32;
-	}
-	1505
-	{
-		title = "Green Flame";
-		sprite = "CFLMA0E0";
-		width = 8;
-		height = 32;
-	}
-	1506
-	{
-		arrow = 1;
-		blocking = 2;
-		title = "Blue Gargoyle";
-		sprite = "BGARD1";
-		width = 16;
-		height = 40;
-	}
-}
-
-dreamhill
-{
-	color = 10; // Green
-	title = "Dream Hill";
-
-	1600
-	{
-		title = "Spring Tree";
-		sprite = "TRE6A0";
-		width = 16;
-		height = 32;
-	}
-	1601
-	{
-		title = "Shleep";
-		sprite = "SHLPA0";
-		width = 24;
-		height = 32;
-	}
-	1602
-	{
-		title = "Nightopian";
-		sprite = "NTPNA1";
-		width = 16;
-		height = 40;
-	}
-}
-
-nightstrk
-{
-	color = 13; // Pink
-	title = "NiGHTS Track";
-	width = 8;
-	height = 4096;
-	sprite = "UNKNA0";
-
-	1700
-	{
-		title = "Axis";
-		sprite = "internal:axis1";
-		circle = 1;
-	}
-	1701
-	{
-		title = "Axis Transfer";
-		sprite = "internal:axis2";
-	}
-	1702
-	{
-		title = "Axis Transfer Line";
-		sprite = "internal:axis3";
-	}
-	1710
-	{
-		title = "Ideya Capture";
-		sprite = "CAPSA0";
-		width = 72;
-		height = 144;
-	}
-}
-
-nights
-{
-	color = 13; // Pink
-	title = "NiGHTS Items";
-	width = 16;
-	height = 32;
-
-	1703
-	{
-		title = "Ideya Drone";
-		sprite = "NDRNA1";
-		width = 16;
-		height = 56;
-	}
-	1704
-	{
-		arrow = 1;
-		title = "NiGHTS Bumper";
-		sprite = "NBMPG3G7";
-		width = 32;
-		height = 64;
-	}
-	1705
-	{
-		arrow = 1;
-		title = "Hoop (Generic)";
-		sprite = "HOOPA0";
-		width = 80;
-		height = 160;
-	}
-	1706
-	{
-		title = "Blue Sphere";
-		sprite = "SPHRA0";
-		width = 16;
-		height = 24;
-	}
-	1707
-	{
-		title = "Super Paraloop";
-		sprite = "NPRUA0";
-	}
-	1708
-	{
-		title = "Drill Refill";
-		sprite = "NPRUB0";
-	}
-	1709
-	{
-		title = "Nightopian Helper";
-		sprite = "NPRUC0";
-	}
-	1711
-	{
-		title = "Extra Time";
-		sprite = "NPRUD0";
-	}
-	1712
-	{
-		title = "Link Freeze";
-		sprite = "NPRUE0";
-	}
-	1713
-	{
-		arrow = 1;
-		title = "Hoop (Customizable)";
-		sprite = "HOOPA0";
-		width = 80;
-		height = 160;
-	}
-	1714
-	{
-		title = "Ideya Anchor Point";
-		sprite = "internal:axis1";
-		width = 8;
-		height = 16;
-	}
-}
-
-mario
-{
-	color = 6; // Brown
-	title = "Mario";
-
-	1800
-	{
-		title = "Coin";
-		sprite = "COINA0";
-		width = 16;
-		height = 24;
-	}
-	1801
-	{
-		arrow = 1;
-		title = "Goomba";
-		sprite = "GOOMA0";
-		width = 24;
-		height = 32;
-	}
-	1802
-	{
-		arrow = 1;
-		title = "Goomba (Blue)";
-		sprite = "BGOMA0";
-		width = 24;
-		height = 32;
-	}
-	1803
-	{
-		title = "Fire Flower";
-		sprite = "FFWRB0";
-		width = 16;
-		height = 32;
-	}
-	1804
-	{
-		title = "Koopa Shell";
-		sprite = "SHLLA1";
-		width = 16;
-		height = 20;
-	}
-	1805
-	{
-		title = "Puma (Jumping Fireball)";
-		sprite = "PUMAA0";
-		width = 8;
-		height = 16;
-	}
-	1806
-	{
-		title = "King Bowser";
-		sprite = "KOOPA0";
-		width = 16;
-		height = 48;
-	}
-	1807
-	{
-		title = "Axe";
-		sprite = "MAXEA0";
-		width = 8;
-		height = 16;
-	}
-	1808
-	{
-		title = "Bush (Short)";
-		sprite = "MUS1A0";
-		width = 16;
-		height = 32;
-	}
-	1809
-	{
-		title = "Bush (Tall)";
-		sprite = "MUS2A0";
-		width = 16;
-		height = 32;
-	}
-	1810
-	{
-		title = "Toad";
-		sprite = "TOADA0";
-		width = 8;
-		height = 32;
-	}
-}
-
-christmasdisco
-{
-	color = 10; // Green
-	title = "Christmas & Disco";
-
-	1850
-	{
-		title = "Christmas Pole";
-		sprite = "XMS1A0";
-		width = 16;
-		height = 40;
-	}
-	1851
-	{
-		title = "Candy Cane";
-		sprite = "XMS2A0";
-		width = 8;
-		height = 32;
-	}
-	1852
-	{
-		blocking = 2;
-		title = "Snowman";
-		sprite = "XMS3A0";
-		width = 16;
-		height = 64;
-	}
-	1853
-	{
-		blocking = 2;
-		title = "Snowman (With Hat)";
-		sprite = "XMS3B0";
-		width = 16;
-		height = 80;
-	}
-	1854
+	editor
 	{
-		title = "Lamp Post";
-		sprite = "XMS4A0";
+		color = 15; // White
+		arrow = 1;
+		title = "<Editor Things>";
+		error = -1;
 		width = 8;
-		height = 120;
+		height = 16;
+		sort = 1;
+
+		3328 = "3D Mode Start";
 	}
-	1855
+
+	starts
 	{
-		title = "Lamp Post (Snow)";
-		sprite = "XMS4B0";
-		width = 8;
-		height = 120;
+		color = 1; // Blue
+		arrow = 1;
+		title = "Player Starts";
+		width = 16;
+		height = 48;
+		sprite = "PLAYA0";
+
+		1
+		{
+			title = "Player 01 Start";
+			sprite = "PLAYA0";
+		}
+		2
+		{
+			title = "Player 02 Start";
+			sprite = "PLAYA0";
+		}
+		3
+		{
+			title = "Player 03 Start";
+			sprite = "PLAYA0";
+		}
+		4
+		{
+			title = "Player 04 Start";
+			sprite = "PLAYA0";
+		}
+		5
+		{
+			title = "Player 05 Start";
+			sprite = "PLAYA0";
+		}
+		6
+		{
+			title = "Player 06 Start";
+			sprite = "PLAYA0";
+		}
+		7
+		{
+			title = "Player 07 Start";
+			sprite = "PLAYA0";
+		}
+		8
+		{
+			title = "Player 08 Start";
+			sprite = "PLAYA0";
+		}
+		9
+		{
+			title = "Player 09 Start";
+			sprite = "PLAYA0";
+		}
+		10
+		{
+			title = "Player 10 Start";
+			sprite = "PLAYA0";
+		}
+		11
+		{
+			title = "Player 11 Start";
+			sprite = "PLAYA0";
+		}
+		12
+		{
+			title = "Player 12 Start";
+			sprite = "PLAYA0";
+		}
+		13
+		{
+			title = "Player 13 Start";
+			sprite = "PLAYA0";
+		}
+		14
+		{
+			title = "Player 14 Start";
+			sprite = "PLAYA0";
+		}
+		15
+		{
+			title = "Player 15 Start";
+			sprite = "PLAYA0";
+		}
+		16
+		{
+			title = "Player 16 Start";
+			sprite = "PLAYA0";
+		}
+		17
+		{
+			title = "Player 17 Start";
+			sprite = "PLAYA0";
+		}
+		18
+		{
+			title = "Player 18 Start";
+			sprite = "PLAYA0";
+		}
+		19
+		{
+			title = "Player 19 Start";
+			sprite = "PLAYA0";
+		}
+		20
+		{
+			title = "Player 20 Start";
+			sprite = "PLAYA0";
+		}
+		21
+		{
+			title = "Player 21 Start";
+			sprite = "PLAYA0";
+		}
+		22
+		{
+			title = "Player 22 Start";
+			sprite = "PLAYA0";
+		}
+		23
+		{
+			title = "Player 23 Start";
+			sprite = "PLAYA0";
+		}
+		24
+		{
+			title = "Player 24 Start";
+			sprite = "PLAYA0";
+		}
+		25
+		{
+			title = "Player 25 Start";
+			sprite = "PLAYA0";
+		}
+		26
+		{
+			title = "Player 26 Start";
+			sprite = "PLAYA0";
+		}
+		27
+		{
+			title = "Player 27 Start";
+			sprite = "PLAYA0";
+		}
+		28
+		{
+			title = "Player 28 Start";
+			sprite = "PLAYA0";
+		}
+		29
+		{
+			title = "Player 29 Start";
+			sprite = "PLAYA0";
+		}
+		30
+		{
+			title = "Player 30 Start";
+			sprite = "PLAYA0";
+		}
+		31
+		{
+			title = "Player 31 Start";
+			sprite = "PLAYA0";
+		}
+		32
+		{
+			title = "Player 32 Start";
+			sprite = "PLAYA0";
+		}
+		33
+		{
+			title = "Match Start";
+			sprite = "NDRNA2A8";
+		}
+		34
+		{
+			title = "CTF Red Team Start";
+			sprite = "SIGNG0";
+		}
+		35
+		{
+			title = "CTF Blue Team Start";
+			sprite = "SIGNE0";
+		}
 	}
-	1856
+
+	enemies
 	{
-		title = "Hanging Star";
-		sprite = "XMS5A0";
-		width = 4;
-		height = 80;
-		hangs = 1;
+		color = 9; // Light_Blue
+		arrow = 1;
+		title = "Enemies";
+
+		100
+		{
+			title = "Crawla (Blue)";
+			sprite = "POSSA1";
+			width = 24;
+			height = 32;
+		}
+		101
+		{
+			title = "Crawla (Red)";
+			sprite = "SPOSA1";
+			width = 24;
+			height = 32;
+		}
+		102
+		{
+			title = "Stupid Dumb Unnamed RoboFish";
+			sprite = "FISHA0";
+			width = 8;
+			height = 28;
+		}
+		103
+		{
+			title = "Buzz (Gold)";
+			sprite = "BUZZA1";
+			width = 28;
+			height = 40;
+		}
+		104
+		{
+			title = "Buzz (Red)";
+			sprite = "RBUZA1";
+			width = 28;
+			height = 40;
+		}
+		108
+		{
+			title = "Deton";
+			sprite = "DETNA1";
+			width = 20;
+			height = 32;
+		}
+		110
+		{
+			title = "Turret";
+			sprite = "TRETA1";
+			width = 16;
+			height = 32;
+		}
+		111
+		{
+			title = "Pop-up Turret";
+			sprite = "TURRI1";
+			width = 12;
+			height = 64;
+		}
+		122
+		{
+			title = "Spring Shell (Green)";
+			sprite = "SSHLA1";
+			width = 24;
+			height = 40;
+		}
+		125
+		{
+			title = "Spring Shell (Yellow)";
+			sprite = "SSHLI1";
+			width = 24;
+			height = 40;
+		}
+		109
+		{
+			title = "Skim";
+			sprite = "SKIMA1";
+			width = 16;
+			height = 24;
+		}
+		113
+		{
+			title = "Jet Jaw";
+			sprite = "JJAWA3A7";
+			width = 12;
+			height = 20;
+		}
+		126
+		{
+			title = "Crushstacean";
+			sprite = "CRABA0";
+			width = 24;
+			height = 32;
+		}
+		138
+		{
+			title = "Banpyura";
+			sprite = "CR2BA0";
+			width = 24;
+			height = 32;
+		}
+		117
+		{
+			title = "Robo-Hood";
+			sprite = "ARCHA1";
+			width = 24;
+			height = 32;
+		}
+		118
+		{
+			title = "Lance-a-Bot";
+			sprite = "CBFSA1";
+			width = 32;
+			height = 72;
+		}
+		1113
+		{
+			title = "Suspicious Lance-a-Bot Statue";
+			sprite = "CBBSA1";
+			width = 32;
+			height = 72;
+		}
+		119
+		{
+			title = "Egg Guard";
+			sprite = "ESHIA1";
+			width = 16;
+			height = 48;
+		}
+		115
+		{
+			title = "Bird Aircraft Strike Hazard";
+			sprite = "VLTRF1";
+			width = 12;
+			height = 24;
+		}
+		120
+		{
+			title = "Green Snapper";
+			sprite = "GSNPA1";
+			width = 24;
+			height = 24;
+		}
+		121
+		{
+			title = "Minus";
+			sprite = "MNUSA0";
+			width = 24;
+			height = 32;
+		}
+		134
+		{
+			title = "Canarivore";
+			sprite = "CANAA0";
+			width = 12;
+			height = 80;
+			hangs = 1;
+		}
+		123
+		{
+			title = "Unidus";
+			sprite = "UNIDA1";
+			width = 18;
+			height = 36;
+		}
+		135
+		{
+			title = "Pterabyte Spawner";
+			sprite = "PTERA2A8";
+			width = 16;
+			height = 16;
+		}
+		136
+		{
+			title = "Pyre Fly";
+			sprite = "PYREA0";
+			width = 24;
+			height = 34;
+		}
+		137
+		{
+			title = "Dragonbomber";
+			sprite = "DRABA1";
+			width = 28;
+			height = 48;
+		}
+		105
+		{
+			title = "Jetty-Syn Bomber";
+			sprite = "JETBB1";
+			width = 20;
+			height = 50;
+		}
+		106
+		{
+			title = "Jetty-Syn Gunner";
+			sprite = "JETGB1";
+			width = 20;
+			height = 48;
+		}
+		112
+		{
+			title = "Spincushion";
+			sprite = "SHRPA1";
+			width = 16;
+			height = 24;
+		}
+		114
+		{
+			title = "Snailer";
+			sprite = "SNLRA3A7";
+			width = 24;
+			height = 48;
+		}
+		129
+		{
+			title = "Penguinator";
+			sprite = "PENGA1";
+			width = 24;
+			height = 32;
+		}
+		130
+		{
+			title = "Pophat";
+			sprite = "POPHA1";
+			width = 24;
+			height = 32;
+		}
+		107
+		{
+			title = "Crawla Commander";
+			sprite = "CCOMA1";
+			width = 16;
+			height = 32;
+		}
+		131
+		{
+			title = "Spinbobert";
+			sprite = "SBOBB0";
+			width = 32;
+			height = 32;
+		}
+		132
+		{
+			title = "Cacolantern";
+			sprite = "CACOA0";
+			width = 32;
+			height = 32;
+		}
+		133
+		{
+			title = "Hangster";
+			sprite = "HBATC1";
+			width = 24;
+			height = 24;
+			hangs = 1;
+		}
+		127
+		{
+			title = "Hive Elemental";
+			sprite = "HIVEA0";
+			width = 32;
+			height = 80;
+		}
+		128
+		{
+			title = "Bumblebore";
+			sprite = "BUMBA1";
+			width = 16;
+			height = 32;
+		}
+		124
+		{
+			title = "Buggle";
+			sprite = "BBUZA1";
+			width = 20;
+			height = 24;
+		}
+		116
+		{
+			title = "Pointy";
+			sprite = "PNTYA1";
+			width = 8;
+			height = 16;
+		}
 	}
-	1857
+
+	bosses
 	{
-		title = "Berry Bush (Snow)";
-		sprite = "BUS1B0";
-		width = 16;
-		height = 32;
+		color = 8; // Dark_Gray
+		arrow = 1;
+		title = "Bosses";
+
+		200
+		{
+			title = "Egg Mobile";
+			sprite = "EGGMA1";
+			width = 24;
+			height = 76;
+		}
+		201
+		{
+			title = "Egg Slimer";
+			sprite = "EGGNA1";
+			width = 24;
+			height = 76;
+		}
+		202
+		{
+			title = "Sea Egg";
+			sprite = "EGGOA1";
+			width = 32;
+			height = 116;
+		}
+		203
+		{
+			title = "Egg Colosseum";
+			sprite = "EGGPA1";
+			width = 24;
+			height = 76;
+		}
+		204
+		{
+			title = "Fang";
+			sprite = "FANGA1";
+			width = 24;
+			height = 60;
+		}
+		206
+		{
+			title = "Brak Eggman (Old)";
+			sprite = "BRAKB1";
+			width = 48;
+			height = 160;
+		}
+		207
+		{
+			title = "Metal Sonic (Race)";
+			sprite = "METLI1";
+			width = 16;
+			height = 48;
+		}
+		208
+		{
+			title = "Metal Sonic (Battle)";
+			sprite = "METLC1";
+			width = 16;
+			height = 48;
+		}
+		209
+		{
+			title = "Brak Eggman";
+			sprite = "BRAK01";
+			width = 48;
+			height = 160;
+		}
+		290
+		{
+			arrow = 0;
+			title = "Boss Escape Point";
+			width = 8;
+			height = 16;
+			sprite = "internal:eggmanend";
+		}
+		291
+		{
+			arrow = 0;
+			title = "Egg Capsule Center";
+			width = 8;
+			height = 16;
+			sprite = "internal:capsule";
+		}
+		292
+		{
+			arrow = 0;
+			title = "Boss Waypoint";
+			width = 8;
+			height = 16;
+			sprite = "internal:eggmanway";
+		}
+		293
+		{
+			title = "Metal Sonic Gather Point";
+			sprite = "internal:metal";
+			width = 8;
+			height = 16;
+		}
+		294
+		{
+			title = "Fang Waypoint";
+			sprite = "internal:eggmanway";
+			width = 8;
+			height = 16;
+		}
 	}
-	1858
+
+	rings
 	{
-		title = "Bush (Snow)";
-		sprite = "BUS2B0";
-		width = 16;
-		height = 32;
+		color = 14; // Yellow
+		title = "Rings and Weapon Panels";
+		width = 24;
+		height = 24;
+		sprite = "RINGA0";
+
+		300
+		{
+			title = "Ring";
+			sprite = "RINGA0";
+			width = 16;
+		}
+		301
+		{
+			title = "Bounce Ring";
+			sprite = "internal:RNGBA0";
+		}
+		302
+		{
+			title = "Rail Ring";
+			sprite = "internal:RNGRA0";
+		}
+		303
+		{
+			title = "Infinity Ring";
+			sprite = "internal:RNGIA0";
+		}
+		304
+		{
+			title = "Automatic Ring";
+			sprite = "internal:RNGAA0";
+		}
+		305
+		{
+			title = "Explosion Ring";
+			sprite = "internal:RNGEA0";
+		}
+		306
+		{
+			title = "Scatter Ring";
+			sprite = "internal:RNGSA0";
+		}
+		307
+		{
+			title = "Grenade Ring";
+			sprite = "internal:RNGGA0";
+		}
+		308
+		{
+			title = "CTF Team Ring (Red)";
+			sprite = "internal:RRNGA0";
+			width = 16;
+		}
+		309
+		{
+			title = "CTF Team Ring (Blue)";
+			sprite = "internal:BRNGA0";
+			width = 16;
+		}
+		330
+		{
+			title = "Bounce Ring Panel";
+			sprite = "internal:PIKBA0";
+		}
+		331
+		{
+			title = "Rail Ring Panel";
+			sprite = "internal:PIKRA0";
+		}
+		332
+		{
+			title = "Automatic Ring Panel";
+			sprite = "internal:PIKAA0";
+		}
+		333
+		{
+			title = "Explosion Ring Panel";
+			sprite = "internal:PIKEA0";
+		}
+		334
+		{
+			title = "Scatter Ring Panel";
+			sprite = "internal:PIKSA0";
+		}
+		335
+		{
+			title = "Grenade Ring Panel";
+			sprite = "internal:PIKGA0";
+		}
 	}
-	1859
+
+	collectibles
 	{
-		title = "Blueberry Bush (Snow)";
-		sprite = "BUS3B0";
+		color = 10; // Light_Green
+		title = "Other Collectibles";
 		width = 16;
 		height = 32;
+		sort = 1;
+		sprite = "CEMGA0";
+
+		310
+		{
+			title = "CTF Red Flag";
+			sprite = "RFLGA0";
+			width = 24;
+			height = 64;
+		}
+		311
+		{
+			title = "CTF Blue Flag";
+			sprite = "BFLGA0";
+			width = 24;
+			height = 64;
+		}
+		312
+		{
+			title = "Emerald Token";
+			sprite = "TOKEA0";
+			width = 16;
+			height = 32;
+		}
+		313
+		{
+			title = "Chaos Emerald 1 (Green)";
+			sprite = "CEMGA0";
+		}
+		314
+		{
+			title = "Chaos Emerald 2 (Purple)";
+			sprite = "CEMGB0";
+		}
+		315
+		{
+			title = "Chaos Emerald 3 (Blue)";
+			sprite = "CEMGC0";
+		}
+		316
+		{
+			title = "Chaos Emerald 4 (Cyan)";
+			sprite = "CEMGD0";
+		}
+		317
+		{
+			title = "Chaos Emerald 5 (Orange)";
+			sprite = "CEMGE0";
+		}
+		318
+		{
+			title = "Chaos Emerald 6 (Red)";
+			sprite = "CEMGF0";
+		}
+		319
+		{
+			title = "Chaos Emerald 7 (Gray)";
+			sprite = "CEMGG0";
+		}
+		320
+		{
+			title = "Emerald Hunt Location";
+			sprite = "SHRDA0";
+		}
+		321
+		{
+			title = "Match Chaos Emerald Spawn";
+			sprite = "CEMGA0";
+		}
+		322
+		{
+			title = "Emblem";
+			sprite = "EMBMA0";
+			width = 16;
+			height = 30;
+		}
 	}
-	1875
+
+	boxes
 	{
-		title = "Disco Ball";
-		sprite = "DBALA0";
-		width = 16;
-		height = 54;
-		hangs = 1;
+		color = 7; // Gray
+		blocking = 2;
+		title = "Monitors";
+		width = 18;
+		height = 40;
+
+		400
+		{
+			title = "Super Ring (10 Rings)";
+			sprite = "TVRIA0";
+		}
+		401
+		{
+			title = "Pity Shield";
+			sprite = "TVPIA0";
+		}
+		402
+		{
+			title = "Attraction Shield";
+			sprite = "TVATA0";
+		}
+		403
+		{
+			title = "Force Shield";
+			sprite = "TVFOA0";
+		}
+		404
+		{
+			title = "Armageddon Shield";
+			sprite = "TVARA0";
+		}
+		405
+		{
+			title = "Whirlwind Shield";
+			sprite = "TVWWA0";
+		}
+		406
+		{
+			title = "Elemental Shield";
+			sprite = "TVELA0";
+		}
+		407
+		{
+			title = "Super Sneakers";
+			sprite = "TVSSA0";
+		}
+		408
+		{
+			title = "Invincibility";
+			sprite = "TVIVA0";
+		}
+		409
+		{
+			title = "Extra Life";
+			sprite = "TV1UA0";
+		}
+		410
+		{
+			title = "Eggman";
+			sprite = "TVEGA0";
+		}
+		411
+		{
+			title = "Teleporter";
+			sprite = "TVMXA0";
+		}
+		413
+		{
+			title = "Gravity Boots";
+			sprite = "TVGVA0";
+		}
+		414
+		{
+			title = "CTF Team Ring Monitor (Red)";
+			sprite = "TRRIA0";
+		}
+		415
+		{
+			title = "CTF Team Ring Monitor (Blue)";
+			sprite = "TBRIA0";
+		}
+		416
+		{
+			title = "Recycler";
+			sprite = "TVRCA0";
+		}
+		418
+		{
+			title = "Score (1,000 Points)";
+			sprite = "TV1KA0";
+		}
+		419
+		{
+			title = "Score (10,000 Points)";
+			sprite = "TVTKA0";
+		}
+		420
+		{
+			title = "Flame Shield";
+			sprite = "TVFLA0";
+		}
+		421
+		{
+			title = "Water Shield";
+			sprite = "TVBBA0";
+		}
+		422
+		{
+			title = "Lightning Shield";
+			sprite = "TVZPA0";
+		}
 	}
-	1876
+
+	boxes2
 	{
-		arrow = 1;
+		color = 18; // Gold
 		blocking = 2;
-		title = "Eggman Disco Statue";
-		sprite = "ESTAB1";
+		title = "Monitors (Respawning)";
 		width = 20;
-		height = 96;
+		height = 44;
+
+		431
+		{
+			title = "Pity Shield (Respawn)";
+			sprite = "TVPIB0";
+		}
+		432
+		{
+			title = "Attraction Shield (Respawn)";
+			sprite = "TVATB0";
+		}
+		433
+		{
+			title = "Force Shield (Respawn)";
+			sprite = "TVFOB0";
+		}
+		434
+		{
+			title = "Armageddon Shield (Respawn)";
+			sprite = "TVARB0";
+		}
+		435
+		{
+			title = "Whirlwind Shield (Respawn)";
+			sprite = "TVWWB0";
+		}
+		436
+		{
+			title = "Elemental Shield (Respawn)";
+			sprite = "TVELB0";
+		}
+		437
+		{
+			title = "Super Sneakers (Respawn)";
+			sprite = "TVSSB0";
+		}
+		438
+		{
+			title = "Invincibility (Respawn)";
+			sprite = "TVIVB0";
+		}
+		440
+		{
+			title = "Eggman (Respawn)";
+			sprite = "TVEGB0";
+		}
+		443
+		{
+			title = "Gravity Boots (Respawn)";
+			sprite = "TVGVB0";
+		}
+		450
+		{
+			title = "Flame Shield (Respawn)";
+			sprite = "TVFLB0";
+		}
+		451
+		{
+			title = "Water Shield (Respawn)";
+			sprite = "TVBBB0";
+		}
+		452
+		{
+			title = "Lightning Shield (Respawn)";
+			sprite = "TVZPB0";
+		}
 	}
-}
 
-stalagmites
-{
-	color = 10; // Green
-	title = "Stalagmites";
-	width = 16;
-	height = 40;
+	generic
+	{
+		color = 11; // Light_Cyan
+		title = "Generic Items & Hazards";
 
-	1900
+		500
+		{
+			title = "Air Bubble Patch";
+			sprite = "BUBLE0";
+			width = 8;
+			height = 16;
+		}
+		501
+		{
+			title = "Signpost";
+			sprite = "SIGND0";
+			width = 8;
+			height = 32;
+		}
+		502
+		{
+			arrow = 1;
+			title = "Star Post";
+			sprite = "STPTA0M0";
+			width = 64;
+			height = 128;
+		}
+		520
+		{
+			title = "Bomb Sphere";
+			sprite = "SPHRD0";
+			width = 16;
+			height = 24;
+		}
+		521
+		{
+			title = "Spikeball";
+			sprite = "SPIKA0";
+			width = 12;
+			height = 8;
+		}
+		522
+		{
+			title = "Wall Spike";
+			sprite = "WSPKALAR";
+			width = 16;
+			height = 14;
+			arrow = 1;
+		}
+		523
+		{
+			title = "Spike";
+			sprite = "USPKA0";
+			width = 8;
+			height = 32;
+		}
+		1130
+		{
+			title = "Small Mace";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+		}
+		1131
+		{
+			title = "Big Mace";
+			sprite = "BMCEA0";
+			width = 34;
+			height = 68;
+		}
+		1136
+		{
+			title = "Small Fireball";
+			sprite = "SFBRA0";
+			width = 17;
+			height = 34;
+		}
+		1137
+		{
+			title = "Large Fireball";
+			sprite = "BFBRA0";
+			width = 34;
+			height = 68;
+		}
+	}
+
+	springs
 	{
-		title = "Brown Stalagmite (Tall)";
-		sprite = "STLGA0";
-		width = 16;
-		height = 40;
+		color = 12; // Light_Red
+		title = "Springs and Fans";
+		width = 20;
+		height = 16;
+		sprite = "RSPRD2";
+
+		540
+		{
+			title = "Fan";
+			sprite = "FANSA0D0";
+			width = 16;
+			height = 8;
+		}
+		541
+		{
+			title = "Gas Jet";
+			sprite = "STEMD0";
+			width = 32;
+		}
+		542
+		{
+			title = "Bumper";
+			sprite = "BUMPA0";
+			width = 32;
+			height = 64;
+		}
+		543
+		{
+			title = "Balloon";
+			sprite = "BLONA0";
+			width = 32;
+			height = 64;
+		}
+		550
+		{
+			title = "Yellow Spring";
+			sprite = "SPRYA0";
+		}
+		551
+		{
+			title = "Red Spring";
+			sprite = "SPRRA0";
+		}
+		552
+		{
+			title = "Blue Spring";
+			sprite = "SPRBA0";
+		}
+		555
+		{
+			arrow = 1;
+			title = "Diagonal Yellow Spring";
+			sprite = "YSPRD2";
+			width = 16;
+		}
+		556
+		{
+			arrow = 1;
+			title = "Diagonal Red Spring";
+			sprite = "RSPRD2";
+			width = 16;
+		}
+		557
+		{
+			arrow = 1;
+			title = "Diagonal Blue Spring";
+			sprite = "BSPRD2";
+			width = 16;
+		}
+		558
+		{
+			arrow = 1;
+			title = "Horizontal Yellow Spring";
+			sprite = "SSWYD2D8";
+			width = 16;
+			height = 32;
+		}
+		559
+		{
+			arrow = 1;
+			title = "Horizontal Red Spring";
+			sprite = "SSWRD2D8";
+			width = 16;
+			height = 32;
+		}
+		560
+		{
+			arrow = 1;
+			title = "Horizontal Blue Spring";
+			sprite = "SSWBD2D8";
+			width = 16;
+			height = 32;
+		}
+		1134
+		{
+			title = "Yellow Spring Ball";
+			sprite = "YSPBA0";
+			width = 17;
+			height = 34;
+		}
+		1135
+		{
+			title = "Red Spring Ball";
+			sprite = "RSPBA0";
+			width = 17;
+			height = 34;
+		}
+		544
+		{
+			arrow = 1;
+			title = "Yellow Boost Panel";
+			sprite = "BSTYA0";
+			width = 28;
+			height = 2;
+		}
+		545
+		{
+			arrow = 1;
+			title = "Red Boost Panel";
+			sprite = "BSTRA0";
+			width = 28;
+			height = 2;
+		}
 	}
-	1901
+
+	patterns
 	{
-		title = "Brown Stalagmite";
-		sprite = "STLGB0";
+		color = 5; // Magenta
+		arrow = 1;
+		title = "Special Placement Patterns";
 		width = 16;
-		height = 40;
+		height = 384;
+		sprite = "RINGA0";
+
+		600
+		{
+			arrow = 0;
+			title = "5 Vertical Rings (Yellow Spring)";
+			sprite = "RINGA0";
+		}
+		601
+		{
+			arrow = 0;
+			title = "5 Vertical Rings (Red Spring)";
+			sprite = "RINGA0";
+			height = 1024;
+		}
+		602
+		{
+			title = "5 Diagonal Rings (Yellow Spring)";
+			sprite = "RINGA0";
+			height = 32;
+		}
+		603
+		{
+			title = "10 Diagonal Rings (Red Spring)";
+			sprite = "RINGA0";
+			height = 32;
+		}
+		604
+		{
+			title = "Circle of Rings";
+			sprite = "RINGA0";
+			width = 96;
+			height = 192;
+		}
+		605
+		{
+			title = "Circle of Rings (Big)";
+			sprite = "RINGA0";
+			width = 192;
+		}
+		606
+		{
+			title = "Circle of Blue Spheres";
+			sprite = "SPHRA0";
+			width = 96;
+			height = 192;
+		}
+		607
+		{
+			title = "Circle of Blue Spheres (Big)";
+			sprite = "SPHRA0";
+			width = 192;
+		}
+		608
+		{
+			title = "Circle of Rings and Spheres";
+			sprite = "SPHRA0";
+			width = 96;
+			height = 192;
+		}
+		609
+		{
+			title = "Circle of Rings and Spheres (Big)";
+			sprite = "SPHRA0";
+			width = 192;
+		}
+	}
+
+	invisible
+	{
+		color = 15; // White
+		title = "Misc. Invisible";
+		width = 0;
+		height = 0;
+		sprite = "UNKNA0";
+		sort = 1;
+		fixedsize = true;
+		blocking = 0;
+
+		700
+		{
+			title = "Water Ambience A (Large)";
+			sprite = "internal:ambiance";
+		}
+
+		701
+		{
+			title = "Water Ambience B (Large)";
+			sprite = "internal:ambiance";
+		}
+
+		702
+		{
+			title = "Water Ambience C (Medium)";
+			sprite = "internal:ambiance";
+		}
+
+		703
+		{
+			title = "Water Ambience D (Medium)";
+			sprite = "internal:ambiance";
+		}
+
+		704
+		{
+			title = "Water Ambience E (Small)";
+			sprite = "internal:ambiance";
+		}
+
+		705
+		{
+			title = "Water Ambience F (Small)";
+			sprite = "internal:ambiance";
+		}
+
+		706
+		{
+			title = "Water Ambience G (Extra Large)";
+			sprite = "internal:ambiance";
+		}
+
+		707
+		{
+			title = "Water Ambience H (Extra Large)";
+			sprite = "internal:ambiance";
+		}
+
+		708
+		{
+			title = "Disco Ambience";
+			sprite = "internal:ambiance";
+		}
+
+		709
+		{
+			title = "Volcano Ambience";
+			sprite = "internal:ambiance";
+		}
+
+		710
+		{
+			title = "Machine Ambience";
+			sprite = "internal:ambiance";
+		}
+
+		750
+		{
+			title = "Slope Vertex";
+			sprite = "internal:vertexslope";
+		}
+
+		751
+		{
+			arrow = 1;
+			title = "Teleport Destination";
+			sprite = "internal:tele";
+		}
+
+		752
+		{
+			arrow = 1;
+			title = "Alternate View Point";
+			sprite = "internal:view";
+		}
+
+		753
+		{
+			title = "Zoom Tube Waypoint";
+			sprite = "internal:zoom";
+		}
+
+		754
+		{
+			title = "Push Point";
+			sprite = "GWLGA0";
+		}
+		755
+		{
+			title = "Pull Point";
+			sprite = "GWLRA0";
+		}
+		756
+		{
+			title = "Blast Linedef Executor";
+			sprite = "TOADA0";
+			width = 32;
+			height = 16;
+		}
+		757
+		{
+			title = "Fan Particle Generator";
+			sprite = "PRTLA0";
+			width = 8;
+			height = 16;
+		}
+		758
+		{
+			title = "Object Angle Anchor";
+			sprite = "internal:view";
+		}
+		760
+		{
+			title = "PolyObject Anchor";
+			sprite = "internal:polyanchor";
+		}
+		761
+		{
+			title = "PolyObject Spawn Point";
+			sprite = "internal:polycenter";
+		}
+		762
+		{
+			title = "PolyObject Spawn Point (Crush)";
+			sprite = "internal:polycentercrush";
+		}
+		780
+		{
+			title = "Skybox View Point";
+			sprite = "internal:skyb";
+		}
 	}
-	1902
+
+	greenflower
 	{
-		title = "Orange Stalagmite (Tall)";
-		sprite = "STLGC0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Greenflower";
+
+		800
+		{
+			title = "GFZ Flower";
+			sprite = "FWR1A0";
+			width = 16;
+			height = 40;
+		}
+		801
+		{
+			title = "Sunflower";
+			sprite = "FWR2A0";
+			width = 16;
+			height = 96;
+		}
+		802
+		{
+			title = "Budding Flower";
+			sprite = "FWR3A0";
+			width = 8;
+			height = 32;
+		}
+		803
+		{
+			title = "Blueberry Bush";
+			sprite = "BUS3A0";
+			width = 16;
+			height = 32;
+		}
+		804
+		{
+			title = "Berry Bush";
+			sprite = "BUS1A0";
+			width = 16;
+			height = 32;
+		}
+		805
+		{
+			title = "Bush";
+			sprite = "BUS2A0";
+			width = 16;
+			height = 32;
+		}
+		806
+		{
+			title = "GFZ Tree";
+			sprite = "TRE1A0";
+			width = 20;
+			height = 128;
+		}
+		807
+		{
+			title = "GFZ Berry Tree";
+			sprite = "TRE1B0";
+			width = 20;
+			height = 128;
+		}
+		808
+		{
+			title = "GFZ Cherry Tree";
+			sprite = "TRE1C0";
+			width = 20;
+			height = 128;
+		}
+		809
+		{
+			title = "Checkered Tree";
+			sprite = "TRE2A0";
+			width = 20;
+			height = 200;
+		}
+		810
+		{
+			title = "Checkered Tree (Sunset)";
+			sprite = "TRE2B0";
+			width = 20;
+			height = 200;
+		}
+		811
+		{
+			title = "Polygon Tree";
+			sprite = "TRE4A0";
+			width = 20;
+			height = 200;
+		}
+		812
+		{
+			title = "Bush Tree";
+			sprite = "TRE5A0";
+			width = 20;
+			height = 200;
+		}
+		813
+		{
+			title = "Red Bush Tree";
+			sprite = "TRE5B0";
+			width = 20;
+			height = 200;
+		}
 	}
-	1903
+
+	technohill
 	{
-		title = "Orange Stalagmite";
-		sprite = "STLGD0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Techno Hill";
+
+		900
+		{
+			title = "THZ Steam Flower";
+			sprite = "THZPA0";
+			width = 8;
+			height = 32;
+		}
+		901
+		{
+			title = "Alarm";
+			sprite = "ALRMA0";
+			width = 8;
+			height = 16;
+			hangs = 1;
+		}
+		902
+		{
+			title = "THZ Spin Flower (Red)";
+			sprite = "FWR5A0";
+			width = 16;
+			height = 64;
+		}
+		903
+		{
+			title = "THZ Spin Flower (Yellow)";
+			sprite = "FWR6A0";
+			width = 16;
+			height = 64;
+		}
+		904
+		{
+			arrow = 1;
+			title = "Whistlebush";
+			sprite = "THZTA0";
+			width = 16;
+			height = 64;
+		}
 	}
-	1904
+
+	deepsea
 	{
-		title = "Red Stalagmite (Tall)";
-		sprite = "STLGE0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Deep Sea";
+
+		1000
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Gargoyle";
+			sprite = "GARGA1";
+			width = 16;
+			height = 40;
+		}
+		1009
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Gargoyle (Big)";
+			sprite = "GARGB1";
+			width = 32;
+			height = 80;
+		}
+		1001
+		{
+			title = "Seaweed";
+			sprite = "SEWEA0";
+			width = 24;
+			height = 56;
+		}
+		1002
+		{
+			title = "Dripping Water";
+			sprite = "DRIPD0";
+			width = 8;
+			height = 16;
+			hangs = 1;
+		}
+		1003
+		{
+			title = "Coral (Green)";
+			sprite = "CORLA0";
+			width = 29;
+			height = 40;
+		}
+		1004
+		{
+			title = "Coral (Red)";
+			sprite = "CORLB0";
+			width = 30;
+			height = 53;
+		}
+		1005
+		{
+			title = "Coral (Orange)";
+			sprite = "CORLC0";
+			width = 28;
+			height = 41;
+		}
+		1006
+		{
+			title = "Blue Crystal";
+			sprite = "BCRYA1";
+			width = 8;
+			height = 16;
+		}
+		1007
+		{
+			title = "Kelp";
+			sprite = "KELPA0";
+			width = 16;
+			height = 292;
+		}
+		1008
+		{
+			title = "Stalagmite (DSZ1)";
+			sprite = "DSTGA0";
+			width = 8;
+			height = 116;
+		}
+		1010
+		{
+			arrow = 1;
+			title = "Light Beam";
+			sprite = "LIBEARAL";
+			width = 16;
+			height = 16;
+		}
+		1011
+		{
+			title = "Stalagmite (DSZ2)";
+			sprite = "DSTGA0";
+			width = 8;
+			height = 116;
+		}
+		1012
+		{
+			arrow = 1;
+			title = "Big Floating Mine";
+			width = 28;
+			height = 56;
+			sprite = "BMNEA1";
+		}
+		1013
+		{
+			title = "Animated Kelp";
+			sprite = "ALGAA0";
+			width = 48;
+			height = 120;
+		}
+		1014
+		{
+			title = "Large Coral (Brown)";
+			sprite = "CORLD0";
+			width = 56;
+			height = 112;
+		}
+		1015
+		{
+			title = "Large Coral (Beige)";
+			sprite = "CORLE0";
+			width = 56;
+			height = 112;
+		}
 	}
-	1905
+
+	castleeggman
 	{
-		title = "Red Stalagmite";
-		sprite = "STLGF0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Castle Eggman";
+
+		1100
+		{
+			title = "Chain (Decorative)";
+			sprite = "CHANA0";
+			width = 4;
+			height = 128;
+			hangs = 1;
+		}
+		1101
+		{
+			title = "Torch";
+			sprite = "FLAMA0E0";
+			width = 8;
+			height = 32;
+		}
+		1102
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Eggman Statue";
+			sprite = "ESTAA1";
+			width = 32;
+			height = 240;
+		}
+		1103
+		{
+			title = "CEZ Flower";
+			sprite = "FWR4A0";
+			width = 16;
+			height = 40;
+		}
+		1104
+		{
+			title = "Mace Spawnpoint";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+		}
+		1105
+		{
+			title = "Chain with Maces Spawnpoint";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+		}
+		1106
+		{
+			title = "Chained Spring Spawnpoint";
+			sprite = "YSPBA0";
+			width = 17;
+			height = 34;
+		}
+		1107
+		{
+			title = "Chain Spawnpoint";
+			sprite = "BMCHA0";
+			width = 17;
+			height = 34;
+		}
+		1108
+		{
+			arrow = 1;
+			title = "Hidden Chain Spawnpoint";
+			sprite = "internal:chain3";
+			width = 17;
+			height = 34;
+		}
+		1109
+		{
+			title = "Firebar Spawnpoint";
+			sprite = "BFBRA0";
+			width = 17;
+			height = 34;
+		}
+		1110
+		{
+			title = "Custom Mace Spawnpoint";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+		}
+		1111
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Crawla Statue";
+			sprite = "CSTAA1";
+			width = 16;
+			height = 40;
+		}
+		1112
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Lance-a-Bot Statue";
+			sprite = "CBBSA1";
+			width = 32;
+			height = 72;
+		}
+		1114
+		{
+			title = "Pine Tree";
+			sprite = "PINEA0";
+			width = 16;
+			height = 628;
+		}
+		1115
+		{
+			title = "CEZ Shrub (Small)";
+			sprite = "CEZBA0";
+			width = 16;
+			height = 24;
+		}
+		1116
+		{
+			title = "CEZ Shrub (Large)";
+			sprite = "CEZBB0";
+			width = 32;
+			height = 48;
+		}
+		1117
+		{
+			arrow = 1;
+			title = "Pole Banner (Red)";
+			sprite = "BANRA0";
+			width = 40;
+			height = 224;
+		}
+		1118
+		{
+			arrow = 1;
+			title = "Pole Banner (Blue)";
+			sprite = "BANRA0";
+			width = 40;
+			height = 224;
+		}
+		1119
+		{
+			title = "Candle";
+			sprite = "CNDLA0";
+			width = 8;
+			height = 48;
+		}
+		1120
+		{
+			title = "Candle Pricket";
+			sprite = "CNDLB0";
+			width = 8;
+			height = 176;
+		}
+		1121
+		{
+			title = "Flame Holder";
+			sprite = "FLMHA0";
+			width = 24;
+			height = 80;
+		}
+		1122
+		{
+			title = "Fire Torch";
+			sprite = "CTRCA0";
+			width = 16;
+			height = 80;
+		}
+		1123
+		{
+			title = "Cannonball Launcher";
+			sprite = "internal:cannonball";
+			width = 8;
+			height = 16;
+		}
+		1124
+		{
+			blocking = 2;
+			title = "Cannonball";
+			sprite = "CBLLA0";
+			width = 20;
+			height = 40;
+		}
+		1125
+		{
+			title = "Brambles";
+			sprite = "CABRALAR";
+			width = 48;
+			height = 32;
+		}
+		1126
+		{
+			title = "Invisible Lockon Object";
+			sprite = "LCKNC0";
+			width = 16;
+			height = 32;
+		}
+		1127
+		{
+			title = "Spectator Eggrobo";
+			sprite = "EGR1A1";
+			width = 20;
+			height = 72;
+		}
+		1128
+		{
+			arrow = 1;
+			title = "Waving Flag (Red)";
+			sprite = "CFLGA0";
+			width = 8;
+			height = 208;
+		}
+		1129
+		{
+			arrow = 1;
+			title = "Waving Flag (Blue)";
+			sprite = "CFLGA0";
+			width = 8;
+			height = 208;
+		}
 	}
-	1906
+
+	aridcanyon
 	{
-		title = "Gray Stalagmite (Tall)";
-		sprite = "STLGG0";
-		width = 24;
-		height = 96;
+		color = 10; // Green
+		title = "Arid Canyon";
+
+		1200
+		{
+			title = "Tumbleweed (Big)";
+			sprite = "BTBLA0";
+			width = 24;
+			height = 48;
+		}
+		1201
+		{
+			title = "Tumbleweed (Small)";
+			sprite = "STBLA0";
+			width = 12;
+			height = 24;
+		}
+		1202
+		{
+			arrow = 1;
+			title = "Rock Spawner";
+			sprite = "ROIAA0";
+			width = 8;
+			height = 16;
+		}
+		1203
+		{
+			title = "Tiny Red Flower Cactus";
+			sprite = "CACTA0";
+			width = 13;
+			height = 24;
+		}
+		1204
+		{
+			title = "Small Red Flower Cactus";
+			sprite = "CACTB0";
+			width = 15;
+			height = 52;
+		}
+		1205
+		{
+			title = "Tiny Blue Flower Cactus";
+			sprite = "CACTC0";
+			width = 13;
+			height = 24;
+		}
+		1206
+		{
+			title = "Small Blue Flower Cactus";
+			sprite = "CACTD0";
+			width = 15;
+			height = 52;
+		}
+		1207
+		{
+			title = "Prickly Pear";
+			sprite = "CACTE0";
+			width = 32;
+			height = 96;
+		}
+		1208
+		{
+			title = "Barrel Cactus";
+			sprite = "CACTF0";
+			width = 20;
+			height = 128;
+		}
+		1209
+		{
+			title = "Tall Barrel Cactus";
+			sprite = "CACTG0";
+			width = 24;
+			height = 224;
+		}
+		1210
+		{
+			title = "Armed Cactus";
+			sprite = "CACTH0";
+			width = 24;
+			height = 256;
+		}
+		1211
+		{
+			title = "Ball Cactus";
+			sprite = "CACTI0";
+			width = 48;
+			height = 96;
+		}
+		1212
+		{
+			title = "Caution Sign";
+			sprite = "WWSGAR";
+			width = 22;
+			height = 64;
+		}
+		1213
+		{
+			title = "Cacti Sign";
+			sprite = "WWS2AR";
+			width = 22;
+			height = 64;
+		}
+		1214
+		{
+			title = "Sharp Turn Sign";
+			sprite = "WWS3ALAR";
+			width = 16;
+			height = 192;
+		}
+		1215
+		{
+			title = "Mine Oil Lamp";
+			sprite = "OILLA0";
+			width = 22;
+			height = 64;
+			hangs = 1;
+		}
+		1216
+		{
+			title = "TNT Barrel";
+			sprite = "BARRA1";
+			width = 24;
+			height = 63;
+		}
+		1217
+		{
+			title = "TNT Proximity Shell";
+			sprite = "REMTA0";
+			width = 64;
+			height = 40;
+		}
+		1218
+		{
+			title = "Dust Devil";
+			sprite = "TAZDCR";
+			width = 80;
+			height = 416;
+		}
+		1219
+		{
+			title = "Minecart Spawner";
+			sprite = "MCRTCLFR";
+			width = 22;
+			height = 32;
+		}
+		1220
+		{
+			title = "Minecart Stopper";
+			sprite = "MCRTIR";
+			width = 32;
+			height = 32;
+		}
+		1221
+		{
+			title = "Minecart Saloon Door";
+			sprite = "SALDARAL";
+			width = 96;
+			height = 160;
+		}
+		1222
+		{
+			title = "Train Cameo Spawner";
+			sprite = "TRAEBRBL";
+			width = 28;
+			height = 32;
+		}
+		1223
+		{
+			title = "Train Dust Spawner";
+			sprite = "ADSTA0";
+			width = 4;
+			height = 4;
+		}
+		1224
+		{
+			title = "Train Steam Spawner";
+			sprite = "STEAA0";
+			width = 4;
+			height = 4;
+		}
+		1229
+		{
+			title = "Minecart Switch Point";
+			sprite = "internal:zoom";
+			width = 8;
+			height = 16;
+		}
+		1230
+		{
+			title = "Tiny Cactus";
+			sprite = "CACTJ0";
+			width = 13;
+			height = 28;
+		}
+		1231
+		{
+			title = "Small Cactus";
+			sprite = "CACTK0";
+			width = 15;
+			height = 60;
+		}
 	}
-	1907
+
+	redvolcano
 	{
-		title = "Gray Stalagmite";
-		sprite = "STLGH0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Red Volcano";
+
+		1300
+		{
+			arrow = 1;
+			title = "Flame Jet (Horizontal)";
+			sprite = "internal:flameh";
+			width = 16;
+			height = 40;
+		}
+		1301
+		{
+			title = "Flame Jet (Vertical)";
+			sprite = "internal:flamev";
+			width = 16;
+			height = 40;
+		}
+		1302
+		{
+			title = "Spinning Flame Jet (Counter-Clockwise)";
+			sprite = "internal:flame2";
+			width = 16;
+			height = 24;
+		}
+		1303
+		{
+			title = "Spinning Flame Jet (Clockwise)";
+			sprite = "internal:flame1";
+			width = 16;
+			height = 24;
+		}
+		1304
+		{
+			title = "Lavafall";
+			sprite = "LFALF0";
+			width = 30;
+			height = 32;
+		}
+		1305
+		{
+			title = "Rollout Rock";
+			sprite = "PUMIA1A5";
+			width = 30;
+			height = 60;
+		}
+		1306
+		{
+			title = "Big Fern";
+			sprite = "JPLAB0";
+			width = 32;
+			height = 48;
+		}
+		1307
+		{
+			title = "Jungle Palm";
+			sprite = "JPLAC0";
+			width = 32;
+			height = 48;
+		}
+		1308
+		{
+			title = "Torch Flower";
+			sprite = "TFLOA0";
+			width = 14;
+			height = 110;
+		}
+		1309
+		{
+			title = "RVZ1 Wall Vine (Long)";
+			sprite = "WVINALAR";
+			width = 1;
+			height = 288;
+		}
+		1310
+		{
+			title = "RVZ1 Wall Vine (Short)";
+			sprite = "WVINBLBR";
+			width = 1;
+			height = 288;
+		}
 	}
-	1908
+
+	botanicserenity
 	{
-		title = "Blue Stalagmite (Tall)";
-		sprite = "STLGI0";
+		color = 10; // Green
+		title = "Botanic Serenity";
 		width = 16;
-		height = 40;
+		height = 32;
+		sprite = "BSZ1A0";
+		1400
+		{
+			title = "Tall Flower (Red)";
+			sprite = "BSZ1A0";
+		}
+		1401
+		{
+			title = "Tall Flower (Purple)";
+			sprite = "BSZ1B0";
+		}
+		1402
+		{
+			title = "Tall Flower (Blue)";
+			sprite = "BSZ1C0";
+		}
+		1403
+		{
+			title = "Tall Flower (Cyan)";
+			sprite = "BSZ1D0";
+		}
+		1404
+		{
+			title = "Tall Flower (Yellow)";
+			sprite = "BSZ1E0";
+		}
+		1405
+		{
+			title = "Tall Flower (Orange)";
+			sprite = "BSZ1F0";
+		}
+		1410
+		{
+			title = "Medium Flower (Red)";
+			sprite = "BSZ2A0";
+		}
+		1411
+		{
+			title = "Medium Flower (Purple)";
+			sprite = "BSZ2B0";
+		}
+		1412
+		{
+			title = "Medium Flower (Blue)";
+			sprite = "BSZ2C0";
+		}
+		1413
+		{
+			title = "Medium Flower (Cyan)";
+			sprite = "BSZ2D0";
+		}
+		1414
+		{
+			title = "Medium Flower (Yellow)";
+			sprite = "BSZ2E0";
+		}
+		1415
+		{
+			title = "Medium Flower (Orange)";
+			sprite = "BSZ2F0";
+		}
+		1420
+		{
+			title = "Short Flower (Red)";
+			sprite = "BSZ3A0";
+		}
+		1421
+		{
+			title = "Short Flower (Purple)";
+			sprite = "BSZ3B0";
+		}
+		1422
+		{
+			title = "Short Flower (Blue)";
+			sprite = "BSZ3C0";
+		}
+		1423
+		{
+			title = "Short Flower (Cyan)";
+			sprite = "BSZ3D0";
+		}
+		1424
+		{
+			title = "Short Flower (Yellow)";
+			sprite = "BSZ3E0";
+		}
+		1425
+		{
+			title = "Short Flower (Orange)";
+			sprite = "BSZ3F0";
+		}
+		1430
+		{
+			title = "Tulip (Red)";
+			sprite = "BST1A0";
+		}
+		1431
+		{
+			title = "Tulip (Purple)";
+			sprite = "BST2A0";
+		}
+		1432
+		{
+			title = "Tulip (Blue)";
+			sprite = "BST3A0";
+		}
+		1433
+		{
+			title = "Tulip (Cyan)";
+			sprite = "BST4A0";
+		}
+		1434
+		{
+			title = "Tulip (Yellow)";
+			sprite = "BST5A0";
+		}
+		1435
+		{
+			title = "Tulip (Orange)";
+			sprite = "BST6A0";
+		}
+		1440
+		{
+			title = "Cluster (Red)";
+			sprite = "BSZ5A0";
+		}
+		1441
+		{
+			title = "Cluster (Purple)";
+			sprite = "BSZ5B0";
+		}
+		1442
+		{
+			title = "Cluster (Blue)";
+			sprite = "BSZ5C0";
+		}
+		1443
+		{
+			title = "Cluster (Cyan)";
+			sprite = "BSZ5D0";
+		}
+		1444
+		{
+			title = "Cluster (Yellow)";
+			sprite = "BSZ5E0";
+		}
+		1445
+		{
+			title = "Cluster (Orange)";
+			sprite = "BSZ5F0";
+		}
+		1450
+		{
+			title = "Bush (Red)";
+			sprite = "BSZ6A0";
+		}
+		1451
+		{
+			title = "Bush (Purple)";
+			sprite = "BSZ6B0";
+		}
+		1452
+		{
+			title = "Bush (Blue)";
+			sprite = "BSZ6C0";
+		}
+		1453
+		{
+			title = "Bush (Cyan)";
+			sprite = "BSZ6D0";
+		}
+		1454
+		{
+			title = "Bush (Yellow)";
+			sprite = "BSZ6E0";
+		}
+		1455
+		{
+			title = "Bush (Orange)";
+			sprite = "BSZ6F0";
+		}
+		1460
+		{
+			title = "Vine (Red)";
+			sprite = "BSZ7A0";
+		}
+		1461
+		{
+			title = "Vine (Purple)";
+			sprite = "BSZ7B0";
+		}
+		1462
+		{
+			title = "Vine (Blue)";
+			sprite = "BSZ7C0";
+		}
+		1463
+		{
+			title = "Vine (Cyan)";
+			sprite = "BSZ7D0";
+		}
+		1464
+		{
+			title = "Vine (Yellow)";
+			sprite = "BSZ7E0";
+		}
+		1465
+		{
+			title = "Vine (Orange)";
+			sprite = "BSZ7F0";
+		}
+		1470
+		{
+			title = "BSZ Shrub";
+			sprite = "BSZ8A0";
+		}
+		1471
+		{
+			title = "BSZ Clover";
+			sprite = "BSZ8B0";
+		}
+		1473
+		{
+			title = "Palm Tree (Big)";
+			width = 16;
+			height = 160;
+			sprite = "BSZ8D0";
+		}
+		1475
+		{
+			title = "Palm Tree (Small)";
+			width = 16;
+			height = 80;
+			sprite = "BSZ8F0";
+		}
 	}
-	1909
+
+	azuretemple
 	{
-		title = "Blue Stalagmite";
-		sprite = "STLGJ0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Azure Temple";
+
+		1500
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Glaregoyle";
+			sprite = "BGARA1";
+			width = 16;
+			height = 40;
+		}
+		1501
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Glaregoyle (Up)";
+			sprite = "BGARA1";
+			width = 16;
+			height = 40;
+		}
+		1502
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Glaregoyle (Down)";
+			sprite = "BGARA1";
+			width = 16;
+			height = 40;
+		}
+		1503
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Glaregoyle (Long)";
+			sprite = "BGARA1";
+			width = 16;
+			height = 40;
+		}
+		1504
+		{
+			title = "ATZ Target";
+			sprite = "RCRYB0";
+			width = 24;
+			height = 32;
+		}
+		1505
+		{
+			title = "Green Flame";
+			sprite = "CFLMA0E0";
+			width = 8;
+			height = 32;
+		}
+		1506
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Blue Gargoyle";
+			sprite = "BGARD1";
+			width = 16;
+			height = 40;
+		}
 	}
-}
 
-hauntedheights
-{
-	color = 10; // Green
-	title = "Haunted Heights";
+	dreamhill
+	{
+		color = 10; // Green
+		title = "Dream Hill";
+
+		1600
+		{
+			title = "Spring Tree";
+			sprite = "TRE6A0";
+			width = 16;
+			height = 32;
+		}
+		1601
+		{
+			title = "Shleep";
+			sprite = "SHLPA0";
+			width = 24;
+			height = 32;
+		}
+		1602
+		{
+			title = "Nightopian";
+			sprite = "NTPNA1";
+			width = 16;
+			height = 40;
+		}
+	}
 
-	2000
+	nightstrk
 	{
-		title = "Smashing Spikeball";
-		sprite = "FMCEA0";
-		width = 18;
-		height = 28;
+		color = 13; // Pink
+		title = "NiGHTS Track";
+		width = 8;
+		height = 4096;
+		sprite = "UNKNA0";
+
+		1700
+		{
+			title = "Axis";
+			sprite = "internal:axis1";
+			circle = 1;
+		}
+		1701
+		{
+			title = "Axis Transfer";
+			sprite = "internal:axis2";
+		}
+		1702
+		{
+			title = "Axis Transfer Line";
+			sprite = "internal:axis3";
+		}
+		1710
+		{
+			title = "Ideya Capture";
+			sprite = "CAPSA0";
+			width = 72;
+			height = 144;
+		}
 	}
-	2001
+
+	nights
 	{
-		title = "HHZ Grass";
-		sprite = "HHZMA0";
+		color = 13; // Pink
+		title = "NiGHTS Items";
 		width = 16;
-		height = 40;
+		height = 32;
+
+		1703
+		{
+			title = "Ideya Drone";
+			sprite = "NDRNA1";
+			width = 16;
+			height = 56;
+		}
+		1704
+		{
+			arrow = 1;
+			title = "NiGHTS Bumper";
+			sprite = "NBMPG3G7";
+			width = 32;
+			height = 64;
+		}
+		1705
+		{
+			arrow = 1;
+			title = "Hoop (Generic)";
+			sprite = "HOOPA0";
+			width = 80;
+			height = 160;
+		}
+		1706
+		{
+			title = "Blue Sphere";
+			sprite = "SPHRA0";
+			width = 16;
+			height = 24;
+		}
+		1707
+		{
+			title = "Super Paraloop";
+			sprite = "NPRUA0";
+		}
+		1708
+		{
+			title = "Drill Refill";
+			sprite = "NPRUB0";
+		}
+		1709
+		{
+			title = "Nightopian Helper";
+			sprite = "NPRUC0";
+		}
+		1711
+		{
+			title = "Extra Time";
+			sprite = "NPRUD0";
+		}
+		1712
+		{
+			title = "Link Freeze";
+			sprite = "NPRUE0";
+		}
+		1713
+		{
+			arrow = 1;
+			title = "Hoop (Customizable)";
+			sprite = "HOOPA0";
+			width = 80;
+			height = 160;
+		}
+		1714
+		{
+			title = "Ideya Anchor Point";
+			sprite = "internal:axis1";
+			width = 8;
+			height = 16;
+		}
 	}
-	2002
+
+	mario
 	{
-		title = "HHZ Tentacle 1";
-		sprite = "HHZMB0";
-		width = 16;
-		height = 40;
+		color = 6; // Brown
+		title = "Mario";
+
+		1800
+		{
+			title = "Coin";
+			sprite = "COINA0";
+			width = 16;
+			height = 24;
+		}
+		1801
+		{
+			arrow = 1;
+			title = "Goomba";
+			sprite = "GOOMA0";
+			width = 24;
+			height = 32;
+		}
+		1802
+		{
+			arrow = 1;
+			title = "Goomba (Blue)";
+			sprite = "BGOMA0";
+			width = 24;
+			height = 32;
+		}
+		1803
+		{
+			title = "Fire Flower";
+			sprite = "FFWRB0";
+			width = 16;
+			height = 32;
+		}
+		1804
+		{
+			title = "Koopa Shell";
+			sprite = "SHLLA1";
+			width = 16;
+			height = 20;
+		}
+		1805
+		{
+			title = "Puma (Jumping Fireball)";
+			sprite = "PUMAA0";
+			width = 8;
+			height = 16;
+		}
+		1806
+		{
+			title = "King Bowser";
+			sprite = "KOOPA0";
+			width = 16;
+			height = 48;
+		}
+		1807
+		{
+			title = "Axe";
+			sprite = "MAXEA0";
+			width = 8;
+			height = 16;
+		}
+		1808
+		{
+			title = "Bush (Short)";
+			sprite = "MUS1A0";
+			width = 16;
+			height = 32;
+		}
+		1809
+		{
+			title = "Bush (Tall)";
+			sprite = "MUS2A0";
+			width = 16;
+			height = 32;
+		}
+		1810
+		{
+			title = "Toad";
+			sprite = "TOADA0";
+			width = 8;
+			height = 32;
+		}
 	}
-	2003
+
+	christmasdisco
 	{
-		title = "HHZ Tentacle 2";
-		sprite = "HHZMC0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Christmas & Disco";
+
+		1850
+		{
+			title = "Christmas Pole";
+			sprite = "XMS1A0";
+			width = 16;
+			height = 40;
+		}
+		1851
+		{
+			title = "Candy Cane";
+			sprite = "XMS2A0";
+			width = 8;
+			height = 32;
+		}
+		1852
+		{
+			blocking = 2;
+			title = "Snowman";
+			sprite = "XMS3A0";
+			width = 16;
+			height = 64;
+		}
+		1853
+		{
+			blocking = 2;
+			title = "Snowman (With Hat)";
+			sprite = "XMS3B0";
+			width = 16;
+			height = 80;
+		}
+		1854
+		{
+			title = "Lamp Post";
+			sprite = "XMS4A0";
+			width = 8;
+			height = 120;
+		}
+		1855
+		{
+			title = "Lamp Post (Snow)";
+			sprite = "XMS4B0";
+			width = 8;
+			height = 120;
+		}
+		1856
+		{
+			title = "Hanging Star";
+			sprite = "XMS5A0";
+			width = 4;
+			height = 80;
+			hangs = 1;
+		}
+		1857
+		{
+			title = "Berry Bush (Snow)";
+			sprite = "BUS1B0";
+			width = 16;
+			height = 32;
+		}
+		1858
+		{
+			title = "Bush (Snow)";
+			sprite = "BUS2B0";
+			width = 16;
+			height = 32;
+		}
+		1859
+		{
+			title = "Blueberry Bush (Snow)";
+			sprite = "BUS3B0";
+			width = 16;
+			height = 32;
+		}
+		1875
+		{
+			title = "Disco Ball";
+			sprite = "DBALA0";
+			width = 16;
+			height = 54;
+			hangs = 1;
+		}
+		1876
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Eggman Disco Statue";
+			sprite = "ESTAB1";
+			width = 20;
+			height = 96;
+		}
 	}
-	2004
+
+	stalagmites
 	{
-		title = "HHZ Stalagmite (Tall)";
-		sprite = "HHZME0";
+		color = 10; // Green
+		title = "Stalagmites";
 		width = 16;
 		height = 40;
+
+		1900
+		{
+			title = "Brown Stalagmite (Tall)";
+			sprite = "STLGA0";
+			width = 16;
+			height = 40;
+		}
+		1901
+		{
+			title = "Brown Stalagmite";
+			sprite = "STLGB0";
+			width = 16;
+			height = 40;
+		}
+		1902
+		{
+			title = "Orange Stalagmite (Tall)";
+			sprite = "STLGC0";
+			width = 16;
+			height = 40;
+		}
+		1903
+		{
+			title = "Orange Stalagmite";
+			sprite = "STLGD0";
+			width = 16;
+			height = 40;
+		}
+		1904
+		{
+			title = "Red Stalagmite (Tall)";
+			sprite = "STLGE0";
+			width = 16;
+			height = 40;
+		}
+		1905
+		{
+			title = "Red Stalagmite";
+			sprite = "STLGF0";
+			width = 16;
+			height = 40;
+		}
+		1906
+		{
+			title = "Gray Stalagmite (Tall)";
+			sprite = "STLGG0";
+			width = 24;
+			height = 96;
+		}
+		1907
+		{
+			title = "Gray Stalagmite";
+			sprite = "STLGH0";
+			width = 16;
+			height = 40;
+		}
+		1908
+		{
+			title = "Blue Stalagmite (Tall)";
+			sprite = "STLGI0";
+			width = 16;
+			height = 40;
+		}
+		1909
+		{
+			title = "Blue Stalagmite";
+			sprite = "STLGJ0";
+			width = 16;
+			height = 40;
+		}
 	}
-	2005
+
+	hauntedheights
 	{
-		title = "HHZ Stalagmite (Short)";
-		sprite = "HHZMF0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Haunted Heights";
+
+		2000
+		{
+			title = "Smashing Spikeball";
+			sprite = "FMCEA0";
+			width = 18;
+			height = 28;
+		}
+		2001
+		{
+			title = "HHZ Grass";
+			sprite = "HHZMA0";
+			width = 16;
+			height = 40;
+		}
+		2002
+		{
+			title = "HHZ Tentacle 1";
+			sprite = "HHZMB0";
+			width = 16;
+			height = 40;
+		}
+		2003
+		{
+			title = "HHZ Tentacle 2";
+			sprite = "HHZMC0";
+			width = 16;
+			height = 40;
+		}
+		2004
+		{
+			title = "HHZ Stalagmite (Tall)";
+			sprite = "HHZME0";
+			width = 16;
+			height = 40;
+		}
+		2005
+		{
+			title = "HHZ Stalagmite (Short)";
+			sprite = "HHZMF0";
+			width = 16;
+			height = 40;
+		}
+		2006
+		{
+			title = "Jack-o'-lantern 1";
+			sprite = "PUMKA0";
+			width = 16;
+			height = 40;
+		}
+		2007
+		{
+			title = "Jack-o'-lantern 2";
+			sprite = "PUMKB0";
+			width = 16;
+			height = 40;
+		}
+		2008
+		{
+			title = "Jack-o'-lantern 3";
+			sprite = "PUMKC0";
+			width = 16;
+			height = 40;
+		}
+		2009
+		{
+			title = "Purple Mushroom";
+			sprite = "SHRMD0";
+			width = 16;
+			height = 48;
+		}
+		2010
+		{
+			title = "HHZ Tree";
+			sprite = "HHPLC0";
+			width = 12;
+			height = 40;
+		}
 	}
-	2006
+
+	frozenhillside
 	{
-		title = "Jack-o'-lantern 1";
-		sprite = "PUMKA0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Frozen Hillside";
+
+		2100
+		{
+			title = "Ice Shard (Small)";
+			sprite = "FHZIA0";
+			width = 8;
+			height = 32;
+		}
+		2101
+		{
+			title = "Ice Shard (Large)";
+			sprite = "FHZIB0";
+			width = 8;
+			height = 32;
+		}
+		2102
+		{
+			title = "Crystal Tree (Aqua)";
+			sprite = "TRE3A0";
+			width = 20;
+			height = 200;
+		}
+		2103
+		{
+			title = "Crystal Tree (Pink)";
+			sprite = "TRE3B0";
+			width = 20;
+			height = 200;
+		}
+		2104
+		{
+			title = "Amy Cameo";
+			sprite = "ROSYA1";
+			width = 16;
+			height = 48;
+		}
+		2105
+		{
+			title = "Mistletoe";
+			sprite = "XMS6A0";
+			width = 52;
+			height = 106;
+		}
 	}
-	2007
+
+	tutorial
 	{
-		title = "Jack-o'-lantern 2";
-		sprite = "PUMKB0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Tutorial";
+
+		799
+		{
+			title = "Tutorial Plant";
+			sprite = "TUPFH0";
+			width = 40;
+			height = 144;
+		}
 	}
-	2008
+
+	flickies
 	{
-		title = "Jack-o'-lantern 3";
-		sprite = "PUMKC0";
-		width = 16;
-		height = 40;
+		color = 10; // Green
+		title = "Flickies";
+		width = 8;
+		height = 20;
+
+		2200
+		{
+			title = "Bluebird";
+			sprite = "FL01A1";
+		}
+		2201
+		{
+			title = "Rabbit";
+			sprite = "FL02A1";
+		}
+		2202
+		{
+			title = "Chicken";
+			sprite = "FL03A1";
+		}
+		2203
+		{
+			title = "Seal";
+			sprite = "FL04A1";
+		}
+		2204
+		{
+			title = "Pig";
+			sprite = "FL05A1";
+		}
+		2205
+		{
+			title = "Chipmunk";
+			sprite = "FL06A1";
+		}
+		2206
+		{
+			title = "Penguin";
+			sprite = "FL07A1";
+		}
+		2207
+		{
+			title = "Fish";
+			sprite = "FL08A1";
+		}
+		2208
+		{
+			title = "Ram";
+			sprite = "FL09A1";
+		}
+		2209
+		{
+			title = "Puffin";
+			sprite = "FL10A1";
+		}
+		2210
+		{
+			title = "Cow";
+			sprite = "FL11A1";
+		}
+		2211
+		{
+			title = "Rat";
+			sprite = "FL12A1";
+		}
+		2212
+		{
+			title = "Bear";
+			sprite = "FL13A1";
+		}
+		2213
+		{
+			title = "Dove";
+			sprite = "FL14A1";
+		}
+		2214
+		{
+			title = "Cat";
+			sprite = "FL15A1";
+		}
+		2215
+		{
+			title = "Canary";
+			sprite = "FL16A1";
+		}
+		2216
+		{
+			title = "Spider";
+			sprite = "FS01A1";
+		}
+		2217
+		{
+			title = "Bat";
+			sprite = "FS02A0";
+		}
+	}
+}
+
+udmf
+{
+	editor
+	{
+		color = 15; // White
+		arrow = 1;
+		title = "<Editor Things>";
+		error = -1;
+		width = 8;
+		height = 16;
+		sort = 1;
+
+		3328 = "3D Mode Start";
 	}
-	2009
+
+	starts
 	{
-		title = "Purple Mushroom";
-		sprite = "SHRMD0";
+		color = 1; // Blue
+		arrow = 1;
+		title = "Player Starts";
 		width = 16;
 		height = 48;
+		sprite = "PLAYA0";
+
+		1
+		{
+			title = "Player 01 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		2
+		{
+			title = "Player 02 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		3
+		{
+			title = "Player 03 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		4
+		{
+			title = "Player 04 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		5
+		{
+			title = "Player 05 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		6
+		{
+			title = "Player 06 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		7
+		{
+			title = "Player 07 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		8
+		{
+			title = "Player 08 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		9
+		{
+			title = "Player 09 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		10
+		{
+			title = "Player 10 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		11
+		{
+			title = "Player 11 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		12
+		{
+			title = "Player 12 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		13
+		{
+			title = "Player 13 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		14
+		{
+			title = "Player 14 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		15
+		{
+			title = "Player 15 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		16
+		{
+			title = "Player 16 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		17
+		{
+			title = "Player 17 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		18
+		{
+			title = "Player 18 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		19
+		{
+			title = "Player 19 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		20
+		{
+			title = "Player 20 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		21
+		{
+			title = "Player 21 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		22
+		{
+			title = "Player 22 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		23
+		{
+			title = "Player 23 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		24
+		{
+			title = "Player 24 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		25
+		{
+			title = "Player 25 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		26
+		{
+			title = "Player 26 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		27
+		{
+			title = "Player 27 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		28
+		{
+			title = "Player 28 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		29
+		{
+			title = "Player 29 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		30
+		{
+			title = "Player 30 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		31
+		{
+			title = "Player 31 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		32
+		{
+			title = "Player 32 Start";
+			sprite = "PLAYA0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		33
+		{
+			title = "Match Start";
+			sprite = "NDRNA2A8";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		34
+		{
+			title = "CTF Red Team Start";
+			sprite = "SIGNG0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		35
+		{
+			title = "CTF Blue Team Start";
+			sprite = "SIGNE0";
+			arg0
+			{
+				title = "Spawn on ceiling?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
 	}
-	2010
+
+	enemies
 	{
-		title = "HHZ Tree";
-		sprite = "HHPLC0";
-		width = 12;
-		height = 40;
+		color = 9; // Light_Blue
+		arrow = 1;
+		title = "Enemies";
+
+		100
+		{
+			title = "Crawla (Blue)";
+			sprite = "POSSA1";
+			width = 24;
+			height = 32;
+		}
+		101
+		{
+			title = "Crawla (Red)";
+			sprite = "SPOSA1";
+			width = 24;
+			height = 32;
+		}
+		102
+		{
+			title = "Stupid Dumb Unnamed RoboFish";
+			sprite = "FISHA0";
+			width = 8;
+			height = 28;
+			arg0
+			{
+				title = "Jump strength";
+			}
+		}
+		103
+		{
+			title = "Buzz (Gold)";
+			sprite = "BUZZA1";
+			width = 28;
+			height = 40;
+			arg0
+			{
+				title = "Can move?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		104
+		{
+			title = "Buzz (Red)";
+			sprite = "RBUZA1";
+			width = 28;
+			height = 40;
+			arg0
+			{
+				title = "Can move?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		108
+		{
+			title = "Deton";
+			sprite = "DETNA1";
+			width = 20;
+			height = 32;
+		}
+		110
+		{
+			title = "Turret";
+			sprite = "TRETA1";
+			width = 16;
+			height = 32;
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+		}
+		111
+		{
+			title = "Pop-up Turret";
+			sprite = "TURRI1";
+			width = 12;
+			height = 64;
+			arg0
+			{
+				title = "Firing delay";
+			}
+		}
+		122
+		{
+			title = "Spring Shell (Green)";
+			sprite = "SSHLA1";
+			width = 24;
+			height = 40;
+		}
+		125
+		{
+			title = "Spring Shell (Yellow)";
+			sprite = "SSHLI1";
+			width = 24;
+			height = 40;
+		}
+		109
+		{
+			title = "Skim";
+			sprite = "SKIMA1";
+			width = 16;
+			height = 24;
+		}
+		113
+		{
+			title = "Jet Jaw";
+			sprite = "JJAWA3A7";
+			width = 12;
+			height = 20;
+		}
+		126
+		{
+			title = "Crushstacean";
+			sprite = "CRABA0";
+			width = 24;
+			height = 32;
+			arg0
+			{
+				title = "Spawn direction";
+				type = 11;
+				enum
+				{
+					0 = "Right";
+					1 = "Left";
+				}
+			}
+		}
+		138
+		{
+			title = "Banpyura";
+			sprite = "CR2BA0";
+			width = 24;
+			height = 32;
+			arg0
+			{
+				title = "Spawn direction";
+				type = 11;
+				enum
+				{
+					0 = "Right";
+					1 = "Left";
+				}
+			}
+		}
+		117
+		{
+			title = "Robo-Hood";
+			sprite = "ARCHA1";
+			width = 24;
+			height = 32;
+			arg0
+			{
+				title = "Can jump?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		118
+		{
+			title = "Lance-a-Bot";
+			sprite = "CBFSA1";
+			width = 32;
+			height = 72;
+		}
+		1113
+		{
+			title = "Suspicious Lance-a-Bot Statue";
+			sprite = "CBBSA1";
+			width = 32;
+			height = 72;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
+		119
+		{
+			title = "Egg Guard";
+			sprite = "ESHIA1";
+			width = 16;
+			height = 48;
+			arg0
+			{
+				title = "Turn direction";
+				type = 11;
+				enum
+				{
+					0 = "Back";
+					1 = "Right";
+					2 = "Left";
+				}
+			}
+			arg1
+			{
+				title = "Double speed?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		115
+		{
+			title = "Bird Aircraft Strike Hazard";
+			sprite = "VLTRF1";
+			width = 12;
+			height = 24;
+		}
+		120
+		{
+			title = "Green Snapper";
+			sprite = "GSNPA1";
+			width = 24;
+			height = 24;
+		}
+		121
+		{
+			title = "Minus";
+			sprite = "MNUSA0";
+			width = 24;
+			height = 32;
+		}
+		134
+		{
+			title = "Canarivore";
+			sprite = "CANAA0";
+			width = 12;
+			height = 80;
+			hangs = 1;
+		}
+		123
+		{
+			title = "Unidus";
+			sprite = "UNIDA1";
+			width = 18;
+			height = 36;
+		}
+		135
+		{
+			title = "Pterabyte Spawner";
+			sprite = "PTERA2A8";
+			width = 16;
+			height = 16;
+			arg0
+			{
+				title = "Number of Pterabytes";
+				default = 1;
+			}
+		}
+		136
+		{
+			title = "Pyre Fly";
+			sprite = "PYREA0";
+			width = 24;
+			height = 34;
+			arg0
+			{
+				title = "Start on fire?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		137
+		{
+			title = "Dragonbomber";
+			sprite = "DRABA1";
+			width = 28;
+			height = 48;
+		}
+		105
+		{
+			title = "Jetty-Syn Bomber";
+			sprite = "JETBB1";
+			width = 20;
+			height = 50;
+			arg0
+			{
+				title = "Can move?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		106
+		{
+			title = "Jetty-Syn Gunner";
+			sprite = "JETGB1";
+			width = 20;
+			height = 48;
+			arg0
+			{
+				title = "Can move?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		112
+		{
+			title = "Spincushion";
+			sprite = "SHRPA1";
+			width = 16;
+			height = 24;
+		}
+		114
+		{
+			title = "Snailer";
+			sprite = "SNLRA3A7";
+			width = 24;
+			height = 48;
+		}
+		129
+		{
+			title = "Penguinator";
+			sprite = "PENGA1";
+			width = 24;
+			height = 32;
+		}
+		130
+		{
+			title = "Pophat";
+			sprite = "POPHA1";
+			width = 24;
+			height = 32;
+		}
+		107
+		{
+			title = "Crawla Commander";
+			sprite = "CCOMA1";
+			width = 16;
+			height = 32;
+		}
+		131
+		{
+			title = "Spinbobert";
+			sprite = "SBOBB0";
+			width = 32;
+			height = 32;
+		}
+		132
+		{
+			title = "Cacolantern";
+			sprite = "CACOA0";
+			width = 32;
+			height = 32;
+			arg0
+			{
+				title = "Can move?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		133
+		{
+			title = "Hangster";
+			sprite = "HBATC1";
+			width = 24;
+			height = 24;
+			hangs = 1;
+		}
+		127
+		{
+			title = "Hive Elemental";
+			sprite = "HIVEA0";
+			width = 32;
+			height = 80;
+			arg0
+			{
+				title = "Number of bees";
+			}
+		}
+		128
+		{
+			title = "Bumblebore";
+			sprite = "BUMBA1";
+			width = 16;
+			height = 32;
+			arg0
+			{
+				title = "Can move?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		124
+		{
+			title = "Buggle";
+			sprite = "BBUZA1";
+			width = 20;
+			height = 24;
+		}
+		116
+		{
+			title = "Pointy";
+			sprite = "PNTYA1";
+			width = 8;
+			height = 16;
+		}
 	}
-}
 
-frozenhillside
-{
-	color = 10; // Green
-	title = "Frozen Hillside";
+	bosses
+	{
+		color = 8; // Dark_Gray
+		arrow = 1;
+		title = "Bosses";
+
+		200
+		{
+			title = "Egg Mobile";
+			sprite = "EGGMA1";
+			width = 24;
+			height = 76;
+			arg0
+			{
+				title = "Boss ID";
+			}
+			arg1
+			{
+				title = "End level on death?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg2
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+			arg3
+			{
+				title = "Victory trigger tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Pinch trigger tag";
+				type = 15;
+			}
+		}
+		201
+		{
+			title = "Egg Slimer";
+			sprite = "EGGNA1";
+			width = 24;
+			height = 76;
+			arg0
+			{
+				title = "Boss ID";
+			}
+			arg1
+			{
+				title = "End level on death?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg2
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+			arg3
+			{
+				title = "Victory trigger tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Pinch trigger tag";
+				type = 15;
+			}
+			arg5
+			{
+				title = "Speed up when hit?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		202
+		{
+			title = "Sea Egg";
+			sprite = "EGGOA1";
+			width = 32;
+			height = 116;
+			arg0
+			{
+				title = "Boss ID";
+			}
+			arg1
+			{
+				title = "End level on death?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg2
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+			arg3
+			{
+				title = "Victory trigger tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Pinch trigger tag";
+				type = 15;
+			}
+		}
+		203
+		{
+			title = "Egg Colosseum";
+			sprite = "EGGPA1";
+			width = 24;
+			height = 76;
+			arg0
+			{
+				title = "Boss ID";
+			}
+			arg1
+			{
+				title = "End level on death?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg2
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+			arg3
+			{
+				title = "Victory trigger tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Pinch trigger tag";
+				type = 15;
+			}
+			arg5
+			{
+				title = "Cage drop trigger tag";
+				type = 15;
+			}
+		}
+		204
+		{
+			title = "Fang";
+			sprite = "FANGA1";
+			width = 24;
+			height = 60;
+			arg0
+			{
+				title = "Boss ID";
+			}
+			arg1
+			{
+				title = "End level on death?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg2
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+			arg3
+			{
+				title = "Victory trigger tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Pinch trigger tag";
+				type = 15;
+			}
+			arg5
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Grayscale";
+					2 = "Skip intro";
+				}
+			}
+		}
+		206
+		{
+			title = "Brak Eggman (Old)";
+			sprite = "BRAKB1";
+			width = 48;
+			height = 160;
+			arg0
+			{
+				title = "Boss ID";
+			}
+			arg1
+			{
+				title = "End level on death?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg2
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+			arg3
+			{
+				title = "Victory trigger tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Pinch trigger tag";
+				type = 15;
+			}
+			arg5
+			{
+				title = "Platform trigger tag";
+				type = 15;
+			}
+		}
+		207
+		{
+			title = "Metal Sonic (Race)";
+			sprite = "METLI1";
+			width = 16;
+			height = 48;
+			arg0
+			{
+				title = "Grayscale?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		208
+		{
+			title = "Metal Sonic (Battle)";
+			sprite = "METLC1";
+			width = 16;
+			height = 48;
+			arg0
+			{
+				title = "Boss ID";
+			}
+			arg1
+			{
+				title = "End level on death?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg2
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+			arg3
+			{
+				title = "Victory trigger tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Pinch trigger tag";
+				type = 15;
+			}
+			arg5
+			{
+				title = "Grayscale?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		209
+		{
+			title = "Brak Eggman";
+			sprite = "BRAK01";
+			width = 48;
+			height = 160;
+			arg0
+			{
+				title = "Boss ID";
+			}
+			arg1
+			{
+				title = "End level on death?";
+				type = 11;
+				enum = "noyes";
+			}
+			arg2
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+			arg3
+			{
+				title = "Victory trigger tag";
+				type = 15;
+			}
+			arg4
+			{
+				title = "Pinch trigger tag";
+				type = 15;
+			}
+			arg5
+			{
+				title = "Attack trigger tag";
+				type = 15;
+			}
+			arg6
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "No origin-fling death";
+					2 = "Electric barrier";
+				}
+			}
+		}
+		290
+		{
+			arrow = 0;
+			title = "Boss Escape Point";
+			width = 8;
+			height = 16;
+			sprite = "internal:eggmanend";
+		}
+		291
+		{
+			arrow = 0;
+			title = "Egg Capsule Center";
+			width = 8;
+			height = 16;
+			sprite = "internal:capsule";
+		}
+		292
+		{
+			arrow = 0;
+			title = "Boss Waypoint";
+			width = 8;
+			height = 16;
+			sprite = "internal:eggmanway";
+			arg0
+			{
+				title = "Sea Egg sequence";
+			}
+			arg1
+			{
+				title = "Brak Eggman sequence";
+			}
+		}
+		293
+		{
+			title = "Metal Sonic Gather Point";
+			sprite = "internal:metal";
+			width = 8;
+			height = 16;
+		}
+		294
+		{
+			title = "Fang Waypoint";
+			sprite = "internal:eggmanway";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Center waypoint?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+	}
 
-	2100
+	rings
 	{
-		title = "Ice Shard (Small)";
-		sprite = "FHZIA0";
-		width = 8;
-		height = 32;
+		color = 14; // Yellow
+		title = "Rings and Weapon Panels";
+		width = 24;
+		height = 24;
+		sprite = "RINGA0";
+		arg0
+		{
+			title = "Float?";
+			type = 11;
+			enum = "yesno";
+		}
+
+		300
+		{
+			title = "Ring";
+			sprite = "RINGA0";
+			width = 16;
+		}
+		301
+		{
+			title = "Bounce Ring";
+			sprite = "internal:RNGBA0";
+		}
+		302
+		{
+			title = "Rail Ring";
+			sprite = "internal:RNGRA0";
+		}
+		303
+		{
+			title = "Infinity Ring";
+			sprite = "internal:RNGIA0";
+		}
+		304
+		{
+			title = "Automatic Ring";
+			sprite = "internal:RNGAA0";
+		}
+		305
+		{
+			title = "Explosion Ring";
+			sprite = "internal:RNGEA0";
+		}
+		306
+		{
+			title = "Scatter Ring";
+			sprite = "internal:RNGSA0";
+		}
+		307
+		{
+			title = "Grenade Ring";
+			sprite = "internal:RNGGA0";
+		}
+		308
+		{
+			title = "CTF Team Ring (Red)";
+			sprite = "internal:RRNGA0";
+			width = 16;
+		}
+		309
+		{
+			title = "CTF Team Ring (Blue)";
+			sprite = "internal:BRNGA0";
+			width = 16;
+		}
+		330
+		{
+			title = "Bounce Ring Panel";
+			sprite = "internal:PIKBA0";
+		}
+		331
+		{
+			title = "Rail Ring Panel";
+			sprite = "internal:PIKRA0";
+		}
+		332
+		{
+			title = "Automatic Ring Panel";
+			sprite = "internal:PIKAA0";
+		}
+		333
+		{
+			title = "Explosion Ring Panel";
+			sprite = "internal:PIKEA0";
+		}
+		334
+		{
+			title = "Scatter Ring Panel";
+			sprite = "internal:PIKSA0";
+		}
+		335
+		{
+			title = "Grenade Ring Panel";
+			sprite = "internal:PIKGA0";
+		}
 	}
-	2101
+
+	collectibles
 	{
-		title = "Ice Shard (Large)";
-		sprite = "FHZIB0";
-		width = 8;
+		color = 10; // Light_Green
+		title = "Other Collectibles";
+		width = 16;
 		height = 32;
+		sort = 1;
+		sprite = "CEMGA0";
+
+		310
+		{
+			title = "CTF Red Flag";
+			sprite = "RFLGA0";
+			width = 24;
+			height = 64;
+		}
+		311
+		{
+			title = "CTF Blue Flag";
+			sprite = "BFLGA0";
+			width = 24;
+			height = 64;
+		}
+		312
+		{
+			title = "Emerald Token";
+			sprite = "TOKEA0";
+			width = 16;
+			height = 32;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		313
+		{
+			title = "Chaos Emerald 1 (Green)";
+			sprite = "CEMGA0";
+		}
+		314
+		{
+			title = "Chaos Emerald 2 (Purple)";
+			sprite = "CEMGB0";
+		}
+		315
+		{
+			title = "Chaos Emerald 3 (Blue)";
+			sprite = "CEMGC0";
+		}
+		316
+		{
+			title = "Chaos Emerald 4 (Cyan)";
+			sprite = "CEMGD0";
+		}
+		317
+		{
+			title = "Chaos Emerald 5 (Orange)";
+			sprite = "CEMGE0";
+		}
+		318
+		{
+			title = "Chaos Emerald 6 (Red)";
+			sprite = "CEMGF0";
+		}
+		319
+		{
+			title = "Chaos Emerald 7 (Gray)";
+			sprite = "CEMGG0";
+		}
+		320
+		{
+			title = "Emerald Hunt Location";
+			sprite = "SHRDA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		321
+		{
+			title = "Match Chaos Emerald Spawn";
+			sprite = "CEMGA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		322
+		{
+			title = "Emblem";
+			sprite = "EMBMA0";
+			width = 16;
+			height = 30;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
 	}
-	2102
+
+	boxes
 	{
-		title = "Crystal Tree (Aqua)";
-		sprite = "TRE3A0";
-		width = 20;
-		height = 200;
+		color = 7; // Gray
+		blocking = 2;
+		title = "Monitors";
+		width = 18;
+		height = 40;
+		arg0
+		{
+			title = "Death trigger tag";
+			type = 15;
+		}
+
+		400
+		{
+			title = "Super Ring (10 Rings)";
+			sprite = "TVRIA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		401
+		{
+			title = "Pity Shield";
+			sprite = "TVPIA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		402
+		{
+			title = "Attraction Shield";
+			sprite = "TVATA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		403
+		{
+			title = "Force Shield";
+			sprite = "TVFOA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		404
+		{
+			title = "Armageddon Shield";
+			sprite = "TVARA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		405
+		{
+			title = "Whirlwind Shield";
+			sprite = "TVWWA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		406
+		{
+			title = "Elemental Shield";
+			sprite = "TVELA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		407
+		{
+			title = "Super Sneakers";
+			sprite = "TVSSA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		408
+		{
+			title = "Invincibility";
+			sprite = "TVIVA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		409
+		{
+			title = "Extra Life";
+			sprite = "TV1UA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+			arg2
+			{
+				title = "Points";
+				type = 11;
+				enum
+				{
+					0 = "1,000";
+					1 = "10,000";
+				}
+			}
+		}
+		410
+		{
+			title = "Eggman";
+			sprite = "TVEGA0";
+		}
+		411
+		{
+			title = "Teleporter";
+			sprite = "TVMXA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		413
+		{
+			title = "Gravity Boots";
+			sprite = "TVGVA0";
+		}
+		414
+		{
+			title = "CTF Team Ring Monitor (Red)";
+			sprite = "TRRIA0";
+		}
+		415
+		{
+			title = "CTF Team Ring Monitor (Blue)";
+			sprite = "TBRIA0";
+		}
+		416
+		{
+			title = "Recycler";
+			sprite = "TVRCA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		418
+		{
+			title = "Score (1,000 Points)";
+			sprite = "TV1KA0";
+		}
+		419
+		{
+			title = "Score (10,000 Points)";
+			sprite = "TVTKA0";
+		}
+		420
+		{
+			title = "Flame Shield";
+			sprite = "TVFLA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		421
+		{
+			title = "Water Shield";
+			sprite = "TVBBA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
+		422
+		{
+			title = "Lightning Shield";
+			sprite = "TVZPA0";
+			arg1
+			{
+				title = "Respawn behavior";
+				type = 11;
+				enum = "monitorrespawn";
+			}
+		}
 	}
-	2103
+
+	boxes2
 	{
-		title = "Crystal Tree (Pink)";
-		sprite = "TRE3B0";
+		color = 18; // Gold
+		blocking = 2;
+		title = "Monitors (Respawning)";
 		width = 20;
-		height = 200;
+		height = 44;
+		arg0
+		{
+			title = "Death trigger tag";
+			type = 15;
+		}
+
+		431
+		{
+			title = "Pity Shield (Respawn)";
+			sprite = "TVPIB0";
+		}
+		432
+		{
+			title = "Attraction Shield (Respawn)";
+			sprite = "TVATB0";
+		}
+		433
+		{
+			title = "Force Shield (Respawn)";
+			sprite = "TVFOB0";
+		}
+		434
+		{
+			title = "Armageddon Shield (Respawn)";
+			sprite = "TVARB0";
+		}
+		435
+		{
+			title = "Whirlwind Shield (Respawn)";
+			sprite = "TVWWB0";
+		}
+		436
+		{
+			title = "Elemental Shield (Respawn)";
+			sprite = "TVELB0";
+		}
+		437
+		{
+			title = "Super Sneakers (Respawn)";
+			sprite = "TVSSB0";
+		}
+		438
+		{
+			title = "Invincibility (Respawn)";
+			sprite = "TVIVB0";
+		}
+		440
+		{
+			title = "Eggman (Respawn)";
+			sprite = "TVEGB0";
+		}
+		443
+		{
+			title = "Gravity Boots (Respawn)";
+			sprite = "TVGVB0";
+		}
+		450
+		{
+			title = "Flame Shield (Respawn)";
+			sprite = "TVFLB0";
+		}
+		451
+		{
+			title = "Water Shield (Respawn)";
+			sprite = "TVBBB0";
+		}
+		452
+		{
+			title = "Lightning Shield (Respawn)";
+			sprite = "TVZPB0";
+		}
 	}
-	2104
+
+	generic
 	{
-		title = "Amy Cameo";
-		sprite = "ROSYA1";
-		width = 16;
-		height = 48;
+		color = 11; // Light_Cyan
+		title = "Generic Items & Hazards";
+
+		500
+		{
+			title = "Air Bubble Patch";
+			sprite = "BUBLE0";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Distance check?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		501
+		{
+			title = "Signpost";
+			sprite = "SIGND0";
+			width = 8;
+			height = 32;
+		}
+		502
+		{
+			arrow = 1;
+			title = "Star Post";
+			sprite = "STPTA0M0";
+			width = 64;
+			height = 128;
+			arg0
+			{
+				title = "Order";
+			}
+			arg1
+			{
+				title = "Respawn at center?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		520
+		{
+			title = "Bomb Sphere";
+			sprite = "SPHRD0";
+			width = 16;
+			height = 24;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		521
+		{
+			title = "Spikeball";
+			sprite = "SPIKA0";
+			width = 12;
+			height = 8;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		522
+		{
+			title = "Wall Spike";
+			sprite = "WSPKALAR";
+			width = 16;
+			height = 14;
+			arrow = 1;
+			arg0
+			{
+				title = "Retraction interval";
+			}
+			arg1
+			{
+				title = "Start interval";
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Start retracted";
+					2 = "Intangible";
+				}
+			}
+		}
+		523
+		{
+			title = "Spike";
+			sprite = "USPKA0";
+			width = 8;
+			height = 32;
+			arg0
+			{
+				title = "Retraction interval";
+			}
+			arg1
+			{
+				title = "Start interval";
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Start retracted";
+					2 = "Intangible";
+				}
+			}
+		}
+		1130
+		{
+			title = "Small Mace";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+		}
+		1131
+		{
+			title = "Big Mace";
+			sprite = "BMCEA0";
+			width = 34;
+			height = 68;
+		}
+		1136
+		{
+			title = "Small Fireball";
+			sprite = "SFBRA0";
+			width = 17;
+			height = 34;
+		}
+		1137
+		{
+			title = "Large Fireball";
+			sprite = "BFBRA0";
+			width = 34;
+			height = 68;
+		}
+	}
+
+	springs
+	{
+		color = 12; // Light_Red
+		title = "Springs and Fans";
+		width = 20;
+		height = 16;
+		sprite = "RSPRD2";
+
+		540
+		{
+			title = "Fan";
+			sprite = "FANSA0D0";
+			width = 16;
+			height = 8;
+			arg0
+			{
+				title = "Lift height";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Invisible";
+					2 = "No distance check";
+				}
+			}
+		}
+		541
+		{
+			title = "Gas Jet";
+			sprite = "STEMD0";
+			width = 32;
+			arg0
+			{
+				title = "Play sound?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		542
+		{
+			title = "Bumper";
+			sprite = "BUMPA0";
+			width = 32;
+			height = 64;
+		}
+		543
+		{
+			title = "Balloon";
+			sprite = "BLONA0";
+			width = 32;
+			height = 64;
+			arg0
+			{
+				title = "Respawn?";
+				type = 11;
+				enum = "noyes";
+			}
+			stringarg0
+			{
+				title = "Color";
+			}
+		}
+		550
+		{
+			title = "Yellow Spring";
+			sprite = "SPRYA0";
+		}
+		551
+		{
+			title = "Red Spring";
+			sprite = "SPRRA0";
+		}
+		552
+		{
+			title = "Blue Spring";
+			sprite = "SPRBA0";
+		}
+		555
+		{
+			arrow = 1;
+			title = "Diagonal Yellow Spring";
+			sprite = "YSPRD2";
+			width = 16;
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Ignore gravity";
+					2 = "Rotate 22.5° CCW";
+				}
+			}
+		}
+		556
+		{
+			arrow = 1;
+			title = "Diagonal Red Spring";
+			sprite = "RSPRD2";
+			width = 16;
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Ignore gravity";
+					2 = "Rotate 22.5° CCW";
+				}
+			}
+		}
+		557
+		{
+			arrow = 1;
+			title = "Diagonal Blue Spring";
+			sprite = "BSPRD2";
+			width = 16;
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Ignore gravity";
+					2 = "Rotate 22.5° CCW";
+				}
+			}
+		}
+		558
+		{
+			arrow = 1;
+			title = "Horizontal Yellow Spring";
+			sprite = "SSWYD2D8";
+			width = 16;
+			height = 32;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		559
+		{
+			arrow = 1;
+			title = "Horizontal Red Spring";
+			sprite = "SSWRD2D8";
+			width = 16;
+			height = 32;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		560
+		{
+			arrow = 1;
+			title = "Horizontal Blue Spring";
+			sprite = "SSWBD2D8";
+			width = 16;
+			height = 32;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		1134
+		{
+			title = "Yellow Spring Ball";
+			sprite = "YSPBA0";
+			width = 17;
+			height = 34;
+		}
+		1135
+		{
+			title = "Red Spring Ball";
+			sprite = "RSPBA0";
+			width = 17;
+			height = 34;
+		}
+		544
+		{
+			arrow = 1;
+			title = "Yellow Boost Panel";
+			sprite = "BSTYA0";
+			width = 28;
+			height = 2;
+			arg0
+			{
+				title = "Force spin?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		545
+		{
+			arrow = 1;
+			title = "Red Boost Panel";
+			sprite = "BSTRA0";
+			width = 28;
+			height = 2;
+			arg0
+			{
+				title = "Force spin?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
 	}
-	2105
+
+	patterns
 	{
-		title = "Mistletoe";
-		sprite = "XMS6A0";
-		width = 52;
-		height = 106;
+		color = 5; // Magenta
+		arrow = 1;
+		title = "Special Placement Patterns";
+		width = 16;
+		height = 384;
+		sprite = "RINGA0";
+
+		600
+		{
+			arrow = 0;
+			title = "5 Vertical Rings (Yellow Spring)";
+			sprite = "RINGA0";
+		}
+		601
+		{
+			arrow = 0;
+			title = "5 Vertical Rings (Red Spring)";
+			sprite = "RINGA0";
+			height = 1024;
+		}
+		602
+		{
+			title = "5 Diagonal Rings (Yellow Spring)";
+			sprite = "RINGA0";
+			height = 32;
+		}
+		603
+		{
+			title = "10 Diagonal Rings (Red Spring)";
+			sprite = "RINGA0";
+			height = 32;
+		}
+		604
+		{
+			title = "Circle of Rings";
+			sprite = "RINGA0";
+			width = 96;
+			height = 192;
+		}
+		605
+		{
+			title = "Circle of Rings (Big)";
+			sprite = "RINGA0";
+			width = 192;
+		}
+		606
+		{
+			title = "Circle of Blue Spheres";
+			sprite = "SPHRA0";
+			width = 96;
+			height = 192;
+		}
+		607
+		{
+			title = "Circle of Blue Spheres (Big)";
+			sprite = "SPHRA0";
+			width = 192;
+		}
+		608
+		{
+			title = "Circle of Rings and Spheres";
+			sprite = "SPHRA0";
+			width = 96;
+			height = 192;
+		}
+		609
+		{
+			title = "Circle of Rings and Spheres (Big)";
+			sprite = "SPHRA0";
+			width = 192;
+		}
+		610
+		{
+			title = "Row of Items";
+			sprite = "RINGA0";
+			arg0
+			{
+				title = "Number of items";
+			}
+			arg1
+			{
+				title = "Horizontal spacing";
+			}
+			arg2
+			{
+				title = "Vertical spacing";
+			}
+			stringarg0
+			{
+				title = "Object types";
+			}
+		}
+		611
+		{
+			title = "Circle of Items";
+			sprite = "RINGA0";
+			width = 96;
+			height = 192;
+			arg0
+			{
+				title = "Number of items";
+			}
+			arg1
+			{
+				title = "Radius";
+			}
+			stringarg0
+			{
+				title = "Object types";
+			}
+		}
 	}
-}
 
-flickies
-{
-	color = 10; // Green
-	title = "Flickies";
-	width = 8;
-	height = 20;
+	invisible
+	{
+		color = 15; // White
+		title = "Misc. Invisible";
+		width = 0;
+		height = 0;
+		sprite = "UNKNA0";
+		sort = 1;
+		fixedsize = true;
+		blocking = 0;
+
+		700
+		{
+			title = "Ambient Sound Effect";
+			sprite = "internal:ambiance";
+			arg0
+			{
+				title = "Repeat speed";
+			}
+			stringarg0
+			{
+				title = "Sound";
+			}
+		}
+
+		750
+		{
+			title = "Slope Vertex";
+			sprite = "internal:vertexslope";
+			arg0
+			{
+				title = "Absolute height?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+
+		751
+		{
+			arrow = 1;
+			title = "Teleport Destination";
+			sprite = "internal:tele";
+		}
+
+		752
+		{
+			arrow = 1;
+			title = "Alternate View Point";
+			sprite = "internal:view";
+		}
+
+		753
+		{
+			title = "Zoom Tube Waypoint";
+			sprite = "internal:zoom";
+			arg0
+			{
+				title = "Sequence";
+			}
+			arg1
+			{
+				title = "Order";
+			}
+		}
 
-	2200
+		754
+		{
+			title = "Push/Pull Point";
+			sprite = "GWLGA0";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Strength";
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Fade using XY";
+					2 = "Push using XYZ";
+					4 = "Non-exclusive";
+				}
+			}
+		}
+		756
+		{
+			title = "Blast Linedef Executor";
+			sprite = "TOADA0";
+			width = 32;
+			height = 16;
+			arg0
+			{
+				title = "Linedef tag";
+				type = 15;
+			}
+		}
+		757
+		{
+			title = "Fan Particle Generator";
+			sprite = "PRTLA0";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Particles";
+			}
+			arg1
+			{
+				title = "Radius";
+			}
+			arg2
+			{
+				title = "Rising speed";
+			}
+			arg3
+			{
+				title = "Rotation speed";
+				type = 8;
+			}
+			arg4
+			{
+				title = "Spawn interval";
+			}
+			arg5
+			{
+				title = "Rising distance";
+			}
+			arg6
+			{
+				title = "Heights control linedef";
+				type = 15;
+			}
+		}
+		758
+		{
+			title = "Object Angle Anchor";
+			sprite = "internal:view";
+		}
+		760
+		{
+			title = "PolyObject Anchor";
+			sprite = "internal:polyanchor";
+		}
+		761
+		{
+			title = "PolyObject Spawn Point";
+			sprite = "internal:polycenter";
+		}
+		762
+		{
+			title = "PolyObject Spawn Point (Crush)";
+			sprite = "internal:polycentercrush";
+		}
+		780
+		{
+			title = "Skybox View Point";
+			sprite = "internal:skyb";
+			arg0
+			{
+				title = "Type";
+				type = 11;
+				enum
+				{
+					0 = "Viewpoint";
+					1 = "Centerpoint";
+				}
+			}
+		}
+	}
+
+	greenflower
 	{
-		title = "Bluebird";
-		sprite = "FL01A1";
+		color = 10; // Green
+		title = "Greenflower";
+
+		800
+		{
+			title = "GFZ Flower";
+			sprite = "FWR1A0";
+			width = 16;
+			height = 40;
+		}
+		801
+		{
+			title = "Sunflower";
+			sprite = "FWR2A0";
+			width = 16;
+			height = 96;
+		}
+		802
+		{
+			title = "Budding Flower";
+			sprite = "FWR3A0";
+			width = 8;
+			height = 32;
+		}
+		803
+		{
+			title = "Blueberry Bush";
+			sprite = "BUS3A0";
+			width = 16;
+			height = 32;
+		}
+		804
+		{
+			title = "Berry Bush";
+			sprite = "BUS1A0";
+			width = 16;
+			height = 32;
+		}
+		805
+		{
+			title = "Bush";
+			sprite = "BUS2A0";
+			width = 16;
+			height = 32;
+		}
+		806
+		{
+			title = "GFZ Tree";
+			sprite = "TRE1A0";
+			width = 20;
+			height = 128;
+		}
+		807
+		{
+			title = "GFZ Berry Tree";
+			sprite = "TRE1B0";
+			width = 20;
+			height = 128;
+		}
+		808
+		{
+			title = "GFZ Cherry Tree";
+			sprite = "TRE1C0";
+			width = 20;
+			height = 128;
+		}
+		809
+		{
+			title = "Checkered Tree";
+			sprite = "TRE2A0";
+			width = 20;
+			height = 200;
+		}
+		810
+		{
+			title = "Checkered Tree (Sunset)";
+			sprite = "TRE2B0";
+			width = 20;
+			height = 200;
+		}
+		811
+		{
+			title = "Polygon Tree";
+			sprite = "TRE4A0";
+			width = 20;
+			height = 200;
+		}
+		812
+		{
+			title = "Bush Tree";
+			sprite = "TRE5A0";
+			width = 20;
+			height = 200;
+		}
+		813
+		{
+			title = "Red Bush Tree";
+			sprite = "TRE5B0";
+			width = 20;
+			height = 200;
+		}
 	}
-	2201
+
+	technohill
 	{
-		title = "Rabbit";
-		sprite = "FL02A1";
+		color = 10; // Green
+		title = "Techno Hill";
+
+		900
+		{
+			title = "THZ Steam Flower";
+			sprite = "THZPA0";
+			width = 8;
+			height = 32;
+		}
+		901
+		{
+			title = "Alarm";
+			sprite = "ALRMA0";
+			width = 8;
+			height = 16;
+			hangs = 1;
+		}
+		902
+		{
+			title = "THZ Spin Flower (Red)";
+			sprite = "FWR5A0";
+			width = 16;
+			height = 64;
+		}
+		903
+		{
+			title = "THZ Spin Flower (Yellow)";
+			sprite = "FWR6A0";
+			width = 16;
+			height = 64;
+		}
+		904
+		{
+			arrow = 1;
+			title = "Whistlebush";
+			sprite = "THZTA0";
+			width = 16;
+			height = 64;
+		}
 	}
-	2202
+
+	deepsea
 	{
-		title = "Chicken";
-		sprite = "FL03A1";
+		color = 10; // Green
+		title = "Deep Sea";
+
+		1000
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Gargoyle";
+			sprite = "GARGA1";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
+		1009
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Gargoyle (Big)";
+			sprite = "GARGB1";
+			width = 32;
+			height = 80;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
+		1001
+		{
+			title = "Seaweed";
+			sprite = "SEWEA0";
+			width = 24;
+			height = 56;
+		}
+		1002
+		{
+			title = "Dripping Water";
+			sprite = "DRIPD0";
+			width = 8;
+			height = 16;
+			hangs = 1;
+			arg0
+			{
+				title = "Dripping delay";
+			}
+		}
+		1003
+		{
+			title = "Coral (Green)";
+			sprite = "CORLA0";
+			width = 29;
+			height = 40;
+		}
+		1004
+		{
+			title = "Coral (Red)";
+			sprite = "CORLB0";
+			width = 30;
+			height = 53;
+		}
+		1005
+		{
+			title = "Coral (Orange)";
+			sprite = "CORLC0";
+			width = 28;
+			height = 41;
+		}
+		1006
+		{
+			title = "Blue Crystal";
+			sprite = "BCRYA1";
+			width = 8;
+			height = 16;
+		}
+		1007
+		{
+			title = "Kelp";
+			sprite = "KELPA0";
+			width = 16;
+			height = 292;
+			arg0
+			{
+				title = "Double size?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1008
+		{
+			title = "Stalagmite (DSZ1)";
+			sprite = "DSTGA0";
+			width = 8;
+			height = 116;
+			arg0
+			{
+				title = "Double size?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1010
+		{
+			arrow = 1;
+			title = "Light Beam";
+			sprite = "LIBEARAL";
+			width = 16;
+			height = 16;
+		}
+		1011
+		{
+			title = "Stalagmite (DSZ2)";
+			sprite = "DSTGA0";
+			width = 8;
+			height = 116;
+			arg0
+			{
+				title = "Double size?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1012
+		{
+			arrow = 1;
+			title = "Big Floating Mine";
+			width = 28;
+			height = 56;
+			sprite = "BMNEA1";
+		}
+		1013
+		{
+			title = "Animated Kelp";
+			sprite = "ALGAA0";
+			width = 48;
+			height = 120;
+		}
+		1014
+		{
+			title = "Large Coral (Brown)";
+			sprite = "CORLD0";
+			width = 56;
+			height = 112;
+		}
+		1015
+		{
+			title = "Large Coral (Beige)";
+			sprite = "CORLE0";
+			width = 56;
+			height = 112;
+		}
 	}
-	2203
+
+	castleeggman
 	{
-		title = "Seal";
-		sprite = "FL04A1";
+		color = 10; // Green
+		title = "Castle Eggman";
+
+		1100
+		{
+			title = "Chain (Decorative)";
+			sprite = "CHANA0";
+			width = 4;
+			height = 128;
+			hangs = 1;
+		}
+		1101
+		{
+			title = "Torch";
+			sprite = "FLAMA0E0";
+			width = 8;
+			height = 32;
+			arg0
+			{
+				title = "Corona?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1102
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Eggman Statue";
+			sprite = "ESTAA1";
+			width = 32;
+			height = 240;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+			arg1
+			{
+				title = "Solid gold?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1103
+		{
+			title = "CEZ Flower";
+			sprite = "FWR4A0";
+			width = 16;
+			height = 40;
+		}
+		1104
+		{
+			title = "Mace Spawnpoint";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+			arg0
+			{
+				title = "Number of links";
+			}
+			arg1
+			{
+				title = "Number of spokes";
+			}
+			arg2
+			{
+				title = "Width";
+			}
+			arg3
+			{
+				title = "Speed";
+			}
+			arg4
+			{
+				title = "Phase";
+				type = 8;
+			}
+			arg5
+			{
+				title = "Pinch";
+				type = 8;
+			}
+			arg6
+			{
+				title = "Omitted spokes";
+			}
+			arg7
+			{
+				title = "Omitted links";
+			}
+			arg8
+			{
+				title = "Flags";
+				type = 12;
+				enum = "maceflags";
+			}
+		}
+		1105
+		{
+			title = "Chain with Maces Spawnpoint";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+			arg0
+			{
+				title = "Number of links";
+			}
+			arg1
+			{
+				title = "Number of spokes";
+			}
+			arg2
+			{
+				title = "Width";
+			}
+			arg3
+			{
+				title = "Speed";
+			}
+			arg4
+			{
+				title = "Phase";
+				type = 8;
+			}
+			arg5
+			{
+				title = "Pinch";
+				type = 8;
+			}
+			arg6
+			{
+				title = "Omitted spokes";
+			}
+			arg7
+			{
+				title = "Omitted links";
+			}
+			arg8
+			{
+				title = "Flags";
+				type = 12;
+				enum = "maceflags";
+			}
+		}
+		1106
+		{
+			title = "Chained Spring Spawnpoint";
+			sprite = "YSPBA0";
+			width = 17;
+			height = 34;
+			arg0
+			{
+				title = "Number of links";
+			}
+			arg1
+			{
+				title = "Number of spokes";
+			}
+			arg2
+			{
+				title = "Width";
+			}
+			arg3
+			{
+				title = "Speed";
+			}
+			arg4
+			{
+				title = "Phase";
+				type = 8;
+			}
+			arg5
+			{
+				title = "Pinch";
+				type = 8;
+			}
+			arg6
+			{
+				title = "Omitted spokes";
+			}
+			arg7
+			{
+				title = "Omitted links";
+			}
+			arg8
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Red spring";
+					2 = "No sounds";
+					4 = "Player-turnable chain";
+					8 = "Swing instead of spin";
+					16 = "Make chain from end item";
+					32 = "Spawn link at origin";
+					64 = "Clip inside ground";
+					128 = "No distance check";
+				}
+			}
+		}
+		1107
+		{
+			title = "Chain Spawnpoint";
+			sprite = "BMCHA0";
+			width = 17;
+			height = 34;
+			arg0
+			{
+				title = "Number of links";
+			}
+			arg1
+			{
+				title = "Number of spokes";
+			}
+			arg2
+			{
+				title = "Width";
+			}
+			arg3
+			{
+				title = "Speed";
+			}
+			arg4
+			{
+				title = "Phase";
+				type = 8;
+			}
+			arg5
+			{
+				title = "Pinch";
+				type = 8;
+			}
+			arg6
+			{
+				title = "Omitted spokes";
+			}
+			arg7
+			{
+				title = "Omitted links";
+			}
+			arg8
+			{
+				title = "Flags";
+				type = 12;
+				enum = "maceflags";
+
+			}
+		}
+		1108
+		{
+			arrow = 1;
+			title = "Hidden Chain Spawnpoint";
+			sprite = "internal:chain3";
+			width = 17;
+			height = 34;
+		}
+		1109
+		{
+			title = "Firebar Spawnpoint";
+			sprite = "BFBRA0";
+			width = 17;
+			height = 34;
+			arg0
+			{
+				title = "Number of links";
+			}
+			arg1
+			{
+				title = "Number of spokes";
+			}
+			arg2
+			{
+				title = "Width";
+			}
+			arg3
+			{
+				title = "Speed";
+			}
+			arg4
+			{
+				title = "Phase";
+				type = 8;
+			}
+			arg5
+			{
+				title = "Pinch";
+				type = 8;
+			}
+			arg6
+			{
+				title = "Omitted spokes";
+			}
+			arg7
+			{
+				title = "Omitted links";
+			}
+			arg8
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Double size";
+					2 = "No sounds";
+					4 = "Player-turnable chain";
+					8 = "Swing instead of spin";
+					16 = "Omit chain links";
+					32 = "Spawn link at origin";
+					64 = "Clip inside ground";
+					128 = "No distance check";
+				}
+			}
+		}
+		1110
+		{
+			title = "Custom Mace Spawnpoint";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+			arg0
+			{
+				title = "Number of links";
+			}
+			arg1
+			{
+				title = "Number of spokes";
+			}
+			arg2
+			{
+				title = "Width";
+			}
+			arg3
+			{
+				title = "Speed";
+			}
+			arg4
+			{
+				title = "Phase";
+				type = 8;
+			}
+			arg5
+			{
+				title = "Pinch";
+				type = 8;
+			}
+			arg6
+			{
+				title = "Omitted spokes";
+			}
+			arg7
+			{
+				title = "Omitted links";
+			}
+			arg8
+			{
+				title = "Flags";
+				type = 12;
+				enum = "maceflags";
+			}
+			stringarg0
+			{
+				title = "Mace object type";
+				type = 2;
+			}
+			stringarg1
+			{
+				title = "Link object type";
+				type = 2;
+			}
+		}
+		1111
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Crawla Statue";
+			sprite = "CSTAA1";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
+		1112
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Lance-a-Bot Statue";
+			sprite = "CBBSA1";
+			width = 32;
+			height = 72;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
+		1114
+		{
+			title = "Pine Tree";
+			sprite = "PINEA0";
+			width = 16;
+			height = 628;
+		}
+		1115
+		{
+			title = "CEZ Shrub (Small)";
+			sprite = "CEZBA0";
+			width = 16;
+			height = 24;
+		}
+		1116
+		{
+			title = "CEZ Shrub (Large)";
+			sprite = "CEZBB0";
+			width = 32;
+			height = 48;
+		}
+		1117
+		{
+			arrow = 1;
+			title = "Pole Banner (Red)";
+			sprite = "BANRA0";
+			width = 40;
+			height = 224;
+		}
+		1118
+		{
+			arrow = 1;
+			title = "Pole Banner (Blue)";
+			sprite = "BANRA0";
+			width = 40;
+			height = 224;
+		}
+		1119
+		{
+			title = "Candle";
+			sprite = "CNDLA0";
+			width = 8;
+			height = 48;
+			arg0
+			{
+				title = "Corona?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1120
+		{
+			title = "Candle Pricket";
+			sprite = "CNDLB0";
+			width = 8;
+			height = 176;
+			arg0
+			{
+				title = "Corona?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1121
+		{
+			title = "Flame Holder";
+			sprite = "FLMHA0";
+			width = 24;
+			height = 80;
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "No flame";
+					2 = "Add corona";
+				}
+			}
+		}
+		1122
+		{
+			title = "Fire Torch";
+			sprite = "CTRCA0";
+			width = 16;
+			height = 80;
+		}
+		1123
+		{
+			title = "Cannonball Launcher";
+			sprite = "internal:cannonball";
+			width = 8;
+			height = 16;
+		}
+		1124
+		{
+			blocking = 2;
+			title = "Cannonball";
+			sprite = "CBLLA0";
+			width = 20;
+			height = 40;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
+		1125
+		{
+			title = "Brambles";
+			sprite = "CABRALAR";
+			width = 48;
+			height = 32;
+		}
+		1126
+		{
+			title = "Invisible Lockon Object";
+			sprite = "LCKNC0";
+			width = 16;
+			height = 32;
+		}
+		1127
+		{
+			title = "Spectator Eggrobo";
+			sprite = "EGR1A1";
+			width = 20;
+			height = 72;
+			arg0
+			{
+				title = "Movement";
+				type = 11;
+				enum
+				{
+					0 = "None";
+					1 = "Right";
+					2 = "Left";
+				}
+			}
+		}
+		1128
+		{
+			arrow = 1;
+			title = "Waving Flag (Red)";
+			sprite = "CFLGA0";
+			width = 8;
+			height = 208;
+		}
+		1129
+		{
+			arrow = 1;
+			title = "Waving Flag (Blue)";
+			sprite = "CFLGA0";
+			width = 8;
+			height = 208;
+		}
 	}
-	2204
+
+	aridcanyon
 	{
-		title = "Pig";
-		sprite = "FL05A1";
+		color = 10; // Green
+		title = "Arid Canyon";
+
+		1200
+		{
+			title = "Tumbleweed (Big)";
+			sprite = "BTBLA0";
+			width = 24;
+			height = 48;
+			arg0
+			{
+				title = "Move perpetually?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1201
+		{
+			title = "Tumbleweed (Small)";
+			sprite = "STBLA0";
+			width = 12;
+			height = 24;
+			arg0
+			{
+				title = "Move perpetually?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1202
+		{
+			arrow = 1;
+			title = "Rock Spawner";
+			sprite = "ROIAA0";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Speed";
+			}
+			arg1
+			{
+				title = "Spawn interval";
+			}
+			arg2
+			{
+				title = "Randomize speed?";
+				type = 11;
+				enum = "noyes";
+			}
+			stringarg0
+			{
+				title = "Object type";
+				type = 2;
+			}
+		}
+		1203
+		{
+			title = "Tiny Red Flower Cactus";
+			sprite = "CACTA0";
+			width = 13;
+			height = 24;
+		}
+		1204
+		{
+			title = "Small Red Flower Cactus";
+			sprite = "CACTB0";
+			width = 15;
+			height = 52;
+		}
+		1205
+		{
+			title = "Tiny Blue Flower Cactus";
+			sprite = "CACTC0";
+			width = 13;
+			height = 24;
+		}
+		1206
+		{
+			title = "Small Blue Flower Cactus";
+			sprite = "CACTD0";
+			width = 15;
+			height = 52;
+		}
+		1207
+		{
+			title = "Prickly Pear";
+			sprite = "CACTE0";
+			width = 32;
+			height = 96;
+		}
+		1208
+		{
+			title = "Barrel Cactus";
+			sprite = "CACTF0";
+			width = 20;
+			height = 128;
+		}
+		1209
+		{
+			title = "Tall Barrel Cactus";
+			sprite = "CACTG0";
+			width = 24;
+			height = 224;
+		}
+		1210
+		{
+			title = "Armed Cactus";
+			sprite = "CACTH0";
+			width = 24;
+			height = 256;
+		}
+		1211
+		{
+			title = "Ball Cactus";
+			sprite = "CACTI0";
+			width = 48;
+			height = 96;
+		}
+		1212
+		{
+			title = "Caution Sign";
+			sprite = "WWSGAR";
+			width = 22;
+			height = 64;
+		}
+		1213
+		{
+			title = "Cacti Sign";
+			sprite = "WWS2AR";
+			width = 22;
+			height = 64;
+		}
+		1214
+		{
+			title = "Sharp Turn Sign";
+			sprite = "WWS3ALAR";
+			width = 16;
+			height = 192;
+		}
+		1215
+		{
+			title = "Mine Oil Lamp";
+			sprite = "OILLA0";
+			width = 22;
+			height = 64;
+			hangs = 1;
+		}
+		1216
+		{
+			title = "TNT Barrel";
+			sprite = "BARRA1";
+			width = 24;
+			height = 63;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
+		1217
+		{
+			title = "TNT Proximity Shell";
+			sprite = "REMTA0";
+			width = 64;
+			height = 40;
+		}
+		1218
+		{
+			title = "Dust Devil";
+			sprite = "TAZDCR";
+			width = 80;
+			height = 416;
+		}
+		1219
+		{
+			title = "Minecart Spawner";
+			sprite = "MCRTCLFR";
+			width = 22;
+			height = 32;
+		}
+		1220
+		{
+			title = "Minecart Stopper";
+			sprite = "MCRTIR";
+			width = 32;
+			height = 32;
+		}
+		1221
+		{
+			title = "Minecart Saloon Door";
+			sprite = "SALDARAL";
+			width = 96;
+			height = 160;
+			arg0
+			{
+				title = "Allow non-minecart players?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1222
+		{
+			title = "Train Cameo Spawner";
+			sprite = "TRAEBRBL";
+			width = 28;
+			height = 32;
+		}
+		1223
+		{
+			title = "Train Dust Spawner";
+			sprite = "ADSTA0";
+			width = 4;
+			height = 4;
+		}
+		1224
+		{
+			title = "Train Steam Spawner";
+			sprite = "STEAA0";
+			width = 4;
+			height = 4;
+		}
+		1229
+		{
+			title = "Minecart Switch Point";
+			sprite = "internal:zoom";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Type";
+				type = 11;
+				enum
+				{
+					0 = "Disable";
+					1 = "Enable";
+				}
+			}
+		}
+		1230
+		{
+			title = "Tiny Cactus";
+			sprite = "CACTJ0";
+			width = 13;
+			height = 28;
+		}
+		1231
+		{
+			title = "Small Cactus";
+			sprite = "CACTK0";
+			width = 15;
+			height = 60;
+		}
 	}
-	2205
+
+	redvolcano
 	{
-		title = "Chipmunk";
-		sprite = "FL06A1";
+		color = 10; // Green
+		title = "Red Volcano";
+
+		1300
+		{
+			arrow = 1;
+			title = "Flame Jet (Horizontal)";
+			sprite = "internal:flameh";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "On time";
+			}
+			arg1
+			{
+				title = "Off time";
+			}
+			arg2
+			{
+				title = "Strength";
+			}
+			arg3
+			{
+				title = "Waving direction";
+				type = 11;
+				enum
+				{
+					0 = "Horizontal";
+					1 = "Vertical";
+				}
+			}
+		}
+		1301
+		{
+			title = "Flame Jet (Vertical)";
+			sprite = "internal:flamev";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "On time";
+			}
+			arg1
+			{
+				title = "Off time";
+			}
+			arg2
+			{
+				title = "Strength";
+			}
+			arg3
+			{
+				title = "Shooting direction";
+				type = 11;
+				enum
+				{
+					0 = "Upwards";
+					1 = "Downwards";
+				}
+			}
+		}
+		1302
+		{
+			title = "Spinning Flame Jet (Counter-Clockwise)";
+			sprite = "internal:flame2";
+			width = 16;
+			height = 24;
+		}
+		1303
+		{
+			title = "Spinning Flame Jet (Clockwise)";
+			sprite = "internal:flame1";
+			width = 16;
+			height = 24;
+		}
+		1304
+		{
+			title = "Lavafall";
+			sprite = "LFALF0";
+			width = 30;
+			height = 32;
+			arg0
+			{
+				title = "Initial delay";
+			}
+			arg1
+			{
+				title = "Double size?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1305
+		{
+			title = "Rollout Rock";
+			sprite = "PUMIA1A5";
+			width = 30;
+			height = 60;
+			arg0
+			{
+				title = "Buoyant?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		1306
+		{
+			title = "Big Fern";
+			sprite = "JPLAB0";
+			width = 32;
+			height = 48;
+		}
+		1307
+		{
+			title = "Jungle Palm";
+			sprite = "JPLAC0";
+			width = 32;
+			height = 48;
+		}
+		1308
+		{
+			title = "Torch Flower";
+			sprite = "TFLOA0";
+			width = 14;
+			height = 110;
+		}
+		1309
+		{
+			title = "RVZ1 Wall Vine (Long)";
+			sprite = "WVINALAR";
+			width = 1;
+			height = 288;
+		}
+		1310
+		{
+			title = "RVZ1 Wall Vine (Short)";
+			sprite = "WVINBLBR";
+			width = 1;
+			height = 288;
+		}
 	}
-	2206
+
+	botanicserenity
 	{
-		title = "Penguin";
-		sprite = "FL07A1";
+		color = 10; // Green
+		title = "Botanic Serenity";
+		width = 16;
+		height = 32;
+		sprite = "BSZ1A0";
+		1400
+		{
+			title = "Tall Flower (Red)";
+			sprite = "BSZ1A0";
+		}
+		1401
+		{
+			title = "Tall Flower (Purple)";
+			sprite = "BSZ1B0";
+		}
+		1402
+		{
+			title = "Tall Flower (Blue)";
+			sprite = "BSZ1C0";
+		}
+		1403
+		{
+			title = "Tall Flower (Cyan)";
+			sprite = "BSZ1D0";
+		}
+		1404
+		{
+			title = "Tall Flower (Yellow)";
+			sprite = "BSZ1E0";
+		}
+		1405
+		{
+			title = "Tall Flower (Orange)";
+			sprite = "BSZ1F0";
+		}
+		1410
+		{
+			title = "Medium Flower (Red)";
+			sprite = "BSZ2A0";
+		}
+		1411
+		{
+			title = "Medium Flower (Purple)";
+			sprite = "BSZ2B0";
+		}
+		1412
+		{
+			title = "Medium Flower (Blue)";
+			sprite = "BSZ2C0";
+		}
+		1413
+		{
+			title = "Medium Flower (Cyan)";
+			sprite = "BSZ2D0";
+		}
+		1414
+		{
+			title = "Medium Flower (Yellow)";
+			sprite = "BSZ2E0";
+		}
+		1415
+		{
+			title = "Medium Flower (Orange)";
+			sprite = "BSZ2F0";
+		}
+		1420
+		{
+			title = "Short Flower (Red)";
+			sprite = "BSZ3A0";
+		}
+		1421
+		{
+			title = "Short Flower (Purple)";
+			sprite = "BSZ3B0";
+		}
+		1422
+		{
+			title = "Short Flower (Blue)";
+			sprite = "BSZ3C0";
+		}
+		1423
+		{
+			title = "Short Flower (Cyan)";
+			sprite = "BSZ3D0";
+		}
+		1424
+		{
+			title = "Short Flower (Yellow)";
+			sprite = "BSZ3E0";
+		}
+		1425
+		{
+			title = "Short Flower (Orange)";
+			sprite = "BSZ3F0";
+		}
+		1430
+		{
+			title = "Tulip (Red)";
+			sprite = "BST1A0";
+		}
+		1431
+		{
+			title = "Tulip (Purple)";
+			sprite = "BST2A0";
+		}
+		1432
+		{
+			title = "Tulip (Blue)";
+			sprite = "BST3A0";
+		}
+		1433
+		{
+			title = "Tulip (Cyan)";
+			sprite = "BST4A0";
+		}
+		1434
+		{
+			title = "Tulip (Yellow)";
+			sprite = "BST5A0";
+		}
+		1435
+		{
+			title = "Tulip (Orange)";
+			sprite = "BST6A0";
+		}
+		1440
+		{
+			title = "Cluster (Red)";
+			sprite = "BSZ5A0";
+		}
+		1441
+		{
+			title = "Cluster (Purple)";
+			sprite = "BSZ5B0";
+		}
+		1442
+		{
+			title = "Cluster (Blue)";
+			sprite = "BSZ5C0";
+		}
+		1443
+		{
+			title = "Cluster (Cyan)";
+			sprite = "BSZ5D0";
+		}
+		1444
+		{
+			title = "Cluster (Yellow)";
+			sprite = "BSZ5E0";
+		}
+		1445
+		{
+			title = "Cluster (Orange)";
+			sprite = "BSZ5F0";
+		}
+		1450
+		{
+			title = "Bush (Red)";
+			sprite = "BSZ6A0";
+		}
+		1451
+		{
+			title = "Bush (Purple)";
+			sprite = "BSZ6B0";
+		}
+		1452
+		{
+			title = "Bush (Blue)";
+			sprite = "BSZ6C0";
+		}
+		1453
+		{
+			title = "Bush (Cyan)";
+			sprite = "BSZ6D0";
+		}
+		1454
+		{
+			title = "Bush (Yellow)";
+			sprite = "BSZ6E0";
+		}
+		1455
+		{
+			title = "Bush (Orange)";
+			sprite = "BSZ6F0";
+		}
+		1460
+		{
+			title = "Vine (Red)";
+			sprite = "BSZ7A0";
+		}
+		1461
+		{
+			title = "Vine (Purple)";
+			sprite = "BSZ7B0";
+		}
+		1462
+		{
+			title = "Vine (Blue)";
+			sprite = "BSZ7C0";
+		}
+		1463
+		{
+			title = "Vine (Cyan)";
+			sprite = "BSZ7D0";
+		}
+		1464
+		{
+			title = "Vine (Yellow)";
+			sprite = "BSZ7E0";
+		}
+		1465
+		{
+			title = "Vine (Orange)";
+			sprite = "BSZ7F0";
+		}
+		1470
+		{
+			title = "BSZ Shrub";
+			sprite = "BSZ8A0";
+		}
+		1471
+		{
+			title = "BSZ Clover";
+			sprite = "BSZ8B0";
+		}
+		1473
+		{
+			title = "Palm Tree (Big)";
+			width = 16;
+			height = 160;
+			sprite = "BSZ8D0";
+		}
+		1475
+		{
+			title = "Palm Tree (Small)";
+			width = 16;
+			height = 80;
+			sprite = "BSZ8F0";
+		}
 	}
-	2207
+
+	azuretemple
 	{
-		title = "Fish";
-		sprite = "FL08A1";
+		color = 10; // Green
+		title = "Azure Temple";
+
+		1500
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Glaregoyle";
+			sprite = "BGARA1";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+			arg1
+			{
+				title = "Starting delay";
+			}
+		}
+		1501
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Glaregoyle (Up)";
+			sprite = "BGARA1";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+			arg1
+			{
+				title = "Starting delay";
+			}
+		}
+		1502
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Glaregoyle (Down)";
+			sprite = "BGARA1";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+			arg1
+			{
+				title = "Starting delay";
+			}
+		}
+		1503
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Glaregoyle (Long)";
+			sprite = "BGARA1";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+			arg1
+			{
+				title = "Starting delay";
+			}
+		}
+		1504
+		{
+			title = "ATZ Target";
+			sprite = "RCRYB0";
+			width = 24;
+			height = 32;
+		}
+		1505
+		{
+			title = "Green Flame";
+			sprite = "CFLMA0E0";
+			width = 8;
+			height = 32;
+		}
+		1506
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Blue Gargoyle";
+			sprite = "BGARD1";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
 	}
-	2208
+
+	dreamhill
 	{
-		title = "Ram";
-		sprite = "FL09A1";
+		color = 10; // Green
+		title = "Dream Hill";
+
+		1600
+		{
+			title = "Spring Tree";
+			sprite = "TRE6A0";
+			width = 16;
+			height = 32;
+		}
+		1601
+		{
+			title = "Shleep";
+			sprite = "SHLPA0";
+			width = 24;
+			height = 32;
+		}
+		1602
+		{
+			title = "Nightopian";
+			sprite = "NTPNA1";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Can move?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
 	}
-	2209
+
+	nightstrk
 	{
-		title = "Puffin";
-		sprite = "FL10A1";
+		color = 13; // Pink
+		title = "NiGHTS Track";
+		width = 8;
+		height = 4096;
+		sprite = "UNKNA0";
+
+		1700
+		{
+			title = "Axis";
+			sprite = "internal:axis1";
+			circle = 1;
+			arg0
+			{
+				title = "Mare";
+			}
+			arg1
+			{
+				title = "Order";
+			}
+			arg2
+			{
+				title = "Radius";
+			}
+			arg3
+			{
+				title = "Direction";
+				type = 11;
+				enum
+				{
+					0 = "Clockwise";
+					1 = "Counterclockwise";
+				}
+			}
+		}
+		1701
+		{
+			title = "Axis Transfer";
+			sprite = "internal:axis2";
+			arg0
+			{
+				title = "Mare";
+			}
+			arg1
+			{
+				title = "Order";
+			}
+		}
+		1702
+		{
+			title = "Axis Transfer Line";
+			sprite = "internal:axis3";
+			arg0
+			{
+				title = "Mare";
+			}
+			arg1
+			{
+				title = "Order";
+			}
+		}
+		1710
+		{
+			title = "Ideya Capture";
+			sprite = "CAPSA0";
+			width = 72;
+			height = 144;
+			arg0
+			{
+				title = "Mare";
+			}
+			arg1
+			{
+				title = "Required spheres";
+			}
+		}
 	}
-	2210
+
+	nights
 	{
-		title = "Cow";
-		sprite = "FL11A1";
+		color = 13; // Pink
+		title = "NiGHTS Items";
+		width = 16;
+		height = 32;
+
+		1703
+		{
+			title = "Ideya Drone";
+			sprite = "NDRNA1";
+			width = 16;
+			height = 56;
+			arg0
+			{
+				title = "Time limit";
+			}
+			arg1
+			{
+				title = "Height";
+			}
+			arg2
+			{
+				title = "Radius";
+			}
+			arg3
+			{
+				title = "Alignment";
+				type = 11;
+				enum
+				{
+					0 = "Bottom with offset";
+					1 = "Bottom";
+					2 = "Middle";
+					3 = "Top";
+				}
+			}
+			arg4
+			{
+				title = "Die upon time up?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1704
+		{
+			arrow = 1;
+			title = "NiGHTS Bumper";
+			sprite = "NBMPG3G7";
+			width = 32;
+			height = 64;
+		}
+		1706
+		{
+			title = "Blue Sphere";
+			sprite = "SPHRA0";
+			width = 16;
+			height = 24;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		1707
+		{
+			title = "Super Paraloop";
+			sprite = "NPRUA0";
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Bonus time only";
+					2 = "Spawn immediately";
+				}
+			}
+		}
+		1708
+		{
+			title = "Drill Refill";
+			sprite = "NPRUB0";
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Bonus time only";
+					2 = "Spawn immediately";
+				}
+			}
+		}
+		1709
+		{
+			title = "Nightopian Helper";
+			sprite = "NPRUC0";
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Bonus time only";
+					2 = "Spawn immediately";
+				}
+			}
+		}
+		1711
+		{
+			title = "Extra Time";
+			sprite = "NPRUD0";
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Bonus time only";
+					2 = "Spawn immediately";
+				}
+			}
+		}
+		1712
+		{
+			title = "Link Freeze";
+			sprite = "NPRUE0";
+			arg0
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Bonus time only";
+					2 = "Spawn immediately";
+				}
+			}
+		}
+		1713
+		{
+			arrow = 1;
+			title = "Hoop";
+			sprite = "HOOPA0";
+			width = 80;
+			height = 160;
+			arg0
+			{
+				title = "Radius";
+			}
+		}
+		1714
+		{
+			title = "Ideya Anchor Point";
+			sprite = "internal:axis1";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Mare";
+			}
+		}
 	}
-	2211
+
+	mario
 	{
-		title = "Rat";
-		sprite = "FL12A1";
+		color = 6; // Brown
+		title = "Mario";
+
+		1800
+		{
+			title = "Coin";
+			sprite = "COINA0";
+			width = 16;
+			height = 24;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		1801
+		{
+			arrow = 1;
+			title = "Goomba";
+			sprite = "GOOMA0";
+			width = 24;
+			height = 32;
+		}
+		1802
+		{
+			arrow = 1;
+			title = "Goomba (Blue)";
+			sprite = "BGOMA0";
+			width = 24;
+			height = 32;
+		}
+		1803
+		{
+			title = "Fire Flower";
+			sprite = "FFWRB0";
+			width = 16;
+			height = 32;
+		}
+		1804
+		{
+			title = "Koopa Shell";
+			sprite = "SHLLA1";
+			width = 16;
+			height = 20;
+		}
+		1805
+		{
+			title = "Puma (Jumping Fireball)";
+			sprite = "PUMAA0";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Jump strength";
+			}
+		}
+		1806
+		{
+			title = "King Bowser";
+			sprite = "KOOPA0";
+			width = 16;
+			height = 48;
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+		}
+		1807
+		{
+			title = "Axe";
+			sprite = "MAXEA0";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
+		}
+		1808
+		{
+			title = "Bush (Short)";
+			sprite = "MUS1A0";
+			width = 16;
+			height = 32;
+		}
+		1809
+		{
+			title = "Bush (Tall)";
+			sprite = "MUS2A0";
+			width = 16;
+			height = 32;
+		}
+		1810
+		{
+			title = "Toad";
+			sprite = "TOADA0";
+			width = 8;
+			height = 32;
+		}
 	}
-	2212
+
+	christmasdisco
 	{
-		title = "Bear";
-		sprite = "FL13A1";
+		color = 10; // Green
+		title = "Christmas & Disco";
+
+		1850
+		{
+			title = "Christmas Pole";
+			sprite = "XMS1A0";
+			width = 16;
+			height = 40;
+		}
+		1851
+		{
+			title = "Candy Cane";
+			sprite = "XMS2A0";
+			width = 8;
+			height = 32;
+		}
+		1852
+		{
+			blocking = 2;
+			title = "Snowman";
+			sprite = "XMS3A0";
+			width = 16;
+			height = 64;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
+		1853
+		{
+			blocking = 2;
+			title = "Snowman (With Hat)";
+			sprite = "XMS3B0";
+			width = 16;
+			height = 80;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
+		1854
+		{
+			title = "Lamp Post";
+			sprite = "XMS4A0";
+			width = 8;
+			height = 120;
+		}
+		1855
+		{
+			title = "Lamp Post (Snow)";
+			sprite = "XMS4B0";
+			width = 8;
+			height = 120;
+		}
+		1856
+		{
+			title = "Hanging Star";
+			sprite = "XMS5A0";
+			width = 4;
+			height = 80;
+			hangs = 1;
+		}
+		1857
+		{
+			title = "Berry Bush (Snow)";
+			sprite = "BUS1B0";
+			width = 16;
+			height = 32;
+		}
+		1858
+		{
+			title = "Bush (Snow)";
+			sprite = "BUS2B0";
+			width = 16;
+			height = 32;
+		}
+		1859
+		{
+			title = "Blueberry Bush (Snow)";
+			sprite = "BUS3B0";
+			width = 16;
+			height = 32;
+		}
+		1875
+		{
+			title = "Disco Ball";
+			sprite = "DBALA0";
+			width = 16;
+			height = 54;
+			hangs = 1;
+		}
+		1876
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Eggman Disco Statue";
+			sprite = "ESTAB1";
+			width = 20;
+			height = 96;
+			arg0
+			{
+				title = "Push behavior";
+				type = 11;
+				enum = "pushablebehavior";
+			}
+		}
 	}
-	2213
+
+	stalagmites
 	{
-		title = "Dove";
-		sprite = "FL14A1";
+		color = 10; // Green
+		title = "Stalagmites";
+		width = 16;
+		height = 40;
+
+		1900
+		{
+			title = "Brown Stalagmite (Tall)";
+			sprite = "STLGA0";
+			width = 16;
+			height = 40;
+		}
+		1901
+		{
+			title = "Brown Stalagmite";
+			sprite = "STLGB0";
+			width = 16;
+			height = 40;
+		}
+		1902
+		{
+			title = "Orange Stalagmite (Tall)";
+			sprite = "STLGC0";
+			width = 16;
+			height = 40;
+		}
+		1903
+		{
+			title = "Orange Stalagmite";
+			sprite = "STLGD0";
+			width = 16;
+			height = 40;
+		}
+		1904
+		{
+			title = "Red Stalagmite (Tall)";
+			sprite = "STLGE0";
+			width = 16;
+			height = 40;
+		}
+		1905
+		{
+			title = "Red Stalagmite";
+			sprite = "STLGF0";
+			width = 16;
+			height = 40;
+		}
+		1906
+		{
+			title = "Gray Stalagmite (Tall)";
+			sprite = "STLGG0";
+			width = 24;
+			height = 96;
+		}
+		1907
+		{
+			title = "Gray Stalagmite";
+			sprite = "STLGH0";
+			width = 16;
+			height = 40;
+		}
+		1908
+		{
+			title = "Blue Stalagmite (Tall)";
+			sprite = "STLGI0";
+			width = 16;
+			height = 40;
+		}
+		1909
+		{
+			title = "Blue Stalagmite";
+			sprite = "STLGJ0";
+			width = 16;
+			height = 40;
+		}
 	}
-	2214
+
+	hauntedheights
 	{
-		title = "Cat";
-		sprite = "FL15A1";
+		color = 10; // Green
+		title = "Haunted Heights";
+
+		2000
+		{
+			title = "Smashing Spikeball";
+			sprite = "FMCEA0";
+			width = 18;
+			height = 28;
+			arg0
+			{
+				title = "Initial delay";
+			}
+		}
+		2001
+		{
+			title = "HHZ Grass";
+			sprite = "HHZMA0";
+			width = 16;
+			height = 40;
+		}
+		2002
+		{
+			title = "HHZ Tentacle 1";
+			sprite = "HHZMB0";
+			width = 16;
+			height = 40;
+		}
+		2003
+		{
+			title = "HHZ Tentacle 2";
+			sprite = "HHZMC0";
+			width = 16;
+			height = 40;
+		}
+		2004
+		{
+			title = "HHZ Stalagmite (Tall)";
+			sprite = "HHZME0";
+			width = 16;
+			height = 40;
+		}
+		2005
+		{
+			title = "HHZ Stalagmite (Short)";
+			sprite = "HHZMF0";
+			width = 16;
+			height = 40;
+		}
+		2006
+		{
+			title = "Jack-o'-lantern 1";
+			sprite = "PUMKA0";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Flicker";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		2007
+		{
+			title = "Jack-o'-lantern 2";
+			sprite = "PUMKB0";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Flicker";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		2008
+		{
+			title = "Jack-o'-lantern 3";
+			sprite = "PUMKC0";
+			width = 16;
+			height = 40;
+			arg0
+			{
+				title = "Flicker";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+		2009
+		{
+			title = "Purple Mushroom";
+			sprite = "SHRMD0";
+			width = 16;
+			height = 48;
+		}
+		2010
+		{
+			title = "HHZ Tree";
+			sprite = "HHPLC0";
+			width = 12;
+			height = 40;
+		}
 	}
-	2215
+
+	frozenhillside
 	{
-		title = "Canary";
-		sprite = "FL16A1";
+		color = 10; // Green
+		title = "Frozen Hillside";
+
+		2100
+		{
+			title = "Ice Shard (Small)";
+			sprite = "FHZIA0";
+			width = 8;
+			height = 32;
+		}
+		2101
+		{
+			title = "Ice Shard (Large)";
+			sprite = "FHZIB0";
+			width = 8;
+			height = 32;
+		}
+		2102
+		{
+			title = "Crystal Tree (Aqua)";
+			sprite = "TRE3A0";
+			width = 20;
+			height = 200;
+		}
+		2103
+		{
+			title = "Crystal Tree (Pink)";
+			sprite = "TRE3B0";
+			width = 20;
+			height = 200;
+		}
+		2104
+		{
+			title = "Amy Cameo";
+			sprite = "ROSYA1";
+			width = 16;
+			height = 48;
+			arg0
+			{
+				title = "Grayscale?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		2105
+		{
+			title = "Mistletoe";
+			sprite = "XMS6A0";
+			width = 52;
+			height = 106;
+		}
 	}
-	2216
+
+	tutorial
 	{
-		title = "Spider";
-		sprite = "FS01A1";
+		color = 10; // Green
+		title = "Tutorial";
+
+		799
+		{
+			title = "Tutorial Plant";
+			sprite = "TUPFH0";
+			width = 40;
+			height = 144;
+			arg0
+			{
+				title = "Start frame";
+			}
+		}
 	}
-	2217
+
+	flickies
 	{
-		title = "Bat";
-		sprite = "FS02A0";
+		color = 10; // Green
+		title = "Flickies";
+		width = 8;
+		height = 20;
+		arg0
+		{
+			title = "Radius";
+		}
+		arg1
+		{
+			title = "Flags";
+			type = 12;
+			enum
+			{
+				1 = "Move aimlessly";
+				2 = "No movement";
+				4 = "Hop";
+			}
+		}
+
+		2200
+		{
+			title = "Bluebird";
+			sprite = "FL01A1";
+		}
+		2201
+		{
+			title = "Rabbit";
+			sprite = "FL02A1";
+		}
+		2202
+		{
+			title = "Chicken";
+			sprite = "FL03A1";
+		}
+		2203
+		{
+			title = "Seal";
+			sprite = "FL04A1";
+		}
+		2204
+		{
+			title = "Pig";
+			sprite = "FL05A1";
+		}
+		2205
+		{
+			title = "Chipmunk";
+			sprite = "FL06A1";
+		}
+		2206
+		{
+			title = "Penguin";
+			sprite = "FL07A1";
+		}
+		2207
+		{
+			title = "Fish";
+			sprite = "FL08A1";
+			arg2
+			{
+				title = "Color";
+				type = 11;
+				enum
+				{
+					0 = "Random";
+					1 = "Red";
+					2 = "Cyan";
+					3 = "Blue";
+					4 = "Vapor";
+					5 = "Purple";
+					6 = "Bubblegum";
+					7 = "Neon";
+					8 = "Black";
+					9 = "Beige";
+					10 = "Lavender";
+					11 = "Ruby";
+					12 = "Salmon";
+					13 = "Sunset";
+					14 = "Orange";
+					15 = "Yellow";
+				}
+			}
+		}
+		2208
+		{
+			title = "Ram";
+			sprite = "FL09A1";
+		}
+		2209
+		{
+			title = "Puffin";
+			sprite = "FL10A1";
+		}
+		2210
+		{
+			title = "Cow";
+			sprite = "FL11A1";
+		}
+		2211
+		{
+			title = "Rat";
+			sprite = "FL12A1";
+		}
+		2212
+		{
+			title = "Bear";
+			sprite = "FL13A1";
+		}
+		2213
+		{
+			title = "Dove";
+			sprite = "FL14A1";
+		}
+		2214
+		{
+			title = "Cat";
+			sprite = "FL15A1";
+		}
+		2215
+		{
+			title = "Canary";
+			sprite = "FL16A1";
+		}
+		2216
+		{
+			title = "Spider";
+			sprite = "FS01A1";
+		}
+		2217
+		{
+			title = "Bat";
+			sprite = "FS02A0";
+		}
 	}
-}
\ No newline at end of file
+}
diff --git a/extras/conf/udb/SRB2_22Doom.cfg b/extras/conf/udb/SRB2_22Doom.cfg
index 891b9d507fefd1ba1eb5c7334cdafc91f3be10ec..9e733aa394581a8d48dab2c592e46008b2142f11 100644
--- a/extras/conf/udb/SRB2_22Doom.cfg
+++ b/extras/conf/udb/SRB2_22Doom.cfg
@@ -25,12 +25,6 @@ scriptlumpnames
 	include("Includes\\SRB222_misc.cfg", "scriptlumpnames");
 }
 
-// THING TYPES
-thingtypes
-{
-	include("Includes\\SRB222_things.cfg");
-}
-
 //Default things filters
 thingsfilters
 {
diff --git a/extras/conf/udb/SRB2_22UDMF.cfg b/extras/conf/udb/SRB2_22UDMF.cfg
index 749cf499abd98560e0be78aa40b1b75b1bd7935a..b6bd224784c43035325c15caf5eb73df1c96cae6 100644
--- a/extras/conf/udb/SRB2_22UDMF.cfg
+++ b/extras/conf/udb/SRB2_22UDMF.cfg
@@ -25,12 +25,6 @@ scriptlumpnames
 	include("Includes\\SRB222_misc.cfg", "scriptlumpnames");
 }
 
-// THING TYPES
-thingtypes
-{
-	include("Includes\\SRB222_things.cfg");
-}
-
 //Default things filters
 thingsfilters
 {
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index d9080d342a432a87e44d7c1b4e54a981111eb4b5..679e102e21124e405ff369b647da09b0c85e7a5c 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -880,7 +880,7 @@ void D_RegisterClientCommands(void)
 
 	// ingame object placing
 	COM_AddCommand("objectplace", Command_ObjectPlace_f);
-	COM_AddCommand("writethings", Command_Writethings_f);
+	//COM_AddCommand("writethings", Command_Writethings_f);
 	CV_RegisterVar(&cv_speed);
 	CV_RegisterVar(&cv_opflags);
 	CV_RegisterVar(&cv_ophoopflags);
diff --git a/src/deh_lua.c b/src/deh_lua.c
index 1f4d22dcae5a316e1504faf3d227957690fa8c0d..09dc155cfae529abf58f3c1090086ea9f2fdac7a 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -330,14 +330,85 @@ static inline int lib_getenum(lua_State *L)
 	}
 	else if (fastncmp("ML_", word, 3)) {
 		p = word+3;
-		for (i = 0; i < 16; i++)
-			if (ML_LIST[i] && fastcmp(p, ML_LIST[i])) {
+		for (i = 0; ML_LIST[i]; i++)
+			if (fastcmp(p, ML_LIST[i])) {
 				lua_pushinteger(L, ((lua_Integer)1<<i));
 				return 1;
 			}
+		// Aliases
+		if (fastcmp(p, "EFFECT1"))
+		{
+			lua_pushinteger(L, (lua_Integer)ML_SKEWTD);
+			return 1;
+		}
+		if (fastcmp(p, "EFFECT2"))
+		{
+			lua_pushinteger(L, (lua_Integer)ML_NOSKEW);
+			return 1;
+		}
+		if (fastcmp(p, "EFFECT3"))
+		{
+			lua_pushinteger(L, (lua_Integer)ML_MIDPEG);
+			return 1;
+		}
+		if (fastcmp(p, "EFFECT4"))
+		{
+			lua_pushinteger(L, (lua_Integer)ML_MIDSOLID);
+			return 1;
+		}
+		if (fastcmp(p, "EFFECT5"))
+		{
+			lua_pushinteger(L, (lua_Integer)ML_WRAPMIDTEX);
+			return 1;
+		}
 		if (mathlib) return luaL_error(L, "linedef flag '%s' could not be found.\n", word);
 		return 0;
 	}
+	else if (fastncmp("MSF_", word, 4)) {
+		p = word + 4;
+		for (i = 0; MSF_LIST[i]; i++)
+			if (fastcmp(p, MSF_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1 << i));
+				return 1;
+			}
+		if (fastcmp(p, "FLIPSPECIAL_BOTH"))
+		{
+			lua_pushinteger(L, (lua_Integer)MSF_FLIPSPECIAL_BOTH);
+			return 1;
+		}
+		if (mathlib) return luaL_error(L, "sector flag '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("SSF_", word, 4)) {
+		p = word + 4;
+		for (i = 0; SSF_LIST[i]; i++)
+			if (fastcmp(p, SSF_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1 << i));
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "sector special flag '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("SD_", word, 3)) {
+		p = word + 3;
+		for (i = 0; SD_LIST[i]; i++)
+			if (fastcmp(p, SD_LIST[i])) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "sector damagetype '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("TO_", word, 3)) {
+		p = word + 3;
+		for (i = 0; TO_LIST[i]; i++)
+			if (fastcmp(p, TO_LIST[i])) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "sector triggerer '%s' could not be found.\n", word);
+		return 0;
+	}
 	else if (fastncmp("S_",word,2)) {
 		p = word+2;
 		for (i = 0; i < NUMSTATEFREESLOTS; i++) {
diff --git a/src/deh_tables.c b/src/deh_tables.c
index 8dbe314cc1cd79ad38969a64860274baf4a8ed44..106e5178569fb90a3a82dddf957f3479da5ec089 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -199,6 +199,7 @@ actionpointer_t actionpointers[] =
 	{{A_Boss3Path},              "A_BOSS3PATH"},
 	{{A_Boss3ShockThink},        "A_BOSS3SHOCKTHINK"},
 	{{A_LinedefExecute},         "A_LINEDEFEXECUTE"},
+	{{A_LinedefExecuteFromArg},  "A_LINEDEFEXECUTEFROMARG"},
 	{{A_PlaySeeSound},           "A_PLAYSEESOUND"},
 	{{A_PlayAttackSound},        "A_PLAYATTACKSOUND"},
 	{{A_PlayActiveSound},        "A_PLAYACTIVESOUND"},
@@ -4171,17 +4172,7 @@ const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for sanity t
 	"MT_FINISHFLAG", // Finish flag
 
 	// Ambient Sounds
-	"MT_AWATERA", // Ambient Water Sound 1
-	"MT_AWATERB", // Ambient Water Sound 2
-	"MT_AWATERC", // Ambient Water Sound 3
-	"MT_AWATERD", // Ambient Water Sound 4
-	"MT_AWATERE", // Ambient Water Sound 5
-	"MT_AWATERF", // Ambient Water Sound 6
-	"MT_AWATERG", // Ambient Water Sound 7
-	"MT_AWATERH", // Ambient Water Sound 8
-	"MT_RANDOMAMBIENT",
-	"MT_RANDOMAMBIENT2",
-	"MT_MACHINEAMBIENCE",
+	"MT_AMBIENT",
 
 	"MT_CORK",
 	"MT_LHRT",
@@ -4285,7 +4276,6 @@ const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for sanity t
 	"MT_CRUMBLEOBJ", // Sound generator for crumbling platform
 	"MT_TUBEWAYPOINT",
 	"MT_PUSH",
-	"MT_PULL",
 	"MT_GHOST",
 	"MT_OVERLAY",
 	"MT_ANGLEMAN",
@@ -4522,23 +4512,85 @@ const char *const GAMETYPERULE_LIST[] = {
 };
 
 // Linedef flags
-const char *const ML_LIST[16] = {
+const char *const ML_LIST[] = {
 	"IMPASSIBLE",
 	"BLOCKMONSTERS",
 	"TWOSIDED",
 	"DONTPEGTOP",
 	"DONTPEGBOTTOM",
-	"EFFECT1",
+	"SKEWTD",
 	"NOCLIMB",
-	"EFFECT2",
-	"EFFECT3",
-	"EFFECT4",
-	"EFFECT5",
-	"NOSONIC",
-	"NOTAILS",
-	"NOKNUX",
+	"NOSKEW",
+	"MIDPEG",
+	"MIDSOLID",
+	"WRAPMIDTEX",
+	"NETONLY",
+	"NONET",
+	"EFFECT6",
 	"BOUNCY",
-	"TFERLINE"
+	"TFERLINE",
+	NULL
+};
+
+// Sector flags
+const char *const MSF_LIST[] = {
+	"FLIPSPECIAL_FLOOR",
+	"FLIPSPECIAL_CEILING",
+	"TRIGGERSPECIAL_TOUCH",
+	"TRIGGERSPECIAL_HEADBUMP",
+	"TRIGGERLINE_PLANE",
+	"TRIGGERLINE_MOBJ",
+	"GRAVITYFLIP",
+	"HEATWAVE",
+	"NOCLIPCAMERA",
+	NULL
+};
+
+// Sector special flags
+const char *const SSF_LIST[] = {
+	"OUTERSPACE",
+	"DOUBLESTEPUP",
+	"WINDCURRENT",
+	"CONVEYOR",
+	"SPEEDPAD",
+	"STARPOSTACTIVATOR",
+	"EXIT",
+	"SPECIALSTAGEPIT",
+	"RETURNFLAG",
+	"REDTEAMBASE",
+	"BLUETEAMBASE",
+	"FAN",
+	"SUPERTRANSFORM",
+	"FORCESPIN",
+	"ZOOMTUBESTART",
+	"ZOOMTUBEEND",
+	"FINISHLINE",
+	"ROPEHANG",
+	NULL
+};
+
+// Sector damagetypes
+const char *const SD_LIST[] = {
+	"NONE",
+	"GENERIC",
+	"WATER",
+	"FIRE",
+	"LAVA",
+	"ELECTRIC",
+	"SPIKE",
+	"DEATHPITTILT",
+	"DEATHPITNOTILT",
+	"INSTAKILL",
+	"SPECIALSTAGE",
+	NULL
+};
+
+// Sector triggerer
+const char *const TO_LIST[] = {
+	"PLAYER",
+	"ALLPLAYERS",
+	"MOBJ",
+	NULL
 };
 
 const char *COLOR_ENUMS[] = {
@@ -5298,7 +5350,7 @@ struct int_const_s const INT_CONST[] = {
 	{"FF_FLOATBOB",FF_FLOATBOB},               ///< Floats on water and bobs if you step on it.
 	{"FF_NORETURN",FF_NORETURN},               ///< Used with ::FF_CRUMBLE. Will not return to its original position after falling.
 	{"FF_CRUMBLE",FF_CRUMBLE},                 ///< Falls 2 seconds after being stepped on, and randomly brings all touching crumbling 3dfloors down with it, providing their master sectors share the same tag (allows crumble platforms above or below, to also exist).
-	{"FF_SHATTERBOTTOM",FF_SHATTERBOTTOM},     ///< Used with ::FF_BUSTUP. Like FF_SHATTER, but only breaks from the bottom. Good for springing up through rubble.
+	{"FF_GOOWATER",FF_GOOWATER},               ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
 	{"FF_MARIO",FF_MARIO},                     ///< Acts like a question block when hit from underneath. Goodie spawned at top is determined by master sector.
 	{"FF_BUSTUP",FF_BUSTUP},                   ///< You can spin through/punch this block and it will crumble!
 	{"FF_QUICKSAND",FF_QUICKSAND},             ///< Quicksand!
@@ -5306,12 +5358,21 @@ struct int_const_s const INT_CONST[] = {
 	{"FF_REVERSEPLATFORM",FF_REVERSEPLATFORM}, ///< A fall-through floor in normal gravity, a platform in reverse gravity.
 	{"FF_INTANGIBLEFLATS",FF_INTANGIBLEFLATS}, ///< Both flats are intangible, but the sides are still solid.
 	{"FF_INTANGABLEFLATS",FF_INTANGIBLEFLATS}, ///< Both flats are intangable, but the sides are still solid.
-	{"FF_SHATTER",FF_SHATTER},                 ///< Used with ::FF_BUSTUP. Bustable on mere touch.
-	{"FF_SPINBUST",FF_SPINBUST},               ///< Used with ::FF_BUSTUP. Also bustable if you're in your spinning frames.
-	{"FF_STRONGBUST",FF_STRONGBUST},           ///< Used with ::FF_BUSTUP. Only bustable by "strong" characters (Knuckles) and abilities (bouncing, twinspin, melee).
 	{"FF_RIPPLE",FF_RIPPLE},                   ///< Ripple the flats
 	{"FF_COLORMAPONLY",FF_COLORMAPONLY},       ///< Only copy the colormap, not the lightlevel
-	{"FF_GOOWATER",FF_GOOWATER},               ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
+	{"FF_BOUNCY",FF_BOUNCY},                   ///< Bounces players
+	{"FF_SPLAT",FF_SPLAT},                     ///< Use splat flat renderer (treat cyan pixels as invisible)
+
+	// FOF bustable flags
+	{"FB_PUSHABLES",FB_PUSHABLES},
+	{"FB_EXECUTOR",FB_EXECUTOR},
+	{"FB_ONLYBOTTOM",FB_ONLYBOTTOM},
+
+	// Bustable FOF type
+	{"BT_TOUCH",BT_TOUCH},
+	{"BT_SPINBUST",BT_SPINBUST},
+	{"BT_REGULAR",BT_REGULAR},
+	{"BT_STRONG",BT_STRONG},
 
 	// PolyObject flags
 	{"POF_CLIPLINES",POF_CLIPLINES},               ///< Test against lines for collision
diff --git a/src/deh_tables.h b/src/deh_tables.h
index 972b08838ea417b210ab7f8b14de39768e5b7cf3..850194a96c6f1aab6145a0e9ff47b8c5bd1e6980 100644
--- a/src/deh_tables.h
+++ b/src/deh_tables.h
@@ -64,7 +64,11 @@ extern const char *const MOBJEFLAG_LIST[];
 extern const char *const MAPTHINGFLAG_LIST[4];
 extern const char *const PLAYERFLAG_LIST[];
 extern const char *const GAMETYPERULE_LIST[];
-extern const char *const ML_LIST[16]; // Linedef flags
+extern const char *const ML_LIST[]; // Linedef flags
+extern const char *const MSF_LIST[]; // Sector flags
+extern const char *const SSF_LIST[]; // Sector special flags
+extern const char *const SD_LIST[]; // Sector damagetype
+extern const char *const TO_LIST[]; // Sector triggerer
 extern const char *COLOR_ENUMS[];
 extern const char *const POWERS_LIST[];
 extern const char *const HUDITEMS_LIST[];
diff --git a/src/doomdata.h b/src/doomdata.h
index 009af00fa4e200f183a059ec5bcb98a7a008c5aa..56fb5e9e9da6bab34a16c3432ce31dc7b0b38049 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -123,15 +123,15 @@ typedef struct
 // lower texture unpegged
 #define ML_DONTPEGBOTTOM       16
 
-#define ML_EFFECT1             32
+#define ML_SKEWTD              32
 
 // Don't let Knuckles climb on this line
 #define ML_NOCLIMB             64
 
-#define ML_EFFECT2             128
-#define ML_EFFECT3             256
-#define ML_EFFECT4             512
-#define ML_EFFECT5            1024
+#define ML_NOSKEW             128
+#define ML_MIDPEG             256
+#define ML_MIDSOLID           512
+#define ML_WRAPMIDTEX        1024
 
 #define ML_NETONLY           2048 // Apply effect only in netgames
 #define ML_NONET             4096 // Apply  effect only in single player games
@@ -196,7 +196,7 @@ typedef struct
 #pragma pack()
 #endif
 
-#define NUMMAPTHINGARGS 6
+#define NUMMAPTHINGARGS 10
 #define NUMMAPTHINGSTRINGARGS 2
 
 // Thing definition, position, orientation and type,
diff --git a/src/doomdef.h b/src/doomdef.h
index 42ae03c3e11c2acbb2a61267c0c297241600b2ae..2b62bcd6e9aa2718c352d018e4453ad6de7270c8 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -401,7 +401,7 @@ extern skincolor_t skincolors[MAXSKINCOLORS];
 
 #define PUSHACCEL (2*FRACUNIT) // Acceleration for MF2_SLIDEPUSH items.
 
-// Special linedef executor tag numbers!
+// Special linedef executor tag numbers! Binary map format only (UDMF has other ways of doing these things).
 enum {
 	LE_PINCHPHASE      =    -2, // A boss entered pinch phase (and, in most cases, is preparing their pinch phase attack!)
 	LE_ALLBOSSESDEAD   =    -3, // All bosses in the map are dead (Egg capsule raise)
@@ -483,8 +483,11 @@ extern void *(*M_Memcpy)(void* dest, const void* src, size_t n) FUNCNONNULL;
 char *va(const char *format, ...) FUNCPRINTF;
 char *M_GetToken(const char *inputString);
 void M_UnGetToken(void);
-UINT32 M_GetTokenPos(void);
-void M_SetTokenPos(UINT32 newPos);
+void M_TokenizerOpen(const char *inputString);
+void M_TokenizerClose(void);
+const char *M_TokenizerRead(UINT32 i);
+UINT32 M_TokenizerGetEndPos(void);
+void M_TokenizerSetEndPos(UINT32 newPos);
 char *sizeu1(size_t num);
 char *sizeu2(size_t num);
 char *sizeu3(size_t num);
diff --git a/src/g_demo.c b/src/g_demo.c
index e293ad9dc5e76a1af37e3b384763383e23326c70..77c9ab10b2f0ee4295d09c7d81e5e25b5cabc0fa 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -2221,7 +2221,7 @@ void G_AddGhost(char *defdemoname)
 		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))
+		if (!!(mthing->args[0]) ^ !!(mthing->options & MTF_OBJECTFLIP))
 		{
 			z = c - offset;
 			if (z < f)
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index fd8a23013e84ebceaafa6cfd9008e3acf6743b2d..a6b08812bd34b6df5202cd3b196dfcff082d0600 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1146,7 +1146,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				// PEGGING
 				if (gl_linedef->flags & ML_DONTPEGTOP)
 					texturevpegtop = 0;
-				else if (gl_linedef->flags & ML_EFFECT1)
+				else if (gl_linedef->flags & ML_SKEWTD)
 					texturevpegtop = worldhigh + textureheight[gl_sidedef->toptexture] - worldtop;
 				else
 					texturevpegtop = gl_backsector->ceilingheight + textureheight[gl_sidedef->toptexture] - gl_frontsector->ceilingheight;
@@ -1162,7 +1162,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
 
 				// Adjust t value for sloped walls
-				if (!(gl_linedef->flags & ML_EFFECT1))
+				if (!(gl_linedef->flags & ML_SKEWTD))
 				{
 					// Unskewed
 					wallVerts[3].t -= (worldtop - gl_frontsector->ceilingheight) * grTex->scaleY;
@@ -1212,7 +1212,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				// PEGGING
 				if (!(gl_linedef->flags & ML_DONTPEGBOTTOM))
 					texturevpegbottom = 0;
-				else if (gl_linedef->flags & ML_EFFECT1)
+				else if (gl_linedef->flags & ML_SKEWTD)
 					texturevpegbottom = worldbottom - worldlow;
 				else
 					texturevpegbottom = gl_frontsector->floorheight - gl_backsector->floorheight;
@@ -1228,7 +1228,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
 
 				// Adjust t value for sloped walls
-				if (!(gl_linedef->flags & ML_EFFECT1))
+				if (!(gl_linedef->flags & ML_SKEWTD))
 				{
 					// Unskewed
 					wallVerts[0].t -= (worldbottom - gl_frontsector->floorheight) * grTex->scaleY;
@@ -1286,7 +1286,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			if (gl_sidedef->repeatcnt)
 				repeats = 1 + gl_sidedef->repeatcnt;
-			else if (gl_linedef->flags & ML_EFFECT5)
+			else if (gl_linedef->flags & ML_WRAPMIDTEX)
 			{
 				fixed_t high, low;
 
@@ -1328,9 +1328,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				popenbottom = max(worldbottom, worldlow);
 			}
 
-			if (gl_linedef->flags & ML_EFFECT2)
+			if (gl_linedef->flags & ML_NOSKEW)
 			{
-				if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
+				if (gl_linedef->flags & ML_MIDPEG)
 				{
 					polybottom = max(front->floorheight, back->floorheight) + gl_sidedef->rowoffset;
 					polytop = polybottom + textureheight[gl_midtexture]*repeats;
@@ -1341,7 +1341,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					polybottom = polytop - textureheight[gl_midtexture]*repeats;
 				}
 			}
-			else if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
+			else if (gl_linedef->flags & ML_MIDPEG)
 			{
 				polybottom = popenbottom + gl_sidedef->rowoffset;
 				polytop = polybottom + textureheight[gl_midtexture]*repeats;
@@ -1371,7 +1371,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			{
 				// PEGGING
-				if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
+				if (gl_linedef->flags & ML_MIDPEG)
 					texturevpeg = textureheight[gl_sidedef->midtexture]*repeats - h + polybottom;
 				else
 					texturevpeg = polytop - h;
@@ -1394,9 +1394,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			{
 				fixed_t midtextureslant;
 
-				if (gl_linedef->flags & ML_EFFECT2)
+				if (gl_linedef->flags & ML_NOSKEW)
 					midtextureslant = 0;
-				else if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
+				else if (gl_linedef->flags & ML_MIDPEG)
 					midtextureslant = worldlow < worldbottom
 							  ? worldbottomslope-worldbottom
 							  : worldlowslope-worldlow;
@@ -1421,7 +1421,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 				{
 					// PEGGING
-					if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
+					if (gl_linedef->flags & ML_MIDPEG)
 						texturevpeg = textureheight[gl_sidedef->midtexture]*repeats - h + polybottom;
 					else
 						texturevpeg = polytop - h;
@@ -1435,44 +1435,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			// set alpha for transparent walls
 			// ooops ! this do not work at all because render order we should render it in backtofront order
-			switch (gl_linedef->special)
+			if (gl_linedef->blendmode && gl_linedef->blendmode != AST_FOG)
 			{
-				//  Translucent
-				case 102:
-				case 121:
-				case 123:
-				case 124:
-				case 125:
-				case 141:
-				case 142:
-				case 144:
-				case 145:
-				case 174:
-				case 175:
-				case 192:
-				case 195:
-				case 221:
-				case 253:
-				case 256:
-					if (gl_linedef->blendmode && gl_linedef->blendmode != AST_FOG)
-						blendmode = HWR_SurfaceBlend(gl_linedef->blendmode, R_GetLinedefTransTable(gl_linedef->alpha), &Surf);
-					else
-						blendmode = PF_Translucent;
-					break;
-				default:
-					if (gl_linedef->blendmode && gl_linedef->blendmode != AST_FOG)
-					{
-						if (gl_linedef->alpha >= 0 && gl_linedef->alpha < FRACUNIT)
-							blendmode = HWR_SurfaceBlend(gl_linedef->blendmode, R_GetLinedefTransTable(gl_linedef->alpha), &Surf);
-						else
-							blendmode = HWR_GetBlendModeFlag(gl_linedef->blendmode);
-					}
-					else if (gl_linedef->alpha >= 0 && gl_linedef->alpha < FRACUNIT)
-						blendmode = HWR_TranstableToAlpha(R_GetLinedefTransTable(gl_linedef->alpha), &Surf);
-					else
-						blendmode = PF_Masked;
-					break;
+				if (gl_linedef->alpha >= 0 && gl_linedef->alpha < FRACUNIT)
+					blendmode = HWR_SurfaceBlend(gl_linedef->blendmode, R_GetLinedefTransTable(gl_linedef->alpha), &Surf);
+				else
+					blendmode = HWR_GetBlendModeFlag(gl_linedef->blendmode);
 			}
+			else if (gl_linedef->alpha >= 0 && gl_linedef->alpha < FRACUNIT)
+				blendmode = HWR_TranstableToAlpha(R_GetLinedefTransTable(gl_linedef->alpha), &Surf);
+			else
+				blendmode = PF_Masked;
 
 			if (gl_curline->polyseg && gl_curline->polyseg->translucency > 0)
 			{
@@ -1538,7 +1511,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			{
 				fixed_t     texturevpeg;
 				// PEGGING
-				if ((gl_linedef->flags & (ML_DONTPEGBOTTOM|ML_EFFECT2)) == (ML_DONTPEGBOTTOM|ML_EFFECT2))
+				if ((gl_linedef->flags & (ML_DONTPEGBOTTOM|ML_NOSKEW)) == (ML_DONTPEGBOTTOM|ML_NOSKEW))
 					texturevpeg = gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight + gl_sidedef->rowoffset;
 				else if (gl_linedef->flags & ML_DONTPEGBOTTOM)
 					texturevpeg = worldbottom + textureheight[gl_sidedef->midtexture] - worldtop + gl_sidedef->rowoffset;
@@ -1554,7 +1527,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
 
 				// Texture correction for slopes
-				if (gl_linedef->flags & ML_EFFECT2) {
+				if (gl_linedef->flags & ML_NOSKEW) {
 					wallVerts[3].t += (gl_frontsector->ceilingheight - worldtop) * grTex->scaleY;
 					wallVerts[2].t += (gl_frontsector->ceilingheight - worldtopslope) * grTex->scaleY;
 					wallVerts[0].t += (gl_frontsector->floorheight - worldbottom) * grTex->scaleY;
@@ -1703,13 +1676,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					{
 						texturevpeg = sides[newline->sidenum[0]].rowoffset;
 						attachtobottom = !!(newline->flags & ML_DONTPEGBOTTOM);
-						slopeskew = !!(newline->flags & ML_DONTPEGTOP);
+						slopeskew = !!(newline->flags & ML_SKEWTD);
 					}
 					else
 					{
 						texturevpeg = sides[rover->master->sidenum[0]].rowoffset;
 						attachtobottom = !!(gl_linedef->flags & ML_DONTPEGBOTTOM);
-						slopeskew = !!(rover->master->flags & ML_DONTPEGTOP);
+						slopeskew = !!(rover->master->flags & ML_SKEWTD);
 					}
 
 					grTex = HWR_GetTexture(texnum);
@@ -3062,13 +3035,13 @@ static void HWR_Subsector(size_t num)
 		}
 
 		light = R_GetPlaneLight(gl_frontsector, locFloorHeight, false);
-		if (gl_frontsector->floorlightsec == -1)
-			floorlightlevel = *gl_frontsector->lightlist[light].lightlevel;
+		if (gl_frontsector->floorlightsec == -1 && !gl_frontsector->floorlightabsolute)
+			floorlightlevel = max(0, min(255, *gl_frontsector->lightlist[light].lightlevel + gl_frontsector->floorlightlevel));
 		floorcolormap = *gl_frontsector->lightlist[light].extra_colormap;
 
 		light = R_GetPlaneLight(gl_frontsector, locCeilingHeight, false);
-		if (gl_frontsector->ceilinglightsec == -1)
-			ceilinglightlevel = *gl_frontsector->lightlist[light].lightlevel;
+		if (gl_frontsector->ceilinglightsec == -1 && !gl_frontsector->ceilinglightabsolute)
+			ceilinglightlevel = max(0, min(255, *gl_frontsector->lightlist[light].lightlevel + gl_frontsector->ceilinglightlevel));
 		ceilingcolormap = *gl_frontsector->lightlist[light].extra_colormap;
 	}
 
@@ -3594,7 +3567,7 @@ static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float v
 		return false;
 
 	cullplane = FIXED_TO_FLOAT(cullheight->frontsector->floorheight);
-	if (cullheight->flags & ML_NOCLIMB) // Group culling
+	if (cullheight->args[1]) // Group culling
 	{
 		if (!viewcullheight)
 			return false;
diff --git a/src/info.c b/src/info.c
index 94293488861c94d85d03e0f45ceac37371ba06ac..179370ca4c989965bee2bc9e65ac33fbc8b35c82 100644
--- a/src/info.c
+++ b/src/info.c
@@ -903,7 +903,7 @@ state_t states[NUMSTATES] =
 	{SPR_TRET, FF_FULLBRIGHT|2, 7, {A_Pain}, 0, 0, S_TURRETSHOCK7},       // S_TURRETSHOCK6
 	{SPR_TRET, FF_FULLBRIGHT|3, 7, {NULL}, 0, 0, S_TURRETSHOCK8},         // S_TURRETSHOCK7
 	{SPR_TRET, FF_FULLBRIGHT|4, 7, {NULL}, 0, 0, S_TURRETSHOCK9},         // S_TURRETSHOCK8
-	{SPR_TRET, FF_FULLBRIGHT|4, 7, {A_LinedefExecute}, LE_TURRET, 0, S_XPLD1}, // S_TURRETSHOCK9
+	{SPR_TRET, FF_FULLBRIGHT|4, 7, {A_LinedefExecuteFromArg}, 0, 0, S_XPLD1}, // S_TURRETSHOCK9
 
 	{SPR_TURR, 0, 1, {A_Look}, 1, 0, S_TURRETPOPDOWN8},          // S_TURRETLOOK
 	{SPR_TURR, 0, 0, {A_FaceTarget}, 0, 0, S_TURRETPOPUP1},  // S_TURRETSEE
@@ -1456,7 +1456,7 @@ state_t states[NUMSTATES] =
 	{SPR_FANG, 18, 16, {A_FaceTarget}, 3, 0, S_FANG_PINCHLOBSHOT1}, // S_FANG_PINCHLOBSHOT0
 	{SPR_FANG, 19,  2, {A_FaceTarget}, 3, 0, S_FANG_PINCHLOBSHOT2}, // S_FANG_PINCHLOBSHOT1
 	{SPR_FANG, 20, 30, {A_Boss5MakeItRain}, MT_FBOMB, -16, S_FANG_PINCHLOBSHOT3}, // S_FANG_PINCHLOBSHOT2
-	{SPR_FANG, 20, 18, {A_LinedefExecute}, LE_BOSS4DROP, 0, S_FANG_PINCHLOBSHOT4}, // S_FANG_PINCHLOBSHOT3
+	{SPR_FANG, 20, 18, {A_LinedefExecuteFromArg}, 4, 0, S_FANG_PINCHLOBSHOT4}, // S_FANG_PINCHLOBSHOT3
 	{SPR_FANG,  0,  0, {A_Boss5Calm}, 0, 0, S_FANG_PATHINGSTART1}, // S_FANG_PINCHLOBSHOT4
 
 	{SPR_FANG, 21, 0, {A_DoNPCPain},                    0, 0, S_FANG_DIE2}, // S_FANG_DIE1
@@ -1588,7 +1588,7 @@ state_t states[NUMSTATES] =
 
 	{SPR_BRAK, 21, 3*TICRATE, {NULL}, 0, 0, S_BLACKEGG_DESTROYPLAT2}, // S_BLACKEGG_DESTROYPLAT1
 	{SPR_BRAK, 21, 1, {A_PlaySound}, sfx_s3k54, 0, S_BLACKEGG_DESTROYPLAT3}, // S_BLACKEGG_DESTROYPLAT2
-	{SPR_BRAK, 21, 14, {A_LinedefExecute}, LE_BRAKPLATFORM, 0, S_BLACKEGG_STND}, // S_BLACKEGG_DESTROYPLAT3
+	{SPR_BRAK, 21, 14, {A_LinedefExecuteFromArg}, 5, 0, S_BLACKEGG_STND}, // S_BLACKEGG_DESTROYPLAT3
 
 	{SPR_NULL, 0, 1, {A_CapeChase}, (160 - 20) << 16, 0, S_BLACKEGG_HELPER}, // S_BLACKEGG_HELPER
 
@@ -1622,7 +1622,7 @@ state_t states[NUMSTATES] =
 	{SPR_BRAK, 26 + FF_FULLBRIGHT, 2, {A_BrakFireShot}, MT_CYBRAKDEMON_FLAMESHOT, 128, S_CYBRAKDEMON_FLAME_ATTACK4}, // S_CYBRAKDEMON_FLAME_ATTACK3 // Fire
 	{SPR_BRAK, 7, 1, {A_Repeat}, 30, S_CYBRAKDEMON_FLAME_ATTACK3, S_CYBRAKDEMON_FINISH_ATTACK1}, // S_CYBRAKDEMON_FLAME_ATTACK4 // Loop
 	{SPR_BRAK, 0, 6, {A_RandomState}, S_CYBRAKDEMON_VILE_ATTACK1, S_CYBRAKDEMON_NAPALM_ATTACK1, S_CYBRAKDEMON_MISSILE_ATTACK1}, // S_CYBRAKDEMON_CHOOSE_ATTACK2
-	{SPR_BRAK, 20, 0, {A_LinedefExecute}, LE_BRAKVILEATACK, 0, S_CYBRAKDEMON_VILE_ATTACK2}, // S_CYBRAKDEMON_VILE_ATTACK1
+	{SPR_BRAK, 20, 0, {A_LinedefExecuteFromArg}, 5, 0, S_CYBRAKDEMON_VILE_ATTACK2}, // S_CYBRAKDEMON_VILE_ATTACK1
 	{SPR_BRAK, 20, 24, {A_VileTarget}, MT_CYBRAKDEMON_TARGET_RETICULE, 1, S_CYBRAKDEMON_VILE_ATTACK3}, // S_CYBRAKDEMON_VILE_ATTACK2
 	{SPR_BRAK, 19, 8, {A_FaceTarget}, 0, 0, S_CYBRAKDEMON_VILE_ATTACK4}, // S_CYBRAKDEMON_VILE_ATTACK3
 	{SPR_BRAK, 18, 8, {A_FaceTarget}, 0, 0, S_CYBRAKDEMON_VILE_ATTACK5}, // S_CYBRAKDEMON_VILE_ATTACK4
@@ -1635,7 +1635,7 @@ state_t states[NUMSTATES] =
 	{SPR_BRAK, 0, 0, {A_SetReactionTime}, 0, 0, S_CYBRAKDEMON_WALK1}, // S_CYBRAKDEMON_FINISH_ATTACK2 // If just attacked, remove MF2_FRET w/out going back to spawnstate
 	{SPR_BRAK, 18, 24, {A_Pain}, 0, 0, S_CYBRAKDEMON_PAIN2}, // S_CYBRAKDEMON_PAIN1
 	{SPR_BRAK, 18, 0, {A_CheckHealth}, 3, S_CYBRAKDEMON_PAIN3, S_CYBRAKDEMON_CHOOSE_ATTACK1}, // S_CYBRAKDEMON_PAIN2
-	{SPR_BRAK, 18, 0, {A_LinedefExecute}, LE_PINCHPHASE, 0, S_CYBRAKDEMON_CHOOSE_ATTACK1}, // S_CYBRAKDEMON_PAIN3
+	{SPR_BRAK, 18, 0, {A_LinedefExecuteFromArg}, 4, 0, S_CYBRAKDEMON_CHOOSE_ATTACK1}, // S_CYBRAKDEMON_PAIN3
 	{SPR_BRAK, 18, 1, {A_Repeat}, 1, S_CYBRAKDEMON_DIE1, S_CYBRAKDEMON_DIE2}, // S_CYBRAKDEMON_DIE1
 	{SPR_BRAK, 18, 2, {A_BossScream}, 2, 0, S_CYBRAKDEMON_DIE3}, // S_CYBRAKDEMON_DIE2
 	{SPR_BRAK, 18, 0, {A_Repeat}, 52, S_CYBRAKDEMON_DIE2, S_CYBRAKDEMON_DIE4}, // S_CYBRAKDEMON_DIE3
@@ -18217,209 +18217,13 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	// ambient water 1a (large)
-	{           // MT_AWATERA
+	// ambient sound effect
+	{           // MT_AMBIENT
 		700,            // doomednum
 		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
-		S_NULL,         // seestate
-		sfx_amwtr1,     // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
-	// ambient water 1b (large)
-	{           // MT_AWATERB
-		701,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
-		S_NULL,         // seestate
-		sfx_amwtr2,     // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
-	// ambient water 2a (medium)
-	{           // MT_AWATERC
-		702,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
-		S_NULL,         // seestate
-		sfx_amwtr3,     // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
-	// ambient water 2b (medium)
-	{           // MT_AWATERD
-		703,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
-		S_NULL,         // seestate
-		sfx_amwtr4,     // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
-	// ambient water 3a (small)
-	{           // MT_AWATERE
-		704,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
-		S_NULL,         // seestate
-		sfx_amwtr5,     // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
-	// ambient water 3b (small)
-	{           // MT_AWATERF
-		705,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
-		S_NULL,         // seestate
-		sfx_amwtr6,     // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
-	// ambient water 4a (extra large)
-	{           // MT_AWATERG
-		706,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
-		S_NULL,         // seestate
-		sfx_amwtr7,     // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
-	// ambient water 4b (extra large)
-	{           // MT_AWATERH
-		707,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		35,             // spawnhealth
+		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_amwtr8,     // seesound
+		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -18441,87 +18245,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_RANDOMAMBIENT
-		708,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		512,            // spawnhealth: repeat speed
-		S_NULL,         // seestate
-		sfx_ambint,     // seesound
-		0,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		255,            // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		1000,           // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
-	{           // MT_RANDOMAMBIENT2
-		709,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		220,            // spawnhealth: repeat speed
-		S_NULL,         // seestate
-		sfx_ambin2,     // seesound
-		0,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		255,            // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		1000,           // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
-	{           // MT_MACHINEAMBIENCE
-		710,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		24,             // spawnhealth: repeat speed
-		S_NULL,         // seestate
-		sfx_ambmac,     // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		200,            // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		1*FRACUNIT,     // speed
-		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		20,             // damage
-		sfx_None,       // activesound
-		MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_AMBIENT, // flags
-		S_NULL          // raisestate
-	},
-
 	{           // MT_CORK
 		-1,             // doomednum
 		S_CORK,         // spawnstate
@@ -20874,34 +20597,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	// for use with wind and current effects
-	{           // MT_PULL
-		755,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		1000,           // spawnhealth
-		S_NULL,         // seestate
-		sfx_None,       // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		8,              // radius
-		8,              // height
-		0,              // display offset
-		10,             // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
-		S_NULL          // raisestate
-	},
-
 	{           // MT_GHOST
 		-1,             // doomednum
 		S_THOK,         // spawnstate
diff --git a/src/info.h b/src/info.h
index 1b7a201cea519d8eef3d041839917153f93ff9cc..a9f68721f42d38c203e2f6ffd665f2fdda39dc6e 100644
--- a/src/info.h
+++ b/src/info.h
@@ -152,6 +152,7 @@ enum actionnum
 	A_BOSS3PATH,
 	A_BOSS3SHOCKTHINK,
 	A_LINEDEFEXECUTE,
+	A_LINEDEFEXECUTEFROMARG,
 	A_PLAYSEESOUND,
 	A_PLAYATTACKSOUND,
 	A_PLAYACTIVESOUND,
@@ -415,6 +416,7 @@ void A_Boss3TakeDamage();
 void A_Boss3Path();
 void A_Boss3ShockThink();
 void A_LinedefExecute();
+void A_LinedefExecuteFromArg();
 void A_PlaySeeSound();
 void A_PlayAttackSound();
 void A_PlayActiveSound();
@@ -4999,17 +5001,7 @@ typedef enum mobj_type
 	MT_FINISHFLAG, // Finish flag
 
 	// Ambient Sounds
-	MT_AWATERA, // Ambient Water Sound 1
-	MT_AWATERB, // Ambient Water Sound 2
-	MT_AWATERC, // Ambient Water Sound 3
-	MT_AWATERD, // Ambient Water Sound 4
-	MT_AWATERE, // Ambient Water Sound 5
-	MT_AWATERF, // Ambient Water Sound 6
-	MT_AWATERG, // Ambient Water Sound 7
-	MT_AWATERH, // Ambient Water Sound 8
-	MT_RANDOMAMBIENT,
-	MT_RANDOMAMBIENT2,
-	MT_MACHINEAMBIENCE,
+	MT_AMBIENT,
 
 	MT_CORK,
 	MT_LHRT,
@@ -5113,7 +5105,6 @@ typedef enum mobj_type
 	MT_CRUMBLEOBJ, // Sound generator for crumbling platform
 	MT_TUBEWAYPOINT,
 	MT_PUSH,
-	MT_PULL,
 	MT_GHOST,
 	MT_OVERLAY,
 	MT_ANGLEMAN,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 120ab671e92e1fafbfba8eaf520ee5133fdad691..da36142716c722701b59bb20479ec8e07a6512c4 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -2202,6 +2202,31 @@ static int lib_pExplodeMissile(lua_State *L)
 	return 0;
 }
 
+static int lib_pMobjTouchingSectorSpecial(lua_State *L)
+{
+	mobj_t *mo = *((mobj_t**)luaL_checkudata(L, 1, META_MOBJ));
+	INT32 section = (INT32)luaL_checkinteger(L, 2);
+	INT32 number = (INT32)luaL_checkinteger(L, 3);
+	//HUDSAFE
+	INLEVEL
+	if (!mo)
+		return LUA_ErrInvalid(L, "mobj_t");
+	LUA_PushUserdata(L, P_MobjTouchingSectorSpecial(mo, section, number), META_SECTOR);
+	return 1;
+}
+
+static int lib_pMobjTouchingSectorSpecialFlag(lua_State *L)
+{
+	mobj_t *mo = *((mobj_t**)luaL_checkudata(L, 1, META_MOBJ));
+	sectorspecialflags_t flag = (INT32)luaL_checkinteger(L, 2);
+	//HUDSAFE
+	INLEVEL
+	if (!mo)
+		return LUA_ErrInvalid(L, "mobj_t");
+	LUA_PushUserdata(L, P_MobjTouchingSectorSpecialFlag(mo, flag), META_SECTOR);
+	return 1;
+}
+
 static int lib_pPlayerTouchingSectorSpecial(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
@@ -2215,6 +2240,18 @@ static int lib_pPlayerTouchingSectorSpecial(lua_State *L)
 	return 1;
 }
 
+static int lib_pPlayerTouchingSectorSpecialFlag(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	sectorspecialflags_t flag = (INT32)luaL_checkinteger(L, 2);
+	//HUDSAFE
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	LUA_PushUserdata(L, P_PlayerTouchingSectorSpecialFlag(player, flag), META_SECTOR);
+	return 1;
+}
+
 static int lib_pFindLowestFloorSurrounding(lua_State *L)
 {
 	sector_t *sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
@@ -2346,23 +2383,13 @@ static int lib_pFadeLight(lua_State *L)
 	INT32 speed = (INT32)luaL_checkinteger(L, 3);
 	boolean ticbased = lua_optboolean(L, 4);
 	boolean force = lua_optboolean(L, 5);
+	boolean relative = lua_optboolean(L, 6);
 	NOHUD
 	INLEVEL
-	P_FadeLight(tag, destvalue, speed, ticbased, force);
+	P_FadeLight(tag, destvalue, speed, ticbased, force, relative);
 	return 0;
 }
 
-static int lib_pThingOnSpecial3DFloor(lua_State *L)
-{
-	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
-	NOHUD
-	INLEVEL
-	if (!mo)
-		return LUA_ErrInvalid(L, "mobj_t");
-	LUA_PushUserdata(L, P_ThingOnSpecial3DFloor(mo), META_SECTOR);
-	return 1;
-}
-
 static int lib_pIsFlagAtBase(lua_State *L)
 {
 	mobjtype_t flag = luaL_checkinteger(L, 1);
@@ -4055,7 +4082,10 @@ static luaL_Reg lib[] = {
 	{"P_SetMobjStateNF",lib_pSetMobjStateNF},
 	{"P_DoSuperTransformation",lib_pDoSuperTransformation},
 	{"P_ExplodeMissile",lib_pExplodeMissile},
+	{"P_MobjTouchingSectorSpecial",lib_pMobjTouchingSectorSpecial},
+	{"P_MobjTouchingSectorSpecialFlag",lib_pMobjTouchingSectorSpecialFlag},
 	{"P_PlayerTouchingSectorSpecial",lib_pPlayerTouchingSectorSpecial},
+	{"P_PlayerTouchingSectorSpecialFlag",lib_pPlayerTouchingSectorSpecialFlag},
 	{"P_FindLowestFloorSurrounding",lib_pFindLowestFloorSurrounding},
 	{"P_FindHighestFloorSurrounding",lib_pFindHighestFloorSurrounding},
 	{"P_FindNextHighestFloor",lib_pFindNextHighestFloor},
@@ -4067,7 +4097,6 @@ static luaL_Reg lib[] = {
 	{"P_LinedefExecute",lib_pLinedefExecute},
 	{"P_SpawnLightningFlash",lib_pSpawnLightningFlash},
 	{"P_FadeLight",lib_pFadeLight},
-	{"P_ThingOnSpecial3DFloor",lib_pThingOnSpecial3DFloor},
 	{"P_IsFlagAtBase",lib_pIsFlagAtBase},
 	{"P_SetupLevelSky",lib_pSetupLevelSky},
 	{"P_SetSkyboxMobj",lib_pSetSkyboxMobj},
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 9e58f4b6b8c117be0d62b8a510cdc98dd852d895..0c3440426fe1f27c0790490b36430541911b1ff5 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -35,6 +35,10 @@ enum sector_e {
 	sector_floorpic,
 	sector_ceilingpic,
 	sector_lightlevel,
+	sector_floorlightlevel,
+	sector_floorlightabsolute,
+	sector_ceilinglightlevel,
+	sector_ceilinglightabsolute,
 	sector_special,
 	sector_tag,
 	sector_taglist,
@@ -44,7 +48,14 @@ enum sector_e {
 	sector_lines,
 	sector_ffloors,
 	sector_fslope,
-	sector_cslope
+	sector_cslope,
+	sector_flags,
+	sector_specialflags,
+	sector_damagetype,
+	sector_triggertag,
+	sector_triggerer,
+	sector_friction,
+	sector_gravity,
 };
 
 static const char *const sector_opt[] = {
@@ -54,6 +65,10 @@ static const char *const sector_opt[] = {
 	"floorpic",
 	"ceilingpic",
 	"lightlevel",
+	"floorlightlevel",
+	"floorlightabsolute",
+	"ceilinglightlevel",
+	"ceilinglightabsolute",
 	"special",
 	"tag",
 	"taglist",
@@ -64,6 +79,13 @@ static const char *const sector_opt[] = {
 	"ffloors",
 	"f_slope",
 	"c_slope",
+	"flags",
+	"specialflags",
+	"damagetype",
+	"triggertag",
+	"triggerer",
+	"friction",
+	"gravity",
 	NULL};
 
 enum subsector_e {
@@ -199,6 +221,12 @@ enum ffloor_e {
 	ffloor_prev,
 	ffloor_alpha,
 	ffloor_blend,
+	ffloor_bustflags,
+	ffloor_busttype,
+	ffloor_busttag,
+	ffloor_sinkspeed,
+	ffloor_friction,
+	ffloor_bouncestrength,
 };
 
 static const char *const ffloor_opt[] = {
@@ -218,6 +246,12 @@ static const char *const ffloor_opt[] = {
 	"prev",
 	"alpha",
 	"blend",
+	"bustflags",
+	"busttype",
+	"busttag",
+	"sinkspeed",
+	"friction",
+	"bouncestrength",
 	NULL};
 
 #ifdef HAVE_LUA_SEGS
@@ -583,6 +617,18 @@ static int sector_get(lua_State *L)
 	case sector_lightlevel:
 		lua_pushinteger(L, sector->lightlevel);
 		return 1;
+	case sector_floorlightlevel:
+		lua_pushinteger(L, sector->floorlightlevel);
+		return 1;
+	case sector_floorlightabsolute:
+		lua_pushboolean(L, sector->floorlightabsolute);
+		return 1;
+	case sector_ceilinglightlevel:
+		lua_pushinteger(L, sector->ceilinglightlevel);
+		return 1;
+	case sector_ceilinglightabsolute:
+		lua_pushboolean(L, sector->ceilinglightabsolute);
+		return 1;
 	case sector_special:
 		lua_pushinteger(L, sector->special);
 		return 1;
@@ -621,6 +667,27 @@ static int sector_get(lua_State *L)
 	case sector_cslope: // c_slope
 		LUA_PushUserdata(L, sector->c_slope, META_SLOPE);
 		return 1;
+	case sector_flags: // flags
+		lua_pushinteger(L, sector->flags);
+		return 1;
+	case sector_specialflags: // specialflags
+		lua_pushinteger(L, sector->specialflags);
+		return 1;
+	case sector_damagetype: // damagetype
+		lua_pushinteger(L, (UINT8)sector->damagetype);
+		return 1;
+	case sector_triggertag: // triggertag
+		lua_pushinteger(L, (INT16)sector->triggertag);
+		return 1;
+	case sector_triggerer: // triggerer
+		lua_pushinteger(L, (UINT8)sector->triggerer);
+		return 1;
+	case sector_friction: // friction
+		lua_pushfixed(L, sector->friction);
+		return 1;
+	case sector_gravity: // gravity
+		lua_pushfixed(L, sector->gravity);
+		return 1;
 	}
 	return 0;
 }
@@ -648,6 +715,7 @@ static int sector_set(lua_State *L)
 	case sector_ffloors: // ffloors
 	case sector_fslope: // f_slope
 	case sector_cslope: // c_slope
+	case sector_friction: // friction
 	default:
 		return luaL_error(L, "sector_t field " LUA_QS " cannot be set.", sector_opt[field]);
 	case sector_floorheight: { // floorheight
@@ -687,6 +755,18 @@ static int sector_set(lua_State *L)
 	case sector_lightlevel:
 		sector->lightlevel = (INT16)luaL_checkinteger(L, 3);
 		break;
+	case sector_floorlightlevel:
+		sector->floorlightlevel = (INT16)luaL_checkinteger(L, 3);
+		break;
+	case sector_floorlightabsolute:
+		sector->floorlightabsolute = luaL_checkboolean(L, 3);
+		break;
+	case sector_ceilinglightlevel:
+		sector->ceilinglightlevel = (INT16)luaL_checkinteger(L, 3);
+		break;
+	case sector_ceilinglightabsolute:
+		sector->ceilinglightabsolute = luaL_checkboolean(L, 3);
+		break;
 	case sector_special:
 		sector->special = (INT16)luaL_checkinteger(L, 3);
 		break;
@@ -695,6 +775,25 @@ static int sector_set(lua_State *L)
 		break;
 	case sector_taglist:
 		return LUA_ErrSetDirectly(L, "sector_t", "taglist");
+	case sector_flags:
+		sector->flags = luaL_checkinteger(L, 3);
+		CheckForReverseGravity |= (sector->flags & MSF_GRAVITYFLIP);
+		break;
+	case sector_specialflags:
+		sector->specialflags = luaL_checkinteger(L, 3);
+		break;
+	case sector_damagetype:
+		sector->damagetype = (UINT8)luaL_checkinteger(L, 3);
+		break;
+	case sector_triggertag:
+		sector->triggertag = (INT16)luaL_checkinteger(L, 3);
+		break;
+	case sector_triggerer:
+		sector->triggerer = (UINT8)luaL_checkinteger(L, 3);
+		break;
+	case sector_gravity:
+		sector->gravity = luaL_checkfixed(L, 3);
+		break;
 	}
 	return 0;
 }
@@ -1817,6 +1916,24 @@ static int ffloor_get(lua_State *L)
 	case ffloor_blend:
 		lua_pushinteger(L, ffloor->blend);
 		return 1;
+	case ffloor_bustflags:
+		lua_pushinteger(L, ffloor->bustflags);
+		return 1;
+	case ffloor_busttype:
+		lua_pushinteger(L, ffloor->busttype);
+		return 1;
+	case ffloor_busttag:
+		lua_pushinteger(L, ffloor->busttag);
+		return 1;
+	case ffloor_sinkspeed:
+		lua_pushfixed(L, ffloor->sinkspeed);
+		return 1;
+	case ffloor_friction:
+		lua_pushfixed(L, ffloor->friction);
+		return 1;
+	case ffloor_bouncestrength:
+		lua_pushfixed(L, ffloor->bouncestrength);
+		return 1;
 	}
 	return 0;
 }
diff --git a/src/lua_polyobjlib.c b/src/lua_polyobjlib.c
index e254b0e4ab698b5f3d85f6db66648b91eac870b5..a91c354f4b6a7077cdc13f8c5b7af03b49bd990e 100644
--- a/src/lua_polyobjlib.c
+++ b/src/lua_polyobjlib.c
@@ -244,13 +244,14 @@ static int lib_polyobj_rotate(lua_State *L)
 {
 	polyobj_t *po = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
 	angle_t delta = luaL_checkangle(L, 2);
-	UINT8 turnthings = (UINT8)luaL_optinteger(L, 3, 0); // don't turn anything by default? (could change this if not desired)
-	boolean checkmobjs = lua_opttrueboolean(L, 4);
+	boolean turnplayers = lua_opttrueboolean(L, 3);
+	boolean turnothers = lua_opttrueboolean(L, 4);
+	boolean checkmobjs = lua_opttrueboolean(L, 5);
 	NOHUD
 	INLEVEL
 	if (!po)
 		return LUA_ErrInvalid(L, "polyobj_t");
-	lua_pushboolean(L, Polyobj_rotate(po, delta, turnthings, checkmobjs));
+	lua_pushboolean(L, Polyobj_rotate(po, delta, turnplayers, turnothers, checkmobjs));
 	return 1;
 }
 
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 82d0b9d5a59af96319b8f004809b5ec2ec0639fc..40b9a1230eb439e5ef1afff546e0368e66514bbc 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -539,7 +539,7 @@ void Command_Teleport_f(void)
 
 			// Flagging a player's ambush will make them start on the ceiling
 			// Objectflip inverts
-			if (!!(mt->options & MTF_AMBUSH) ^ !!(mt->options & MTF_OBJECTFLIP))
+			if (!!(mt->args[0]) ^ !!(mt->options & MTF_OBJECTFLIP))
 				intz = ss->sector->ceilingheight - p->mo->height - offset;
 			else
 				intz = ss->sector->floorheight + offset;
@@ -1004,7 +1004,7 @@ static void OP_CycleThings(INT32 amt)
 		} while
 		(mobjinfo[op_currentthing].doomednum == -1
 			|| op_currentthing == MT_NIGHTSDRONE
-			|| mobjinfo[op_currentthing].flags & (MF_AMBIENT|MF_NOSECTOR)
+			|| mobjinfo[op_currentthing].flags & MF_NOSECTOR
 			|| (states[mobjinfo[op_currentthing].spawnstate].sprite == SPR_NULL
 			 && states[mobjinfo[op_currentthing].seestate].sprite == SPR_NULL)
 		);
@@ -1137,7 +1137,7 @@ void OP_ResetObjectplace(void)
 //
 // Main meat of objectplace: handling functions
 //
-void OP_NightsObjectplace(player_t *player)
+/*void OP_NightsObjectplace(player_t *player)
 {
 	ticcmd_t *cmd = &player->cmd;
 	mapthing_t *mt;
@@ -1283,14 +1283,14 @@ void OP_NightsObjectplace(player_t *player)
 		mt = OP_CreateNewMapThing(player, (UINT16)cv_mapthingnum.value, false);
 		mt->angle = angle;
 
-		if (mt->type >= 600 && mt->type <= 609) // Placement patterns
+		if (mt->type >= 600 && mt->type <= 611) // Placement patterns
 			P_SpawnItemPattern(mt, false);
-		else if (mt->type == 1705 || mt->type == 1713) // NiGHTS Hoops
+		else if (mt->type == 1713) // NiGHTS Hoops
 			P_SpawnHoop(mt);
 		else
 			P_SpawnMapThing(mt);
 	}
-}
+}*/
 
 //
 // OP_ObjectplaceMovement
@@ -1414,9 +1414,9 @@ void OP_ObjectplaceMovement(player_t *player)
 			return;
 
 		mt = OP_CreateNewMapThing(player, (UINT16)spawnthing, ceiling);
-		if (mt->type >= 600 && mt->type <= 609) // Placement patterns
+		if (mt->type >= 600 && mt->type <= 611) // Placement patterns
 			P_SpawnItemPattern(mt, false);
-		else if (mt->type == 1705 || mt->type == 1713) // NiGHTS Hoops
+		else if (mt->type == 1713) // NiGHTS Hoops
 			P_SpawnHoop(mt);
 		else
 			P_SpawnMapThing(mt);
@@ -1428,14 +1428,14 @@ void OP_ObjectplaceMovement(player_t *player)
 //
 // Objectplace related commands.
 //
-void Command_Writethings_f(void)
+/*void Command_Writethings_f(void)
 {
 	REQUIRE_INLEVEL;
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_OBJECTPLACE;
 
 	P_WriteThings();
-}
+}*/
 
 void Command_ObjectPlace_f(void)
 {
diff --git a/src/m_cheat.h b/src/m_cheat.h
index c22e262fbb4898e33d4a9581956e204486ad7675..086117579ed2b79533758581d343a1fe13182fd1 100644
--- a/src/m_cheat.h
+++ b/src/m_cheat.h
@@ -26,7 +26,7 @@ void cht_Init(void);
 // ObjectPlace
 //
 void Command_ObjectPlace_f(void);
-void Command_Writethings_f(void);
+//void Command_Writethings_f(void);
 
 extern consvar_t cv_opflags, cv_ophoopflags, cv_mapthingnum, cv_speed;
 //extern consvar_t cv_snapto, cv_grid;
@@ -38,7 +38,7 @@ extern UINT32 op_displayflags;
 
 boolean OP_FreezeObjectplace(void);
 void OP_ResetObjectplace(void);
-void OP_NightsObjectplace(player_t *player);
+//void OP_NightsObjectplace(player_t *player);
 void OP_ObjectplaceMovement(player_t *player);
 
 //
diff --git a/src/m_misc.c b/src/m_misc.c
index d7d6d6bbb6e9d4931c545b985e386d303af54941..f9afa4803e17d75e1e2c2c9ab89e009039b98a77 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -1970,18 +1970,168 @@ void M_UnGetToken(void)
 	endPos = oldendPos;
 }
 
-/** Returns the current token's position.
- */
-UINT32 M_GetTokenPos(void)
+#define NUMTOKENS 2
+static const char *tokenizerInput = NULL;
+static UINT32 tokenCapacity[NUMTOKENS] = {0};
+static char *tokenizerToken[NUMTOKENS] = {NULL};
+static UINT32 tokenizerStartPos = 0;
+static UINT32 tokenizerEndPos = 0;
+static UINT32 tokenizerInputLength = 0;
+static UINT8 tokenizerInComment = 0; // 0 = not in comment, 1 = // Single-line, 2 = /* Multi-line */
+
+void M_TokenizerOpen(const char *inputString)
 {
-	return endPos;
+	size_t i;
+
+	tokenizerInput = inputString;
+	for (i = 0; i < NUMTOKENS; i++)
+	{
+		tokenCapacity[i] = 1024;
+		tokenizerToken[i] = (char*)Z_Malloc(tokenCapacity[i] * sizeof(char), PU_STATIC, NULL);
+	}
+	tokenizerInputLength = strlen(tokenizerInput);
 }
 
-/** Sets the current token's position.
- */
-void M_SetTokenPos(UINT32 newPos)
+void M_TokenizerClose(void)
+{
+	size_t i;
+
+	tokenizerInput = NULL;
+	for (i = 0; i < NUMTOKENS; i++)
+		Z_Free(tokenizerToken[i]);
+	tokenizerStartPos = 0;
+	tokenizerEndPos = 0;
+	tokenizerInComment = 0;
+}
+
+static void M_DetectComment(UINT32 *pos)
+{
+	if (tokenizerInComment)
+		return;
+
+	if (*pos >= tokenizerInputLength - 1)
+		return;
+
+	if (tokenizerInput[*pos] != '/')
+		return;
+
+	//Single-line comment start
+	if (tokenizerInput[*pos + 1] == '/')
+		tokenizerInComment = 1;
+	//Multi-line comment start
+	else if (tokenizerInput[*pos + 1] == '*')
+		tokenizerInComment = 2;
+}
+
+static void M_ReadTokenString(UINT32 i)
+{
+	UINT32 tokenLength = tokenizerEndPos - tokenizerStartPos;
+	if (tokenLength + 1 > tokenCapacity[i])
+	{
+		tokenCapacity[i] = tokenLength + 1;
+		// Assign the memory. Don't forget an extra byte for the end of the string!
+		tokenizerToken[i] = (char *)Z_Malloc(tokenCapacity[i] * sizeof(char), PU_STATIC, NULL);
+	}
+	// Copy the string.
+	M_Memcpy(tokenizerToken[i], tokenizerInput + tokenizerStartPos, (size_t)tokenLength);
+	// Make the final character NUL.
+	tokenizerToken[i][tokenLength] = '\0';
+}
+
+const char *M_TokenizerRead(UINT32 i)
+{
+	if (!tokenizerInput)
+		return NULL;
+
+	tokenizerStartPos = tokenizerEndPos;
+
+	// Try to detect comments now, in case we're pointing right at one
+	M_DetectComment(&tokenizerStartPos);
+
+	// Find the first non-whitespace char, or else the end of the string trying
+	while ((tokenizerInput[tokenizerStartPos] == ' '
+			|| tokenizerInput[tokenizerStartPos] == '\t'
+			|| tokenizerInput[tokenizerStartPos] == '\r'
+			|| tokenizerInput[tokenizerStartPos] == '\n'
+			|| tokenizerInput[tokenizerStartPos] == '\0'
+			|| tokenizerInput[tokenizerStartPos] == '=' || tokenizerInput[tokenizerStartPos] == ';' // UDMF TEXTMAP.
+			|| tokenizerInComment != 0)
+			&& tokenizerStartPos < tokenizerInputLength)
+	{
+		// Try to detect comment endings now
+		if (tokenizerInComment == 1	&& tokenizerInput[tokenizerStartPos] == '\n')
+			tokenizerInComment = 0; // End of line for a single-line comment
+		else if (tokenizerInComment == 2
+			&& tokenizerStartPos < tokenizerInputLength - 1
+			&& tokenizerInput[tokenizerStartPos] == '*'
+			&& tokenizerInput[tokenizerStartPos+1] == '/')
+		{
+			// End of multi-line comment
+			tokenizerInComment = 0;
+			tokenizerStartPos++; // Make damn well sure we're out of the comment ending at the end of it all
+		}
+
+		tokenizerStartPos++;
+		M_DetectComment(&tokenizerStartPos);
+	}
+
+	// If the end of the string is reached, no token is to be read
+	if (tokenizerStartPos == tokenizerInputLength) {
+		tokenizerEndPos = tokenizerInputLength;
+		return NULL;
+	}
+	// Else, if it's one of these three symbols, capture only this one character
+	else if (tokenizerInput[tokenizerStartPos] == ','
+			|| tokenizerInput[tokenizerStartPos] == '{'
+			|| tokenizerInput[tokenizerStartPos] == '}')
+	{
+		tokenizerEndPos = tokenizerStartPos + 1;
+		tokenizerToken[i][0] = tokenizerInput[tokenizerStartPos];
+		tokenizerToken[i][1] = '\0';
+		return tokenizerToken[i];
+	}
+	// Return entire string within quotes, except without the quotes.
+	else if (tokenizerInput[tokenizerStartPos] == '"')
+	{
+		tokenizerEndPos = ++tokenizerStartPos;
+		while (tokenizerInput[tokenizerEndPos] != '"' && tokenizerEndPos < tokenizerInputLength)
+			tokenizerEndPos++;
+
+		M_ReadTokenString(i);
+		tokenizerEndPos++;
+		return tokenizerToken[i];
+	}
+
+	// Now find the end of the token. This includes several additional characters that are okay to capture as one character, but not trailing at the end of another token.
+	tokenizerEndPos = tokenizerStartPos + 1;
+	while ((tokenizerInput[tokenizerEndPos] != ' '
+			&& tokenizerInput[tokenizerEndPos] != '\t'
+			&& tokenizerInput[tokenizerEndPos] != '\r'
+			&& tokenizerInput[tokenizerEndPos] != '\n'
+			&& tokenizerInput[tokenizerEndPos] != ','
+			&& tokenizerInput[tokenizerEndPos] != '{'
+			&& tokenizerInput[tokenizerEndPos] != '}'
+			&& tokenizerInput[tokenizerEndPos] != '=' && tokenizerInput[tokenizerEndPos] != ';' // UDMF TEXTMAP.
+			&& tokenizerInComment == 0)
+			&& tokenizerEndPos < tokenizerInputLength)
+	{
+		tokenizerEndPos++;
+		// Try to detect comment starts now; if it's in a comment, we don't want it in this token
+		M_DetectComment(&tokenizerEndPos);
+	}
+
+	M_ReadTokenString(i);
+	return tokenizerToken[i];
+}
+
+UINT32 M_TokenizerGetEndPos(void)
+{
+	return tokenizerEndPos;
+}
+
+void M_TokenizerSetEndPos(UINT32 newPos)
 {
-	endPos = newPos;
+	tokenizerEndPos = newPos;
 }
 
 /** Count bits in a number.
diff --git a/src/p_ceilng.c b/src/p_ceilng.c
index 50344ee0ccf9df0daf12f45321521cf6af0ed6e3..d88d9be86a2416d9fa9ff7c7fa0a3a5d01743385 100644
--- a/src/p_ceilng.c
+++ b/src/p_ceilng.c
@@ -34,7 +34,6 @@ INT32 ceilmovesound = sfx_None;
 void T_MoveCeiling(ceiling_t *ceiling)
 {
 	result_e res;
-	boolean dontupdate = false;
 
 	if (ceiling->delaytimer)
 	{
@@ -42,259 +41,81 @@ void T_MoveCeiling(ceiling_t *ceiling)
 		return;
 	}
 
-	switch (ceiling->direction)
+	res = T_MovePlane(ceiling->sector, ceiling->speed, (ceiling->direction == 1) ? ceiling->topheight : ceiling->bottomheight, false, true, ceiling->direction);
+
+	if (ceiling->type == bounceCeiling)
 	{
-		case 0: // IN STASIS
-			break;
-		case 1: // UP
-			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->topheight, false, true, ceiling->direction);
+		const fixed_t origspeed = FixedDiv(ceiling->origspeed, (ELEVATORSPEED/2));
+		const fixed_t fs = abs(ceiling->sector->ceilingheight - lines[ceiling->sourceline].frontsector->ceilingheight);
+		const fixed_t bs = abs(ceiling->sector->ceilingheight - lines[ceiling->sourceline].backsector->ceilingheight);
+		if (fs < bs)
+			ceiling->speed = FixedDiv(fs, 25*FRACUNIT) + FRACUNIT/4;
+		else
+			ceiling->speed = FixedDiv(bs, 25*FRACUNIT) + FRACUNIT/4;
+		ceiling->speed = FixedMul(ceiling->speed, origspeed);
+	}
 
-			if (ceiling->type == bounceCeiling)
+	if (res == pastdest)
+	{
+		switch (ceiling->type)
+		{
+			case instantMoveCeilingByFrontSector:
+				if (ceiling->texture > -1) // flat changing
+					ceiling->sector->ceilingpic = ceiling->texture;
+				ceiling->sector->ceilingdata = NULL;
+				ceiling->sector->ceilspeed = 0;
+				P_RemoveThinker(&ceiling->thinker);
+				return;
+			case moveCeilingByFrontSector:
+				if (ceiling->tag) // chained linedef executing
+					P_LinedefExecute(ceiling->tag, NULL, NULL);
+				if (ceiling->texture > -1) // flat changing
+					ceiling->sector->ceilingpic = ceiling->texture;
+				/* FALLTHRU */
+			case raiseToHighest:
+			case moveCeilingByDistance:
+				ceiling->sector->ceilingdata = NULL;
+				ceiling->sector->ceilspeed = 0;
+				P_RemoveThinker(&ceiling->thinker);
+				return;
+			case bounceCeiling:
+			case bounceCeilingCrush:
 			{
-				const fixed_t origspeed = FixedDiv(ceiling->origspeed,(ELEVATORSPEED/2));
-				const fixed_t fs = abs(ceiling->sector->ceilingheight - lines[ceiling->texture].frontsector->ceilingheight);
-				const fixed_t bs = abs(ceiling->sector->ceilingheight - lines[ceiling->texture].backsector->ceilingheight);
-				if (fs < bs)
-					ceiling->speed = FixedDiv(fs,25*FRACUNIT) + FRACUNIT/4;
-				else
-					ceiling->speed = FixedDiv(bs,25*FRACUNIT) + FRACUNIT/4;
+				fixed_t dest = (ceiling->direction == 1) ? ceiling->topheight : ceiling->bottomheight;
 
-				ceiling->speed = FixedMul(ceiling->speed,origspeed);
-			}
-
-			if (res == pastdest)
-			{
-				switch (ceiling->type)
+				if (dest == lines[ceiling->sourceline].frontsector->ceilingheight)
 				{
-					case instantMoveCeilingByFrontSector:
-						if (ceiling->texture > -1)
-							ceiling->sector->ceilingpic = ceiling->texture;
-						ceiling->sector->ceilingdata = NULL;
-						ceiling->sector->ceilspeed = 0;
-						P_RemoveThinker(&ceiling->thinker);
-						dontupdate = true;
-						break;
-					case moveCeilingByFrontSector:
-						if (ceiling->texture < -1) // chained linedef executing
-							P_LinedefExecute((INT16)(ceiling->texture + INT16_MAX + 2), NULL, NULL);
-						if (ceiling->texture > -1) // flat changing
-							ceiling->sector->ceilingpic = ceiling->texture;
-						/* FALLTHRU */
-					case raiseToHighest:
-//					case raiseCeilingByLine:
-					case moveCeilingByFrontTexture:
-						ceiling->sector->ceilingdata = NULL;
-						ceiling->sector->ceilspeed = 0;
-						P_RemoveThinker(&ceiling->thinker);
-						dontupdate = true;
-						break;
-
-					case fastCrushAndRaise:
-					case crushAndRaise:
-						ceiling->direction = -1;
-						break;
-
-					case bounceCeiling:
-					{
-						fixed_t dest = ceiling->topheight;
-
-						if (dest == lines[ceiling->texture].frontsector->ceilingheight)
-							dest = lines[ceiling->texture].backsector->ceilingheight;
-						else
-							dest = lines[ceiling->texture].frontsector->ceilingheight;
-
-						if (dest < ceiling->sector->ceilingheight) // must move down
-						{
-							ceiling->direction = -1;
-							ceiling->bottomheight = dest;
-						}
-						else // must move up
-						{
-							ceiling->direction = 1;
-							ceiling->topheight = dest;
-						}
-
-						ceiling->delaytimer = ceiling->delay;
-
-						// That's it. Do not set dontupdate, do not remove the thinker.
-						break;
-					}
-
-					case bounceCeilingCrush:
-					{
-						fixed_t dest = ceiling->topheight;
-
-						if (dest == lines[ceiling->texture].frontsector->ceilingheight)
-						{
-							dest = lines[ceiling->texture].backsector->ceilingheight;
-							ceiling->speed = ceiling->origspeed = FixedDiv(abs(lines[ceiling->texture].dy),4*FRACUNIT); // return trip, use dy
-						}
-						else
-						{
-							dest = lines[ceiling->texture].frontsector->ceilingheight;
-							ceiling->speed = ceiling->origspeed = FixedDiv(abs(lines[ceiling->texture].dx),4*FRACUNIT); // going frontways, use dx
-						}
-
-						if (dest < ceiling->sector->ceilingheight) // must move down
-						{
-							ceiling->direction = -1;
-							ceiling->bottomheight = dest;
-						}
-						else // must move up
-						{
-							ceiling->direction = 1;
-							ceiling->topheight = dest;
-						}
-
-						ceiling->delaytimer = ceiling->delay;
-
-						// That's it. Do not set dontupdate, do not remove the thinker.
-						break;
-					}
-
-					default:
-						break;
+					dest = lines[ceiling->sourceline].backsector->ceilingheight;
+					ceiling->origspeed = lines[ceiling->sourceline].args[3] << (FRACBITS - 2); // return trip, use args[3]
 				}
-			}
-			break;
-
-		case -1: // DOWN
-			res = T_MovePlane(ceiling->sector, ceiling->speed, ceiling->bottomheight, ceiling->crush, true, ceiling->direction);
-
-			if (ceiling->type == bounceCeiling)
-			{
-				const fixed_t origspeed = FixedDiv(ceiling->origspeed,(ELEVATORSPEED/2));
-				const fixed_t fs = abs(ceiling->sector->ceilingheight - lines[ceiling->texture].frontsector->ceilingheight);
-				const fixed_t bs = abs(ceiling->sector->ceilingheight - lines[ceiling->texture].backsector->ceilingheight);
-				if (fs < bs)
-					ceiling->speed = FixedDiv(fs,25*FRACUNIT) + FRACUNIT/4;
 				else
-					ceiling->speed = FixedDiv(bs,25*FRACUNIT) + FRACUNIT/4;
-				ceiling->speed = FixedMul(ceiling->speed,origspeed);
-			}
-
-			if (res == pastdest)
-			{
-				switch (ceiling->type)
 				{
-					// make platform stop at bottom of all crusher strokes
-					// except generalized ones, reset speed, start back up
-					case crushAndRaise:
-						ceiling->speed = CEILSPEED;
-						/* FALLTHRU */
-					case fastCrushAndRaise:
-						ceiling->direction = 1;
-						break;
-
-					case instantMoveCeilingByFrontSector:
-						if (ceiling->texture > -1)
-							ceiling->sector->ceilingpic = ceiling->texture;
-						ceiling->sector->ceilingdata = NULL;
-						ceiling->sector->ceilspeed = 0;
-						P_RemoveThinker(&ceiling->thinker);
-						dontupdate = true;
-						break;
-
-					case moveCeilingByFrontSector:
-						if (ceiling->texture < -1) // chained linedef executing
-							P_LinedefExecute((INT16)(ceiling->texture + INT16_MAX + 2), NULL, NULL);
-						if (ceiling->texture > -1) // flat changing
-							ceiling->sector->ceilingpic = ceiling->texture;
-						// don't break
-						/* FALLTHRU */
-
-					// in all other cases, just remove the active ceiling
-					case lowerAndCrush:
-					case lowerToLowest:
-					case raiseToLowest:
-//					case lowerCeilingByLine:
-					case moveCeilingByFrontTexture:
-						ceiling->sector->ceilingdata = NULL;
-						ceiling->sector->ceilspeed = 0;
-						P_RemoveThinker(&ceiling->thinker);
-						dontupdate = true;
-						break;
-					case bounceCeiling:
-					{
-						fixed_t dest = ceiling->bottomheight;
-
-						if (dest == lines[ceiling->texture].frontsector->ceilingheight)
-							dest = lines[ceiling->texture].backsector->ceilingheight;
-						else
-							dest = lines[ceiling->texture].frontsector->ceilingheight;
-
-						if (dest < ceiling->sector->ceilingheight) // must move down
-						{
-							ceiling->direction = -1;
-							ceiling->bottomheight = dest;
-						}
-						else // must move up
-						{
-							ceiling->direction = 1;
-							ceiling->topheight = dest;
-						}
-
-						ceiling->delaytimer = ceiling->delay;
-
-						// That's it. Do not set dontupdate, do not remove the thinker.
-						break;
-					}
+					dest = lines[ceiling->sourceline].frontsector->ceilingheight;
+					ceiling->origspeed = lines[ceiling->sourceline].args[2] << (FRACBITS - 2); // going frontways, use args[2]
+				}
 
-					case bounceCeilingCrush:
-					{
-						fixed_t dest = ceiling->bottomheight;
-
-						if (dest == lines[ceiling->texture].frontsector->ceilingheight)
-						{
-							dest = lines[ceiling->texture].backsector->ceilingheight;
-							ceiling->speed = ceiling->origspeed = FixedDiv(abs(lines[ceiling->texture].dy),4*FRACUNIT); // return trip, use dy
-						}
-						else
-						{
-							dest = lines[ceiling->texture].frontsector->ceilingheight;
-							ceiling->speed = ceiling->origspeed = FixedDiv(abs(lines[ceiling->texture].dx),4*FRACUNIT); // going frontways, use dx
-						}
-
-						if (dest < ceiling->sector->ceilingheight) // must move down
-						{
-							ceiling->direction = -1;
-							ceiling->bottomheight = dest;
-						}
-						else // must move up
-						{
-							ceiling->direction = 1;
-							ceiling->topheight = dest;
-						}
-
-						ceiling->delaytimer = ceiling->delay;
-
-						// That's it. Do not set dontupdate, do not remove the thinker.
-						break;
-					}
+				if (ceiling->type == bounceCeilingCrush)
+					ceiling->speed = ceiling->origspeed;
 
-					default:
-						break;
+				if (dest < ceiling->sector->ceilingheight) // must move down
+				{
+					ceiling->direction = -1;
+					ceiling->bottomheight = dest;
 				}
-			}
-			else if (res == crushed)
-			{
-				switch (ceiling->type)
+				else // must move up
 				{
-					case crushAndRaise:
-					case lowerAndCrush:
-						ceiling->speed = FixedDiv(CEILSPEED,8*FRACUNIT);
-						break;
-
-					default:
-						break;
+					ceiling->direction = 1;
+					ceiling->topheight = dest;
 				}
+
+				ceiling->delaytimer = ceiling->delay;
+				break;
 			}
-		break;
+			default:
+				break;
+		}
 	}
-	if (!dontupdate)
-		ceiling->sector->ceilspeed = ceiling->speed*ceiling->direction;
-	else
-		ceiling->sector->ceilspeed = 0;
+	ceiling->sector->ceilspeed = ceiling->speed*ceiling->direction;
 }
 
 /** Moves a ceiling crusher.
@@ -322,11 +143,7 @@ void T_CrushCeiling(ceiling_t *ceiling)
 			if (res == pastdest)
 			{
 				ceiling->direction = -1;
-
-				if (lines[ceiling->sourceline].flags & ML_EFFECT4)
-					ceiling->speed = ceiling->oldspeed;
-				else
-					ceiling->speed = ceiling->oldspeed*2;
+				ceiling->speed = lines[ceiling->sourceline].args[2] << (FRACBITS - 2);
 
 				if (ceiling->type == crushCeilOnce
 					|| ceiling->type == crushBothOnce)
@@ -367,12 +184,8 @@ void T_CrushCeiling(ceiling_t *ceiling)
 				ceiling->sector->soundorg.z = ceiling->sector->floorheight;
 				S_StartSound(mp,sfx_pstop);
 
-				if (lines[ceiling->sourceline].flags & ML_EFFECT4)
-					ceiling->speed = ceiling->oldspeed;
-				else
-					ceiling->speed = ceiling->oldspeed/2;
-
 				ceiling->direction = 1;
+				ceiling->speed = lines[ceiling->sourceline].args[3] << (FRACBITS - 2);
 			}
 			break;
 	}
@@ -385,18 +198,18 @@ void T_CrushCeiling(ceiling_t *ceiling)
 
 /** Starts a ceiling mover.
   *
+  * \param tag Tag.
   * \param line The source line.
   * \param type The type of ceiling movement.
   * \return 1 if at least one ceiling mover was started, 0 otherwise.
   * \sa EV_DoCrush, EV_DoFloor, EV_DoElevator, T_MoveCeiling
   */
-INT32 EV_DoCeiling(line_t *line, ceiling_e type)
+INT32 EV_DoCeiling(mtag_t tag, line_t *line, ceiling_e type)
 {
 	INT32 rtn = 0, firstone = 1;
 	INT32 secnum = -1;
 	sector_t *sec;
 	ceiling_t *ceiling;
-	mtag_t tag = Tag_FGet(&line->tags);
 
 	TAG_ITER_SECTORS(tag, secnum)
 	{
@@ -417,44 +230,12 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 
 		switch (type)
 		{
-			case fastCrushAndRaise:
-				ceiling->crush = true;
-				ceiling->topheight = sec->ceilingheight;
-				ceiling->bottomheight = sec->floorheight + (8*FRACUNIT);
-				ceiling->direction = -1;
-				ceiling->speed = CEILSPEED * 2;
-				break;
-
-			case crushAndRaise:
-				ceiling->crush = true;
-				ceiling->topheight = sec->ceilingheight;
-				/* FALLTHRU */
-			case lowerAndCrush:
-				ceiling->bottomheight = sec->floorheight;
-				ceiling->bottomheight += 4*FRACUNIT;
-				ceiling->direction = -1;
-				ceiling->speed = line->dx;
-				break;
-
 			case raiseToHighest:
 				ceiling->topheight = P_FindHighestCeilingSurrounding(sec);
 				ceiling->direction = 1;
 				ceiling->speed = CEILSPEED;
 				break;
 
-			//SoM: 3/6/2000: Added Boom types
-			case lowerToLowest:
-				ceiling->bottomheight = P_FindLowestCeilingSurrounding(sec);
-				ceiling->direction = -1;
-				ceiling->speed = CEILSPEED;
-				break;
-
-			case raiseToLowest: // Graue 09-07-2004
-				ceiling->topheight = P_FindLowestCeilingSurrounding(sec) - 4*FRACUNIT;
-				ceiling->direction = 1;
-				ceiling->speed = line->dx; // hack
-				break;
-
 			case lowerToLowestFast:
 				ceiling->bottomheight = P_FindLowestCeilingSurrounding(sec);
 				ceiling->direction = -1;
@@ -469,8 +250,7 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 
 			//  Linedef executor excellence
 			case moveCeilingByFrontSector:
-				ceiling->speed = P_AproxDistance(line->dx, line->dy);
-				ceiling->speed = FixedDiv(ceiling->speed,8*FRACUNIT);
+				ceiling->speed = line->args[2] << (FRACBITS - 3);
 				if (line->frontsector->ceilingheight >= sec->ceilingheight) // Move up
 				{
 					ceiling->direction = 1;
@@ -483,21 +263,13 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 				}
 
 				// chained linedef executing ability
-				if (line->flags & ML_BLOCKMONSTERS)
-				{
-					// only set it on ONE of the moving sectors (the smallest numbered)
-					// and front side x offset must be positive
-					if (firstone && sides[line->sidenum[0]].textureoffset > 0)
-						ceiling->texture = (sides[line->sidenum[0]].textureoffset>>FRACBITS) - 32769;
-					else
-						ceiling->texture = -1;
-				}
+				// only set it on ONE of the moving sectors (the smallest numbered)
+				// only set it if there isn't also a floor mover
+				if (line->args[3] && line->args[1] == 1)
+					ceiling->tag = firstone ? (INT16)line->args[3] : 0;
 
 				// flat changing ability
-				else if (line->flags & ML_NOCLIMB)
-					ceiling->texture = line->frontsector->ceilingpic;
-				else
-					ceiling->texture = -1;
+				ceiling->texture = line->args[4] ? line->frontsector->ceilingpic : -1;
 				break;
 
 			// More linedef executor junk
@@ -514,67 +286,30 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 					ceiling->direction = -1;
 					ceiling->bottomheight = line->frontsector->ceilingheight;
 				}
-				if (line->flags & ML_NOCLIMB)
-					ceiling->texture = -1;
-				else
-					ceiling->texture = line->frontsector->ceilingpic;
+
+				// If flag is set, change ceiling texture after moving
+				ceiling->texture = line->args[2] ? line->frontsector->ceilingpic : -1;
 				break;
 
-			case moveCeilingByFrontTexture:
-				if (line->flags & ML_NOCLIMB)
+			case moveCeilingByDistance:
+				if (line->args[4])
 					ceiling->speed = INT32_MAX/2; // as above, "instant" is one tic
 				else
-					ceiling->speed = FixedDiv(sides[line->sidenum[0]].textureoffset,8*FRACUNIT); // texture x offset
-				if (sides[line->sidenum[0]].rowoffset > 0)
+					ceiling->speed = line->args[3] << (FRACBITS - 3);
+				if (line->args[2] > 0)
 				{
 					ceiling->direction = 1; // up
-					ceiling->topheight = sec->ceilingheight + sides[line->sidenum[0]].rowoffset; // texture y offset
+					ceiling->topheight = sec->ceilingheight + (line->args[2] << FRACBITS);
 				}
 				else {
 					ceiling->direction = -1; // down
-					ceiling->bottomheight = sec->ceilingheight + sides[line->sidenum[0]].rowoffset; // texture y offset
+					ceiling->bottomheight = sec->ceilingheight + (line->args[2] << FRACBITS);
 				}
 				break;
 
-/*
-			case lowerCeilingByLine:
-				ceiling->speed = FixedDiv(abs(line->dx),8*FRACUNIT);
-				ceiling->direction = -1; // Move down
-				ceiling->bottomheight = sec->ceilingheight - abs(line->dy);
-				break;
-
-			case raiseCeilingByLine:
-				ceiling->speed = FixedDiv(abs(line->dx),8*FRACUNIT);
-				ceiling->direction = 1; // Move up
-				ceiling->topheight = sec->ceilingheight + abs(line->dy);
-				break;
-*/
-
 			case bounceCeiling:
-				ceiling->speed = P_AproxDistance(line->dx, line->dy); // same speed as elevateContinuous
-				ceiling->speed = FixedDiv(ceiling->speed,4*FRACUNIT);
-				ceiling->origspeed = ceiling->speed;
-				if (line->frontsector->ceilingheight >= sec->ceilingheight) // Move up
-				{
-					ceiling->direction = 1;
-					ceiling->topheight = line->frontsector->ceilingheight;
-				}
-				else // Move down
-				{
-					ceiling->direction = -1;
-					ceiling->bottomheight = line->frontsector->ceilingheight;
-				}
-
-				// Any delay?
-				ceiling->delay = sides[line->sidenum[0]].textureoffset >> FRACBITS;
-				ceiling->delaytimer = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Initial delay
-
-				ceiling->texture = (fixed_t)(line - lines); // hack: use texture to store sourceline number
-				break;
-
 			case bounceCeilingCrush:
-				ceiling->speed = abs(line->dx); // same speed as elevateContinuous
-				ceiling->speed = FixedDiv(ceiling->speed,4*FRACUNIT);
+				ceiling->speed = line->args[2] << (FRACBITS - 2); // same speed as elevateContinuous
 				ceiling->origspeed = ceiling->speed;
 				if (line->frontsector->ceilingheight >= sec->ceilingheight) // Move up
 				{
@@ -588,10 +323,8 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 				}
 
 				// Any delay?
-				ceiling->delay = sides[line->sidenum[0]].textureoffset >> FRACBITS;
-				ceiling->delaytimer = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Initial delay
-
-				ceiling->texture = (fixed_t)(line - lines); // hack: use texture to store sourceline number
+				ceiling->delay = line->args[5];
+				ceiling->delaytimer = line->args[4]; // Initial delay
 				break;
 
 			default:
@@ -599,7 +332,6 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 
 		}
 
-		ceiling->tag = tag;
 		ceiling->type = type;
 		firstone = 0;
 	}
@@ -608,19 +340,19 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 
 /** Starts a ceiling crusher.
   *
+  * \param tag Tag.
   * \param line The source line.
   * \param type The type of ceiling, either ::crushAndRaise or
   *             ::fastCrushAndRaise.
   * \return 1 if at least one crusher was started, 0 otherwise.
   * \sa EV_DoCeiling, EV_DoFloor, EV_DoElevator, T_CrushCeiling
   */
-INT32 EV_DoCrush(line_t *line, ceiling_e type)
+INT32 EV_DoCrush(mtag_t tag, line_t *line, ceiling_e type)
 {
 	INT32 rtn = 0;
 	INT32 secnum = -1;
 	sector_t *sec;
 	ceiling_t *ceiling;
-	mtag_t tag = Tag_FGet(&line->tags);
 
 	TAG_ITER_SECTORS(tag, secnum)
 	{
@@ -638,46 +370,33 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 		ceiling->sector = sec;
 		ceiling->crush = true;
 		ceiling->sourceline = (INT32)(line-lines);
-
-		if (line->flags & ML_EFFECT4)
-			ceiling->oldspeed = FixedDiv(abs(line->dx),4*FRACUNIT);
-		else
-			ceiling->oldspeed = (R_PointToDist2(line->v2->x, line->v2->y, line->v1->x, line->v1->y)/16);
+		ceiling->speed = ceiling->origspeed = line->args[2] << (FRACBITS - 2);
 
 		switch(type)
 		{
-			case fastCrushAndRaise: // Up and then down
+			case raiseAndCrush: // Up and then down
 				ceiling->topheight = P_FindHighestCeilingSurrounding(sec);
 				ceiling->direction = 1;
-				ceiling->speed = ceiling->oldspeed;
+				// Retain stupid behavior for backwards compatibility
+				if (!udmf && !(line->flags & ML_MIDSOLID))
+					ceiling->speed /= 2;
+				else
+					ceiling->speed = line->args[3] << (FRACBITS - 2);
 				ceiling->bottomheight = sec->floorheight + FRACUNIT;
 				break;
 			case crushBothOnce:
 				ceiling->topheight = sec->ceilingheight;
 				ceiling->bottomheight = sec->floorheight + (sec->ceilingheight-sec->floorheight)/2;
 				ceiling->direction = -1;
-
-				if (line->flags & ML_EFFECT4)
-					ceiling->speed = ceiling->oldspeed;
-				else
-					ceiling->speed = ceiling->oldspeed*2;
-
 				break;
 			case crushCeilOnce:
 			default: // Down and then up.
 				ceiling->topheight = sec->ceilingheight;
 				ceiling->direction = -1;
-
-				if (line->flags & ML_EFFECT4)
-					ceiling->speed = ceiling->oldspeed;
-				else
-					ceiling->speed = ceiling->oldspeed*2;
-
 				ceiling->bottomheight = sec->floorheight + FRACUNIT;
 				break;
 		}
 
-		ceiling->tag = tag;
 		ceiling->type = type;
 	}
 	return rtn;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 87aa5ff2f80e7f61fd092ab2fa4f1dedac9d3370..cd381e712c8ec32e95e6d5f18f41a8004e8dcbf2 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -12,6 +12,7 @@
 /// \brief Enemy thinking, AI
 ///        Action Pointer Functions that are associated with states/frames
 
+#include "dehacked.h"
 #include "doomdef.h"
 #include "g_game.h"
 #include "p_local.h"
@@ -174,6 +175,7 @@ void A_Boss3TakeDamage(mobj_t *actor);
 void A_Boss3Path(mobj_t *actor);
 void A_Boss3ShockThink(mobj_t *actor);
 void A_LinedefExecute(mobj_t *actor);
+void A_LinedefExecuteFromArg(mobj_t *actor);
 void A_PlaySeeSound(mobj_t *actor);
 void A_PlayAttackSound(mobj_t *actor);
 void A_PlayActiveSound(mobj_t *actor);
@@ -3709,8 +3711,8 @@ void A_MonitorPop(mobj_t *actor)
 
 	// Run a linedef executor immediately upon popping
 	// You may want to delay your effects by 18 tics to sync with the reward giving
-	if (actor->spawnpoint && actor->lastlook)
-		P_LinedefExecute(actor->lastlook, actor->target, NULL);
+	if (actor->spawnpoint && actor->spawnpoint->args[0])
+		P_LinedefExecute(actor->spawnpoint->args[0], actor->target, NULL);
 }
 
 // Function: A_GoldMonitorPop
@@ -3795,8 +3797,8 @@ void A_GoldMonitorPop(mobj_t *actor)
 
 	// Run a linedef executor immediately upon popping
 	// You may want to delay your effects by 18 tics to sync with the reward giving
-	if (actor->spawnpoint && actor->lastlook)
-		P_LinedefExecute(actor->lastlook, actor->target, NULL);
+	if (actor->spawnpoint && actor->spawnpoint->args[0])
+		P_LinedefExecute(actor->spawnpoint->args[0], actor->target, NULL);
 }
 
 // Function: A_GoldMonitorRestore
@@ -3854,58 +3856,61 @@ void A_Explode(mobj_t *actor)
 	P_RadiusAttack(actor, actor->target, actor->info->damage, locvar1, true);
 }
 
-// Function: A_BossDeath
-//
-// Description: Possibly trigger special effects when boss dies.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_BossDeath(mobj_t *mo)
+static mobj_t *P_FindBossFlyPoint(mobj_t *mo, INT32 tag)
 {
-	thinker_t *th;
-	mobj_t *mo2;
-	line_t junk;
 	INT32 i;
+	mobj_t *closest = NULL;
 
-	if (LUA_CallAction(A_BOSSDEATH, mo))
-		return;
+	TAG_ITER_THINGS(tag, i)
+	{
+		mobj_t *mo2 = mapthings[i].mobj;
 
-	if (mo->spawnpoint && mo->spawnpoint->extrainfo)
-		P_LinedefExecute(LE_BOSSDEAD+(mo->spawnpoint->extrainfo*LE_PARAMWIDTH), mo, NULL);
-	else
-		P_LinedefExecute(LE_BOSSDEAD, mo, NULL);
-	mo->health = 0;
+		if (!mo2)
+			continue;
 
-	// Boss is dead (but not necessarily fleeing...)
-	// Lua may use this to ignore bosses after they start fleeing
-	mo->flags2 |= MF2_BOSSDEAD;
+		if (mo2->type != MT_BOSSFLYPOINT)
+			continue;
 
-	// make sure there is a player alive for victory
-	for (i = 0; i < MAXPLAYERS; i++)
-		if (playeringame[i] && ((players[i].mo && players[i].mo->health)
-			|| ((netgame || multiplayer) && (players[i].lives || players[i].continues))))
-			break;
+		// If this one's further than the last one, don't go for it.
+		if (closest &&
+			P_AproxDistance(P_AproxDistance(mo->x - mo2->x, mo->y - mo2->y), mo->z - mo2->z) >
+			P_AproxDistance(P_AproxDistance(mo->x - closest->x, mo->y - closest->y), mo->z - closest->z))
+			continue;
 
-	if (i == MAXPLAYERS)
-		return; // no one left alive, so do not end game
+		closest = mo2;
+	}
 
-	// scan the remaining thinkers to see
-	// if all bosses are dead
+	return closest;
+}
+
+static void P_DoBossVictory(mobj_t *mo)
+{
+	thinker_t *th;
+	mobj_t *mo2;
+	INT32 i;
+
+	// scan the remaining thinkers to see if all bosses are dead
 	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 != mo && (mo2->flags & MF_BOSS) && mo2->health > 0)
-			goto bossjustdie; // other boss not dead - just go straight to dying!
+
+		if (mo2 == mo)
+			continue;
+
+		if ((mo2->flags & MF_BOSS) && mo2->health > 0)
+			return;
 	}
 
 	// victory!
-	P_LinedefExecute(LE_ALLBOSSESDEAD, mo, NULL);
+	if (mo->spawnpoint)
+		P_LinedefExecute(mo->spawnpoint->args[3], mo, NULL);
+
 	if (stoppedclock && modeattacking) // if you're just time attacking, skip making the capsule appear since you don't need to step on it anyways.
-		goto bossjustdie;
+		return;
+
 	if (mo->flags2 & MF2_BOSSNOTRAP)
 	{
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -3917,18 +3922,14 @@ void A_BossDeath(mobj_t *mo)
 	}
 	else
 	{
-		// Initialize my junk
-		junk.tags.tags = NULL;
-		junk.tags.count = 0;
-
-		// Bring the egg trap up to the surface
-		// Incredibly shitty code ahead
-		Tag_FSet(&junk.tags, LE_CAPSULE0);
-		EV_DoElevator(&junk, elevateHighest, false);
-		Tag_FSet(&junk.tags, LE_CAPSULE1);
-		EV_DoElevator(&junk, elevateUp, false);
-		Tag_FSet(&junk.tags, LE_CAPSULE2);
-		EV_DoElevator(&junk, elevateHighest, false);
+		if (!udmf)
+		{
+			// Bring the egg trap up to the surface
+			// Incredibly shitty code ahead
+			EV_DoElevator(LE_CAPSULE0, NULL, elevateHighest);
+			EV_DoElevator(LE_CAPSULE1, NULL, elevateUp);
+			EV_DoElevator(LE_CAPSULE2, NULL, elevateHighest);
+		}
 
 		if (mapheaderinfo[gamemap-1]->muspostbossname[0] &&
 			S_MusicExists(mapheaderinfo[gamemap-1]->muspostbossname, !midi_disabled, !digital_disabled))
@@ -3952,8 +3953,196 @@ void A_BossDeath(mobj_t *mo)
 					mapheaderinfo[gamemap-1]->muspostbossfadein);
 		}
 	}
+}
+
+static void P_SpawnBoss1Junk(mobj_t *mo)
+{
+	mobj_t *mo2 = P_SpawnMobjFromMobj(mo,
+	P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+	P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+	32<<FRACBITS, MT_BOSSJUNK);
+	mo2->angle = mo->angle;
+	P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
+	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+	P_SetMobjState(mo2, S_BOSSEGLZ1);
+
+	mo2 = P_SpawnMobjFromMobj(mo,
+		P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
+		P_ReturnThrustY(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
+		32<<FRACBITS, MT_BOSSJUNK);
+	mo2->angle = mo->angle;
+	P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
+	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+	P_SetMobjState(mo2, S_BOSSEGLZ2);
+}
+
+static void P_SpawnBoss2Junk(mobj_t *mo)
+{
+	mobj_t *mo2 = P_SpawnMobjFromMobj(mo,
+	P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+	P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+	32<<FRACBITS, MT_BOSSJUNK);
+	mo2->angle = mo->angle;
+	P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
+	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+	P_SetMobjState(mo2, S_BOSSTANK1);
+
+	mo2 = P_SpawnMobjFromMobj(mo,
+		P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
+		P_ReturnThrustY(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
+		32<<FRACBITS, MT_BOSSJUNK);
+	mo2->angle = mo->angle;
+	P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
+	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+	P_SetMobjState(mo2, S_BOSSTANK2);
+
+	mo2 = P_SpawnMobjFromMobj(mo, 0, 0,
+		mobjinfo[MT_EGGMOBILE2].height + (32<<FRACBITS),
+		MT_BOSSJUNK);
+	mo2->angle = mo->angle;
+	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+	mo2->momz += mo->momz;
+	P_SetMobjState(mo2, S_BOSSSPIGOT);
+}
+
+static void P_SpawnBoss3Junk(mobj_t *mo)
+{
+	mobj_t *mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_BOSSJUNK);
+	mo2->angle = mo->angle;
+	P_SetMobjState(mo2, S_BOSSSEBH1);
+}
+
+static void P_DoCybrakdemonDeath(mobj_t *mo)
+{
+	mo->flags |= MF_NOCLIP;
+	mo->flags &= ~(MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT);
+
+	S_StartSound(NULL, sfx_bedie2);
+	P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_CYBRAKDEMON_VILE_EXPLOSION);
+	mo->z += P_MobjFlip(mo);
+	P_SetObjectMomZ(mo, 12*FRACUNIT, false);
+	S_StartSound(mo, sfx_bgxpld);
+	if (mo->spawnpoint && !(mo->spawnpoint->args[6] & TMB_NODEATHFLING))
+		P_InstaThrust(mo, R_PointToAngle2(0, 0, mo->x, mo->y), 14*FRACUNIT);
+}
+
+static void P_DoBoss5Death(mobj_t *mo)
+{
+	if (mo->flags2 & MF2_SLIDEPUSH)
+	{
+		P_RemoveMobj(mo);
+		return;
+	}
+	if (mo->tracer)
+	{
+		var1 = var2 = 0;
+		A_Boss5Jump(mo);
+		mo->momx = ((16 - 1)*mo->momx)/16;
+		mo->momy = ((16 - 1)*mo->momy)/16;
+		{
+			const fixed_t time = FixedHypot(mo->tracer->x - mo->x, mo->tracer->y - mo->y)/FixedHypot(mo->momx, mo->momy);
+			const fixed_t speed = 64*FRACUNIT;
+			mobj_t *pole = P_SpawnMobj(
+				mo->tracer->x - P_ReturnThrustX(mo->tracer, mo->tracer->angle, speed*time),
+				mo->tracer->y - P_ReturnThrustY(mo->tracer, mo->tracer->angle, speed*time),
+				mo->tracer->floorz + (256+1)*FRACUNIT,
+				MT_FSGNB);
+			P_SetTarget(&pole->tracer, P_SpawnMobj(
+				pole->x, pole->y,
+				pole->z - 256*FRACUNIT,
+				MT_FSGNB));
+			P_SetTarget(&pole->tracer->tracer, P_SpawnMobj(
+				pole->x + P_ReturnThrustX(pole, mo->tracer->angle, FRACUNIT),
+				pole->y + P_ReturnThrustY(pole, mo->tracer->angle, FRACUNIT),
+				pole->z + 256*FRACUNIT,
+				MT_FSGNA));
+			pole->tracer->flags |= MF_NOCLIPTHING;
+			P_SetScale(pole, (pole->destscale = 2*FRACUNIT));
+			P_SetScale(pole->tracer, (pole->tracer->destscale = 2*FRACUNIT));
+			pole->angle = pole->tracer->angle = mo->tracer->angle;
+			pole->tracer->tracer->angle = pole->angle - ANGLE_90;
+			pole->momx = P_ReturnThrustX(pole, pole->angle, speed);
+			pole->momy = P_ReturnThrustY(pole, pole->angle, speed);
+			pole->tracer->momx = pole->momx;
+			pole->tracer->momy = pole->momy;
+			pole->tracer->tracer->momx = pole->momx;
+			pole->tracer->tracer->momy = pole->momy;
+		}
+	}
+	else
+	{
+		P_SetObjectMomZ(mo, 10*FRACUNIT, false);
+		mo->flags |= MF_NOGRAVITY;
+	}
+	mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
+}
+
+static void P_DoBossDefaultDeath(mobj_t *mo)
+{
+	INT32 bossid = (mo->spawnpoint ? mo->spawnpoint->args[0] : 0);
+
+	// Stop exploding and prepare to run.
+	P_SetMobjState(mo, mo->info->xdeathstate);
+	if (P_MobjWasRemoved(mo))
+		return;
+
+	P_SetTarget(&mo->target, P_FindBossFlyPoint(mo, bossid));
+
+	mo->flags |= MF_NOGRAVITY|MF_NOCLIP;
+	mo->flags |= MF_NOCLIPHEIGHT;
+
+	mo->movedir = 0;
+	mo->extravalue1 = 35;
+	mo->flags2 |= MF2_BOSSFLEE;
+	mo->momz = P_MobjFlip(mo)*2*mo->scale;
+
+	if (mo->target)
+	{
+		angle_t diff = R_PointToAngle2(mo->x, mo->y, mo->target->x, mo->target->y) - mo->angle;
+		if (diff)
+		{
+			if (diff > ANGLE_180)
+				diff = InvAngle(InvAngle(diff)/mo->extravalue1);
+			else
+				diff /= mo->extravalue1;
+			mo->movedir = diff;
+		}
+	}
+}
+
+// Function: A_BossDeath
+//
+// Description: Possibly trigger special effects when boss dies.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_BossDeath(mobj_t *mo)
+{
+	INT32 i;
+
+	if (LUA_CallAction(A_BOSSDEATH, mo))
+		return;
+
+	if (mo->spawnpoint)
+		P_LinedefExecute(mo->spawnpoint->args[2], mo, NULL);
+	mo->health = 0;
+
+	// Boss is dead (but not necessarily fleeing...)
+	// Lua may use this to ignore bosses after they start fleeing
+	mo->flags2 |= MF2_BOSSDEAD;
+
+	// make sure there is a player alive for victory
+	for (i = 0; i < MAXPLAYERS; i++)
+		if (playeringame[i] && ((players[i].mo && players[i].mo->health)
+			|| ((netgame || multiplayer) && (players[i].lives || players[i].continues))))
+			break;
+
+	if (i == MAXPLAYERS)
+		return; // no one left alive, so do not end game
+
+	P_DoBossVictory(mo);
 
-bossjustdie:
 	if (LUA_HookMobj(mo, MOBJ_HOOK(BossDeath)))
 		return;
 	else if (P_MobjWasRemoved(mo))
@@ -3965,61 +4154,13 @@ bossjustdie:
 		default:
 			break;
 		case MT_EGGMOBILE: // twin laser pods
-			{
-				mo2 = P_SpawnMobjFromMobj(mo,
-					P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
-					P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
-					32<<FRACBITS, MT_BOSSJUNK);
-				mo2->angle = mo->angle;
-				P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
-				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-				P_SetMobjState(mo2, S_BOSSEGLZ1);
-
-				mo2 = P_SpawnMobjFromMobj(mo,
-					P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
-					P_ReturnThrustY(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
-					32<<FRACBITS, MT_BOSSJUNK);
-				mo2->angle = mo->angle;
-				P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
-				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-				P_SetMobjState(mo2, S_BOSSEGLZ2);
-			}
+			P_SpawnBoss1Junk(mo);
 			break;
 		case MT_EGGMOBILE2: // twin tanks + spigot
-			{
-				mo2 = P_SpawnMobjFromMobj(mo,
-					P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
-					P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
-					32<<FRACBITS, MT_BOSSJUNK);
-				mo2->angle = mo->angle;
-				P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
-				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-				P_SetMobjState(mo2, S_BOSSTANK1);
-
-				mo2 = P_SpawnMobjFromMobj(mo,
-					P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
-					P_ReturnThrustY(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
-					32<<FRACBITS, MT_BOSSJUNK);
-				mo2->angle = mo->angle;
-				P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
-				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-				P_SetMobjState(mo2, S_BOSSTANK2);
-
-				mo2 = P_SpawnMobjFromMobj(mo, 0, 0,
-					mobjinfo[MT_EGGMOBILE2].height + (32<<FRACBITS),
-					MT_BOSSJUNK);
-				mo2->angle = mo->angle;
-				P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-				mo2->momz += mo->momz;
-				P_SetMobjState(mo2, S_BOSSSPIGOT);
-			}
+			P_SpawnBoss2Junk(mo);
 			break;
 		case MT_EGGMOBILE3:
-			{
-				mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_BOSSJUNK);
-				mo2->angle = mo->angle;
-				P_SetMobjState(mo2, S_BOSSSEBH1);
-			}
+			P_SpawnBoss3Junk(mo);
 			break;
 	}
 
@@ -4027,147 +4168,20 @@ bossjustdie:
 	switch (mo->type)
 	{
 		case MT_BLACKEGGMAN:
-		{
 			mo->flags |= MF_NOCLIP;
 			mo->flags &= ~MF_SPECIAL;
 
 			S_StartSound(NULL, sfx_befall);
 			break;
-		}
 		case MT_CYBRAKDEMON:
-		{
-			mo->flags |= MF_NOCLIP;
-			mo->flags &= ~(MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIPHEIGHT);
-
-			S_StartSound(NULL, sfx_bedie2);
-			P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_CYBRAKDEMON_VILE_EXPLOSION);
-			mo->z += P_MobjFlip(mo);
-			P_SetObjectMomZ(mo, 12*FRACUNIT, false);
-			S_StartSound(mo, sfx_bgxpld);
-			if (mo->spawnpoint && !(mo->spawnpoint->options & MTF_EXTRA))
-				P_InstaThrust(mo, R_PointToAngle2(0, 0, mo->x, mo->y), 14*FRACUNIT);
+			P_DoCybrakdemonDeath(mo);
 			break;
-		}
-		case MT_KOOPA:
-		{
-			// Initialize my junk
-			junk.tags.tags = NULL;
-			junk.tags.count = 0;
-
-			Tag_FSet(&junk.tags, LE_KOOPA);
-			EV_DoCeiling(&junk, raiseToHighest);
-			return;
-		}
 		case MT_FANG:
-		{
-			if (mo->flags2 & MF2_SLIDEPUSH)
-			{
-				P_RemoveMobj(mo);
-				return;
-			}
-			if (mo->tracer)
-			{
-				var1 = var2 = 0;
-				A_Boss5Jump(mo);
-				mo->momx = ((16 - 1)*mo->momx)/16;
-				mo->momy = ((16 - 1)*mo->momy)/16;
-				{
-					const fixed_t time = FixedHypot(mo->tracer->x - mo->x, mo->tracer->y - mo->y)/FixedHypot(mo->momx, mo->momy);
-					const fixed_t speed = 64*FRACUNIT;
-					mobj_t *pole = P_SpawnMobj(
-						mo->tracer->x - P_ReturnThrustX(mo->tracer, mo->tracer->angle, speed*time),
-						mo->tracer->y - P_ReturnThrustY(mo->tracer, mo->tracer->angle, speed*time),
-						mo->tracer->floorz + (256+1)*FRACUNIT,
-						MT_FSGNB);
-					P_SetTarget(&pole->tracer, P_SpawnMobj(
-						pole->x, pole->y,
-						pole->z - 256*FRACUNIT,
-						MT_FSGNB));
-					P_SetTarget(&pole->tracer->tracer, P_SpawnMobj(
-						pole->x + P_ReturnThrustX(pole, mo->tracer->angle, FRACUNIT),
-						pole->y + P_ReturnThrustY(pole, mo->tracer->angle, FRACUNIT),
-						pole->z + 256*FRACUNIT,
-						MT_FSGNA));
-					pole->tracer->flags |= MF_NOCLIPTHING;
-					P_SetScale(pole, (pole->destscale = 2*FRACUNIT));
-					P_SetScale(pole->tracer, (pole->tracer->destscale = 2*FRACUNIT));
-					pole->angle = pole->tracer->angle = mo->tracer->angle;
-					pole->tracer->tracer->angle = pole->angle - ANGLE_90;
-					pole->momx = P_ReturnThrustX(pole, pole->angle, speed);
-					pole->momy = P_ReturnThrustY(pole, pole->angle, speed);
-					pole->tracer->momx = pole->momx;
-					pole->tracer->momy = pole->momy;
-					pole->tracer->tracer->momx = pole->momx;
-					pole->tracer->tracer->momy = pole->momy;
-				}
-			}
-			else
-			{
-				P_SetObjectMomZ(mo, 10*FRACUNIT, false);
-				mo->flags |= MF_NOGRAVITY;
-			}
-			mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
-			return;
-		}
+			P_DoBoss5Death(mo);
+			break;
 		default: //eggmobiles
-		{
-			UINT8 extrainfo = (mo->spawnpoint ? mo->spawnpoint->extrainfo : 0);
-
-			// Stop exploding and prepare to run.
-			P_SetMobjState(mo, mo->info->xdeathstate);
-			if (P_MobjWasRemoved(mo))
-				return;
-
-			P_SetTarget(&mo->target, NULL);
-
-			// Flee! Flee! Find a point to escape to! If none, just shoot upward!
-			// scan the thinkers to find the runaway point
-			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
-			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-					continue;
-
-				mo2 = (mobj_t *)th;
-
-				if (mo2->type != MT_BOSSFLYPOINT)
-					continue;
-
-				if (mo2->spawnpoint && mo2->spawnpoint->extrainfo != extrainfo)
-					continue;
-
-				// If this one's further then the last one, don't go for it.
-				if (mo->target &&
-					P_AproxDistance(P_AproxDistance(mo->x - mo2->x, mo->y - mo2->y), mo->z - mo2->z) >
-					P_AproxDistance(P_AproxDistance(mo->x - mo->target->x, mo->y - mo->target->y), mo->z - mo->target->z))
-						continue;
-
-				// Otherwise... Do!
-				P_SetTarget(&mo->target, mo2);
-			}
-
-			mo->flags |= MF_NOGRAVITY|MF_NOCLIP;
-			mo->flags |= MF_NOCLIPHEIGHT;
-
-			mo->movedir = 0;
-			mo->extravalue1 = 35;
-			mo->flags2 |= MF2_BOSSFLEE;
-			mo->momz = P_MobjFlip(mo)*2*mo->scale;
-
-			if (mo->target)
-			{
-				angle_t diff = R_PointToAngle2(mo->x, mo->y, mo->target->x, mo->target->y) - mo->angle;
-				if (diff)
-				{
-					if (diff > ANGLE_180)
-						diff = InvAngle(InvAngle(diff)/mo->extravalue1);
-					else
-						diff /= mo->extravalue1;
-					mo->movedir = diff;
-				}
-			}
-
+			P_DoBossDefaultDeath(mo);
 			break;
-		}
 	}
 }
 
@@ -4836,12 +4850,16 @@ void A_FishJump(mobj_t *actor)
 		fixed_t jumpval;
 
 		if (locvar1)
-			jumpval = var1;
+			jumpval = locvar1;
 		else
-			jumpval = FixedMul(AngleFixed(actor->angle)/4, actor->scale);
+		{
+			if (actor->spawnpoint && actor->spawnpoint->args[0])
+				jumpval = actor->spawnpoint->args[0];
+			else
+				jumpval = 44;
+		}
 
-		if (!jumpval) jumpval = FixedMul(44*(FRACUNIT/4), actor->scale);
-		actor->momz = jumpval;
+		actor->momz = FixedMul(jumpval << (FRACBITS - 2), actor->scale);
 		P_SetMobjStateNF(actor, actor->info->seestate);
 	}
 
@@ -6218,48 +6236,34 @@ void A_RockSpawn(mobj_t *actor)
 {
 	mobj_t *mo;
 	mobjtype_t type;
-	INT32 i = Tag_FindLineSpecial(12, (INT16)actor->threshold);
-	line_t *line;
 	fixed_t dist;
-	fixed_t randomoomph;
 
 	if (LUA_CallAction(A_ROCKSPAWN, actor))
 		return;
 
-	if (i == -1)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "A_RockSpawn: Unable to find parameter line 12 (tag %d)!\n", actor->threshold);
+	if (!actor->spawnpoint)
 		return;
-	}
 
-	line = &lines[i];
+	type = actor->spawnpoint->stringargs[0] ? get_number(actor->spawnpoint->stringargs[0]) : MT_ROCKCRUMBLE1;
 
-	if (!(sides[line->sidenum[0]].textureoffset >> FRACBITS))
+	if (type < MT_NULL || type >= NUMMOBJTYPES)
 	{
-		CONS_Debug(DBG_GAMELOGIC, "A_RockSpawn: No X-offset detected! (tag %d)!\n", actor->threshold);
+		CONS_Debug(DBG_GAMELOGIC, "A_RockSpawn: Invalid mobj type %s!\n", actor->spawnpoint->stringargs[0]);
 		return;
 	}
 
-	dist = P_AproxDistance(line->dx, line->dy)/16;
-
-	if (dist < 1)
-		dist = 1;
-
-	type = MT_ROCKCRUMBLE1 + (sides[line->sidenum[0]].rowoffset >> FRACBITS);
-
-	if (line->flags & ML_NOCLIMB)
-		randomoomph = P_RandomByte() * (FRACUNIT/32);
-	else
-		randomoomph = 0;
+	dist = max(actor->spawnpoint->args[0] << (FRACBITS - 4), 1);
+	if (actor->spawnpoint->args[2])
+		dist += actor->spawnpoint->args[2] ? P_RandomByte() * (FRACUNIT/32) : 0; // random oomph
 
 	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_FALLINGROCK);
 	P_SetMobjState(mo, mobjinfo[type].spawnstate);
-	mo->angle = R_PointToAngle2(line->v2->x, line->v2->y, line->v1->x, line->v1->y);
+	mo->angle = FixedAngle(actor->spawnpoint->angle << FRACBITS);
 
-	P_InstaThrust(mo, mo->angle, dist + randomoomph);
-	mo->momz = dist + randomoomph;
+	P_InstaThrust(mo, mo->angle, dist);
+	mo->momz = dist;
 
-	var1 = sides[line->sidenum[0]].textureoffset >> FRACBITS;
+	var1 = actor->spawnpoint->args[1];
 	A_SetTics(actor);
 }
 
@@ -7093,10 +7097,8 @@ void A_Boss1Chase(mobj_t *actor)
 		}
 		else
 		{
-			if (actor->spawnpoint && actor->spawnpoint->extrainfo)
-				P_LinedefExecute(LE_PINCHPHASE+(actor->spawnpoint->extrainfo*LE_PARAMWIDTH), actor, NULL);
-			else
-				P_LinedefExecute(LE_PINCHPHASE, actor, NULL);
+			if (actor->spawnpoint)
+				P_LinedefExecute(actor->spawnpoint->args[4], actor, NULL);
 			P_SetMobjState(actor, actor->info->raisestate);
 		}
 
@@ -7220,7 +7222,7 @@ void A_Boss2Chase(mobj_t *actor)
 	}
 	else
 	{
-		// Only speed up if you have the 'Deaf' flag.
+		// Only speed up if you have the ambush flag.
 		if (actor->flags2 & MF2_AMBUSH)
 			speedvar = actor->health;
 		else
@@ -7933,12 +7935,21 @@ void A_GuardChase(mobj_t *actor)
 			false)
 		&& speed > 0) // can't be the same check as previous so that P_TryMove gets to happen.
 		{
-			if (actor->spawnpoint && ((actor->spawnpoint->options & (MTF_EXTRA|MTF_OBJECTSPECIAL)) == MTF_OBJECTSPECIAL))
-				actor->angle += ANGLE_90;
-			else if (actor->spawnpoint && ((actor->spawnpoint->options & (MTF_EXTRA|MTF_OBJECTSPECIAL)) == MTF_EXTRA))
-				actor->angle -= ANGLE_90;
-			else
-				actor->angle += ANGLE_180;
+			INT32 direction = actor->spawnpoint ? actor->spawnpoint->args[0] : TMGD_BACK;
+
+			switch (direction)
+			{
+				case TMGD_BACK:
+				default:
+					actor->angle += ANGLE_180;
+					break;
+				case TMGD_RIGHT:
+					actor->angle -= ANGLE_90;
+					break;
+				case TMGD_LEFT:
+					actor->angle += ANGLE_90;
+					break;
+			}
 		}
 
 		if (actor->extravalue1 < actor->info->speed)
@@ -8138,10 +8149,6 @@ void A_Boss3TakeDamage(mobj_t *actor)
 
 	actor->movecount = var1;
 	actor->movefactor = -512*FRACUNIT;
-
-	/*if (actor->target && actor->target->spawnpoint)
-		actor->threshold = actor->target->spawnpoint->extrainfo;*/
-
 }
 
 // Function: A_Boss3Path
@@ -8181,27 +8188,21 @@ void A_Boss3Path(mobj_t *actor)
 
 		if (!(actor->flags2 & MF2_STRONGBOX))
 		{
-			thinker_t *th;
 			mobj_t *mo2;
+			INT32 i;
 
 			P_SetTarget(&actor->target, NULL);
 
-			// scan the thinkers
-			// to find a point that matches
-			// the number
-			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+			// Find waypoint
+			TAG_ITER_THINGS(actor->cusval, i)
 			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-					continue;
+				mo2 = mapthings[i].mobj;
 
-				mo2 = (mobj_t *)th;
-				if (mo2->type != MT_BOSS3WAYPOINT)
-					continue;
-				if (!mo2->spawnpoint)
+				if (!mo2)
 					continue;
-				if (mo2->spawnpoint->angle != actor->threshold)
+				if (mo2->type != MT_BOSS3WAYPOINT)
 					continue;
-				if (mo2->spawnpoint->extrainfo != actor->cusval)
+				if (mapthings[i].args[0] != actor->threshold)
 					continue;
 
 				P_SetTarget(&actor->target, mo2);
@@ -8347,8 +8348,6 @@ void A_LinedefExecute(mobj_t *actor)
 
 	if (locvar2)
 		tagnum += locvar2*(AngleFixed(actor->angle)>>FRACBITS);
-	else if (actor->spawnpoint && actor->spawnpoint->extrainfo)
-		tagnum += (actor->spawnpoint->extrainfo*LE_PARAMWIDTH);
 
 	CONS_Debug(DBG_GAMELOGIC, "A_LinedefExecute: Running mobjtype %d's sector with tag %d\n", actor->type, tagnum);
 
@@ -8356,6 +8355,38 @@ void A_LinedefExecute(mobj_t *actor)
 	P_LinedefExecute((INT16)tagnum, actor, actor->subsector->sector);
 }
 
+// Function: A_LinedefExecuteFromArg
+//
+// Description: Object's location is used to set the calling sector. The tag used is the spawnpoint mapthing's args[var1].
+//
+// var1 = mapthing arg to take tag from
+// var2 = unused
+//
+void A_LinedefExecuteFromArg(mobj_t *actor)
+{
+	INT32 tagnum;
+	INT32 locvar1 = var1;
+
+	if (LUA_CallAction(A_LINEDEFEXECUTEFROMARG, actor))
+		return;
+
+	if (!actor->spawnpoint)
+		return;
+
+	if (locvar1 < 0 || locvar1 > NUMMAPTHINGARGS)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "A_LinedefExecuteFromArg: Invalid mapthing arg %d\n", locvar1);
+		return;
+	}
+
+	tagnum = actor->spawnpoint->args[locvar1];
+
+	CONS_Debug(DBG_GAMELOGIC, "A_LinedefExecuteFromArg: Running mobjtype %d's sector with tag %d\n", actor->type, tagnum);
+
+	// tag 32768 displayed in map editors is actually tag -32768, tag 32769 is -32767, 65535 is -1 etc.
+	P_LinedefExecute((INT16)tagnum, actor, actor->subsector->sector);
+}
+
 // Function: A_PlaySeeSound
 //
 // Description: Plays the object's seesound.
@@ -11496,10 +11527,7 @@ void A_BrakLobShot(mobj_t *actor)
 		return; // Don't even bother if we've got nothing to aim at.
 
 	// Look up actor's current gravity situation
-	if (actor->subsector->sector->gravity)
-		g = FixedMul(gravity,(FixedDiv(*actor->subsector->sector->gravity>>FRACBITS, 1000)));
-	else
-		g = gravity;
+	g = FixedMul(gravity, P_GetSectorGravityFactor(actor->subsector->sector));
 
 	// Look up distance between actor and its target
 	x = P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y);
@@ -11611,10 +11639,7 @@ void A_NapalmScatter(mobj_t *actor)
 		airtime = 16<<FRACBITS;
 
 	// Look up actor's current gravity situation
-	if (actor->subsector->sector->gravity)
-		g = FixedMul(gravity,(FixedDiv(*actor->subsector->sector->gravity>>FRACBITS, 1000)));
-	else
-		g = gravity;
+	g = FixedMul(gravity, P_GetSectorGravityFactor(actor->subsector->sector));
 
 	// vy = (g*(airtime-1))/2
 	vy = FixedMul(g,(airtime-(1<<FRACBITS)))>>1;
@@ -11737,7 +11762,7 @@ void A_FlickySpawn(mobj_t *actor)
 }
 
 // Internal Flicky color setting
-void P_InternalFlickySetColor(mobj_t *actor, UINT8 extrainfo)
+void P_InternalFlickySetColor(mobj_t *actor, UINT8 color)
 {
 	UINT8 flickycolors[] = {
 		SKINCOLOR_RED,
@@ -11757,11 +11782,11 @@ void P_InternalFlickySetColor(mobj_t *actor, UINT8 extrainfo)
 		SKINCOLOR_YELLOW,
 	};
 
-	if (extrainfo == 0)
+	if (color == 0)
 		// until we can customize flicky colors by level header, just stick to SRB2's defaults
 		actor->color = flickycolors[P_RandomKey(2)]; //flickycolors[P_RandomKey(sizeof(flickycolors))];
 	else
-		actor->color = flickycolors[min(extrainfo-1, 14)]; // sizeof(flickycolors)-1
+		actor->color = flickycolors[min(color-1, 14)]; // sizeof(flickycolors)-1
 }
 
 // Function: A_FlickyCenter
@@ -11771,17 +11796,17 @@ void P_InternalFlickySetColor(mobj_t *actor, UINT8 extrainfo)
 // var1:
 //        Lower 16 bits = if 0, spawns random flicky based on level header. Else, spawns the designated thing type.
 //        Bits 17-20 = Flicky color, up to 15. Applies to fish.
-//        Bit 21 = Flag MF_SLIDEME (see below)
-//        Bit 22 = Flag MF_GRENADEBOUNCE (see below)
-//        Bit 23 = Flag MF_NOCLIPTHING (see below)
+//        Bit 21 = Flag TMFF_AIMLESS (see below)
+//        Bit 22 = Flag TMFF_STATIONARY (see below)
+//        Bit 23 = Flag TMFF_HOP (see below)
 //
 //        If actor is placed from a spawnpoint (map Thing), the Thing's properties take precedence.
 //
-// var2 = maximum default distance away from spawn the flickies are allowed to travel. If angle != 0, then that's the radius.
+// var2 = maximum default distance away from spawn the flickies are allowed to travel. If args[0] != 0, then that's the radius.
 //
-// If MTF_EXTRA (MF_SLIDEME): is flagged, Flickies move aimlessly. Else, orbit around the target.
-// If MTF_OBJECTSPECIAL (MF_GRENADEBOUNCE): Flickies stand in-place without gravity (unless they hop, then gravity is applied.)
-// If MTF_AMBUSH (MF_NOCLIPTHING): is flagged, Flickies hop.
+// If TMFF_AIMLESS (MF_SLIDEME): is flagged, Flickies move aimlessly. Else, orbit around the target.
+// If TMFF_STATIONARY (MF_GRENADEBOUNCE): Flickies stand in-place without gravity (unless they hop, then gravity is applied.)
+// If TMFF_HOP (MF_NOCLIPTHING): is flagged, Flickies hop.
 //
 void A_FlickyCenter(mobj_t *actor)
 {
@@ -11803,14 +11828,15 @@ void A_FlickyCenter(mobj_t *actor)
 		if (actor->spawnpoint)
 		{
 			actor->flags &= ~(MF_SLIDEME|MF_GRENADEBOUNCE|MF_NOCLIPTHING);
-			actor->flags |= (
-				((actor->spawnpoint->options & MTF_EXTRA) ? MF_SLIDEME : 0)
-				| ((actor->spawnpoint->options & MTF_OBJECTSPECIAL) ? MF_GRENADEBOUNCE : 0)
-				| ((actor->spawnpoint->options & MTF_AMBUSH) ? MF_NOCLIPTHING : 0)
-			);
-			actor->extravalue1 = actor->spawnpoint->angle ? abs(actor->spawnpoint->angle) * FRACUNIT
-				: locvar2 ? abs(locvar2) : 384 * FRACUNIT;
-			actor->extravalue2 = actor->spawnpoint->extrainfo;
+			if (actor->spawnpoint->args[1] & TMFF_AIMLESS)
+				actor->flags |= MF_SLIDEME;
+			if (actor->spawnpoint->args[1] & TMFF_STATIONARY)
+				actor->flags |= MF_GRENADEBOUNCE;
+			if (actor->spawnpoint->args[1] & TMFF_HOP)
+				actor->flags |= MF_NOCLIPTHING;
+			actor->extravalue1 = actor->spawnpoint->args[0] ? abs(actor->spawnpoint->args[0])*FRACUNIT
+				: locvar2 ? abs(locvar2) : 384*FRACUNIT;
+			actor->extravalue2 = actor->spawnpoint->args[2];
 			actor->friction = actor->spawnpoint->x*FRACUNIT;
 			actor->movefactor = actor->spawnpoint->y*FRACUNIT;
 			actor->watertop = actor->spawnpoint->z*FRACUNIT;
@@ -11818,11 +11844,12 @@ void A_FlickyCenter(mobj_t *actor)
 		else
 		{
 			actor->flags &= ~(MF_SLIDEME|MF_GRENADEBOUNCE|MF_NOCLIPTHING);
-			actor->flags |= (
-				((flickyflags & 1) ? MF_SLIDEME : 0)
-				| ((flickyflags & 2) ? MF_GRENADEBOUNCE : 0)
-				| ((flickyflags & 4) ? MF_NOCLIPTHING : 0)
-			);
+			if (flickyflags & TMFF_AIMLESS)
+				actor->flags |= MF_SLIDEME;
+			if (flickyflags & TMFF_STATIONARY)
+				actor->flags |= MF_GRENADEBOUNCE;
+			if (flickyflags & TMFF_HOP)
+				actor->flags |= MF_NOCLIPTHING;
 			actor->extravalue1 = abs(locvar2);
 			actor->extravalue2 = flickycolor;
 			actor->friction = actor->x;
@@ -12322,10 +12349,7 @@ void A_Boss5Jump(mobj_t *actor)
 		return; // Don't even bother if we've got nothing to aim at.
 
 	// Look up actor's current gravity situation
-	if (actor->subsector->sector->gravity)
-		g = FixedMul(gravity,(FixedDiv(*actor->subsector->sector->gravity>>FRACBITS, 1000)));
-	else
-		g = gravity;
+	g = FixedMul(gravity, P_GetSectorGravityFactor(actor->subsector->sector));
 
 	// Look up distance between actor and its tracer
 	x = P_AproxDistance(actor->tracer->x - actor->x, actor->tracer->y - actor->y);
@@ -12747,67 +12771,43 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 	boolean avoidcenter;
-	UINT32 i;
-	UINT8 extrainfo = (actor->spawnpoint ? actor->spawnpoint->extrainfo : 0);
+	INT32 i;
+	INT32 bossid = (actor->spawnpoint ? actor->spawnpoint->args[0] : 0);
 
 	if (LUA_CallAction(A_BOSS5FINDWAYPOINT, actor))
 		return;
 
 	avoidcenter = !actor->tracer || (actor->health == actor->info->damage+1);
 
-	if (locvar1 == 2) // look for the boss waypoint
+	if (locvar1 == 2) // look for the boss flypoint
 	{
-		thinker_t *th;
-		mobj_t *mo2;
-		P_SetTarget(&actor->tracer, NULL);
-		// Flee! Flee! Find a point to escape to! If none, just shoot upward!
-		// scan the thinkers to find the runaway point
-		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
-		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-				continue;
-
-			mo2 = (mobj_t *)th;
-
-			if (mo2->type != MT_BOSSFLYPOINT)
-				continue;
-
-			if (mo2->spawnpoint && mo2->spawnpoint->extrainfo != extrainfo)
-				continue;
+		P_SetTarget(&actor->tracer, P_FindBossFlyPoint(actor, bossid));
 
-			// If this one's further then the last one, don't go for it.
-			if (actor->tracer &&
-				P_AproxDistance(P_AproxDistance(actor->x - mo2->x, actor->y - mo2->y), actor->z - mo2->z) >
-				P_AproxDistance(P_AproxDistance(actor->x - actor->tracer->x, actor->y - actor->tracer->y), actor->z - actor->tracer->z))
-					continue;
-
-			// Otherwise... Do!
-			P_SetTarget(&actor->tracer, mo2);
-		}
 		if (!actor->tracer)
 			return; // no boss flypoints found
 	}
 	else if (locvar1 == 1) // always go to ambush-marked waypoint
 	{
+		boolean found = false;
+
 		if (avoidcenter)
 			goto nowaypoints; // if we can't go the center, why on earth are we doing this?
 
-		for (i = 0; i < nummapthings; i++)
+		TAG_ITER_THINGS(bossid, i)
 		{
 			if (!mapthings[i].mobj)
 				continue;
 			if (mapthings[i].mobj->type != MT_FANGWAYPOINT)
 				continue;
-			if (mapthings[i].extrainfo != extrainfo)
-				continue;
-			if (!(mapthings[i].options & MTF_AMBUSH))
+			if (!(mapthings[i].args[0]))
 				continue;
 
 			P_SetTarget(&actor->tracer, mapthings[i].mobj);
+			found = true;
 			break;
 		}
 
-		if (i == nummapthings)
+		if (!found)
 			goto nowaypoints;
 	}
 	else // locvar1 == 0
@@ -12820,7 +12820,7 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 		actor->z += hackoffset;
 
 		// first, count how many waypoints we have
-		for (i = 0; i < nummapthings; i++)
+		TAG_ITER_THINGS(bossid, i)
 		{
 			if (!mapthings[i].mobj)
 				continue;
@@ -12828,9 +12828,7 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 				continue;
 			if (actor->tracer == mapthings[i].mobj) // this was your tracer last time
 				continue;
-			if (mapthings[i].extrainfo != extrainfo)
-				continue;
-			if (mapthings[i].options & MTF_AMBUSH)
+			if (mapthings[i].args[0])
 			{
 				if (avoidcenter)
 					continue;
@@ -12877,7 +12875,7 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 		numfangwaypoints = 0;
 
 		// now find them again and add them to the table!
-		for (i = 0; i < nummapthings; i++)
+		TAG_ITER_THINGS(bossid, i)
 		{
 			if (!mapthings[i].mobj)
 				continue;
@@ -12885,9 +12883,7 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 				continue;
 			if (actor->tracer == mapthings[i].mobj) // this was your tracer last time
 				continue;
-			if (mapthings[i].extrainfo != extrainfo)
-				continue;
-			if (mapthings[i].options & MTF_AMBUSH)
+			if (mapthings[i].args[0])
 			{
 				if (avoidcenter)
 					continue;
@@ -14324,7 +14320,7 @@ void A_SpawnPterabytes(mobj_t *actor)
 		return;
 
 	if (actor->spawnpoint)
-		amount = actor->spawnpoint->extrainfo + 1;
+		amount = min(1, actor->spawnpoint->args[0]);
 
 	interval = FixedAngle(FRACUNIT*360/amount);
 
diff --git a/src/p_floor.c b/src/p_floor.c
index 5536ee913acf2c958694e3c1d6bb451a50bcbfb8..b65435c21a745b0fa9fd5ed22c0906d344f94d85 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -11,6 +11,7 @@
 /// \file  p_floor.c
 /// \brief Floor animation, elevators
 
+#include "dehacked.h"
 #include "doomdef.h"
 #include "doomstat.h"
 #include "m_random.h"
@@ -162,7 +163,7 @@ result_e T_MovePlane(sector_t *sector, fixed_t speed, fixed_t dest, boolean crus
 void T_MoveFloor(floormove_t *movefloor)
 {
 	result_e res = 0;
-	boolean dontupdate = false;
+	boolean remove = false;
 
 	if (movefloor->delaytimer)
 	{
@@ -178,8 +179,8 @@ void T_MoveFloor(floormove_t *movefloor)
 	if (movefloor->type == bounceFloor)
 	{
 		const fixed_t origspeed = FixedDiv(movefloor->origspeed,(ELEVATORSPEED/2));
-		const fixed_t fs = abs(movefloor->sector->floorheight - lines[movefloor->texture].frontsector->floorheight);
-		const fixed_t bs = abs(movefloor->sector->floorheight - lines[movefloor->texture].backsector->floorheight);
+		const fixed_t fs = abs(movefloor->sector->floorheight - lines[movefloor->sourceline].frontsector->floorheight);
+		const fixed_t bs = abs(movefloor->sector->floorheight - lines[movefloor->sourceline].backsector->floorheight);
 		if (fs < bs)
 			movefloor->speed = FixedDiv(fs,25*FRACUNIT) + FRACUNIT/4;
 		else
@@ -190,113 +191,62 @@ void T_MoveFloor(floormove_t *movefloor)
 
 	if (res == pastdest)
 	{
-		if (movefloor->direction == 1)
+		switch (movefloor->type)
 		{
-			switch (movefloor->type)
-			{
-				case moveFloorByFrontSector:
-					if (movefloor->texture < -1) // chained linedef executing
-						P_LinedefExecute((INT16)(movefloor->texture + INT16_MAX + 2), NULL, NULL);
-					/* FALLTHRU */
-				case instantMoveFloorByFrontSector:
-					if (movefloor->texture > -1) // flat changing
-						movefloor->sector->floorpic = movefloor->texture;
-					break;
-				case bounceFloor: // Graue 03-12-2004
-					if (movefloor->floordestheight == lines[movefloor->texture].frontsector->floorheight)
-						movefloor->floordestheight = lines[movefloor->texture].backsector->floorheight;
-					else
-						movefloor->floordestheight = lines[movefloor->texture].frontsector->floorheight;
-					movefloor->direction = (movefloor->floordestheight < movefloor->sector->floorheight) ? -1 : 1;
-					movefloor->sector->floorspeed = movefloor->speed * movefloor->direction;
-					movefloor->delaytimer = movefloor->delay;
-					P_RecalcPrecipInSector(movefloor->sector);
-					return; // not break, why did this work? Graue 04-03-2004
-				case bounceFloorCrush: // Graue 03-27-2004
-					if (movefloor->floordestheight == lines[movefloor->texture].frontsector->floorheight)
-					{
-						movefloor->floordestheight = lines[movefloor->texture].backsector->floorheight;
-						movefloor->speed = movefloor->origspeed = FixedDiv(abs(lines[movefloor->texture].dy),4*FRACUNIT); // return trip, use dy
-					}
-					else
-					{
-						movefloor->floordestheight = lines[movefloor->texture].frontsector->floorheight;
-						movefloor->speed = movefloor->origspeed = FixedDiv(abs(lines[movefloor->texture].dx),4*FRACUNIT); // forward again, use dx
-					}
-					movefloor->direction = (movefloor->floordestheight < movefloor->sector->floorheight) ? -1 : 1;
-					movefloor->sector->floorspeed = movefloor->speed * movefloor->direction;
-					movefloor->delaytimer = movefloor->delay;
-					P_RecalcPrecipInSector(movefloor->sector);
-					return; // not break, why did this work? Graue 04-03-2004
-				case crushFloorOnce:
-					movefloor->floordestheight = lines[movefloor->texture].frontsector->floorheight;
+			case moveFloorByFrontSector:
+				if (movefloor->tag) // chained linedef executing
+					P_LinedefExecute(movefloor->tag, NULL, NULL);
+				/* FALLTHRU */
+			case instantMoveFloorByFrontSector:
+				if (movefloor->texture > -1) // flat changing
+					movefloor->sector->floorpic = movefloor->texture;
+				remove = true;
+				break;
+			case bounceFloor: // Graue 03-12-2004
+			case bounceFloorCrush: // Graue 03-27-2004
+				if (movefloor->floordestheight == lines[movefloor->sourceline].frontsector->floorheight)
+				{
+					movefloor->floordestheight = lines[movefloor->sourceline].backsector->floorheight;
+					movefloor->origspeed = lines[movefloor->sourceline].args[3] << (FRACBITS - 2); // return trip, use args[3]
+				}
+				else
+				{
+					movefloor->floordestheight = lines[movefloor->sourceline].frontsector->floorheight;
+					movefloor->origspeed = lines[movefloor->sourceline].args[2] << (FRACBITS - 2); // forward again, use args[2]
+				}
+				if (movefloor->type == bounceFloorCrush)
+					movefloor->speed = movefloor->origspeed;
+				movefloor->direction = (movefloor->floordestheight < movefloor->sector->floorheight) ? -1 : 1;
+				movefloor->delaytimer = movefloor->delay;
+				remove = false;
+				break;
+			case crushFloorOnce:
+				if (movefloor->direction == 1)
+				{
+					movefloor->floordestheight = lines[movefloor->sourceline].frontsector->floorheight;
 					movefloor->direction = -1;
+					movefloor->speed = lines[movefloor->sourceline].args[3] << (FRACBITS - 2);
 					movefloor->sector->soundorg.z = movefloor->sector->floorheight;
-					S_StartSound(&movefloor->sector->soundorg,sfx_pstop);
-					P_RecalcPrecipInSector(movefloor->sector);
-					return;
-				default:
-					break;
-			}
-		}
-		else if (movefloor->direction == -1)
-		{
-			switch (movefloor->type)
-			{
-				case moveFloorByFrontSector:
-					if (movefloor->texture < -1) // chained linedef executing
-						P_LinedefExecute((INT16)(movefloor->texture + INT16_MAX + 2), NULL, NULL);
-					/* FALLTHRU */
-				case instantMoveFloorByFrontSector:
-					if (movefloor->texture > -1) // flat changing
-						movefloor->sector->floorpic = movefloor->texture;
-					break;
-				case bounceFloor: // Graue 03-12-2004
-					if (movefloor->floordestheight == lines[movefloor->texture].frontsector->floorheight)
-						movefloor->floordestheight = lines[movefloor->texture].backsector->floorheight;
-					else
-						movefloor->floordestheight = lines[movefloor->texture].frontsector->floorheight;
-					movefloor->direction = (movefloor->floordestheight < movefloor->sector->floorheight) ? -1 : 1;
-					movefloor->sector->floorspeed = movefloor->speed * movefloor->direction;
-					movefloor->delaytimer = movefloor->delay;
-					P_RecalcPrecipInSector(movefloor->sector);
-					return; // not break, why did this work? Graue 04-03-2004
-				case bounceFloorCrush: // Graue 03-27-2004
-					if (movefloor->floordestheight == lines[movefloor->texture].frontsector->floorheight)
-					{
-						movefloor->floordestheight = lines[movefloor->texture].backsector->floorheight;
-						movefloor->speed = movefloor->origspeed = FixedDiv(abs(lines[movefloor->texture].dy),4*FRACUNIT); // return trip, use dy
-					}
-					else
-					{
-						movefloor->floordestheight = lines[movefloor->texture].frontsector->floorheight;
-						movefloor->speed = movefloor->origspeed = FixedDiv(abs(lines[movefloor->texture].dx),4*FRACUNIT); // forward again, use dx
-					}
-					movefloor->direction = (movefloor->floordestheight < movefloor->sector->floorheight) ? -1 : 1;
-					movefloor->sector->floorspeed = movefloor->speed * movefloor->direction;
-					movefloor->delaytimer = movefloor->delay;
-					P_RecalcPrecipInSector(movefloor->sector);
-					return; // not break, why did this work? Graue 04-03-2004
-				case crushFloorOnce:
-					movefloor->sector->floordata = NULL; // Clear up the thinker so others can use it
-					P_RemoveThinker(&movefloor->thinker);
-					movefloor->sector->floorspeed = 0;
-					P_RecalcPrecipInSector(movefloor->sector);
-					return;
-				default:
-					break;
-			}
+					S_StartSound(&movefloor->sector->soundorg, sfx_pstop);
+					remove = false;
+				}
+				else
+					remove = true;
+				break;
+			default:
+				remove = true;
+				break;
 		}
+	}
 
+	if (remove)
+	{
 		movefloor->sector->floordata = NULL; // Clear up the thinker so others can use it
 		movefloor->sector->floorspeed = 0;
 		P_RemoveThinker(&movefloor->thinker);
-		dontupdate = true;
 	}
-	if (!dontupdate)
-		movefloor->sector->floorspeed = movefloor->speed*movefloor->direction;
 	else
-		movefloor->sector->floorspeed = 0;
+		movefloor->sector->floorspeed = movefloor->speed*movefloor->direction;
 
 	P_RecalcPrecipInSector(movefloor->sector);
 }
@@ -634,7 +584,6 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 	sector_t *actionsector;
 	boolean remove;
 	INT32 i;
-	mtag_t tag = Tag_FGet(&bouncer->sourceline->tags);
 
 	if (bouncer->sector->crumblestate == CRUMBLE_RESTORE || bouncer->sector->crumblestate == CRUMBLE_WAIT
 		|| bouncer->sector->crumblestate == CRUMBLE_ACTIVATED) // Oops! Crumbler says to remove yourself!
@@ -649,7 +598,7 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 	}
 
 	// You can use multiple target sectors, but at your own risk!!!
-	TAG_ITER_SECTORS(tag, i)
+	TAG_ITER_SECTORS(bouncer->sourceline->args[0], i)
 	{
 		actionsector = &sectors[i];
 		actionsector->moved = true;
@@ -773,7 +722,7 @@ void T_StartCrumble(crumble_t *crumble)
 	ffloor_t *rover;
 	sector_t *sector;
 	INT32 i;
-	mtag_t tag = Tag_FGet(&crumble->sourceline->tags);
+	mtag_t tag = crumble->sourceline->args[0];
 
 	// Once done, the no-return thinker just sits there,
 	// constantly 'returning'... kind of an oxymoron, isn't it?
@@ -1097,23 +1046,6 @@ void T_MarioBlockChecker(mariocheck_t *block)
 	}
 }
 
-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(thwomp_t *thwomp)
@@ -1290,7 +1222,7 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 	sector_t *sec = NULL;
 	INT32 secnum = -1;
 	boolean FOFsector = false;
-	mtag_t tag = Tag_FGet(&nobaddies->sourceline->tags);
+	mtag_t tag = nobaddies->sourceline->args[0];
 
 	TAG_ITER_SECTORS(tag, secnum)
 	{
@@ -1302,14 +1234,13 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 		for (i = 0; i < sec->linecount; i++)
 		{
 			INT32 targetsecnum = -1;
-			mtag_t tag2 = Tag_FGet(&sec->lines[i]->tags);
 
 			if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300)
 				continue;
 
 			FOFsector = true;
 
-			TAG_ITER_SECTORS(tag2, targetsecnum)
+			TAG_ITER_SECTORS(sec->lines[i]->args[0], targetsecnum)
 			{
 				if (T_SectorHasEnemies(&sectors[targetsecnum]))
 					return;
@@ -1327,217 +1258,68 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 	P_RemoveThinker(&nobaddies->thinker);
 }
 
-//
-// P_IsObjectOnRealGround
-//
-// Helper function for T_EachTimeThinker
-// Like P_IsObjectOnGroundIn, except ONLY THE REAL GROUND IS CONSIDERED, NOT FOFS
-// I'll consider whether to make this a more globally accessible function or whatever in future
-// -- Monster Iestyn
-//
-static boolean P_IsObjectOnRealGround(mobj_t *mo, sector_t *sec)
-{
-	// Is the object in reverse gravity?
-	if (mo->eflags & MFE_VERTICALFLIP)
-	{
-		// Detect if the player is on the ceiling.
-		if (mo->z+mo->height >= P_GetSpecialTopZ(mo, sec, sec))
-			return true;
-	}
-	// Nope!
-	else
-	{
-		// Detect if the player is on the floor.
-		if (mo->z <= P_GetSpecialBottomZ(mo, sec, sec))
-			return true;
-	}
-	return false;
-}
-
-static boolean P_IsMobjTouchingSector(mobj_t *mo, sector_t *sec)
+static boolean P_CheckAllTrigger(eachtime_t *eachtime)
 {
-	msecnode_t *node;
-
-	if (mo->subsector->sector == sec)
-		return true;
-
-	if (!(sec->flags & SF_TRIGGERSPECIAL_TOUCH))
-		return false;
+	size_t i;
 
-	for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (node->m_sector == sec)
-			return true;
+		if (P_CanPlayerTrigger(i) && !eachtime->playersInArea[i])
+			return false;
 	}
 
-	return false;
+	return true;
 }
 
-//
-// T_EachTimeThinker
-//
-// Runs a linedef exec whenever a player enters an area.
-// Keeps track of players currently in the area and notices any changes.
-//
-// \sa P_AddEachTimeThinker
-//
 void T_EachTimeThinker(eachtime_t *eachtime)
 {
-	size_t i, j;
-	sector_t *sec = NULL;
-	sector_t *targetsec = NULL;
-	INT32 secnum = -1;
+	size_t i;
 	boolean oldPlayersInArea[MAXPLAYERS];
-	boolean oldPlayersOnArea[MAXPLAYERS];
-	boolean *oldPlayersArea;
-	boolean *playersArea;
-	boolean FOFsector = false;
-	boolean floortouch = false;
-	fixed_t bottomheight, topheight;
-	ffloor_t *rover;
-	mtag_t tag = Tag_FGet(&eachtime->sourceline->tags);
+	sector_t *caller[MAXPLAYERS];
+	boolean allPlayersChecked = false;
+	boolean allPlayersTrigger = false;
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		oldPlayersInArea[i] = eachtime->playersInArea[i];
-		oldPlayersOnArea[i] = eachtime->playersOnArea[i];
-		eachtime->playersInArea[i] = false;
-		eachtime->playersOnArea[i] = false;
-	}
-
-	TAG_ITER_SECTORS(tag, secnum)
-	{
-		sec = &sectors[secnum];
-
-		FOFsector = false;
-
-		if (GETSECSPECIAL(sec->special, 2) == 3 || GETSECSPECIAL(sec->special, 2) == 5)
-			floortouch = true;
-		else if (GETSECSPECIAL(sec->special, 2) >= 1 && GETSECSPECIAL(sec->special, 2) <= 8)
-			floortouch = false;
-		else
-			continue;
-
-		// Check the lines of this sector, to see if it is a FOF control sector.
-		for (i = 0; i < sec->linecount; i++)
-		{
-			INT32 targetsecnum = -1;
-			mtag_t tag2 = Tag_FGet(&sec->lines[i]->tags);
-
-			if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300)
-				continue;
-
-			FOFsector = true;
-
-			TAG_ITER_SECTORS(tag2, targetsecnum)
-			{
-				targetsec = &sectors[targetsecnum];
-
-				// Find the FOF corresponding to the control linedef
-				for (rover = targetsec->ffloors; rover; rover = rover->next)
-				{
-					if (rover->master == sec->lines[i])
-						break;
-				}
-
-				if (!rover) // This should be impossible, but don't complain if it is the case somehow
-					continue;
-
-				if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
-					continue;
-
-				for (j = 0; j < MAXPLAYERS; j++)
-				{
-					if (!P_IsPlayerValid(j))
-						continue;
-
-					if (!P_IsMobjTouchingSector(players[j].mo, targetsec))
-						continue;
-
-					topheight = P_GetSpecialTopZ(players[j].mo, sec, targetsec);
-					bottomheight = P_GetSpecialBottomZ(players[j].mo, sec, targetsec);
-
-					if (players[j].mo->z > topheight)
-						continue;
-
-					if (players[j].mo->z + players[j].mo->height < bottomheight)
-						continue;
-
-					if (floortouch && P_IsObjectOnGroundIn(players[j].mo, targetsec))
-						eachtime->playersOnArea[j] = true;
-					else
-						eachtime->playersInArea[j] = true;
-				}
-			}
-		}
-
-		if (!FOFsector)
-		{
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!P_IsPlayerValid(i))
-					continue;
-
-				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 && P_IsObjectOnRealGround(players[i].mo, sec))
-					eachtime->playersOnArea[i] = true;
-				else
-					eachtime->playersInArea[i] = true;
-			}
-		}
-	}
-
-	// Check if a new player entered.
-	// If not, check if a player hit the floor.
-	// If either condition is true, execute.
-	if (floortouch)
-	{
-		playersArea = eachtime->playersOnArea;
-		oldPlayersArea = oldPlayersOnArea;
-	}
-	else
-	{
-		playersArea = eachtime->playersInArea;
-		oldPlayersArea = oldPlayersInArea;
+		caller[i] = P_CanPlayerTrigger(i) ? P_FindPlayerTrigger(&players[i], eachtime->sourceline) : NULL;
+		eachtime->playersInArea[i] = caller[i] != NULL;
 	}
 
 	// Easy check... nothing has changed
-	if (!memcmp(playersArea, oldPlayersArea, sizeof(boolean)*MAXPLAYERS))
+	if (!memcmp(eachtime->playersInArea, oldPlayersInArea, 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)
-	{
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (P_IsPlayerValid(i) && !playersArea[i])
-				return;
-		}
-	}
-
 	// Trigger for every player who has entered (and exited, if triggerOnExit)
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (playersArea[i] == oldPlayersArea[i])
+		if (eachtime->playersInArea[i] == oldPlayersInArea[i])
 			continue;
 
 		// If player has just left, check if still valid
-		if (!playersArea[i] && (!eachtime->triggerOnExit || !P_IsPlayerValid(i)))
+		if (!eachtime->playersInArea[i] && (!eachtime->triggerOnExit || !P_CanPlayerTrigger(i)))
 			continue;
 
-		CONS_Debug(DBG_GAMELOGIC, "Trying to activate each time executor with tag %d\n", tag);
+		// If sector has an "all players" trigger type, all players need to be in area
+		if (caller[i] && caller[i]->triggerer == TO_ALLPLAYERS)
+		{
+			if (!allPlayersChecked)
+			{
+				allPlayersChecked = true;
+				allPlayersTrigger = P_CheckAllTrigger(eachtime);
+			}
+
+			if (!allPlayersTrigger)
+				continue;
+		}
+
+		CONS_Debug(DBG_GAMELOGIC, "Trying to activate each time executor with tag %d\n", Tag_FGet(&eachtime->sourceline->tags));
 
 		// 03/08/14 -Monster Iestyn
 		// 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[i].mo, sec);
+		P_RunTriggerLinedef(eachtime->sourceline, players[i].mo, caller[i]);
 
 		if (!eachtime->sourceline->special) // this happens only for "Trigger on X calls" linedefs
 			P_RemoveThinker(&eachtime->thinker);
@@ -1806,13 +1588,12 @@ void T_PlaneDisplace(planedisplace_t *pd)
 // (egg capsule button), P_PlayerInSpecialSector (buttons),
 // and P_SpawnSpecials (continuous floor movers and instant lower).
 //
-void EV_DoFloor(line_t *line, floor_e floortype)
+void EV_DoFloor(mtag_t tag, line_t *line, floor_e floortype)
 {
 	INT32 firstone = 1;
 	INT32 secnum = -1;
 	sector_t *sec;
 	floormove_t *dofloor;
-	mtag_t tag = Tag_FGet(&line->tags);
 
 	TAG_ITER_SECTORS(tag, secnum)
 	{
@@ -1833,34 +1614,25 @@ void EV_DoFloor(line_t *line, floor_e floortype)
 		dofloor->type = floortype;
 		dofloor->crush = false; // default: types that crush will change this
 		dofloor->sector = sec;
+		dofloor->sourceline = (INT32)(line - lines);
 
 		switch (floortype)
 		{
-			// Lowers a floor to the lowest surrounding floor.
-			case lowerFloorToLowest:
-				dofloor->direction = -1; // down
-				dofloor->speed = FLOORSPEED*2; // 2 fracunits per tic
-				dofloor->floordestheight = P_FindLowestFloorSurrounding(sec);
-				break;
-
-			// Used for part of the Egg Capsule, when an FOF with type 666 is
-			// contacted by the player.
+			// Used to open the top of an Egg Capsule.
 			case raiseFloorToNearestFast:
 				dofloor->direction = -1; // down
 				dofloor->speed = FLOORSPEED*4; // 4 fracunits per tic
 				dofloor->floordestheight = P_FindNextHighestFloor(sec, sec->floorheight);
 				break;
 
-			// Used for sectors tagged to 50 linedefs (effectively
-			// changing the base height for placing things in that sector).
+			// Instantly lower floor to surrounding sectors.
+			// Used as a hack in the binary map format to allow thing heights above 4096.
 			case instantLower:
 				dofloor->direction = -1; // down
 				dofloor->speed = INT32_MAX/2; // "instant" means "takes one tic"
 				dofloor->floordestheight = P_FindLowestFloorSurrounding(sec);
 				break;
 
-			// Linedef executor command, linetype 101.
-			// Front sector floor = destination height.
 			case instantMoveFloorByFrontSector:
 				dofloor->speed = INT32_MAX/2; // as above, "instant" is one tic
 				dofloor->floordestheight = line->frontsector->floorheight;
@@ -1870,22 +1642,12 @@ void EV_DoFloor(line_t *line, floor_e floortype)
 				else
 					dofloor->direction = -1; // down
 
-				// New for 1.09: now you can use the no climb flag to
-				// DISABLE the flat changing. This makes it work
-				// totally opposite the way linetype 106 does. Yet
-				// another reason I'll be glad to break backwards
-				// compatibility for the final.
-				if (line->flags & ML_NOCLIMB)
-					dofloor->texture = -1; // don't mess with the floorpic
-				else
-					dofloor->texture = line->frontsector->floorpic;
+				// If flag is set, change floor texture after moving
+				dofloor->texture = line->args[2] ? line->frontsector->floorpic : -1;
 				break;
 
-			// Linedef executor command, linetype 106.
-			// Line length = speed, front sector floor = destination height.
 			case moveFloorByFrontSector:
-				dofloor->speed = P_AproxDistance(line->dx, line->dy);
-				dofloor->speed = FixedDiv(dofloor->speed,8*FRACUNIT);
+				dofloor->speed = line->args[2] << (FRACBITS - 3);
 				dofloor->floordestheight = line->frontsector->floorheight;
 
 				if (dofloor->floordestheight >= sec->floorheight)
@@ -1894,85 +1656,31 @@ void EV_DoFloor(line_t *line, floor_e floortype)
 					dofloor->direction = -1; // down
 
 				// chained linedef executing ability
-				if (line->flags & ML_BLOCKMONSTERS)
-				{
-					// Only set it on one of the moving sectors (the
-					// smallest numbered) and only if the front side
-					// x offset is positive, indicating a valid tag.
-					if (firstone && sides[line->sidenum[0]].textureoffset > 0)
-						dofloor->texture = (sides[line->sidenum[0]].textureoffset>>FRACBITS) - 32769;
-					else
-						dofloor->texture = -1;
-				}
+				// Only set it on one of the moving sectors (the smallest numbered)
+				if (line->args[3])
+					dofloor->tag = firstone ? (INT16)line->args[3] : -1;
 
 				// flat changing ability
-				else if (line->flags & ML_NOCLIMB)
-					dofloor->texture = line->frontsector->floorpic;
-				else
-					dofloor->texture = -1; // nothing special to do after movement completes
-
+				dofloor->texture = line->args[4] ? line->frontsector->floorpic : -1;
 				break;
 
-			case moveFloorByFrontTexture:
-				if (line->flags & ML_NOCLIMB)
+			case moveFloorByDistance:
+				if (line->args[4])
 					dofloor->speed = INT32_MAX/2; // as above, "instant" is one tic
 				else
-					dofloor->speed = FixedDiv(sides[line->sidenum[0]].textureoffset,8*FRACUNIT); // texture x offset
-				dofloor->floordestheight = sec->floorheight + sides[line->sidenum[0]].rowoffset; // texture y offset
+					dofloor->speed = line->args[3] << (FRACBITS - 3);
+				dofloor->floordestheight = sec->floorheight + (line->args[2] << FRACBITS);
 				if (dofloor->floordestheight > sec->floorheight)
 					dofloor->direction = 1; // up
 				else
 					dofloor->direction = -1; // down
 				break;
 
-/*
-			// Linedef executor command, linetype 108.
-			// dx = speed, dy = amount to lower.
-			case lowerFloorByLine:
-				dofloor->direction = -1; // down
-				dofloor->speed = FixedDiv(abs(line->dx),8*FRACUNIT);
-				dofloor->floordestheight = sec->floorheight - abs(line->dy);
-				if (dofloor->floordestheight > sec->floorheight) // wrapped around
-					I_Error("Can't lower sector %d\n", secnum);
-				break;
-
-			// Linedef executor command, linetype 109.
-			// dx = speed, dy = amount to raise.
-			case raiseFloorByLine:
-				dofloor->direction = 1; // up
-				dofloor->speed = FixedDiv(abs(line->dx),8*FRACUNIT);
-				dofloor->floordestheight = sec->floorheight + abs(line->dy);
-				if (dofloor->floordestheight < sec->floorheight) // wrapped around
-					I_Error("Can't raise sector %d\n", secnum);
-				break;
-*/
-
-			// Linetypes 2/3.
-			// Move floor up and down indefinitely like the old elevators.
+			// Move floor up and down indefinitely.
+			// bounceFloor has slowdown at the top and bottom of movement.
 			case bounceFloor:
-				dofloor->speed = P_AproxDistance(line->dx, line->dy); // same speed as elevateContinuous
-				dofloor->speed = FixedDiv(dofloor->speed,4*FRACUNIT);
-				dofloor->origspeed = dofloor->speed; // it gets slowed down at the top and bottom
-				dofloor->floordestheight = line->frontsector->floorheight;
-
-				if (dofloor->floordestheight >= sec->floorheight)
-					dofloor->direction = 1; // up
-				else
-					dofloor->direction = -1; // down
-
-				// Any delay?
-				dofloor->delay = sides[line->sidenum[0]].textureoffset >> FRACBITS;
-				dofloor->delaytimer = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Initial delay
-
-				dofloor->texture = (fixed_t)(line - lines); // hack: store source line number
-				break;
-
-			// Linetypes 6/7.
-			// Like 2/3, but no slowdown at the top and bottom of movement,
-			// and the speed is line->dx the first way, line->dy for the
-			// return trip. Good for crushers.
 			case bounceFloorCrush:
-				dofloor->speed = FixedDiv(abs(line->dx),4*FRACUNIT);
+				dofloor->speed = line->args[2] << (FRACBITS - 2); // same speed as elevateContinuous
 				dofloor->origspeed = dofloor->speed;
 				dofloor->floordestheight = line->frontsector->floorheight;
 
@@ -1982,27 +1690,18 @@ void EV_DoFloor(line_t *line, floor_e floortype)
 					dofloor->direction = -1; // down
 
 				// Any delay?
-				dofloor->delay = sides[line->sidenum[0]].textureoffset >> FRACBITS;
-				dofloor->delaytimer = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Initial delay
-
-				dofloor->texture = (fixed_t)(line - lines); // hack: store source line number
+				dofloor->delay = line->args[5];
+				dofloor->delaytimer = line->args[4]; // Initial delay
 				break;
 
 			case crushFloorOnce:
-				dofloor->speed = FixedDiv(abs(line->dx),4*FRACUNIT);
-				dofloor->origspeed = dofloor->speed;
+				dofloor->speed = dofloor->origspeed = line->args[2] << (FRACBITS - 2);
 				dofloor->floordestheight = line->frontsector->ceilingheight;
 
 				if (dofloor->floordestheight >= sec->floorheight)
 					dofloor->direction = 1; // up
 				else
 					dofloor->direction = -1; // down
-
-				// Any delay?
-				dofloor->delay = sides[line->sidenum[0]].textureoffset >> FRACBITS;
-				dofloor->delaytimer = sides[line->sidenum[0]].rowoffset >> FRACBITS;
-
-				dofloor->texture = (fixed_t)(line - lines); // hack: store source line number
 				break;
 
 			default:
@@ -2023,14 +1722,13 @@ void EV_DoFloor(line_t *line, floor_e floortype)
 //
 // jff 2/22/98 new type to move floor and ceiling in parallel
 //
-void EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
+void EV_DoElevator(mtag_t tag, line_t *line, elevator_e elevtype)
 {
 	INT32 secnum = -1;
 	sector_t *sec;
 	elevator_t *elevator;
-	mtag_t tag = Tag_FGet(&line->tags);
 
-	// act on all sectors with the same tag as the triggering linedef
+	// act on all sectors with the given tag
 	TAG_ITER_SECTORS(tag, secnum)
 	{
 		sec = &sectors[secnum];
@@ -2048,6 +1746,7 @@ void EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 		elevator->type = elevtype;
 		elevator->sourceline = line;
 		elevator->distance = 1; // Always crush unless otherwise
+		elevator->sector = sec;
 
 		// set up the fields according to the type of elevator action
 		switch (elevtype)
@@ -2055,91 +1754,57 @@ void EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 			// elevator down to next floor
 			case elevateDown:
 				elevator->direction = -1;
-				elevator->sector = sec;
 				elevator->speed = ELEVATORSPEED/2; // half speed
 				elevator->floordestheight = P_FindNextLowestFloor(sec, sec->floorheight);
-				elevator->ceilingdestheight = elevator->floordestheight
-					+ sec->ceilingheight - sec->floorheight;
 				break;
 
 			// elevator up to next floor
 			case elevateUp:
 				elevator->direction = 1;
-				elevator->sector = sec;
 				elevator->speed = ELEVATORSPEED/4; // quarter speed
 				elevator->floordestheight = P_FindNextHighestFloor(sec, sec->floorheight);
-				elevator->ceilingdestheight = elevator->floordestheight
-					+ sec->ceilingheight - sec->floorheight;
 				break;
 
 			// elevator up to highest floor
 			case elevateHighest:
 				elevator->direction = 1;
-				elevator->sector = sec;
 				elevator->speed = ELEVATORSPEED/4; // quarter speed
 				elevator->floordestheight = P_FindHighestFloorSurrounding(sec);
-				elevator->ceilingdestheight = elevator->floordestheight
-					+ sec->ceilingheight - sec->floorheight;
-				break;
-
-			// elevator to floor height of activating switch's front sector
-			case elevateCurrent:
-				elevator->sector = sec;
-				elevator->speed = ELEVATORSPEED;
-				elevator->floordestheight = line->frontsector->floorheight;
-				elevator->ceilingdestheight = elevator->floordestheight
-					+ sec->ceilingheight - sec->floorheight;
-				elevator->direction = elevator->floordestheight > sec->floorheight?  1 : -1;
 				break;
 
 			case elevateContinuous:
-				if (customspeed)
-				{
-					elevator->origspeed = P_AproxDistance(line->dx, line->dy);
-					elevator->origspeed = FixedDiv(elevator->origspeed,4*FRACUNIT);
-					elevator->speed = elevator->origspeed;
-				}
-				else
-				{
-					elevator->speed = ELEVATORSPEED/2;
-					elevator->origspeed = elevator->speed;
-				}
+				elevator->origspeed = line->args[1] << (FRACBITS - 2);
+				elevator->speed = elevator->origspeed;
 
-				elevator->sector = sec;
-				elevator->low = !(line->flags & ML_NOCLIMB); // go down first unless noclimb is on
+				elevator->low = !line->args[4]; // go down first unless args[4] is set
 				if (elevator->low)
 				{
 					elevator->direction = 1;
 					elevator->floordestheight = P_FindNextHighestFloor(sec, sec->floorheight);
-					elevator->ceilingdestheight = elevator->floordestheight
-						+ sec->ceilingheight - sec->floorheight;
 				}
 				else
 				{
 					elevator->direction = -1;
 					elevator->floordestheight = P_FindNextLowestFloor(sec,sec->floorheight);
-					elevator->ceilingdestheight = elevator->floordestheight
-						+ sec->ceilingheight - sec->floorheight;
 				}
 				elevator->floorwasheight = elevator->sector->floorheight;
 				elevator->ceilingwasheight = elevator->sector->ceilingheight;
 
-				elevator->delay = sides[line->sidenum[0]].textureoffset >> FRACBITS;
-				elevator->delaytimer = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Initial delay
+				elevator->delay = line->args[3];
+				elevator->delaytimer = line->args[2]; // Initial delay
 				break;
 
 			case bridgeFall:
 				elevator->direction = -1;
-				elevator->sector = sec;
 				elevator->speed = ELEVATORSPEED*4; // quadruple speed
 				elevator->floordestheight = P_FindNextLowestFloor(sec, sec->floorheight);
-				elevator->ceilingdestheight = elevator->floordestheight
-					+ sec->ceilingheight - sec->floorheight;
 				break;
 
 			default:
 				break;
 		}
+
+		elevator->ceilingdestheight = elevator->floordestheight + sec->ceilingheight - sec->floorheight;
 	}
 }
 
@@ -2149,7 +1814,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	fixed_t leftx, rightx, topy, bottomy, topz, bottomz, widthfactor, heightfactor, a, b, c, spacing;
 	mobjtype_t type;
 	tic_t lifetime;
-	INT16 flags;
+	boolean fromcenter;
 
 	sector_t *controlsec = rover->master->frontsector;
 	mtag_t tag = Tag_FGet(&controlsec->tags);
@@ -2179,25 +1844,20 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	spacing = (32<<FRACBITS);
 	type = MT_ROCKCRUMBLE1;
 	lifetime = 3*TICRATE;
-	flags = 0;
+	fromcenter = false;
 
 	if (tag != 0)
 	{
 		INT32 tagline = Tag_FindLineSpecial(14, tag);
 		if (tagline != -1)
 		{
-			if (sides[lines[tagline].sidenum[0]].toptexture)
-				type = (mobjtype_t)sides[lines[tagline].sidenum[0]].toptexture; // Set as object type in p_setup.c...
-			if (sides[lines[tagline].sidenum[0]].textureoffset)
-				spacing = sides[lines[tagline].sidenum[0]].textureoffset;
-			if (sides[lines[tagline].sidenum[0]].rowoffset)
-			{
-				if (sides[lines[tagline].sidenum[0]].rowoffset>>FRACBITS != -1)
-					lifetime = (sides[lines[tagline].sidenum[0]].rowoffset>>FRACBITS);
-				else
-					lifetime = 0;
-			}
-			flags = lines[tagline].flags;
+			if (lines[tagline].stringargs[0])
+				type = get_number(lines[tagline].stringargs[0]);
+			if (lines[tagline].args[0])
+				spacing = lines[tagline].args[0] << FRACBITS;
+			if (lines[tagline].args[1])
+				lifetime = (lines[tagline].args[1] != -1) ? lines[tagline].args[1] : 0;
+			fromcenter = !!lines[tagline].args[2];
 		}
 	}
 
@@ -2232,7 +1892,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	topz = *rover->topheight-(spacing>>1);
 	bottomz = *rover->bottomheight;
 
-	if (flags & ML_EFFECT1)
+	if (fromcenter)
 	{
 		widthfactor = (rightx + topy - leftx - bottomy)>>3;
 		heightfactor = (topz - *rover->bottomheight)>>2;
@@ -2255,7 +1915,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 					spawned = P_SpawnMobj(a, b, c, type);
 					spawned->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
 
-					if (flags & ML_EFFECT1)
+					if (fromcenter)
 					{
 						P_InstaThrust(spawned, R_PointToAngle2(sec->soundorg.x, sec->soundorg.y, a, b), FixedDiv(P_AproxDistance(a - sec->soundorg.x, b - sec->soundorg.y), widthfactor));
 						P_SetObjectMomZ(spawned, FixedDiv((c - bottomz), heightfactor), false);
@@ -2327,7 +1987,7 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 	crumble_t *crumble;
 	sector_t *foundsec;
 	INT32 i;
-	mtag_t tag = Tag_FGet(&rover->master->tags);
+	mtag_t tag = rover->master->args[0];
 
 	// If floor is already activated, skip it
 	if (sec->floordata)
@@ -2419,7 +2079,7 @@ void EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 		block->direction = 1;
 		block->floorstartheight = block->sector->floorheight;
 		block->ceilingstartheight = block->sector->ceilingheight;
-		block->tag = (INT16)Tag_FGet(&sector->tags);
+		block->tag = (INT16)rover->master->args[0];
 
 		if (itsamonitor)
 		{
diff --git a/src/p_inter.c b/src/p_inter.c
index 582cdb0d091df4fc6b0ee9d5698babcff6acd9fb..c230ce178ad5b388ca123cde1352e8771b80f8c5 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -762,6 +762,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 //				return;
 			{
 				UINT8 flagteam = (special->type == MT_REDFLAG) ? 1 : 2;
+				sectorspecialflags_t specialflag = (special->type == MT_REDFLAG) ? SSF_REDTEAMBASE : SSF_BLUETEAMBASE;
 				const char *flagtext;
 				char flagcolor;
 				char plname[MAXPLAYERNAME+4];
@@ -791,7 +792,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						special->fuse = 1;
 						special->flags2 |= MF2_JUSTATTACKED;
 
-						if (!P_PlayerTouchingSectorSpecial(player, 4, 2 + flagteam))
+						if (!P_PlayerTouchingSectorSpecialFlag(player, specialflag))
 						{
 							CONS_Printf(M_GetText("%s returned the %c%s%c to base.\n"), plname, flagcolor, flagtext, 0x80);
 
@@ -1380,19 +1381,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			return;
 		case MT_AXE:
 			{
-				line_t junk;
 				thinker_t  *th;
 				mobj_t *mo2;
 
 				if (player->bot && player->bot != BOT_MPAI)
 					return;
 
-				// Initialize my junk
-				junk.tags.tags = NULL;
-				junk.tags.count = 0;
-
-				Tag_FSet(&junk.tags, LE_AXE);
-				EV_DoElevator(&junk, bridgeFall, false);
+				if (special->spawnpoint)
+					EV_DoElevator(special->spawnpoint->args[0], NULL, bridgeFall);
 
 				// scan the remaining thinkers to find koopa
 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@@ -1441,7 +1437,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 // Misc touchables //
 // *************** //
 		case MT_STARPOST:
-			P_TouchStarPost(special, player, special->spawnpoint && (special->spawnpoint->options & MTF_OBJECTSPECIAL));
+			P_TouchStarPost(special, player, special->spawnpoint && special->spawnpoint->args[1]);
 			return;
 
 		case MT_FAKEMOBILE:
@@ -2775,7 +2771,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 
 		case MT_BLASTEXECUTOR:
 			if (target->spawnpoint)
-				P_LinedefExecute(target->spawnpoint->angle, (source ? source : inflictor), target->subsector->sector);
+				P_LinedefExecute(target->spawnpoint->args[0], (source ? source : inflictor), target->subsector->sector);
 			break;
 
 		case MT_SPINBOBERT:
diff --git a/src/p_lights.c b/src/p_lights.c
index d7bbea90cea852f48820663e3e8da9b3935509d5..4c783f884edd5f322dd86e1e7270e462f757383a 100644
--- a/src/p_lights.c
+++ b/src/p_lights.c
@@ -30,7 +30,7 @@ void P_RemoveLighting(sector_t *sector)
 		// The thinker is the first member in all the lighting action structs,
 		// so just let the thinker get freed, and that will free the whole
 		// structure.
-		P_RemoveThinker(&((elevator_t *)sector->lightingdata)->thinker);
+		P_RemoveThinker(&((thinkerdata_t *)sector->lightingdata)->thinker);
 		sector->lightingdata = NULL;
 	}
 }
@@ -54,43 +54,36 @@ void T_FireFlicker(fireflicker_t *flick)
 	amount = (INT16)((UINT8)(P_RandomByte() & 3) * 16);
 
 	if (flick->sector->lightlevel - amount < flick->minlight)
-		flick->sector->lightlevel = (INT16)flick->minlight;
+		flick->sector->lightlevel = flick->minlight;
 	else
-		flick->sector->lightlevel = (INT16)((INT16)flick->maxlight - amount);
+		flick->sector->lightlevel = flick->maxlight - amount;
 
 	flick->count = flick->resetcount;
 }
 
 /** Spawns an adjustable fire flicker effect in a sector.
   *
-  * \param minsector Sector whose light level is used as the darkest.
-  * \param maxsector Sector whose light level is used as the brightest,
-  *                  and also the target sector for the effect.
+  * \param sector    Target sector for the effect.
+  * \param lighta    One of the two light levels to move between.
+  * \param lightb    The other light level.
   * \param length    Four times the number of tics between flickers.
   * \sa T_FireFlicker
   */
-fireflicker_t *P_SpawnAdjustableFireFlicker(sector_t *minsector, sector_t *maxsector, INT32 length)
+fireflicker_t *P_SpawnAdjustableFireFlicker(sector_t *sector, INT16 lighta, INT16 lightb, INT32 length)
 {
 	fireflicker_t *flick;
 
-	P_RemoveLighting(maxsector); // out with the old, in with the new
+	P_RemoveLighting(sector); // out with the old, in with the new
 	flick = Z_Calloc(sizeof (*flick), PU_LEVSPEC, NULL);
 
 	P_AddThinker(THINK_MAIN, &flick->thinker);
 
 	flick->thinker.function.acp1 = (actionf_p1)T_FireFlicker;
-	flick->sector = maxsector;
-	flick->maxlight = maxsector->lightlevel;
-	flick->minlight = minsector->lightlevel;
-	if (flick->minlight > flick->maxlight)
-	{
-		// You mixed them up, you dummy.
-		INT32 oops = flick->minlight;
-		flick->minlight = flick->maxlight;
-		flick->maxlight = oops;
-	}
+	flick->sector = sector;
+	flick->maxlight = max(lighta, lightb);
+	flick->minlight = min(lighta, lightb);
 	flick->count = flick->resetcount = length/4;
-	maxsector->lightingdata = flick;
+	sector->lightingdata = flick;
 
 	// input bounds checking and stuff
 	if (!flick->resetcount)
@@ -103,6 +96,9 @@ fireflicker_t *P_SpawnAdjustableFireFlicker(sector_t *minsector, sector_t *maxse
 			flick->maxlight++;
 	}
 
+	// Make sure the starting light level is in range.
+	sector->lightlevel = max(flick->minlight, min(flick->maxlight, sector->lightlevel));
+
 	return flick;
 }
 
@@ -148,7 +144,7 @@ void P_SpawnLightningFlash(sector_t *sector)
 			minlight = ((lightflash_t *)sector->lightingdata)->minlight;
 		}
 
-		P_RemoveThinker(&((elevator_t *)sector->lightingdata)->thinker);
+		P_RemoveThinker(&((thinkerdata_t *)sector->lightingdata)->thinker);
 	}
 
 	sector->lightingdata = NULL;
@@ -182,21 +178,21 @@ void T_StrobeFlash(strobe_t *flash)
 
 	if (flash->sector->lightlevel == flash->minlight)
 	{
-		flash->sector->lightlevel = (INT16)flash->maxlight;
+		flash->sector->lightlevel = flash->maxlight;
 		flash->count = flash->brighttime;
 	}
 	else
 	{
-		flash->sector->lightlevel = (INT16)flash->minlight;
+		flash->sector->lightlevel = flash->minlight;
 		flash->count = flash->darktime;
 	}
 }
 
 /** Spawns an adjustable strobe light effect in a sector.
   *
-  * \param minsector  Sector whose light level is used as the darkest.
-  * \param maxsector  Sector whose light level is used as the brightest,
-  *                   and also the target sector for the effect.
+  * \param sector     Target sector for the effect.
+  * \param lighta     One of the two light levels to move between.
+  * \param lightb     The other light level.
   * \param darktime   Time in tics for the light to be dark.
   * \param brighttime Time in tics for the light to be bright.
   * \param inSync     If true, the effect will be kept in sync
@@ -207,29 +203,21 @@ void T_StrobeFlash(strobe_t *flash)
   *                   the strobe flash is random.
   * \sa T_StrobeFlash
   */
-strobe_t *P_SpawnAdjustableStrobeFlash(sector_t *minsector, sector_t *maxsector, INT32 darktime, INT32 brighttime, boolean inSync)
+strobe_t *P_SpawnAdjustableStrobeFlash(sector_t *sector, INT16 lighta, INT16 lightb, INT32 darktime, INT32 brighttime, boolean inSync)
 {
 	strobe_t *flash;
 
-	P_RemoveLighting(maxsector); // out with the old, in with the new
+	P_RemoveLighting(sector); // out with the old, in with the new
 	flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL);
 
 	P_AddThinker(THINK_MAIN, &flash->thinker);
 
-	flash->sector = maxsector;
+	flash->sector = sector;
 	flash->darktime = darktime;
 	flash->brighttime = brighttime;
 	flash->thinker.function.acp1 = (actionf_p1)T_StrobeFlash;
-	flash->maxlight = maxsector->lightlevel;
-	flash->minlight = minsector->lightlevel;
-
-	if (flash->minlight > flash->maxlight)
-	{
-		// You mixed them up, you dummy.
-		INT32 oops = flash->minlight;
-		flash->minlight = flash->maxlight;
-		flash->maxlight = oops;
-	}
+	flash->maxlight = max(lighta, lightb);
+	flash->minlight = min(lighta, lightb);
 
 	if (flash->minlight == flash->maxlight)
 		flash->minlight = 0;
@@ -239,7 +227,10 @@ strobe_t *P_SpawnAdjustableStrobeFlash(sector_t *minsector, sector_t *maxsector,
 	else
 		flash->count = 1;
 
-	maxsector->lightingdata = flash;
+	// Make sure the starting light level is in range.
+	sector->lightlevel = max(flash->minlight, min(flash->maxlight, sector->lightlevel));
+
+	sector->lightingdata = flash;
 	return flash;
 }
 
@@ -254,20 +245,20 @@ void T_Glow(glow_t *g)
 	{
 		case -1:
 			// DOWN
-			g->sector->lightlevel = (INT16)(g->sector->lightlevel - (INT16)g->speed);
+			g->sector->lightlevel -= g->speed;
 			if (g->sector->lightlevel <= g->minlight)
 			{
-				g->sector->lightlevel = (INT16)(g->sector->lightlevel + (INT16)g->speed);
+				g->sector->lightlevel += g->speed;
 				g->direction = 1;
 			}
 			break;
 
 		case 1:
 			// UP
-			g->sector->lightlevel = (INT16)(g->sector->lightlevel + (INT16)g->speed);
+			g->sector->lightlevel += g->speed;
 			if (g->sector->lightlevel >= g->maxlight)
 			{
-				g->sector->lightlevel = (INT16)(g->sector->lightlevel - (INT16)g->speed);
+				g->sector->lightlevel -= g->speed;
 				g->direction = -1;
 			}
 			break;
@@ -276,34 +267,27 @@ void T_Glow(glow_t *g)
 
 /** Spawns an adjustable glowing light effect in a sector.
   *
-  * \param minsector Sector whose light level is used as the darkest.
-  * \param maxsector Sector whose light level is used as the brightest,
-  *                  and also the target sector for the effect.
+  * \param sector    Target sector for the effect.
+  * \param lighta    One of the two light levels to move between.
+  * \param lightb    The other light level.
   * \param length    The speed of the effect.
   * \sa T_Glow
   */
-glow_t *P_SpawnAdjustableGlowingLight(sector_t *minsector, sector_t *maxsector, INT32 length)
+glow_t *P_SpawnAdjustableGlowingLight(sector_t *sector, INT16 lighta, INT16 lightb, INT32 length)
 {
 	glow_t *g;
 
-	P_RemoveLighting(maxsector); // out with the old, in with the new
+	P_RemoveLighting(sector); // out with the old, in with the new
 	g = Z_Calloc(sizeof (*g), PU_LEVSPEC, NULL);
 
 	P_AddThinker(THINK_MAIN, &g->thinker);
 
-	g->sector = maxsector;
-	g->minlight = minsector->lightlevel;
-	g->maxlight = maxsector->lightlevel;
-	if (g->minlight > g->maxlight)
-	{
-		// You mixed them up, you dummy.
-		INT32 oops = g->minlight;
-		g->minlight = g->maxlight;
-		g->maxlight = oops;
-	}
+	g->sector = sector;
+	g->minlight = min(lighta, lightb);
+	g->maxlight = max(lighta, lightb);
 	g->thinker.function.acp1 = (actionf_p1)T_Glow;
 	g->direction = 1;
-	g->speed = length/4;
+	g->speed = (INT16)(length/4);
 	if (g->speed > (g->maxlight - g->minlight)/2) // don't make it ridiculous speed
 		g->speed = (g->maxlight - g->minlight)/2;
 
@@ -317,7 +301,10 @@ glow_t *P_SpawnAdjustableGlowingLight(sector_t *minsector, sector_t *maxsector,
 		g->speed = (g->maxlight - g->minlight)/2;
 	}
 
-	maxsector->lightingdata = g;
+	// Make sure the starting light level is in range.
+	sector->lightlevel = max(g->minlight, min(g->maxlight, sector->lightlevel));
+
+	sector->lightingdata = g;
 
 	return g;
 }
@@ -371,9 +358,10 @@ void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean
 	}
 }
 
-void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, boolean force)
+void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, boolean force, boolean relative)
 {
 	INT32 i;
+	INT32 realdestvalue;
 
 	// search all sectors for ones with tag
 	TAG_ITER_SECTORS(tag, i)
@@ -386,7 +374,9 @@ void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, bool
 			CONS_Debug(DBG_GAMELOGIC, "Line type 420 Executor: Fade light thinker already exists, timer: %d\n", ((lightlevel_t*)sectors[i].lightingdata)->timer);
 			continue;
 		}
-		P_FadeLightBySector(&sectors[i], destvalue, speed, ticbased);
+
+		realdestvalue = relative ? max(0, min(255, sectors[i].lightlevel + destvalue)) : destvalue;
+		P_FadeLightBySector(&sectors[i], realdestvalue, speed, ticbased);
 	}
 }
 
diff --git a/src/p_local.h b/src/p_local.h
index ba8cbe166aa1df73a96536c9d813565255a1841b..f50606117cd91566d3a9f2dc46de434efc66c6d2 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -148,7 +148,6 @@ boolean P_PlayerShouldUseSpinHeight(player_t *player);
 
 boolean P_IsObjectInGoop(mobj_t *mo);
 boolean P_IsObjectOnGround(mobj_t *mo);
-boolean P_IsObjectOnGroundIn(mobj_t *mo, sector_t *sec);
 boolean P_InSpaceSector(mobj_t *mo);
 boolean P_InQuicksand(mobj_t *mo);
 boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff);
@@ -373,7 +372,7 @@ void P_NewChaseDir(mobj_t *actor);
 boolean P_LookForPlayers(mobj_t *actor, boolean allaround, boolean tracer, fixed_t dist);
 
 mobj_t *P_InternalFlickySpawn(mobj_t *actor, mobjtype_t flickytype, fixed_t momz, boolean lookforplayers, SINT8 moveforward);
-void P_InternalFlickySetColor(mobj_t *actor, UINT8 extrainfo);
+void P_InternalFlickySetColor(mobj_t *actor, UINT8 color);
 #define P_IsFlickyCenter(type) (type > MT_FLICKY_01 && type < MT_SEED && (type - MT_FLICKY_01) % 2 ? 1 : 0)
 void P_InternalFlickyBubble(mobj_t *actor);
 void P_InternalFlickyFly(mobj_t *actor, fixed_t flyspeed, fixed_t targetdist, fixed_t chasez);
diff --git a/src/p_map.c b/src/p_map.c
index 9f80d92b5558d2590407a752d7b4bc937d975a1d..aee13ae7e03501f468f21ca4273d3a3befdded3d 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -492,7 +492,7 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object)
 	switch (spring->type)
 	{
 		case MT_FAN: // fan
-			if (zdist > (spring->health << FRACBITS)) // max z distance determined by health (set by map thing angle)
+			if (zdist > (spring->health << FRACBITS)) // max z distance determined by health (set by map thing args[0])
 				break;
 			if (flipval*object->momz >= FixedMul(speed, spring->scale)) // if object's already moving faster than your best, don't bother
 				break;
@@ -2331,7 +2331,7 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 
 	mapcampointer = thiscam;
 
-	if (GETSECSPECIAL(newsubsec->sector->special, 4) == 12)
+	if (newsubsec->sector->flags & MSF_NOCLIPCAMERA)
 	{ // Camera noclip on entire sector.
 		tmfloorz = tmdropoffz = thiscam->z;
 		tmceilingz = tmdrpoffceilz = thiscam->z + thiscam->height;
@@ -2371,7 +2371,7 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 		for (rover = newsubsec->sector->ffloors; rover; rover = rover->next)
 		{
 			fixed_t topheight, bottomheight;
-			if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERALL) || GETSECSPECIAL(rover->master->frontsector->special, 4) == 12)
+			if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERALL) || (rover->master->frontsector->flags & MSF_NOCLIPCAMERA))
 				continue;
 
 			topheight = P_CameraGetFOFTopZ(thiscam, newsubsec->sector, rover, x, y, NULL);
@@ -2443,7 +2443,7 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 						// We're inside it! Yess...
 						polysec = po->lines[0]->backsector;
 
-						if (GETSECSPECIAL(polysec->special, 4) == 12)
+						if (polysec->flags & MSF_NOCLIPCAMERA)
 						{ // Camera noclip polyobj.
 							plink = (polymaplink_t *)(plink->link.next);
 							continue;
@@ -2711,14 +2711,14 @@ increment_move
 
 			if (thing->player)
 			{
-				// If using type Section1:13, double the maxstep.
-				if (P_PlayerTouchingSectorSpecial(thing->player, 1, 13)
-				|| GETSECSPECIAL(R_PointInSubsector(x, y)->sector->special, 1) == 13)
+				// If using SSF_DOUBLESTEPUP, double the maxstep.
+				if (P_PlayerTouchingSectorSpecialFlag(thing->player, SSF_DOUBLESTEPUP)
+				|| (R_PointInSubsector(x, y)->sector->specialflags & SSF_DOUBLESTEPUP))
 					maxstep <<= 1;
 
-				// If using type Section1:14, no maxstep.
-				if (P_PlayerTouchingSectorSpecial(thing->player, 1, 14)
-				|| GETSECSPECIAL(R_PointInSubsector(x, y)->sector->special, 1) == 14)
+				// If using SSF_NOSTEPDOWN, no maxstep.
+				if (P_PlayerTouchingSectorSpecialFlag(thing->player, SSF_NOSTEPDOWN)
+				|| (R_PointInSubsector(x, y)->sector->specialflags & SSF_NOSTEPDOWN))
 					maxstep = 0;
 
 				// Don't 'step up' while springing,
@@ -2729,12 +2729,12 @@ increment_move
 			}
 			else if (thing->flags & MF_PUSHABLE)
 			{
-				// If using type Section1:13, double the maxstep.
-				if (GETSECSPECIAL(R_PointInSubsector(x, y)->sector->special, 1) == 13)
+				// If using SSF_DOUBLESTEPUP, double the maxstep.
+				if (R_PointInSubsector(x, y)->sector->specialflags & SSF_DOUBLESTEPUP)
 					maxstep <<= 1;
 
-				// If using type Section1:14, no maxstep.
-				if (GETSECSPECIAL(R_PointInSubsector(x, y)->sector->special, 1) == 14)
+				// If using SSF_NOSTEPDOWN, no maxstep.
+				if (R_PointInSubsector(x, y)->sector->specialflags & SSF_NOSTEPDOWN)
 					maxstep = 0;
 			}
 
@@ -3498,7 +3498,7 @@ static boolean PTR_SlideTraverse(intercept_t *in)
 	// see if it is closer than best so far
 	if (li->polyobj && slidemo->player)
 	{
-		if ((li->polyobj->lines[0]->backsector->flags & SF_TRIGGERSPECIAL_TOUCH) && !(li->polyobj->flags & POF_NOSPECIALS))
+		if ((li->polyobj->lines[0]->backsector->flags & MSF_TRIGGERSPECIAL_TOUCH) && !(li->polyobj->flags & POF_NOSPECIALS))
 			P_ProcessSpecialSector(slidemo->player, slidemo->subsector->sector, li->polyobj->lines[0]->backsector);
 	}
 
@@ -3637,10 +3637,7 @@ static void P_CheckLavaWall(mobj_t *mo, sector_t *sec)
 		if (!(rover->flags & FF_SWIMMABLE))
 			continue;
 
-		if (GETSECSPECIAL(rover->master->frontsector->special, 1) != 3)
-			continue;
-
-		if (rover->master->flags & ML_BLOCKMONSTERS)
+		if (rover->master->frontsector->damagetype != SD_LAVA)
 			continue;
 
 		topheight = P_GetFFloorTopZAt(rover, mo->x, mo->y);
diff --git a/src/p_maputl.c b/src/p_maputl.c
index 43a7e92a185f4ba88ef5a2767d774bb928252bca..614db93e36883982331a45ae819c09ce2f5777ca 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -374,7 +374,7 @@ void P_CameraLineOpening(line_t *linedef)
 				for (rover = front->ffloors; rover; rover = rover->next)
 				{
 					fixed_t topheight, bottomheight;
-					if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_RENDERALL) || !(rover->flags & FF_EXISTS) || GETSECSPECIAL(rover->master->frontsector->special, 4) == 12)
+					if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_RENDERALL) || !(rover->flags & FF_EXISTS) || (rover->master->frontsector->flags & MSF_NOCLIPCAMERA))
 						continue;
 
 					topheight = P_CameraGetFOFTopZ(mapcampointer, front, rover, tmx, tmy, linedef);
@@ -398,7 +398,7 @@ void P_CameraLineOpening(line_t *linedef)
 				for (rover = back->ffloors; rover; rover = rover->next)
 				{
 					fixed_t topheight, bottomheight;
-					if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_RENDERALL) || !(rover->flags & FF_EXISTS) || GETSECSPECIAL(rover->master->frontsector->special, 4) == 12)
+					if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_RENDERALL) || !(rover->flags & FF_EXISTS) || (rover->master->frontsector->flags & MSF_NOCLIPCAMERA))
 						continue;
 
 					topheight = P_CameraGetFOFTopZ(mapcampointer, back, rover, tmx, tmy, linedef);
@@ -491,7 +491,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 		fixed_t thingtop = mobj->z + mobj->height;
 
 		// Check for collision with front side's midtexture if Effect 4 is set
-		if (linedef->flags & ML_EFFECT4
+		if (linedef->flags & ML_MIDSOLID
 			&& !linedef->polyobj // don't do anything for polyobjects! ...for now
 			) {
 			side_t *side = &sides[linedef->sidenum[0]];
@@ -508,10 +508,10 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				// 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)) {
-					if (linedef->flags & ML_EFFECT5 && !side->repeatcnt) { // "infinite" repeat
+					if (linedef->flags & ML_WRAPMIDTEX && !side->repeatcnt) { // "infinite" repeat
 						texbottom = back->floorheight + side->rowoffset;
 						textop = back->ceilingheight + side->rowoffset;
-					} else if (!!(linedef->flags & ML_DONTPEGBOTTOM) ^ !!(linedef->flags & ML_EFFECT3)) {
+					} else if (linedef->flags & ML_MIDTEX) {
 						texbottom = back->floorheight + side->rowoffset;
 						textop = texbottom + texheight*(side->repeatcnt+1);
 					} else {
@@ -521,10 +521,10 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				} else
 #endif
 				{
-					if (linedef->flags & ML_EFFECT5 && !side->repeatcnt) { // "infinite" repeat
+					if (linedef->flags & ML_WRAPMIDTEX && !side->repeatcnt) { // "infinite" repeat
 						texbottom = openbottom + side->rowoffset;
 						textop = opentop + side->rowoffset;
-					} else if (!!(linedef->flags & ML_DONTPEGBOTTOM) ^ !!(linedef->flags & ML_EFFECT3)) {
+					} else if (linedef->flags & ML_MIDPEG) {
 						texbottom = openbottom + side->rowoffset;
 						textop = texbottom + texheight*(side->repeatcnt+1);
 					} else {
diff --git a/src/p_mobj.c b/src/p_mobj.c
index e88cd4b9f64fce4bef6d51e3d6ffd6e8b1129c8c..9017c2f162c2f1d4cd20e8ae865f6afb39c3b6ec 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -11,6 +11,7 @@
 /// \file  p_mobj.c
 /// \brief Moving object handling. Spawn functions
 
+#include "dehacked.h"
 #include "doomdef.h"
 #include "g_game.h"
 #include "g_input.h"
@@ -1445,6 +1446,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 	if (mo->subsector->sector->ffloors) // Check for 3D floor gravity too.
 	{
 		ffloor_t *rover;
+		fixed_t gravfactor;
 
 		for (rover = mo->subsector->sector->ffloors; rover; rover = rover->next)
 		{
@@ -1454,13 +1456,14 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 			if ((rover->flags & (FF_SWIMMABLE|FF_GOOWATER)) == (FF_SWIMMABLE|FF_GOOWATER))
 				goopgravity = true;
 
-			if (!(rover->master->frontsector->gravity))
+			gravfactor = P_GetSectorGravityFactor(mo->subsector->sector);
+
+			if (gravfactor == FRACUNIT)
 				continue;
 
-			gravityadd = -FixedMul(gravity,
-				(FixedDiv(*rover->master->frontsector->gravity>>FRACBITS, 1000)));
+			gravityadd = -FixedMul(gravity, gravfactor);
 
-			if (rover->master->frontsector->verticalflip && gravityadd > 0)
+			if ((rover->master->frontsector->flags & MSF_GRAVITYFLIP) && gravityadd > 0)
 				mo->eflags |= MFE_VERTICALFLIP;
 
 			no3dfloorgrav = false;
@@ -1470,13 +1473,9 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 
 	if (no3dfloorgrav)
 	{
-		if (mo->subsector->sector->gravity)
-			gravityadd = -FixedMul(gravity,
-				(FixedDiv(*mo->subsector->sector->gravity>>FRACBITS, 1000)));
-		else
-			gravityadd = -gravity;
+		gravityadd = -FixedMul(gravity, P_GetSectorGravityFactor(mo->subsector->sector));
 
-		if (mo->subsector->sector->verticalflip && gravityadd > 0)
+		if ((mo->subsector->sector->flags & MSF_GRAVITYFLIP) && gravityadd > 0)
 			mo->eflags |= MFE_VERTICALFLIP;
 	}
 
@@ -1721,8 +1720,7 @@ static void P_PushableCheckBustables(mobj_t *mo)
 			if (!(rover->flags & FF_BUSTUP))
 				continue;
 
-			// Needs ML_EFFECT4 flag for pushables to break it
-			if (!(rover->master->flags & ML_EFFECT4))
+			if (!(rover->bustflags & FB_PUSHABLES))
 				continue;
 
 			if (rover->master->frontsector->crumblestate != CRUMBLE_NONE)
@@ -1732,7 +1730,7 @@ static void P_PushableCheckBustables(mobj_t *mo)
 			bottomheight = P_GetFOFBottomZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
 
 			// Height checks
-			if (rover->flags & FF_SHATTERBOTTOM)
+			if (rover->bustflags & FB_ONLYBOTTOM)
 			{
 				if (mo->z + mo->momz + mo->height < bottomheight)
 					continue;
@@ -1740,36 +1738,42 @@ static void P_PushableCheckBustables(mobj_t *mo)
 				if (mo->z + mo->height > bottomheight)
 					continue;
 			}
-			else if (rover->flags & FF_SPINBUST)
+			else
 			{
-				if (mo->z + mo->momz > topheight)
-					continue;
+				switch (rover->busttype)
+				{
+				case BT_TOUCH:
+					if (mo->z + mo->momz > topheight)
+						continue;
 
-				if (mo->z + mo->height < bottomheight)
-					continue;
-			}
-			else if (rover->flags & FF_SHATTER)
-			{
-				if (mo->z + mo->momz > topheight)
-					continue;
+					if (mo->z + mo->momz + mo->height < bottomheight)
+						continue;
 
-				if (mo->z + mo->momz + mo->height < bottomheight)
-					continue;
-			}
-			else
-			{
-				if (mo->z >= topheight)
-					continue;
+					break;
+				case BT_SPINBUST:
+					if (mo->z + mo->momz > topheight)
+						continue;
 
-				if (mo->z + mo->height < bottomheight)
-					continue;
+					if (mo->z + mo->height < bottomheight)
+						continue;
+
+					break;
+				default:
+					if (mo->z >= topheight)
+						continue;
+
+					if (mo->z + mo->height < bottomheight)
+						continue;
+
+					break;
+				}
 			}
 
 			EV_CrumbleChain(NULL, rover); // node->m_sector
 
 			// Run a linedef executor??
-			if (rover->master->flags & ML_EFFECT5)
-				P_LinedefExecute((INT16)(P_AproxDistance(rover->master->dx, rover->master->dy)>>FRACBITS), mo, node->m_sector);
+			if (rover->bustflags & FB_EXECUTOR)
+				P_LinedefExecute(rover->busttag, mo, node->m_sector);
 
 			goto bustupdone;
 		}
@@ -2318,11 +2322,11 @@ boolean P_CheckDeathPitCollide(mobj_t *mo)
 		return false;
 
 	if (((mo->z <= mo->subsector->sector->floorheight
-		&& ((mo->subsector->sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_FLOOR))
+		&& ((mo->subsector->sector->flags & MSF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & MSF_FLIPSPECIAL_FLOOR))
 	|| (mo->z + mo->height >= mo->subsector->sector->ceilingheight
-		&& ((mo->subsector->sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_CEILING)))
-	&& (GETSECSPECIAL(mo->subsector->sector->special, 1) == 6
-	|| GETSECSPECIAL(mo->subsector->sector->special, 1) == 7))
+		&& ((mo->subsector->sector->flags & MSF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & MSF_FLIPSPECIAL_CEILING)))
+	&& (mo->subsector->sector->damagetype == SD_DEATHPITTILT
+	|| mo->subsector->sector->damagetype == SD_DEATHPITNOTILT))
 		return true;
 
 	return false;
@@ -2330,11 +2334,7 @@ boolean P_CheckDeathPitCollide(mobj_t *mo)
 
 boolean P_CheckSolidLava(ffloor_t *rover)
 {
-	if (rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3
-		&& !(rover->master->flags & ML_BLOCKMONSTERS))
-			return true;
-
-	return false;
+	return (rover->flags & FF_SWIMMABLE) && (rover->master->frontsector->damagetype == SD_LAVA);
 }
 
 //
@@ -2675,7 +2675,7 @@ boolean P_ZMovement(mobj_t *mo)
 					{
 						if (mo->flags2 & MF2_AMBUSH)
 						{
-							// If deafed, give the tumbleweed another random kick if it runs out of steam.
+							// Give the tumbleweed another random kick if it runs out of steam.
 							mom.z += P_MobjFlip(mo)*FixedMul(6*FRACUNIT, mo->scale);
 
 							if (P_RandomChance(FRACUNIT/2))
@@ -2840,7 +2840,7 @@ static void P_CheckMarioBlocks(mobj_t *mo)
 			if (*rover->bottomheight != mo->ceilingz)
 				continue;
 
-			if (rover->flags & FF_SHATTERBOTTOM) // Brick block!
+			if (rover->flags & FF_GOOWATER) // Brick block!
 				EV_CrumbleChain(node->m_sector, rover);
 			else // Question block!
 				EV_MarioBlock(rover, node->m_sector, mo);
@@ -3301,7 +3301,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 
 		if (mobj->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER))
 		{
-			if (GETSECSPECIAL(rover->master->frontsector->special, 1) == 3)
+			if (rover->master->frontsector->damagetype == SD_FIRE || rover->master->frontsector->damagetype == SD_LAVA)
 				mobj->eflags |= MFE_TOUCHLAVA;
 
 			if (rover->flags & FF_GOOWATER && !(mobj->flags & MF_NOGRAVITY))
@@ -3545,19 +3545,16 @@ static boolean P_CameraCheckHeat(camera_t *thiscam)
 {
 	sector_t *sector;
 	fixed_t halfheight = thiscam->z + (thiscam->height >> 1);
-	size_t i;
 
 	// see if we are in water
 	sector = thiscam->subsector->sector;
 
-	for (i = 0; i < sector->tags.count; i++)
-		if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
-			return true;
+	if (sector->flags & MSF_HEATWAVE)
+		return true;
 
 	if (sector->ffloors)
 	{
 		ffloor_t *rover;
-		size_t j;
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
@@ -3569,8 +3566,7 @@ static boolean P_CameraCheckHeat(camera_t *thiscam)
 			if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y))
 				continue;
 
-			for (j = 0; j < rover->master->frontsector->tags.count; j++)
-			if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
+			if (rover->master->frontsector->flags & MSF_HEATWAVE)
 				return true;
 		}
 	}
@@ -4096,9 +4092,11 @@ static void P_KillRingsInLava(mobj_t *mo)
 			{
 				if (!(rover->flags & FF_EXISTS)) continue; // fof must be real
 
-				if (!(rover->flags & FF_SWIMMABLE // fof must be water
-					&& GETSECSPECIAL(rover->master->frontsector->special, 1) == 3)) // fof must be lava water
-					continue;
+				if (!(rover->flags & FF_SWIMMABLE))
+					continue; // fof must be water
+
+				if (rover->master->frontsector->damagetype != SD_FIRE && rover->master->frontsector->damagetype != SD_LAVA)
+					continue;  // fof must have fire or lava damage
 
 				// find heights of FOF
 				topheight = P_GetFOFTopZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
@@ -4341,7 +4339,8 @@ static void P_Boss2Thinker(mobj_t *mobj)
 	{
 		mobj->flags &= ~MF_NOGRAVITY;
 		A_Boss2Pogo(mobj);
-		P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
+		if (mobj->spawnpoint)
+			P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
 	}
 }
 
@@ -4470,7 +4469,8 @@ static void P_Boss3Thinker(mobj_t *mobj)
 			dummy->cusval = mobj->cusval;
 
 			CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", way0, way1, way2);
-			P_LinedefExecute(LE_PINCHPHASE+(mobj->cusval*LE_PARAMWIDTH), mobj, NULL);
+			if (mobj->spawnpoint)
+				P_LinedefExecute(mobj->spawnpoint->args[3], mobj, NULL);
 		}
 	}
 	else if (mobj->movecount) // Firing mode
@@ -4512,27 +4512,21 @@ static void P_Boss3Thinker(mobj_t *mobj)
 
 		if (!(mobj->flags2 & MF2_STRONGBOX))
 		{
-			thinker_t *th;
 			mobj_t *mo2;
+			INT32 i;
 
 			P_SetTarget(&mobj->tracer, NULL);
 
-			// scan the thinkers
-			// to find a point that matches
-			// the number
-			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+			// Find waypoint
+			TAG_ITER_THINGS(mobj->cusval, i)
 			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-					continue;
+				mo2 = mapthings[i].mobj;
 
-				mo2 = (mobj_t *)th;
-				if (mo2->type != MT_BOSS3WAYPOINT)
+				if (!mo2)
 					continue;
-				if (!mo2->spawnpoint)
-					continue;
-				if (mo2->spawnpoint->angle != mobj->threshold)
+				if (mo2->type != MT_BOSS3WAYPOINT)
 					continue;
-				if (mo2->spawnpoint->extrainfo != mobj->cusval)
+				if (mapthings[i].args[0] != mobj->threshold)
 					continue;
 
 				P_SetTarget(&mobj->tracer, mo2);
@@ -4636,12 +4630,14 @@ static void P_Boss3Thinker(mobj_t *mobj)
 // Move Boss4's sectors by delta.
 static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta)
 {
-	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
 	INT32 snum;
 	sector_t *sector;
 	boolean gotcage = false;
 
-	TAG_ITER_SECTORS(tag, snum)
+	if (!mobj->spawnpoint)
+		return false;
+
+	TAG_ITER_SECTORS(mobj->spawnpoint->args[4], snum)
 	{
 		sector = &sectors[snum];
 		sector->floorheight += delta;
@@ -4720,13 +4716,15 @@ static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
 // Destroy cage FOFs.
 static void P_Boss4DestroyCage(mobj_t *mobj)
 {
-	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
 	INT32 snum;
 	size_t a;
 	sector_t *sector, *rsec;
 	ffloor_t *rover;
 
-	TAG_ITER_SECTORS(tag, snum)
+	if (!mobj->spawnpoint)
+		return;
+
+	TAG_ITER_SECTORS(mobj->spawnpoint->args[4], snum)
 	{
 		sector = &sectors[snum];
 
@@ -4999,13 +4997,15 @@ static void P_Boss4Thinker(mobj_t *mobj)
 			{ // Proceed to pinch phase!
 				P_Boss4DestroyCage(mobj);
 				mobj->movedir = 3;
-				P_LinedefExecute(LE_PINCHPHASE + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
+				if (mobj->spawnpoint)
+					P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
 				P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0);
 				var1 = 3;
 				A_BossJetFume(mobj);
 				return;
 			}
-			P_LinedefExecute(LE_BOSS4DROP - (mobj->info->spawnhealth-mobj->health) + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
+			if (mobj->spawnpoint)
+				P_LinedefExecute(mobj->spawnpoint->args[5] - (mobj->info->spawnhealth-mobj->health), mobj, NULL);
 			// 1 -> 1.5 second timer
 			mobj->threshold = TICRATE+(TICRATE*(mobj->info->spawnhealth-mobj->health)/10);
 			if (mobj->threshold < 1)
@@ -5037,7 +5037,8 @@ static void P_Boss4Thinker(mobj_t *mobj)
 	{ // Proceed to pinch phase!
 		P_Boss4DestroyCage(mobj);
 		mobj->movedir = 3;
-		P_LinedefExecute(LE_PINCHPHASE + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
+		if (mobj->spawnpoint)
+			P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
 		var1 = 3;
 		A_BossJetFume(mobj);
 		return;
@@ -5177,7 +5178,8 @@ static void P_Boss7Thinker(mobj_t *mobj)
 			// Begin platform destruction
 			mobj->flags2 |= MF2_FRET;
 			P_SetMobjState(mobj, mobj->info->raisestate);
-			P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
+			if (mobj->spawnpoint)
+				P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
 		}
 	}
 	else if (mobj->state == &states[S_BLACKEGG_HITFACE4] && mobj->tics == mobj->state->tics)
@@ -5267,11 +5269,10 @@ static void P_Boss7Thinker(mobj_t *mobj)
 		fixed_t vertical, horizontal;
 		fixed_t airtime = 5*TICRATE;
 		INT32 waypointNum = 0;
-		thinker_t *th;
-		INT32 i;
+		INT32 i, j;
 		boolean foundgoop = false;
 		INT32 closestNum;
-		UINT8 extrainfo = (mobj->spawnpoint ? mobj->spawnpoint->extrainfo : 0);
+		UINT8 bossid = (mobj->spawnpoint ? mobj->spawnpoint->args[0] : 0);
 
 		// Looks for players in goop. If you find one, try to jump on him.
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -5291,19 +5292,15 @@ static void P_Boss7Thinker(mobj_t *mobj)
 				closestdist = INT32_MAX; // Just in case...
 
 				// Find waypoint he is closest to
-				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+				TAG_ITER_THINGS(bossid, j)
 				{
-					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-						continue;
+					mo2 = mapthings[j].mobj;
 
-					mo2 = (mobj_t *)th;
-					if (mo2->type != MT_BOSS3WAYPOINT)
+					if (!mo2)
 						continue;
-					if (!mo2->spawnpoint)
-						continue;
-					if (mo2->spawnpoint->extrainfo != extrainfo)
+					if (mo2->type != MT_BOSS3WAYPOINT)
 						continue;
-					if (mobj->health <= mobj->info->damage && !(mo2->spawnpoint->options & 7))
+					if (mobj->health <= mobj->info->damage && !mapthings[j].args[1])
 						continue; // don't jump to center
 
 					dist = P_AproxDistance(players[i].mo->x - mo2->x, players[i].mo->y - mo2->y);
@@ -5311,7 +5308,7 @@ static void P_Boss7Thinker(mobj_t *mobj)
 					if (!(closestNum == -1 || dist < closestdist))
 						continue;
 
-					closestNum = (mo2->spawnpoint->options & 7);
+					closestNum = mapthings[j].args[1];
 					closestdist = dist;
 					foundgoop = true;
 				}
@@ -5331,7 +5328,7 @@ static void P_Boss7Thinker(mobj_t *mobj)
 		}
 
 		if (mobj->tracer && mobj->tracer->type == MT_BOSS3WAYPOINT
-			&& mobj->tracer->spawnpoint && (mobj->tracer->spawnpoint->options & 7) == waypointNum)
+			&& mobj->tracer->spawnpoint && mobj->tracer->spawnpoint->args[1] == waypointNum)
 		{
 			if (P_RandomChance(FRACUNIT/2))
 				waypointNum++;
@@ -5344,28 +5341,25 @@ static void P_Boss7Thinker(mobj_t *mobj)
 				waypointNum = ((waypointNum + 5) % 5);
 		}
 
-		// scan the thinkers to find
-		// the waypoint to use
-		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		// Scan mapthings to find the waypoint to use
+		TAG_ITER_THINGS(bossid, i)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			mo2 = mapthings[i].mobj;
+
+			if (!mo2)
 				continue;
 
-			mo2 = (mobj_t *)th;
 			if (mo2->type != MT_BOSS3WAYPOINT)
 				continue;
-			if (!mo2->spawnpoint)
-				continue;
-			if ((mo2->spawnpoint->options & 7) != waypointNum)
-				continue;
-			if (mo2->spawnpoint->extrainfo != extrainfo)
+
+			if (mapthings[i].args[1] != waypointNum)
 				continue;
 
 			hitspot = mo2;
 			break;
 		}
 
-		if (hitspot == NULL)
+		if (!hitspot)
 		{
 			CONS_Debug(DBG_GAMELOGIC, "BlackEggman unable to find waypoint #%d!\n", waypointNum);
 			P_SetMobjState(mobj, mobj->info->spawnstate);
@@ -6034,7 +6028,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 						mobj->watertop = mobj->floorz + 16*FRACUNIT;
 					else
 						mobj->watertop = mobj->target->floorz + 16*FRACUNIT;
-					P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
+					if (mobj->spawnpoint)
+						P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
 
 #if 0
 					whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
@@ -6933,7 +6928,6 @@ static void P_RemoveOverlay(mobj_t *thing)
 	}
 }
 
-void A_BossDeath(mobj_t *mo);
 // AI for the Koopa boss.
 static void P_KoopaThinker(mobj_t *koopa)
 {
@@ -6941,7 +6935,8 @@ static void P_KoopaThinker(mobj_t *koopa)
 
 	if (koopa->watertop > koopa->z + koopa->height + FixedMul(128*FRACUNIT, koopa->scale) && koopa->health > 0)
 	{
-		A_BossDeath(koopa);
+		if (koopa->spawnpoint)
+			EV_DoCeiling(koopa->spawnpoint->args[0], NULL, raiseToHighest);
 		P_RemoveMobj(koopa);
 		return;
 	}
@@ -7225,8 +7220,7 @@ static void P_FlameJetSceneryThink(mobj_t *mobj)
 	else
 		flame->angle += FixedAngle(mobj->fuse<<FRACBITS);
 
-	strength = 20*FRACUNIT;
-	strength -= ((20*FRACUNIT)/16)*mobj->movedir;
+	strength = (mobj->movedir ? mobj->movedir : 80)<<(FRACBITS-2);
 
 	P_InstaThrust(flame, flame->angle, strength);
 	S_StartSound(flame, sfx_fire);
@@ -7256,8 +7250,7 @@ static void P_VerticalFlameJetSceneryThink(mobj_t *mobj)
 
 	flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
 
-	strength = 20*FRACUNIT;
-	strength -= ((20*FRACUNIT)/16)*mobj->movedir;
+	strength = (mobj->movedir ? mobj->movedir : 80)<<(FRACBITS-2);
 
 	// If deaf'd, the object spawns on the ceiling.
 	if (mobj->flags2 & MF2_AMBUSH)
@@ -7291,8 +7284,22 @@ static boolean P_ParticleGenSceneryThink(mobj_t *mobj)
 
 		mobj->fuse = (tic_t)mobj->reactiontime;
 
-		bottomheight = lines[line].frontsector->floorheight;
-		topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
+		if (line != -1)
+		{
+			bottomheight = lines[line].frontsector->floorheight;
+			topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
+		}
+		else if (mobj->flags2 & MF2_OBJECTFLIP)
+		{
+			bottomheight = mobj->z - mobj->extravalue1;
+			topheight = mobj->z - mobjinfo[(mobjtype_t)type].height;
+		}
+		else
+		{
+			bottomheight = mobj->z;
+			topheight = mobj->z + mobj->extravalue1 - mobjinfo[(mobjtype_t)type].height;
+		}
+
 
 		if (mobj->waterbottom != bottomheight || mobj->watertop != topheight)
 		{
@@ -7301,7 +7308,8 @@ static boolean P_ParticleGenSceneryThink(mobj_t *mobj)
 			else
 				mobj->health = 0;
 
-			mobj->z = ((mobj->flags2 & MF2_OBJECTFLIP) ? topheight : bottomheight);
+			if (line != -1)
+				mobj->z = ((mobj->flags2 & MF2_OBJECTFLIP) ? topheight : bottomheight);
 		}
 
 		if (!mobj->health)
@@ -8584,9 +8592,9 @@ static boolean P_EggRobo1Think(mobj_t *mobj)
 	{
 		fixed_t basex = mobj->cusval, basey = mobj->cvmem;
 
-		if (mobj->spawnpoint && mobj->spawnpoint->options & (MTF_AMBUSH|MTF_OBJECTSPECIAL))
+		if (mobj->spawnpoint && mobj->spawnpoint->args[0] != TMED_NONE)
 		{
-			angle_t sideang = mobj->movedir + ((mobj->spawnpoint->options & MTF_AMBUSH) ? ANGLE_90 : -ANGLE_90);
+			angle_t sideang = mobj->movedir + ((mobj->spawnpoint->args[0] == TMED_LEFT) ? ANGLE_90 : -ANGLE_90);
 			fixed_t oscillate = FixedMul(FINESINE(((leveltime * ANG1) >> (ANGLETOFINESHIFT + 2)) & FINEMASK), 250*mobj->scale);
 			basex += P_ReturnThrustX(mobj, sideang, oscillate);
 			basey += P_ReturnThrustY(mobj, sideang, oscillate);
@@ -8681,245 +8689,242 @@ static boolean P_EggRobo1Think(mobj_t *mobj)
 
 static void P_NiGHTSDroneThink(mobj_t *mobj)
 {
-	{
-		// variable setup
-		mobj_t *goalpost = NULL;
-		mobj_t *sparkle = NULL;
-		mobj_t *droneman = NULL;
+	mobj_t *goalpost = NULL;
+	mobj_t *sparkle = NULL;
+	mobj_t *droneman = NULL;
 
-		boolean flip = mobj->flags2 & MF2_OBJECTFLIP;
-		boolean topaligned = (mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
-		boolean middlealigned = (mobj->flags & MF_GRENADEBOUNCE) && !(mobj->flags & MF_SLIDEME);
-		boolean bottomoffsetted = !(mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
-		boolean flipchanged = false;
+	boolean flip = mobj->flags2 & MF2_OBJECTFLIP;
+	boolean topaligned = (mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
+	boolean middlealigned = (mobj->flags & MF_GRENADEBOUNCE) && !(mobj->flags & MF_SLIDEME);
+	boolean bottomoffsetted = !(mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
+	boolean flipchanged = false;
 
-		fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
+	fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
 
-		if (mobj->target && mobj->target->type == MT_NIGHTSDRONE_GOAL)
-		{
-			goalpost = mobj->target;
-			if (goalpost->target && goalpost->target->type == MT_NIGHTSDRONE_SPARKLING)
-				sparkle = goalpost->target;
-			if (goalpost->tracer && goalpost->tracer->type == MT_NIGHTSDRONE_MAN)
-				droneman = goalpost->tracer;
-		}
+	if (mobj->target && mobj->target->type == MT_NIGHTSDRONE_GOAL)
+	{
+		goalpost = mobj->target;
+		if (goalpost->target && goalpost->target->type == MT_NIGHTSDRONE_SPARKLING)
+			sparkle = goalpost->target;
+		if (goalpost->tracer && goalpost->tracer->type == MT_NIGHTSDRONE_MAN)
+			droneman = goalpost->tracer;
+	}
 
-		if (!goalpost || !sparkle || !droneman)
-			return;
+	if (!goalpost || !sparkle || !droneman)
+		return;
 
-		// did NIGHTSDRONE position, scale, flip, or flags change? all elements need to be synced
-		droneboxmandiff = max(mobj->height - droneman->height, 0);
-		dronemangoaldiff = max(droneman->height - goalpost->height, 0);
+	// did NIGHTSDRONE position, scale, flip, or flags change? all elements need to be synced
+	droneboxmandiff = max(mobj->height - droneman->height, 0);
+	dronemangoaldiff = max(droneman->height - goalpost->height, 0);
 
-		if (!(goalpost->flags2 & MF2_OBJECTFLIP) && (mobj->flags2 & MF2_OBJECTFLIP))
-		{
-			goalpost->eflags |= MFE_VERTICALFLIP;
-			goalpost->flags2 |= MF2_OBJECTFLIP;
-			sparkle->eflags |= MFE_VERTICALFLIP;
-			sparkle->flags2 |= MF2_OBJECTFLIP;
-			droneman->eflags |= MFE_VERTICALFLIP;
-			droneman->flags2 |= MF2_OBJECTFLIP;
-			flipchanged = true;
-		}
-		else if ((goalpost->flags2 & MF2_OBJECTFLIP) && !(mobj->flags2 & MF2_OBJECTFLIP))
-		{
-			goalpost->eflags &= ~MFE_VERTICALFLIP;
-			goalpost->flags2 &= ~MF2_OBJECTFLIP;
-			sparkle->eflags &= ~MFE_VERTICALFLIP;
-			sparkle->flags2 &= ~MF2_OBJECTFLIP;
-			droneman->eflags &= ~MFE_VERTICALFLIP;
-			droneman->flags2 &= ~MF2_OBJECTFLIP;
-			flipchanged = true;
-		}
+	if (!(goalpost->flags2 & MF2_OBJECTFLIP) && (mobj->flags2 & MF2_OBJECTFLIP))
+	{
+		goalpost->eflags |= MFE_VERTICALFLIP;
+		goalpost->flags2 |= MF2_OBJECTFLIP;
+		sparkle->eflags |= MFE_VERTICALFLIP;
+		sparkle->flags2 |= MF2_OBJECTFLIP;
+		droneman->eflags |= MFE_VERTICALFLIP;
+		droneman->flags2 |= MF2_OBJECTFLIP;
+		flipchanged = true;
+	}
+	else if ((goalpost->flags2 & MF2_OBJECTFLIP) && !(mobj->flags2 & MF2_OBJECTFLIP))
+	{
+		goalpost->eflags &= ~MFE_VERTICALFLIP;
+		goalpost->flags2 &= ~MF2_OBJECTFLIP;
+		sparkle->eflags &= ~MFE_VERTICALFLIP;
+		sparkle->flags2 &= ~MF2_OBJECTFLIP;
+		droneman->eflags &= ~MFE_VERTICALFLIP;
+		droneman->flags2 &= ~MF2_OBJECTFLIP;
+		flipchanged = true;
+	}
 
-		if (goalpost->destscale != mobj->destscale
-			|| goalpost->movefactor != mobj->z
-			|| goalpost->friction != mobj->height
-			|| flipchanged
-			|| goalpost->threshold != (INT32)(mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE)))
-		{
-			goalpost->destscale = sparkle->destscale = droneman->destscale = mobj->destscale;
+	if (goalpost->destscale != mobj->destscale
+		|| goalpost->movefactor != mobj->z
+		|| goalpost->friction != mobj->height
+		|| flipchanged
+		|| goalpost->threshold != (INT32)(mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE)))
+	{
+		goalpost->destscale = sparkle->destscale = droneman->destscale = mobj->destscale;
 
-			// straight copy-pasta from P_SpawnMapThing, case MT_NIGHTSDRONE
-			if (!flip)
+		// straight copy-pasta from P_SpawnMapThing, case MT_NIGHTSDRONE
+		if (!flip)
+		{
+			if (topaligned) // Align droneman to top of hitbox
 			{
-				if (topaligned) // Align droneman to top of hitbox
-				{
-					dronemanoffset = droneboxmandiff;
-					goaloffset = dronemangoaldiff/2 + dronemanoffset;
-				}
-				else if (middlealigned) // Align droneman to center of hitbox
-				{
-					dronemanoffset = droneboxmandiff/2;
-					goaloffset = dronemangoaldiff/2 + dronemanoffset;
-				}
-				else if (bottomoffsetted)
-				{
-					dronemanoffset = 24*FRACUNIT;
-					goaloffset = dronemangoaldiff + dronemanoffset;
-				}
-				else
-				{
-					dronemanoffset = 0;
-					goaloffset = dronemangoaldiff/2 + dronemanoffset;
-				}
-
-				sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
+				dronemanoffset = droneboxmandiff;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
 			}
-			else
+			else if (middlealigned) // Align droneman to center of hitbox
 			{
-				if (topaligned) // Align droneman to top of hitbox
-				{
-					dronemanoffset = 0;
-					goaloffset = dronemangoaldiff/2 + dronemanoffset;
-				}
-				else if (middlealigned) // Align droneman to center of hitbox
-				{
-					dronemanoffset = droneboxmandiff/2;
-					goaloffset = dronemangoaldiff/2 + dronemanoffset;
-				}
-				else if (bottomoffsetted)
-				{
-					dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
-					goaloffset = dronemangoaldiff + dronemanoffset;
-				}
-				else
-				{
-					dronemanoffset = droneboxmandiff;
-					goaloffset = dronemangoaldiff/2 + dronemanoffset;
-				}
-
-				sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
+				dronemanoffset = droneboxmandiff/2;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
 			}
-
-			P_TeleportMove(goalpost, mobj->x, mobj->y, mobj->z + goaloffset);
-			P_TeleportMove(sparkle, mobj->x, mobj->y, mobj->z + sparkleoffset);
-			if (goalpost->movefactor != mobj->z || goalpost->friction != mobj->height)
+			else if (bottomoffsetted)
+			{
+				dronemanoffset = 24*FRACUNIT;
+				goaloffset = dronemangoaldiff + dronemanoffset;
+			}
+			else
 			{
-				P_TeleportMove(droneman, mobj->x, mobj->y, mobj->z + dronemanoffset);
-				goalpost->movefactor = mobj->z;
-				goalpost->friction = mobj->height;
+				dronemanoffset = 0;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
 			}
-			goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
+
+			sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
 		}
 		else
 		{
-			if (goalpost->x != mobj->x || goalpost->y != mobj->y)
+			if (topaligned) // Align droneman to top of hitbox
+			{
+				dronemanoffset = 0;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
+			}
+			else if (middlealigned) // Align droneman to center of hitbox
+			{
+				dronemanoffset = droneboxmandiff/2;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
+			}
+			else if (bottomoffsetted)
+			{
+				dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
+				goaloffset = dronemangoaldiff + dronemanoffset;
+			}
+			else
 			{
-				P_TeleportMove(goalpost, mobj->x, mobj->y, goalpost->z);
-				P_TeleportMove(sparkle, mobj->x, mobj->y, sparkle->z);
+				dronemanoffset = droneboxmandiff;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
 			}
 
-			if (droneman->x != mobj->x || droneman->y != mobj->y)
-				P_TeleportMove(droneman, mobj->x, mobj->y,
-					droneman->z >= mobj->floorz && droneman->z <= mobj->ceilingz ? droneman->z : mobj->z);
+			sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
 		}
 
-		// now toggle states!
-		// GOAL mode?
-		if (sparkle->state >= &states[S_NIGHTSDRONE_SPARKLING1] && sparkle->state <= &states[S_NIGHTSDRONE_SPARKLING16])
+		P_TeleportMove(goalpost, mobj->x, mobj->y, mobj->z + goaloffset);
+		P_TeleportMove(sparkle, mobj->x, mobj->y, mobj->z + sparkleoffset);
+		if (goalpost->movefactor != mobj->z || goalpost->friction != mobj->height)
 		{
-			INT32 i;
-			boolean bonustime = false;
+			P_TeleportMove(droneman, mobj->x, mobj->y, mobj->z + dronemanoffset);
+			goalpost->movefactor = mobj->z;
+			goalpost->friction = mobj->height;
+		}
+		goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
+	}
+	else
+	{
+		if (goalpost->x != mobj->x || goalpost->y != mobj->y)
+		{
+			P_TeleportMove(goalpost, mobj->x, mobj->y, goalpost->z);
+			P_TeleportMove(sparkle, mobj->x, mobj->y, sparkle->z);
+		}
 
-			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
-				{
-					bonustime = true;
-					break;
-				}
+		if (droneman->x != mobj->x || droneman->y != mobj->y)
+			P_TeleportMove(droneman, mobj->x, mobj->y,
+				droneman->z >= mobj->floorz && droneman->z <= mobj->ceilingz ? droneman->z : mobj->z);
+	}
+
+	// now toggle states!
+	// GOAL mode?
+	if (sparkle->state >= &states[S_NIGHTSDRONE_SPARKLING1] && sparkle->state <= &states[S_NIGHTSDRONE_SPARKLING16])
+	{
+		INT32 i;
+		boolean bonustime = false;
 
-			if (!bonustime)
+		for (i = 0; i < MAXPLAYERS; i++)
+			if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 			{
-				CONS_Debug(DBG_NIGHTSBASIC, "Removing goal post\n");
-				if (goalpost && goalpost->state != &states[S_INVISIBLE])
-					P_SetMobjState(goalpost, S_INVISIBLE);
-				if (sparkle && sparkle->state != &states[S_INVISIBLE])
-					P_SetMobjState(sparkle, S_INVISIBLE);
+				bonustime = true;
+				break;
 			}
+
+		if (!bonustime)
+		{
+			CONS_Debug(DBG_NIGHTSBASIC, "Removing goal post\n");
+			if (goalpost && goalpost->state != &states[S_INVISIBLE])
+				P_SetMobjState(goalpost, S_INVISIBLE);
+			if (sparkle && sparkle->state != &states[S_INVISIBLE])
+				P_SetMobjState(sparkle, S_INVISIBLE);
+		}
+	}
+	// Invisible/bouncing mode.
+	else
+	{
+		INT32 i;
+		boolean bonustime = false;
+		fixed_t zcomp;
+
+		// Bouncy bouncy!
+		if (!flip)
+		{
+			if (topaligned)
+				zcomp = droneboxmandiff + mobj->z;
+			else if (middlealigned)
+				zcomp = (droneboxmandiff/2) + mobj->z;
+			else if (bottomoffsetted)
+				zcomp = mobj->z + FixedMul(24*FRACUNIT, mobj->scale);
+			else
+				zcomp = mobj->z;
 		}
-		// Invisible/bouncing mode.
 		else
 		{
-			INT32 i;
-			boolean bonustime = false;
-			fixed_t zcomp;
-
-			// Bouncy bouncy!
-			if (!flip)
-			{
-				if (topaligned)
-					zcomp = droneboxmandiff + mobj->z;
-				else if (middlealigned)
-					zcomp = (droneboxmandiff/2) + mobj->z;
-				else if (bottomoffsetted)
-					zcomp = mobj->z + FixedMul(24*FRACUNIT, mobj->scale);
-				else
-					zcomp = mobj->z;
-			}
+			if (topaligned)
+				zcomp = mobj->z;
+			else if (middlealigned)
+				zcomp = (droneboxmandiff/2) + mobj->z;
+			else if (bottomoffsetted)
+				zcomp = mobj->z + droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
 			else
+				zcomp = mobj->z + droneboxmandiff;
+		}
+
+		droneman->angle += ANG10;
+		if (!flip && droneman->z <= zcomp)
+			droneman->momz = FixedMul(5*FRACUNIT, droneman->scale);
+		else if (flip && droneman->z >= zcomp)
+			droneman->momz = FixedMul(-5*FRACUNIT, droneman->scale);
+
+		// state switching logic
+		for (i = 0; i < MAXPLAYERS; i++)
+			if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 			{
-				if (topaligned)
-					zcomp = mobj->z;
-				else if (middlealigned)
-					zcomp = (droneboxmandiff/2) + mobj->z;
-				else if (bottomoffsetted)
-					zcomp = mobj->z + droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
-				else
-					zcomp = mobj->z + droneboxmandiff;
+				bonustime = true;
+				break;
 			}
 
-			droneman->angle += ANG10;
-			if (!flip && droneman->z <= zcomp)
-				droneman->momz = FixedMul(5*FRACUNIT, droneman->scale);
-			else if (flip && droneman->z >= zcomp)
-				droneman->momz = FixedMul(-5*FRACUNIT, droneman->scale);
-
-			// state switching logic
+		if (bonustime)
+		{
+			CONS_Debug(DBG_NIGHTSBASIC, "Adding goal post\n");
+			if (!(droneman->flags2 & MF2_DONTDRAW))
+				droneman->flags2 |= MF2_DONTDRAW;
+			if (goalpost->state == &states[S_INVISIBLE])
+				P_SetMobjState(goalpost, mobjinfo[goalpost->type].meleestate);
+			if (sparkle->state == &states[S_INVISIBLE])
+				P_SetMobjState(sparkle, mobjinfo[sparkle->type].meleestate);
+		}
+		else if (!G_IsSpecialStage(gamemap))
+		{
 			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
+				if (playeringame[i] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
 				{
-					bonustime = true;
+					bonustime = true; // variable reuse
 					break;
 				}
 
 			if (bonustime)
 			{
-				CONS_Debug(DBG_NIGHTSBASIC, "Adding goal post\n");
-				if (!(droneman->flags2 & MF2_DONTDRAW))
-					droneman->flags2 |= MF2_DONTDRAW;
-				if (goalpost->state == &states[S_INVISIBLE])
-					P_SetMobjState(goalpost, mobjinfo[goalpost->type].meleestate);
-				if (sparkle->state == &states[S_INVISIBLE])
-					P_SetMobjState(sparkle, mobjinfo[sparkle->type].meleestate);
+				// show droneman if at least one player is non-nights
+				if (goalpost->state != &states[S_INVISIBLE])
+					P_SetMobjState(goalpost, S_INVISIBLE);
+				if (sparkle->state != &states[S_INVISIBLE])
+					P_SetMobjState(sparkle, S_INVISIBLE);
+				if (droneman->state != &states[mobjinfo[droneman->type].meleestate])
+					P_SetMobjState(droneman, mobjinfo[droneman->type].meleestate);
+				if (droneman->flags2 & MF2_DONTDRAW)
+					droneman->flags2 &= ~MF2_DONTDRAW;
 			}
-			else if (!G_IsSpecialStage(gamemap))
+			else
 			{
-				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
-					{
-						bonustime = true; // variable reuse
-						break;
-					}
-
-				if (bonustime)
-				{
-					// show droneman if at least one player is non-nights
-					if (goalpost->state != &states[S_INVISIBLE])
-						P_SetMobjState(goalpost, S_INVISIBLE);
-					if (sparkle->state != &states[S_INVISIBLE])
-						P_SetMobjState(sparkle, S_INVISIBLE);
-					if (droneman->state != &states[mobjinfo[droneman->type].meleestate])
-						P_SetMobjState(droneman, mobjinfo[droneman->type].meleestate);
-					if (droneman->flags2 & MF2_DONTDRAW)
-						droneman->flags2 &= ~MF2_DONTDRAW;
-				}
-				else
-				{
-					// else, hide it
-					if (!(droneman->flags2 & MF2_DONTDRAW))
-						droneman->flags2 |= MF2_DONTDRAW;
-				}
+				// else, hide it
+				if (!(droneman->flags2 & MF2_DONTDRAW))
+					droneman->flags2 |= MF2_DONTDRAW;
 			}
 		}
 	}
@@ -9238,6 +9243,118 @@ static void P_DragonbomberThink(mobj_t *mobj)
 #undef DRAGONTURNSPEED
 }
 
+static mobj_t *pushmobj;
+
+#define PUSH_FACTOR 7
+
+static inline boolean PIT_PushThing(mobj_t *thing)
+{
+	if (thing->eflags & MFE_PUSHED)
+		return false;
+
+	if (thing->player && thing->player->powers[pw_carry] == CR_ROPEHANG)
+		return false;
+
+	if (!pushmobj)
+		return false;
+
+	if (!pushmobj->spawnpoint)
+		return false;
+
+	// Allow this to affect pushable objects at some point?
+	if (thing->player && !(thing->flags & (MF_NOGRAVITY|MF_NOCLIP)))
+	{
+		INT32 dist;
+		INT32 speed;
+		INT32 sx = pushmobj->x;
+		INT32 sy = pushmobj->y;
+		INT32 sz = pushmobj->z;
+		fixed_t radius = pushmobj->spawnpoint->args[0] << FRACBITS;
+
+		if (pushmobj->spawnpoint->args[2] & TMPP_NOZFADE)
+			dist = P_AproxDistance(thing->x - sx, thing->y - sy);
+		else
+		{
+			// Make sure the Z is in range
+			if (thing->z < sz - radius || thing->z > sz + radius)
+				return false;
+
+			dist = P_AproxDistance(P_AproxDistance(thing->x - sx, thing->y - sy), thing->z - sz);
+		}
+
+		speed = (abs(pushmobj->spawnpoint->args[1]) - ((dist>>FRACBITS)>>1))<<(FRACBITS - PUSH_FACTOR - 1);
+
+		// If speed <= 0, you're outside the effective radius. You also have
+		// to be able to see the push/pull source point.
+
+		// Written with bits and pieces of P_HomingAttack
+		if ((speed > 0) && (P_CheckSight(thing, pushmobj)))
+		{
+			fixed_t tmpmomx, tmpmomy, tmpmomz;
+
+			if (pushmobj->spawnpoint->args[2] & TMPP_PUSHZ)
+			{
+				tmpmomx = FixedMul(FixedDiv(sx - thing->x, dist), speed);
+				tmpmomy = FixedMul(FixedDiv(sy - thing->y, dist), speed);
+				tmpmomz = FixedMul(FixedDiv(sz - thing->z, dist), speed);
+			}
+			else
+			{
+				angle_t pushangle = R_PointToAngle2(thing->x, thing->y, sx, sy) >> ANGLETOFINESHIFT;
+				tmpmomx = FixedMul(speed, FINECOSINE(pushangle));
+				tmpmomy = FixedMul(speed, FINESINE(pushangle));
+				tmpmomz = 0;
+			}
+
+			if (pushmobj->spawnpoint->args[1] > 0) // away!
+			{
+				tmpmomx *= -1;
+				tmpmomy *= -1;
+				tmpmomz *= -1;
+			}
+
+			thing->momx += tmpmomx;
+			thing->momy += tmpmomy;
+			thing->momz += tmpmomz;
+
+			if (thing->player)
+			{
+				thing->player->cmomx += tmpmomx;
+				thing->player->cmomy += tmpmomy;
+				thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
+				thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
+			}
+		}
+	}
+
+	if (!(pushmobj->spawnpoint->args[2] & TMPP_NONEXCLUSIVE))
+		thing->eflags |= MFE_PUSHED;
+
+	return true;
+}
+
+static void P_PointPushThink(mobj_t *mobj)
+{
+	INT32 xl, xh, yl, yh, bx, by;
+	fixed_t radius;
+
+	if (!mobj->spawnpoint)
+		return;
+
+	// Seek out all pushable things within the force radius of this
+	// point pusher. Crosses sectors, so use blockmap.
+	radius = mobj->spawnpoint->args[0] << FRACBITS;
+
+	pushmobj = mobj;
+	xl = (unsigned)(mobj->x - radius - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
+	xh = (unsigned)(mobj->x + radius - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
+	yl = (unsigned)(mobj->y - radius - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
+	yh = (unsigned)(mobj->y + radius - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
+	for (bx = xl; bx <= xh; bx++)
+		for (by = yl; by <= yh; by++)
+			P_BlockThingsIterator(bx, by, PIT_PushThing);
+}
+
 static boolean P_MobjRegularThink(mobj_t *mobj)
 {
 	if ((mobj->flags & MF_ENEMY) && (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1))
@@ -9249,7 +9366,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 	switch (mobj->type)
 	{
 	case MT_WALLSPIKEBASE:
-		if (!mobj->target) {
+		if (!mobj->target)
+		{
 			P_RemoveMobj(mobj);
 			return false;
 		}
@@ -9616,13 +9734,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 		break;
 	case MT_BLUEFLAG:
 	case MT_REDFLAG:
-	{
-		sector_t* sec2;
-		sec2 = P_ThingOnSpecial3DFloor(mobj);
-		if ((sec2 && GETSECSPECIAL(sec2->special, 4) == 2) || (GETSECSPECIAL(mobj->subsector->sector->special, 4) == 2))
+		if (P_MobjTouchingSectorSpecialFlag(mobj, SSF_RETURNFLAG))
 			mobj->fuse = 1; // Return to base.
 		break;
-	}
 	case MT_SPINDUST: // Spindash dust
 		mobj->momx = FixedMul(mobj->momx, (3*FRACUNIT)/4); // originally 50000
 		mobj->momy = FixedMul(mobj->momy, (3*FRACUNIT)/4); // same
@@ -9729,6 +9843,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 		else
 			mobj->rollangle = R_PointToAngle2(0, 0, P_MobjFlip(mobj)*mobj->momz, (mobj->scale << 1) - min(abs(mobj->momz), mobj->scale << 1));
 		break;
+	case MT_PUSH:
+		P_PointPushThink(mobj);
+		break;
 	case MT_SPINFIRE:
 		if (mobj->flags & MF_NOGRAVITY)
 		{
@@ -9953,16 +10070,9 @@ static boolean P_FuseThink(mobj_t *mobj)
 	case MT_METALSONIC_BATTLE:
 		break; // don't remove
 	case MT_SPIKE:
-		P_SetMobjState(mobj, mobj->state->nextstate);
-		mobj->fuse = mobj->info->speed;
-		if (mobj->spawnpoint)
-			mobj->fuse += mobj->spawnpoint->angle;
-		break;
 	case MT_WALLSPIKE:
 		P_SetMobjState(mobj, mobj->state->nextstate);
-		mobj->fuse = mobj->info->speed;
-		if (mobj->spawnpoint)
-			mobj->fuse += (mobj->spawnpoint->angle / 360);
+		mobj->fuse = mobj->spawnpoint ? mobj->spawnpoint->args[0] : mobj->info->speed;
 		break;
 	case MT_NIGHTSCORE:
 		P_RemoveMobj(mobj);
@@ -10035,7 +10145,7 @@ void P_MobjThinker(mobj_t *mobj)
 	if (mobj->flags & MF_NOTHINK)
 		return;
 
-	if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<<mobj->spawnpoint->extrainfo)))
+	if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<<mobj->spawnpoint->args[0])))
 		return;
 
 	// Remove dead target/tracer.
@@ -10052,16 +10162,8 @@ void P_MobjThinker(mobj_t *mobj)
 
 	tmfloorthing = tmhitthing = NULL;
 
-	// Sector special (2,8) allows ANY mobj to trigger a linedef exec
-	if (mobj->subsector && GETSECSPECIAL(mobj->subsector->sector->special, 2) == 8)
-	{
-		sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
-		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
-		{
-			mtag_t tag = Tag_FGet(&sec2->tags);
-			P_LinedefExecute(tag, mobj, sec2);
-		}
-	}
+	// Sector flag MSF_TRIGGERLINE_MOBJ allows ANY mobj to trigger a linedef exec
+	P_CheckMobjTrigger(mobj, false);
 
 	if (mobj->scale != mobj->destscale)
 		P_MobjScaleThink(mobj); // Slowly scale up/down to reach your destscale.
@@ -10131,10 +10233,12 @@ void P_MobjThinker(mobj_t *mobj)
 	if (mobj->flags2 & MF2_FIRING)
 		P_FiringThink(mobj);
 
-	if (mobj->flags & MF_AMBIENT)
+	if (mobj->type == MT_AMBIENT)
 	{
-		if (!(leveltime % mobj->health) && mobj->info->seesound)
-			S_StartSound(mobj, mobj->info->seesound);
+		if (leveltime % mobj->health)
+			return;
+		if (mobj->threshold)
+			S_StartSound(mobj, mobj->threshold);
 		return;
 	}
 
@@ -10276,28 +10380,10 @@ boolean P_RailThinker(mobj_t *mobj)
 // Unquick, unoptimized function for pushables
 void P_PushableThinker(mobj_t *mobj)
 {
-	sector_t *sec;
-
 	I_Assert(mobj != NULL);
 	I_Assert(!P_MobjWasRemoved(mobj));
 
-	sec = mobj->subsector->sector;
-
-	if (GETSECSPECIAL(sec->special, 2) == 1 && mobj->z == sec->floorheight)
-	{
-		mtag_t tag = Tag_FGet(&sec->tags);
-		P_LinedefExecute(tag, mobj, sec);
-	}
-
-//	else if (GETSECSPECIAL(sec->special, 2) == 8)
-	{
-		sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
-		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
-		{
-			mtag_t tag = Tag_FGet(&sec2->tags);
-			P_LinedefExecute(tag, mobj, sec2);
-		}
-	}
+	P_CheckMobjTrigger(mobj, true);
 
 	// it has to be pushable RIGHT NOW for this part to happen
 	if (mobj->flags & MF_PUSHABLE && !(mobj->momx || mobj->momy))
@@ -10939,8 +11025,8 @@ static precipmobj_t *P_SpawnPrecipMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype
 
 	if (mobj->floorz != starting_floorz)
 		mobj->precipflags |= PCF_FOF;
-	else if (GETSECSPECIAL(mobj->subsector->sector->special, 1) == 7
-	 || GETSECSPECIAL(mobj->subsector->sector->special, 1) == 6
+	else if (mobj->subsector->sector->damagetype == SD_DEATHPITNOTILT
+	 || mobj->subsector->sector->damagetype == SD_DEATHPITTILT
 	 || mobj->subsector->sector->floorpic == skyflatnum)
 		mobj->precipflags |= PCF_PIT;
 
@@ -11167,7 +11253,7 @@ void P_SpawnPrecipitation(void)
 		if (curWeather == PRECIP_SNOW)
 		{
 			// Not in a sector with visible sky -- exception for NiGHTS.
-			if ((!(maptol & TOL_NIGHTS) && (precipsector->sector->ceilingpic != skyflatnum)) == !(precipsector->sector->flags & SF_INVERTPRECIP))
+			if ((!(maptol & TOL_NIGHTS) && (precipsector->sector->ceilingpic != skyflatnum)) == !(precipsector->sector->flags & MSF_INVERTPRECIP))
 				continue;
 
 			rainmo = P_SpawnSnowMobj(x, y, height, MT_SNOWFLAKE);
@@ -11180,7 +11266,7 @@ void P_SpawnPrecipitation(void)
 		else // everything else.
 		{
 			// Not in a sector with visible sky.
-			if ((precipsector->sector->ceilingpic != skyflatnum) == !(precipsector->sector->flags & SF_INVERTPRECIP))
+			if ((precipsector->sector->ceilingpic != skyflatnum) == !(precipsector->sector->flags & MSF_INVERTPRECIP))
 				continue;
 
 			rainmo = P_SpawnRainMobj(x, y, height, MT_RAIN);
@@ -11586,9 +11672,9 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
 	{
 		fixed_t offset = mthing->z << FRACBITS;
 
-		// Flagging a player's ambush will make them start on the ceiling
+		// Setting the spawnpoint's args[0] will make the player start on the ceiling
 		// Objectflip inverts
-		if (!!(mthing->options & MTF_AMBUSH) ^ !!(mthing->options & MTF_OBJECTFLIP))
+		if (!!(mthing->args[0]) ^ !!(mthing->options & MTF_OBJECTFLIP))
 			z = ceilingspawn - offset;
 		else
 			z = floor + offset;
@@ -11598,7 +11684,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
 			mobj->eflags |= MFE_VERTICALFLIP;
 			mobj->flags2 |= MF2_OBJECTFLIP;
 		}
-		if (mthing->options & MTF_AMBUSH)
+		if (mthing->args[0])
 			P_SetPlayerMobjState(mobj, S_PLAY_FALL);
 		else if (metalrecording)
 			P_SetPlayerMobjState(mobj, S_PLAY_WAIT);
@@ -11712,11 +11798,6 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 
 	switch (mobjtype)
 	{
-	// Bumpers never spawn flipped.
-	case MT_NIGHTSBUMPER:
-		flip = false;
-		break;
-
 	// Objects with a non-zero default height.
 	case MT_CRAWLACOMMANDER:
 	case MT_DETON:
@@ -11736,14 +11817,14 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 			dz = 288*FRACUNIT;
 		break;
 
-	// Horizontal springs, may float additional units with MTF_AMBUSH.
+	// Horizontal springs, float additional units unless args[0] is set.
 	case MT_YELLOWHORIZ:
 	case MT_REDHORIZ:
 	case MT_BLUEHORIZ:
-		offset += mthing->options & MTF_AMBUSH ? 16*FRACUNIT : 0;
+		offset += mthing->args[0] ? 0 : 16*FRACUNIT;
 		break;
 
-	// Ring-like items, may float additional units with MTF_AMBUSH.
+	// Ring-like items, float additional units unless args[0] is set.
 	case MT_SPIKEBALL:
 	case MT_EMERHUNT:
 	case MT_EMERALDSPAWN:
@@ -11757,13 +11838,13 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 	case MT_BOMBSPHERE:
 	case MT_NIGHTSCHIP:
 	case MT_NIGHTSSTAR:
-		offset += mthing->options & MTF_AMBUSH ? 24*FRACUNIT : 0;
+		offset += mthing->args[0] ? 0 : 24*FRACUNIT;
 		break;
 
 	// Remaining objects.
 	default:
 		if (P_WeaponOrPanel(mobjtype))
-			offset += mthing->options & MTF_AMBUSH ? 24*FRACUNIT : 0;
+			offset += mthing->args[0] ? 0 : 24*FRACUNIT;
 	}
 
 	if (!(dz + offset)) // Snap to the surfaces when there's no offset set.
@@ -11825,8 +11906,8 @@ static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing)
 		return true;
 	}
 	else if (mthing->type == 750 // Slope vertex point (formerly chaos spawn)
-		     || (mthing->type >= 600 && mthing->type <= 609) // Special placement patterns
-		     || mthing->type == 1705 || mthing->type == 1713) // Hoops
+		     || (mthing->type >= 600 && mthing->type <= 611) // Special placement patterns
+		     || mthing->type == 1713) // Hoops
 		return true; // These are handled elsewhere.
 	else if (mthing->type == mobjinfo[MT_EMERHUNT].doomednum)
 	{
@@ -11870,7 +11951,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 		runemeraldmanager = true;
 		break;
 	case MT_ROSY:
-		if (!(G_CoopGametype() || (mthing->options & MTF_EXTRA)))
+		if (!(G_CoopGametype() || mthing->args[0]))
 			return false; // she doesn't hang out here
 
 		if (!(netgame || multiplayer) && players[consoleplayer].skin == 3)
@@ -11994,7 +12075,7 @@ static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i)
 			case 2: // Unchanging
 				if (i == MT_MYSTERY_BOX)
 					return MT_NULL; // don't spawn
-				mthing->options &= ~(MTF_AMBUSH|MTF_OBJECTSPECIAL); // no random respawning!
+				mthing->args[1] = TMMR_SAME; // no random respawning!
 				return i;
 			case 3: // Don't spawn
 				return MT_NULL;
@@ -12024,8 +12105,7 @@ static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i)
 
 	if (modeattacking && i == MT_1UP_BOX) // 1UPs -->> Score TVs
 	{
-		// Either or, doesn't matter which.
-		if (mthing->options & (MTF_AMBUSH | MTF_OBJECTSPECIAL))
+		if (mthing->args[2])
 			return MT_SCORE10K_BOX; // 10,000
 		else
 			return MT_SCORE1K_BOX; // 1,000
@@ -12043,7 +12123,7 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
 
 	while (emblem)
 	{
-		if ((emblem->type == ET_GLOBAL || emblem->type == ET_SKIN) && emblem->tag == mthing->angle)
+		if ((emblem->type == ET_GLOBAL || emblem->type == ET_SKIN) && emblem->tag == Tag_FGet(&mthing->tags))
 			break;
 
 		emblem = M_GetLevelEmblems(-1);
@@ -12051,7 +12131,7 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
 
 	if (!emblem)
 	{
-		CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, mthing->angle);
+		CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, Tag_FGet(&mthing->tags));
 		return false;
 	}
 
@@ -12113,59 +12193,20 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 	mobjflag_t mflagsapply;
 	mobjflag2_t mflags2apply;
 	mobjeflag_t meflagsapply;
-	INT32 line;
 	const size_t mthingi = (size_t)(mthing - mapthings);
 
-	// Find the corresponding linedef special, using angle as tag
-	line = Tag_FindLineSpecial(9, mthing->angle);
-
-	if (line == -1)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Mace chain (mapthing #%s) needs to be tagged to a #9 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
-		return false;
-	}
-	/*
-	mapthing -
-	MTF_AMBUSH :
-		MT_SPRINGBALLPOINT - upgrade from yellow to red spring
-		anything else - bigger mace/chain theory
-	MTF_OBJECTSPECIAL - force silent
-	MTF_GRAVFLIP - flips objects, doesn't affect chain arrangements
-	Parameter value : number of "spokes"
-
-	linedef -
-	ML_NOCLIMB :
-		MT_CHAINPOINT/MT_CHAINMACEPOINT with ML_EFFECT1 applied - Direction not controllable
-		anything else - no functionality
-	ML_EFFECT1 : Swings instead of spins
-	ML_EFFECT2 : Linktype is replaced with macetype for all spokes not ending in chains (inverted for MT_FIREBARPOINT)
-	ML_EFFECT3 : Spawn a bonus linktype at the hinge point
-	ML_EFFECT4 : Don't clip inside the ground
-	ML_EFFECT5 : Don't stop thinking when too far away
-	*/
-	mlength = abs(lines[line].dx >> FRACBITS);
-	mspeed = abs(lines[line].dy >> (FRACBITS - 4));
-	mphase = (sides[lines[line].sidenum[0]].textureoffset >> FRACBITS) % 360;
-	if ((mminlength = -sides[lines[line].sidenum[0]].rowoffset >> FRACBITS) < 0)
-		mminlength = 0;
-	else if (mminlength > mlength - 1)
-		mminlength = mlength - 1;
-	mpitch = (lines[line].frontsector->floorheight >> FRACBITS) % 360;
-	myaw = (lines[line].frontsector->ceilingheight >> FRACBITS) % 360;
-
-	mnumspokes = mthing->extrainfo + 1;
+	mlength = abs(mthing->args[0]);
+	mnumspokes = mthing->args[1] + 1;
 	mspokeangle = FixedAngle((360*FRACUNIT)/mnumspokes) >> ANGLETOFINESHIFT;
-
-	if (lines[line].backsector)
-	{
-		mpinch = (lines[line].backsector->floorheight >> FRACBITS) % 360;
-		mroll = (lines[line].backsector->ceilingheight >> FRACBITS) % 360;
-		mnumnospokes = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS);
-		if ((mwidth = sides[lines[line].sidenum[1]].rowoffset >> FRACBITS) < 0)
-			mwidth = 0;
-	}
-	else
-		mpinch = mroll = mnumnospokes = mwidth = 0;
+	mwidth = max(0, mthing->args[2]);
+	mspeed = abs(mthing->args[3] << 4);
+	mphase = mthing->args[4] % 360;
+	mpinch = mthing->args[5] % 360;
+	mnumnospokes = mthing->args[6];
+	mminlength = max(0, min(mlength - 1, mthing->args[7]));
+	mpitch = mthing->pitch % 360;
+	myaw = mthing->angle % 360;
+	mroll = mthing->roll % 360;
 
 	CONS_Debug(DBG_GAMELOGIC, "Mace/Chain (mapthing #%s):\n"
 		"Length is %d (minus %d)\n"
@@ -12196,26 +12237,23 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 	switch (mobj->type)
 	{
 	case MT_SPRINGBALLPOINT:
-		macetype = ((mthing->options & MTF_AMBUSH)
+		macetype = ((mthing->args[8] & TMM_DOUBLESIZE)
 			? MT_REDSPRINGBALL
 			: MT_YELLOWSPRINGBALL);
 		chainlink = MT_SMALLMACECHAIN;
 		break;
 	case MT_FIREBARPOINT:
-		macetype = ((mthing->options & MTF_AMBUSH)
+		macetype = ((mthing->args[8] & TMM_DOUBLESIZE)
 			? MT_BIGFIREBAR
 			: MT_SMALLFIREBAR);
 		chainlink = MT_NULL;
 		break;
 	case MT_CUSTOMMACEPOINT:
-		macetype = (mobjtype_t)sides[lines[line].sidenum[0]].toptexture;
-		if (lines[line].backsector)
-			chainlink = (mobjtype_t)sides[lines[line].sidenum[1]].toptexture;
-		else
-			chainlink = MT_NULL;
+		macetype = mthing->stringargs[0] ? get_number(mthing->stringargs[0]) : MT_NULL;
+		chainlink = mthing->stringargs[1] ? get_number(mthing->stringargs[1]) : MT_NULL;
 		break;
 	case MT_CHAINPOINT:
-		if (mthing->options & MTF_AMBUSH)
+		if (mthing->args[8] & TMM_DOUBLESIZE)
 		{
 			macetype = MT_BIGGRABCHAIN;
 			chainlink = MT_BIGMACECHAIN;
@@ -12228,7 +12266,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 		mchainlike = true;
 		break;
 	default:
-		if (mthing->options & MTF_AMBUSH)
+		if (mthing->args[8] & TMM_DOUBLESIZE)
 		{
 			macetype = MT_BIGMACE;
 			chainlink = MT_BIGMACECHAIN;
@@ -12255,11 +12293,11 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 	firsttype = macetype;
 
 	// Adjustable direction
-	if (lines[line].flags & ML_NOCLIMB)
+	if (mthing->args[8] & TMM_ALLOWYAWCONTROL)
 		mobj->flags |= MF_SLIDEME;
 
 	// Swinging
-	if (lines[line].flags & ML_EFFECT1)
+	if (mthing->args[8] & TMM_SWING)
 	{
 		mobj->flags2 |= MF2_STRONGBOX;
 		mmin = ((mnumnospokes > 1) ? 1 : 0);
@@ -12268,11 +12306,11 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 		mmin = mnumspokes;
 
 	// If over distance away, don't move UNLESS this flag is applied
-	if (lines[line].flags & ML_EFFECT5)
+	if (mthing->args[8] & TMM_ALWAYSTHINK)
 		mobj->flags2 |= MF2_BOSSNOTRAP;
 
 	// Make the links the same type as the end - repeated below
-	if ((mobj->type != MT_CHAINPOINT) && (((lines[line].flags & ML_EFFECT2) == ML_EFFECT2) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
+	if ((mobj->type != MT_CHAINPOINT) && (((mthing->args[8] & TMM_MACELINKS) == TMM_MACELINKS) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
 	{
 		linktype = macetype;
 		radiusfactor = 2; // Double the radius.
@@ -12284,7 +12322,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 		mchainlike = (firsttype == chainlink);
 	widthfactor = (mchainlike ? 1 : 2);
 
-	mflagsapply = ((lines[line].flags & ML_EFFECT4) ? 0 : (MF_NOCLIP | MF_NOCLIPHEIGHT));
+	mflagsapply = (mthing->args[8] & TMM_CLIP) ? 0 : (MF_NOCLIP|MF_NOCLIPHEIGHT);
 	mflags2apply = ((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0);
 	meflagsapply = ((mthing->options & MTF_OBJECTFLIP) ? MFE_VERTICALFLIP : 0);
 
@@ -12310,14 +12348,14 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 	hprev = spawnee;\
 }
 
-	mdosound = (mspeed && !(mthing->options & MTF_OBJECTSPECIAL));
-	mdocenter = (macetype && (lines[line].flags & ML_EFFECT3));
+	mdosound = (mspeed && !(mthing->args[8] & TMM_SILENT));
+	mdocenter = (macetype && (mthing->args[8] & TMM_CENTERLINK));
 
 	// The actual spawning of spokes
 	while (mnumspokes-- > 0)
 	{
 		// Offsets
-		if (lines[line].flags & ML_EFFECT1) // Swinging
+		if (mthing->args[8] & TMM_SWING) // Swinging
 			mroll = (mroll - mspokeangle) & FINEMASK;
 		else // Spinning
 			mphase = (mphase - mspokeangle) & FINEMASK;
@@ -12328,7 +12366,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 				continue;
 
 			linktype = chainlink;
-			firsttype = ((mthing->options & MTF_AMBUSH) ? MT_BIGGRABCHAIN : MT_SMALLGRABCHAIN);
+			firsttype = ((mthing->args[8] & TMM_DOUBLESIZE) ? MT_BIGGRABCHAIN : MT_SMALLGRABCHAIN);
 			mmaxlength = 1 + (mlength - 1) * radiusfactor;
 			radiusfactor = widthfactor = 1;
 		}
@@ -12337,7 +12375,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 			if (mobj->type == MT_CHAINMACEPOINT)
 			{
 				// Make the links the same type as the end - repeated above
-				if (lines[line].flags & ML_EFFECT2)
+				if (mthing->args[8] & TMM_MACELINKS)
 				{
 					linktype = macetype;
 					radiusfactor = 2;
@@ -12420,50 +12458,43 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 
 static boolean P_SetupParticleGen(mapthing_t *mthing, mobj_t *mobj)
 {
-	fixed_t radius, speed;
+	fixed_t radius, speed, zdist;
 	INT32 type, numdivisions, anglespeed, ticcount;
 	angle_t angledivision;
 	INT32 line;
 	const size_t mthingi = (size_t)(mthing - mapthings);
 
-	// Find the corresponding linedef special, using angle as tag
-	line = Tag_FindLineSpecial(15, mthing->angle);
-
-	if (line == -1)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Particle generator (mapthing #%s) needs to be tagged to a #15 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
-		return false;
-	}
+	// Find the corresponding linedef special, using args[6] as tag
+	line = mthing->args[6] ? Tag_FindLineSpecial(15, mthing->args[6]) : -1;
 
-	if (sides[lines[line].sidenum[0]].toptexture)
-		type = sides[lines[line].sidenum[0]].toptexture; // Set as object type in p_setup.c...
-	else
-		type = (INT32)MT_PARTICLE;
+	type = mthing->stringargs[0] ? get_number(mthing->stringargs[0]) : MT_PARTICLE;
 
-	if (!lines[line].backsector
-		|| (ticcount = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS)) < 1)
+	ticcount = mthing->args[4];
+	if (ticcount < 1)
 		ticcount = 3;
 
-	numdivisions = mthing->z;
+	numdivisions = mthing->args[0];
 
 	if (numdivisions)
 	{
-		radius = R_PointToDist2(lines[line].v1->x, lines[line].v1->y, lines[line].v2->x, lines[line].v2->y);
-		anglespeed = (sides[lines[line].sidenum[0]].rowoffset >> FRACBITS) % 360;
+		radius = mthing->args[1] << FRACBITS;
+		anglespeed = (mthing->args[3]) % 360;
 		angledivision = 360/numdivisions;
 	}
 	else
 	{
-		numdivisions = 1; // Simple trick to make A_ParticleSpawn simpler.
+		numdivisions = 1; // Simple trick to make P_ParticleGenSceneryThink simpler.
 		radius = 0;
 		anglespeed = 0;
 		angledivision = 0;
 	}
 
-	speed = abs(sides[lines[line].sidenum[0]].textureoffset);
+	speed = abs(mthing->args[2]) << FRACBITS;
 	if (mthing->options & MTF_OBJECTFLIP)
 		speed *= -1;
 
+	zdist = abs(mthing->args[5]) << FRACBITS;
+
 	CONS_Debug(DBG_GAMELOGIC, "Particle Generator (mapthing #%s):\n"
 		"Radius is %d\n"
 		"Speed is %d\n"
@@ -12471,9 +12502,14 @@ static boolean P_SetupParticleGen(mapthing_t *mthing, mobj_t *mobj)
 		"Numdivisions is %d\n"
 		"Angledivision is %d\n"
 		"Type is %d\n"
-		"Tic seperation is %d\n",
+		"Tic separation is %d\n",
 		sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, type, ticcount);
 
+	if (line == -1)
+		CONS_Debug(DBG_GAMELOGIC, "Spawn Z is %d\nZ dist is %d\n", mobj->z, zdist);
+	else
+		CONS_Debug(DBG_GAMELOGIC, "Heights are taken from control sector\n");
+
 	mobj->angle = 0;
 	mobj->movefactor = speed;
 	mobj->lastlook = numdivisions;
@@ -12482,21 +12518,19 @@ static boolean P_SetupParticleGen(mapthing_t *mthing, mobj_t *mobj)
 	mobj->friction = radius;
 	mobj->threshold = type;
 	mobj->reactiontime = ticcount;
+	mobj->extravalue1 = zdist;
 	mobj->cvmem = line;
 	mobj->watertop = mobj->waterbottom = 0;
 	return true;
 }
 
-static boolean P_SetupNiGHTSDrone(mapthing_t* mthing, mobj_t* mobj)
+static boolean P_SetupNiGHTSDrone(mapthing_t *mthing, mobj_t *mobj)
 {
 	boolean flip = mthing->options & MTF_OBJECTFLIP;
-	boolean topaligned = (mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
-	boolean middlealigned = (mthing->options & MTF_EXTRA) && !(mthing->options & MTF_OBJECTSPECIAL);
-	boolean bottomoffsetted = !(mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
-
-	INT16 timelimit = mthing->angle & 0xFFF;
-	fixed_t hitboxradius = ((mthing->angle & 0xF000) >> 12)*32*FRACUNIT;
-	fixed_t hitboxheight = mthing->extrainfo*32*FRACUNIT;
+	INT16 timelimit = mthing->args[0];
+	fixed_t hitboxheight = mthing->args[1] << FRACBITS;
+	fixed_t hitboxradius = mthing->args[2] << FRACBITS;
+	INT32 dronemanalignment = mthing->args[3];
 	fixed_t oldheight = mobj->height;
 	fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
 
@@ -12511,6 +12545,9 @@ static boolean P_SetupNiGHTSDrone(mapthing_t* mthing, mobj_t* mobj)
 	else
 		mobj->height = mobjinfo[MT_NIGHTSDRONE].height;
 
+	if (mthing->args[4])
+		mobj->flags2 |= MF2_AMBUSH; //Kill player upon time up
+
 	droneboxmandiff = max(mobj->height - mobjinfo[MT_NIGHTSDRONE_MAN].height, 0);
 	dronemangoaldiff = max(mobjinfo[MT_NIGHTSDRONE_MAN].height - mobjinfo[MT_NIGHTSDRONE_GOAL].height, 0);
 
@@ -12519,25 +12556,25 @@ static boolean P_SetupNiGHTSDrone(mapthing_t* mthing, mobj_t* mobj)
 
 	if (!flip)
 	{
-		if (topaligned) // Align droneman to top of hitbox
-		{
-			dronemanoffset = droneboxmandiff;
-			goaloffset = dronemangoaldiff/2 + dronemanoffset;
-		}
-		else if (middlealigned) // Align droneman to center of hitbox
-		{
-			dronemanoffset = droneboxmandiff/2;
-			goaloffset = dronemangoaldiff/2 + dronemanoffset;
-		}
-		else if (bottomoffsetted)
-		{
-			dronemanoffset = 24*FRACUNIT;
-			goaloffset = dronemangoaldiff + dronemanoffset;
-		}
-		else
+		switch (dronemanalignment)
 		{
-			dronemanoffset = 0;
-			goaloffset = dronemangoaldiff/2 + dronemanoffset;
+			case TMDA_BOTTOMOFFSET:
+			default:
+				dronemanoffset = FixedMul(24*FRACUNIT, mobj->scale);
+				goaloffset = dronemangoaldiff + dronemanoffset;
+				break;
+			case TMDA_BOTTOM:
+				dronemanoffset = 0;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
+				break;
+			case TMDA_MIDDLE:
+				dronemanoffset = droneboxmandiff/2;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
+				break;
+			case TMDA_TOP:
+				dronemanoffset = droneboxmandiff;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
+				break;
 		}
 
 		sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
@@ -12547,25 +12584,25 @@ static boolean P_SetupNiGHTSDrone(mapthing_t* mthing, mobj_t* mobj)
 		mobj->eflags |= MFE_VERTICALFLIP;
 		mobj->flags2 |= MF2_OBJECTFLIP;
 
-		if (topaligned) // Align droneman to top of hitbox
-		{
-			dronemanoffset = 0;
-			goaloffset = dronemangoaldiff/2 + dronemanoffset;
-		}
-		else if (middlealigned) // Align droneman to center of hitbox
+		switch (dronemanalignment)
 		{
-			dronemanoffset = droneboxmandiff/2;
-			goaloffset = dronemangoaldiff/2 + dronemanoffset;
-		}
-		else if (bottomoffsetted)
-		{
-			dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
-			goaloffset = dronemangoaldiff + dronemanoffset;
-		}
-		else
-		{
-			dronemanoffset = droneboxmandiff;
-			goaloffset = dronemangoaldiff/2 + dronemanoffset;
+			case TMDA_BOTTOMOFFSET:
+			default:
+				dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
+				goaloffset = dronemangoaldiff + dronemanoffset;
+				break;
+			case TMDA_BOTTOM:
+				dronemanoffset = droneboxmandiff;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
+				break;
+			case TMDA_MIDDLE:
+				dronemanoffset = droneboxmandiff/2;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
+				break;
+			case TMDA_TOP:
+				dronemanoffset = 0;
+				goaloffset = dronemangoaldiff/2 + dronemanoffset;
+				break;
 		}
 
 		sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
@@ -12573,9 +12610,9 @@ static boolean P_SetupNiGHTSDrone(mapthing_t* mthing, mobj_t* mobj)
 
 	// spawn visual elements
 	{
-		mobj_t* goalpost = P_SpawnMobjFromMobj(mobj, 0, 0, goaloffset, MT_NIGHTSDRONE_GOAL);
-		mobj_t* sparkle = P_SpawnMobjFromMobj(mobj, 0, 0, sparkleoffset, MT_NIGHTSDRONE_SPARKLING);
-		mobj_t* droneman = P_SpawnMobjFromMobj(mobj, 0, 0, dronemanoffset, MT_NIGHTSDRONE_MAN);
+		mobj_t *goalpost = P_SpawnMobjFromMobj(mobj, 0, 0, goaloffset, MT_NIGHTSDRONE_GOAL);
+		mobj_t *sparkle = P_SpawnMobjFromMobj(mobj, 0, 0, sparkleoffset, MT_NIGHTSDRONE_SPARKLING);
+		mobj_t *droneman = P_SpawnMobjFromMobj(mobj, 0, 0, dronemanoffset, MT_NIGHTSDRONE_MAN);
 
 		P_SetTarget(&mobj->target, goalpost);
 		P_SetTarget(&goalpost->target, sparkle);
@@ -12591,12 +12628,21 @@ static boolean P_SetupNiGHTSDrone(mapthing_t* mthing, mobj_t* mobj)
 
 		// Remember position preference for later
 		mobj->flags &= ~(MF_SLIDEME|MF_GRENADEBOUNCE);
-		if (topaligned)
-			mobj->flags |= MF_SLIDEME;
-		else if (middlealigned)
-			mobj->flags |= MF_GRENADEBOUNCE;
-		else if (!bottomoffsetted)
-			mobj->flags |= MF_SLIDEME|MF_GRENADEBOUNCE;
+		switch (dronemanalignment)
+		{
+			case TMDA_BOTTOMOFFSET:
+			default:
+				mobj->flags |= MF_SLIDEME|MF_GRENADEBOUNCE;
+				break;
+			case TMDA_BOTTOM:
+				break;
+			case TMDA_MIDDLE:
+				mobj->flags |= MF_GRENADEBOUNCE;
+				break;
+			case TMDA_TOP:
+				mobj->flags |= MF_SLIDEME;
+				break;
+		}
 
 		// Remember old Z position and flags for correction detection
 		goalpost->movefactor = mobj->z;
@@ -12647,6 +12693,37 @@ static boolean P_SetupBooster(mapthing_t* mthing, mobj_t* mobj, boolean strong)
 	return true;
 }
 
+static mobj_t *P_MakeSoftwareCorona(mobj_t *mo, INT32 height)
+{
+	mobj_t *corona = P_SpawnMobjFromMobj(mo, 0, 0, height<<FRACBITS, MT_PARTICLE);
+	corona->sprite = SPR_FLAM;
+	corona->frame = (FF_FULLBRIGHT|FF_TRANS90|12);
+	corona->tics = -1;
+	return corona;
+}
+
+static boolean P_MapAlreadyHasStarPost(mobj_t *mobj)
+{
+	thinker_t *th;
+	mobj_t *mo2;
+
+	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 == mobj)
+			continue;
+
+		if (mo2->type == MT_STARPOST && mo2->health == mobj->health)
+			return true;
+	}
+
+	return false;
+}
+
 static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 {
 	boolean override = LUA_HookMapThingSpawn(mobj, mthing);
@@ -12674,24 +12751,32 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 			break;
 		}
 
-		if (mthing->options & MTF_OBJECTSPECIAL)
+		if (mthing->args[0])
 			skyboxcenterpnts[tag] = mobj;
 		else
 			skyboxviewpnts[tag] = mobj;
 		break;
 	}
 	case MT_EGGSTATUE:
-		if (mthing->options & MTF_EXTRA)
+		if (mthing->args[1])
 		{
 			mobj->color = SKINCOLOR_GOLD;
 			mobj->colorized = true;
 		}
 		break;
+	case MT_EGGMOBILE2:
+		if (!mthing->args[5])
+			mobj->flags2 |= MF2_AMBUSH;
+		break;
 	case MT_EGGMOBILE3:
-		mobj->cusval = mthing->extrainfo;
+		mobj->cusval = mthing->args[0];
+		break;
+	case MT_BUBBLES:
+		if (mthing->args[0])
+			mobj->flags2 |= MF2_AMBUSH;
 		break;
 	case MT_FAN:
-		if (mthing->options & MTF_OBJECTSPECIAL)
+		if (mthing->args[1] & TMF_INVISIBLE)
 		{
 			P_UnsetThingPosition(mobj);
 			if (sector_list)
@@ -12702,16 +12787,34 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 			mobj->flags |= MF_NOSECTOR; // this flag basically turns it invisible
 			P_SetThingPosition(mobj);
 		}
-		if (mthing->angle)
-			mobj->health = mthing->angle;
+		if (mthing->args[1] & TMF_NODISTANCECHECK)
+			mobj->flags2 |= MF2_AMBUSH;
+		if (mthing->args[0])
+			mobj->health = mthing->args[0];
 		else
 			mobj->health = FixedMul(mobj->subsector->sector->ceilingheight - mobj->subsector->sector->floorheight, 3*(FRACUNIT/4)) >> FRACBITS;
 		break;
-	case MT_METALSONIC_RACE:
-	case MT_METALSONIC_BATTLE:
 	case MT_FANG:
+		if (mthing->args[5] & TMF_GRAYSCALE)
+		{
+			mobj->color = SKINCOLOR_SILVER;
+			mobj->colorized = true;
+			mobj->flags2 |= MF2_SLIDEPUSH;
+		}
+		if (mthing->args[5] & TMF_SKIPINTRO)
+			mobj->flags2 |= MF2_AMBUSH;
+		break;
+	case MT_METALSONIC_BATTLE:
+		if (mthing->args[5])
+		{
+			mobj->color = SKINCOLOR_SILVER;
+			mobj->colorized = true;
+			mobj->flags2 |= MF2_SLIDEPUSH;
+		}
+		break;
+	case MT_METALSONIC_RACE:
 	case MT_ROSY:
-		if (mthing->options & MTF_EXTRA)
+		if (mthing->args[0])
 		{
 			mobj->color = SKINCOLOR_SILVER;
 			mobj->colorized = true;
@@ -12719,33 +12822,28 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		}
 		break;
 	case MT_BALLOON:
-		if (mthing->angle > 0)
-			mobj->color = ((mthing->angle - 1) % (numskincolors - 1)) + 1;
-		break;
-#define makesoftwarecorona(mo, h) \
-			corona = P_SpawnMobjFromMobj(mo, 0, 0, h<<FRACBITS, MT_PARTICLE);\
-			corona->sprite = SPR_FLAM;\
-			corona->frame = (FF_FULLBRIGHT|FF_TRANS90|12);\
-			corona->tics = -1
+		if (mthing->stringargs[0])
+			mobj->color = get_number(mthing->stringargs[0]);
+		if (mthing->args[0])
+			mobj->flags2 |= MF2_AMBUSH;
+		break;
 	case MT_FLAME:
-		if (mthing->options & MTF_EXTRA)
+		if (mthing->args[0])
 		{
-			mobj_t *corona;
-			makesoftwarecorona(mobj, 20);
+			mobj_t *corona = P_MakeSoftwareCorona(mobj, 20);
 			P_SetScale(corona, (corona->destscale = mobj->scale*3));
 			P_SetTarget(&mobj->tracer, corona);
 		}
 		break;
 	case MT_FLAMEHOLDER:
-		if (!(mthing->options & MTF_OBJECTSPECIAL)) // Spawn the fire
+		if (!(mthing->args[0] & TMFH_NOFLAME)) // Spawn the fire
 		{
 			mobj_t *flame = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_FLAME);
 			P_SetTarget(&flame->target, mobj);
 			flame->flags2 |= MF2_BOSSNOTRAP;
-			if (mthing->options & MTF_EXTRA)
+			if (mthing->args[0] & TMFH_CORONA)
 			{
-				mobj_t *corona;
-				makesoftwarecorona(flame, 20);
+				mobj_t *corona = P_MakeSoftwareCorona(flame, 20);
 				P_SetScale(corona, (corona->destscale = flame->scale*3));
 				P_SetTarget(&flame->tracer, corona);
 			}
@@ -12753,17 +12851,13 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		break;
 	case MT_CANDLE:
 	case MT_CANDLEPRICKET:
-		if (mthing->options & MTF_EXTRA)
-		{
-			mobj_t *corona;
-			makesoftwarecorona(mobj, ((mobj->type == MT_CANDLE) ? 42 : 176));
-		}
+		if (mthing->args[0])
+			P_MakeSoftwareCorona(mobj, ((mobj->type == MT_CANDLE) ? 42 : 176));
 		break;
-#undef makesoftwarecorona
 	case MT_JACKO1:
 	case MT_JACKO2:
 	case MT_JACKO3:
-		if (!(mthing->options & MTF_EXTRA)) // take the torch out of the crafting recipe
+		if (!(mthing->args[0])) // take the torch out of the crafting recipe
 		{
 			mobj_t *overlay = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY);
 			P_SetTarget(&overlay->target, mobj);
@@ -12771,17 +12865,15 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		}
 		break;
 	case MT_WATERDRIP:
-		mobj->tics = 3*TICRATE + mthing->angle;
+		mobj->tics = 3*TICRATE + mthing->args[0];
 		break;
 	case MT_FLAMEJET:
 	case MT_VERTICALFLAMEJET:
-		mobj->threshold = (mthing->angle >> 10) & 7;
-		mobj->movecount = (mthing->angle >> 13);
-
-		mobj->threshold *= (TICRATE/2);
-		mobj->movecount *= (TICRATE/2);
-
-		mobj->movedir = mthing->extrainfo;
+		mobj->movecount = mthing->args[0];
+		mobj->threshold = mthing->args[1];
+		mobj->movedir = mthing->args[2];
+		if (mthing->args[3])
+			mobj->flags2 |= MF2_AMBUSH;
 		break;
 	case MT_MACEPOINT:
 	case MT_CHAINMACEPOINT:
@@ -12796,60 +12888,50 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		if (!P_SetupParticleGen(mthing, mobj))
 			return false;
 		break;
-	case MT_ROCKSPAWNER:
-		mobj->threshold = mthing->angle;
-		mobj->movecount = mthing->extrainfo;
-		break;
 	case MT_POPUPTURRET:
-		if (mthing->angle)
-			mobj->threshold = mthing->angle;
-		else
-			mobj->threshold = (TICRATE*2)-1;
+		mobj->threshold = mthing->args[0] ? mthing->args[0] : (TICRATE*2)-1;
 		break;
 	case MT_NIGHTSBUMPER:
-		// Lower 4 bits specify the angle of
-		// the bumper in 30 degree increments.
-		mobj->threshold = (mthing->options & 15) % 12; // It loops over, etc
+		// Pitch of the bumper is set in 30 degree increments.
+		mobj->threshold = ((mthing->pitch/30) + 3) % 12;
 		P_SetMobjState(mobj, mobj->info->spawnstate + mobj->threshold);
 		break;
 	case MT_EGGCAPSULE:
-		if (mthing->angle <= 0)
-			mthing->angle = 20; // prevent 0 health
-
-		mobj->health = mthing->angle;
-		mobj->threshold = min(mthing->extrainfo, 7);
+		mobj->threshold = min(mthing->args[0], 7);
+		mobj->health = mthing->args[1];
+		if (mobj->health <= 0)
+			mobj->health = 20; // prevent 0 health
 		break;
 	case MT_TUBEWAYPOINT:
 	{
-		UINT8 sequence = mthing->angle >> 8;
-		UINT8 id = mthing->angle & 255;
+		UINT8 sequence = mthing->args[0];
+		UINT8 id = mthing->args[1];
 		mobj->health = id;
 		mobj->threshold = sequence;
 		P_AddWaypoint(sequence, id, mobj);
 		break;
 	}
 	case MT_IDEYAANCHOR:
-		mobj->health = mthing->extrainfo;
+		mobj->health = mthing->args[0];
 		break;
 	case MT_NIGHTSDRONE:
 		if (!P_SetupNiGHTSDrone(mthing, mobj))
 			return false;
 		break;
 	case MT_HIVEELEMENTAL:
-		if (mthing->extrainfo)
-			mobj->extravalue1 = mthing->extrainfo;
+		if (mthing->args[0])
+			mobj->extravalue1 = mthing->args[0];
 		break;
 	case MT_GLAREGOYLE:
 	case MT_GLAREGOYLEUP:
 	case MT_GLAREGOYLEDOWN:
 	case MT_GLAREGOYLELONG:
-		if (mthing->angle >= 360)
-			mobj->tics += 7*(mthing->angle/360) + 1; // starting delay
+		mobj->tics += mthing->args[1]; // starting delay
 		break;
 	case MT_DSZSTALAGMITE:
 	case MT_DSZ2STALAGMITE:
 	case MT_KELP:
-		if (mthing->options & MTF_OBJECTSPECIAL) { // make mobj twice as big as normal
+		if (mthing->args[0]) { // make mobj twice as big as normal
 			P_SetScale(mobj, 2*mobj->scale); // not 2*FRACUNIT in case of something like the old ERZ3 mode
 			mobj->destscale = mobj->scale;
 		}
@@ -12870,15 +12952,15 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		{
 			segment = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_TUTORIALLEAF);
 			segment->angle = mobj->angle + FixedAngle(i*60*FRACUNIT);
-			P_SetMobjState(segment, S_TUTORIALLEAF1 + mthing->extrainfo);
+			P_SetMobjState(segment, S_TUTORIALLEAF1 + mthing->args[0]);
 		}
 		for (i = 0; i < 3; i++)
 		{
 			segment = P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWER);
 			segment->angle = mobj->angle + FixedAngle(i*120*FRACUNIT);
-			P_SetMobjState(segment, S_TUTORIALFLOWER1 + mthing->extrainfo);
+			P_SetMobjState(segment, S_TUTORIALFLOWER1 + mthing->args[0]);
 		}
-		P_SetMobjState(P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWERF), S_TUTORIALFLOWERF1 + mthing->extrainfo);
+		P_SetMobjState(P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWERF), S_TUTORIALFLOWERF1 + mthing->args[0]);
 	}
 	break;
 	case MT_CEZPOLE1:
@@ -12908,20 +12990,20 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 	}
 	break;
 	case MT_SMASHINGSPIKEBALL:
-		if (mthing->angle > 0)
-			mobj->tics += mthing->angle;
+		if (mthing->args[0] > 0)
+			mobj->tics += mthing->args[0];
 		break;
 	case MT_LAVAFALL:
-		mobj->fuse = 30 + mthing->angle;
-		if (mthing->options & MTF_AMBUSH)
+		mobj->fuse = 30 + mthing->args[0];
+		if (mthing->args[1])
 		{
 			P_SetScale(mobj, 2*mobj->scale);
 			mobj->destscale = mobj->scale;
 		}
 		break;
 	case MT_PYREFLY:
-		//start on fire if Ambush flag is set, otherwise behave normally
-		if (mthing->options & MTF_AMBUSH)
+		//start on fire if args[0], otherwise behave normally
+		if (mthing->args[0])
 		{
 			P_SetMobjState(mobj, mobj->info->meleestate);
 			mobj->extravalue2 = 2;
@@ -12949,20 +13031,19 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 			return false;
 		break;
 	case MT_AXIS:
-		// Inverted if uppermost bit is set
-		if (mthing->angle & 16384)
+		// Inverted if args[3] is set
+		if (mthing->args[3])
 			mobj->flags2 |= MF2_AMBUSH;
 
-		if (mthing->angle > 0)
-			mobj->radius = (mthing->angle & 16383) << FRACBITS;
+		mobj->radius = abs(mthing->args[2]) << FRACBITS;
 		// FALLTHRU
 	case MT_AXISTRANSFER:
 	case MT_AXISTRANSFERLINE:
 		// Mare it belongs to
-		mobj->threshold = min(mthing->extrainfo, 7);
+		mobj->threshold = min(mthing->args[0], 7);
 
 		// # in the mare
-		mobj->health = mthing->options;
+		mobj->health = mthing->args[1];
 
 		mobj->flags2 |= MF2_AXIS;
 		break;
@@ -12972,7 +13053,7 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		mobj->health = 1 << (tokenbits - 1);
 		break;
 	case MT_CYBRAKDEMON:
-		if (mthing->options & MTF_AMBUSH)
+		if (mthing->args[6] & TMB_BARRIER)
 		{
 			mobj_t* elecmobj;
 			elecmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_CYBRAKDEMON_ELECTRIC_BARRIER);
@@ -12983,54 +13064,21 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		}
 		break;
 	case MT_STARPOST:
-	{
-		thinker_t* th;
-		mobj_t* mo2;
-		boolean foundanother = false;
-
-		if (mthing->extrainfo)
-			// Allow thing Parameter to define star post num too!
-			// For starposts above param 15 (the 16th), add 360 to the angle like before and start parameter from 1 (NOT 0)!
-			// So the 16th starpost is angle=0 param=15, the 17th would be angle=360 param=1.
-			// This seems more intuitive for mappers to use until UDMF is ready, since most SP maps won't have over 16 consecutive star posts.
-			mobj->health = mthing->extrainfo + (mthing->angle/360)*15 + 1;
-		else
-			// Old behavior if Parameter is 0; add 360 to the angle for each consecutive star post.
-			mobj->health = (mthing->angle/360) + 1;
-
-		// See if other starposts exist in this level that have the same value.
-		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 == mobj)
-				continue;
-
-			if (mo2->type == MT_STARPOST && mo2->health == mobj->health)
-			{
-				foundanother = true;
-				break;
-			}
-		}
-
-		if (!foundanother)
+		mobj->health = mthing->args[0] + 1;
+		if (!P_MapAlreadyHasStarPost(mobj))
 			numstarposts++;
 		break;
-	}
 	case MT_SPIKE:
 		// Pop up spikes!
-		if (mthing->options & MTF_OBJECTSPECIAL)
+		if (mthing->args[0])
 		{
 			mobj->flags &= ~MF_SCENERY;
-			mobj->fuse = (16 - mthing->extrainfo)*(mthing->angle + mobj->info->speed)/16;
-			if (mthing->options & MTF_EXTRA)
-				P_SetMobjState(mobj, mobj->info->meleestate);
+			mobj->fuse = mthing->args[1];
 		}
-		// Use per-thing collision for spikes if the deaf flag isn't checked.
-		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
+		if (mthing->args[2] & TMSF_RETRACTED)
+			P_SetMobjState(mobj, mobj->info->meleestate);
+		// Use per-thing collision for spikes unless the intangible flag is checked.
+		if (!(mthing->args[2] & TMSF_INTANGIBLE) && !metalrecording)
 		{
 			P_UnsetThingPosition(mobj);
 			mobj->flags &= ~(MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT);
@@ -13040,22 +13088,21 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		break;
 	case MT_WALLSPIKE:
 		// Pop up spikes!
-		if (mthing->options & MTF_OBJECTSPECIAL)
+		if (mthing->args[0])
 		{
 			mobj->flags &= ~MF_SCENERY;
-			mobj->fuse = (16 - mthing->extrainfo)*((mthing->angle/360) + mobj->info->speed)/16;
-			if (mthing->options & MTF_EXTRA)
-				P_SetMobjState(mobj, mobj->info->meleestate);
+			mobj->fuse = mthing->args[1];
 		}
-		// Use per-thing collision for spikes if the deaf flag isn't checked.
-		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
+		if (mthing->args[2] & TMSF_RETRACTED)
+			P_SetMobjState(mobj, mobj->info->meleestate);
+		// Use per-thing collision for spikes unless the intangible flag is checked.
+		if (!(mthing->args[2] & TMSF_INTANGIBLE) && !metalrecording)
 		{
 			P_UnsetThingPosition(mobj);
 			mobj->flags &= ~(MF_NOBLOCKMAP | MF_NOCLIPHEIGHT);
 			mobj->flags |= MF_SOLID;
 			P_SetThingPosition(mobj);
 		}
-
 		// spawn base
 		{
 			const angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS); // the mobj's own angle hasn't been set quite yet so...
@@ -13078,12 +13125,13 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		break;
 	case MT_BIGTUMBLEWEED:
 	case MT_LITTLETUMBLEWEED:
-		if (mthing->options & MTF_AMBUSH)
+		if (mthing->args[0])
 		{
 			fixed_t offset = FixedMul(16*FRACUNIT, mobj->scale);
 			mobj->momx += P_RandomChance(FRACUNIT/2) ? offset : -offset;
 			mobj->momy += P_RandomChance(FRACUNIT/2) ? offset : -offset;
 			mobj->momz += offset;
+			mobj->flags2 |= MF2_AMBUSH;
 		}
 		break;
 	case MT_REDFLAG:
@@ -13094,87 +13142,119 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		blueflag = mobj;
 		bflagpoint = mobj->spawnpoint;
 		break;
-	case MT_PUSH:
-	case MT_PULL:
-		mobj->health = 0; // Default behaviour: pushing uses XY, fading uses XYZ
-
-		if (mthing->options & MTF_AMBUSH)
-			mobj->health |= 1; // If ambush is set, push using XYZ
-		if (mthing->options & MTF_OBJECTSPECIAL)
-			mobj->health |= 2; // If object special is set, fade using XY
-
-		if (G_IsSpecialStage(gamemap))
-			P_SetMobjState(mobj, (mobj->type == MT_PUSH) ? S_GRAVWELLGREEN : S_GRAVWELLRED);
-		break;
 	case MT_NIGHTSSTAR:
 		if (maptol & TOL_XMAS)
 			P_SetMobjState(mobj, mobj->info->seestate);
 		break;
+	case MT_YELLOWDIAG:
+	case MT_REDDIAG:
+	case MT_BLUEDIAG:
+		mobj->angle = FixedAngle(mthing->angle << FRACBITS);
+		if (mthing->args[0] & TMDS_NOGRAVITY)
+			mobj->flags |= MF_NOGRAVITY;
+		if (mthing->args[0] & TMDS_ROTATEEXTRA)
+			mobj->angle += ANGLE_22h;
+		*doangle = false;
+		break;
+	case MT_AMBIENT:
+		if (mthing->stringargs[0])
+			mobj->threshold = get_number(mthing->stringargs[0]);
+		mobj->health = mthing->args[0] ? mthing->args[0] : TICRATE;
+		break;
+	case MT_GOLDBUZZ:
+	case MT_REDBUZZ:
+	case MT_JETTBOMBER:
+	case MT_JETTGUNNER:
+	case MT_ROBOHOOD:
+	case MT_CRUSHSTACEAN:
+	case MT_BANPYURA:
+	case MT_BUMBLEBORE:
+	case MT_CACOLANTERN:
+	case MT_PIAN:
+		if (mthing->args[0])
+			mobj->flags2 |= MF2_AMBUSH;
+		break;
+	case MT_EGGGUARD:
+		if (mthing->args[1])
+			mobj->flags2 |= MF2_AMBUSH;
+		break;
+	case MT_STEAM:
+		if (mthing->args[0])
+			mobj->flags2 |= MF2_AMBUSH;
+		break;
+	case MT_SALOONDOORCENTER:
+		if (mthing->args[0])
+			mobj->flags2 |= MF2_AMBUSH;
+		break;
+	case MT_MINECARTSWITCHPOINT:
+		if (mthing->args[0])
+			mobj->flags2 |= MF2_AMBUSH;
+		break;
+	case MT_ROLLOUTSPAWN:
+		if (mthing->args[0])
+			mobj->flags2 |= MF2_AMBUSH;
+		break;
 	default:
 		break;
 	}
 
 	if (mobj->flags & MF_BOSS)
 	{
-		if (mthing->options & MTF_OBJECTSPECIAL) // No egg trap for this boss
+		if (mthing->args[1]) // No egg trap for this boss
 			mobj->flags2 |= MF2_BOSSNOTRAP;
 	}
-
-	return true;
-}
-
-static void P_SetAmbush(mobj_t *mobj)
-{
-	if (mobj->type == MT_YELLOWDIAG || mobj->type == MT_REDDIAG || mobj->type == MT_BLUEDIAG)
-		mobj->angle += ANGLE_22h;
-
 	if (mobj->flags & MF_NIGHTSITEM)
 	{
+		// Requires you to be in bonus time to activate
+		if (mthing->args[0] & TMNI_BONUSONLY)
+			mobj->flags2 |= MF2_STRONGBOX;
 		// Spawn already displayed
-		mobj->flags |= MF_SPECIAL;
-		mobj->flags &= ~MF_NIGHTSITEM;
+		if (mthing->args[0] & TMNI_REVEAL)
+		{
+			mobj->flags |= MF_SPECIAL;
+			mobj->flags &= ~MF_NIGHTSITEM;
+		}
 	}
-
 	if (mobj->flags & MF_PUSHABLE)
-		mobj->flags &= ~MF_PUSHABLE;
-
-	if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
 	{
-		// flag for strong/weak random boxes
-		// any monitor with nonzero speed is allowed to respawn like this
-		mobj->flags2 |= MF2_AMBUSH;
+		switch (mthing->args[0])
+		{
+			case TMP_NORMAL:
+			default:
+				break;
+			case TMP_SLIDE:
+				mobj->flags2 |= MF2_SLIDEPUSH;
+				mobj->flags |= MF_BOUNCE;
+				break;
+			case TMP_IMMOVABLE:
+				mobj->flags &= ~MF_PUSHABLE;
+				break;
+			case TMP_CLASSIC:
+				mobj->flags2 |= MF2_CLASSICPUSH;
+				break;
+		}
 	}
-
-	else if (mobj->type != MT_AXIS &&
-		mobj->type != MT_AXISTRANSFER &&
-		mobj->type != MT_AXISTRANSFERLINE &&
-		mobj->type != MT_NIGHTSBUMPER &&
-		mobj->type != MT_STARPOST)
-		mobj->flags2 |= MF2_AMBUSH;
-}
-
-static void P_SetObjectSpecial(mobj_t *mobj)
-{
-	if (mobj->type == MT_YELLOWDIAG || mobj->type == MT_REDDIAG || mobj->type == MT_BLUEDIAG)
-		mobj->flags |= MF_NOGRAVITY;
-
-	if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
+	if (mobj->flags & MF_SPRING && mobj->info->painchance == 3)
 	{
-		// flag for strong/weak random boxes
-		// any monitor with nonzero speed is allowed to respawn like this
-		mobj->flags2 |= MF2_STRONGBOX;
+		if (mthing->args[0])
+			mobj->flags2 |= MF2_AMBUSH;
 	}
-
-	// Requires you to be in bonus time to activate
-	if (mobj->flags & MF_NIGHTSITEM)
-		mobj->flags2 |= MF2_STRONGBOX;
-
-	// Pushables bounce and slide coolly with object special flag set
-	if (mobj->flags & MF_PUSHABLE)
+	if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
 	{
-		mobj->flags2 |= MF2_SLIDEPUSH;
-		mobj->flags |= MF_BOUNCE;
+		switch (mthing->args[1])
+		{
+			case TMMR_SAME:
+			default:
+				break;
+			case TMMR_WEAK:
+				mobj->flags2 |= MF2_AMBUSH;
+				break;
+			case TMMR_STRONG:
+				mobj->flags2 |= MF2_STRONGBOX;
+		}
 	}
+
+	return true;
 }
 
 static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t i)
@@ -13199,23 +13279,6 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y,
 
 	mthing->mobj = mobj;
 
-	// ignore MTF_ flags and return early
-	if (i == MT_NIGHTSBUMPER)
-		return mobj;
-
-	if ((mthing->options & MTF_AMBUSH)
-		&& (mthing->options & MTF_OBJECTSPECIAL)
-		&& (mobj->flags & MF_PUSHABLE))
-		mobj->flags2 |= MF2_CLASSICPUSH;
-	else
-	{
-		if (mthing->options & MTF_AMBUSH)
-			P_SetAmbush(mobj);
-
-		if (mthing->options & MTF_OBJECTSPECIAL)
-			P_SetObjectSpecial(mobj);
-	}
-
 	// Generic reverse gravity for individual objects flag.
 	if (mthing->options & MTF_OBJECTFLIP)
 	{
@@ -13223,16 +13286,6 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y,
 		mobj->flags2 |= MF2_OBJECTFLIP;
 	}
 
-	// Extra functionality
-	if (mthing->options & MTF_EXTRA)
-	{
-		if (mobj->flags & MF_MONITOR && (mthing->angle & 16384))
-		{
-			// Store line exec tag to run upon popping
-			mobj->lastlook = (mthing->angle & 16383);
-		}
-	}
-
 	// Final set of not being able to draw nightsitems.
 	if (mobj->flags & MF_NIGHTSITEM)
 		mobj->flags2 |= MF2_DONTDRAW;
@@ -13281,13 +13334,18 @@ mobj_t *P_SpawnMapThing(mapthing_t *mthing)
 	return P_SpawnMobjFromMapThing(mthing, x, y, z, i);
 }
 
-static void P_SpawnHoopInternal(mapthing_t *mthing, INT32 hoopsize, fixed_t sizefactor)
+void P_SpawnHoop(mapthing_t *mthing)
 {
+	if (metalrecording)
+		return;
+
 	mobj_t *mobj = NULL;
 	mobj_t *nextmobj = NULL;
 	mobj_t *hoopcenter;
 	TMatrix *pitchmatrix, *yawmatrix;
-	fixed_t radius = hoopsize*sizefactor;
+	fixed_t radius = mthing->args[0] << FRACBITS;
+	fixed_t sizefactor = 4*FRACUNIT;
+	fixed_t hoopsize = radius/sizefactor;
 	INT32 i;
 	angle_t fa;
 	TVector v, *res;
@@ -13304,10 +13362,9 @@ static void P_SpawnHoopInternal(mapthing_t *mthing, INT32 hoopsize, fixed_t size
 	hoopcenter->y = y;
 	P_SetThingPosition(hoopcenter);
 
-	// Scale 0-255 to 0-359 =(
-	hoopcenter->movedir = ((mthing->angle & 255)*360)/256; // Pitch
+	hoopcenter->movedir = mthing->pitch;
 	pitchmatrix = RotateXMatrix(FixedAngle(hoopcenter->movedir << FRACBITS));
-	hoopcenter->movecount = (((UINT16)mthing->angle >> 8)*360)/256; // Yaw
+	hoopcenter->movecount = mthing->angle;
 	yawmatrix = RotateZMatrix(FixedAngle(hoopcenter->movecount << FRACBITS));
 
 	// For the hoop when it flies away
@@ -13387,19 +13444,6 @@ static void P_SpawnHoopInternal(mapthing_t *mthing, INT32 hoopsize, fixed_t size
 	} while (hoopsize >= 8);
 }
 
-void P_SpawnHoop(mapthing_t *mthing)
-{
-	if (metalrecording)
-		return;
-
-	if (mthing->type == 1705) // Generic hoop
-		P_SpawnHoopInternal(mthing, 24, 4*FRACUNIT);
-	else // Customizable hoop
-		// For each flag add 16 fracunits to the size
-		// Default (0 flags) is 32 fracunits
-		P_SpawnHoopInternal(mthing, 8 + (4*(mthing->options & 0xF)), 4*FRACUNIT);
-}
-
 void P_SetBonusTime(mobj_t *mobj)
 {
 	if (!mobj)
@@ -13411,7 +13455,7 @@ void P_SetBonusTime(mobj_t *mobj)
 	P_SetMobjState(mobj, mobj->info->raisestate);
 }
 
-static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t* itemtypes, UINT8 numitemtypes, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle, boolean bonustime)
+static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numitemtypes, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle, boolean bonustime)
 {
 	mapthing_t dummything;
 	mobj_t *mobj = NULL;
@@ -13462,7 +13506,7 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t* itemtypes, UINT8 numi
 	}
 }
 
-static void P_SpawnSingularItemRow(mapthing_t* mthing, mobjtype_t itemtype, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle, boolean bonustime)
+static void P_SpawnSingularItemRow(mapthing_t *mthing, mobjtype_t itemtype, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle, boolean bonustime)
 {
 	mobjtype_t itemtypes[1] = { itemtype };
 	return P_SpawnItemRow(mthing, itemtypes, 1, numitems, horizontalspacing, verticalspacing, fixedangle, bonustime);
@@ -13526,6 +13570,35 @@ static void P_SpawnItemCircle(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 n
 	}
 }
 
+static void P_ParseItemTypes(char *itemstring, mobjtype_t *itemtypes, UINT8 *numitemtypes)
+{
+	char *tok;
+
+	*numitemtypes = 0;
+	if (itemstring)
+	{
+		char *stringcopy = Z_Malloc(strlen(itemstring) + 1, PU_LEVEL, NULL);
+		M_Memcpy(stringcopy, itemstring, strlen(itemstring));
+		stringcopy[strlen(itemstring)] = '\0';
+
+		tok = strtok(stringcopy, " ");
+		while (tok && *numitemtypes < 128)
+		{
+			itemtypes[*numitemtypes] = get_number(tok);
+			tok = strtok(NULL, " ");
+			(*numitemtypes)++;
+		}
+
+		Z_Free(stringcopy);
+	}
+	else
+	{
+		//If no types are supplied, default to ring
+		itemtypes[0] = MT_RING;
+		*numitemtypes = 1;
+	}
+}
+
 void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime)
 {
 	switch (mthing->type)
@@ -13563,6 +13636,27 @@ void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime)
 		P_SpawnItemCircle(mthing, itemtypes, 2, numitems, size, bonustime);
 		return;
 	}
+	case 610: // Generic item row
+	{
+		mobjtype_t itemtypes[128]; //If you want to have a row with more than 128 different object types, you're crazy.
+		UINT8 numitemtypes;
+		if (!udmf)
+			return;
+		P_ParseItemTypes(mthing->stringargs[0], itemtypes, &numitemtypes);
+		P_SpawnItemRow(mthing, itemtypes, numitemtypes, mthing->args[0], mthing->args[1] << FRACBITS, mthing->args[2] << FRACBITS, mthing->angle, bonustime);
+		return;
+	}
+	case 611: // Generic item circle
+	{
+		mobjtype_t itemtypes[128]; //If you want to have a circle with more than 128 different object types, you're crazy.
+		UINT8 numitemtypes;
+		if (!udmf)
+			return;
+		CONS_Printf("Itemstring: %s\n", mthing->stringargs[0]);
+		P_ParseItemTypes(mthing->stringargs[0], itemtypes, &numitemtypes);
+		P_SpawnItemCircle(mthing, itemtypes, numitemtypes, mthing->args[0], mthing->args[1] << FRACBITS, bonustime);
+		return;
+	}
 	default:
 		return;
 	}
diff --git a/src/p_mobj.h b/src/p_mobj.h
index b1b79fd82e8a14009ece5e5a7b2cb55d261621d8..da8d9ea2b6d9b18abdb720d32cd78037fd1ac6ca 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -118,7 +118,7 @@ typedef enum
 	// Don't apply gravity (every tic); object will float, keeping current height
 	//  or changing it actively.
 	MF_NOGRAVITY        = 1<<9,
-	// This object is an ambient sound.
+	// This object is an ambient sound. Obsolete, but keep this around for backwards compatibility.
 	MF_AMBIENT          = 1<<10,
 	// Slide this object when it hits a wall.
 	MF_SLIDEME          = 1<<11,
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 6d7ef399904317675d913e5e0a65489401689adf..77e514bee7a7a648069992d20160aef70cc7bddc 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -963,7 +963,7 @@ static INT32 Polyobj_clipThings(polyobj_t *po, line_t *line)
 					else
 						Polyobj_pushThing(po, line, mo);
 
-					if (mo->player && (po->lines[0]->backsector->flags & SF_TRIGGERSPECIAL_TOUCH) && !(po->flags & POF_NOSPECIALS))
+					if (mo->player && (po->lines[0]->backsector->flags & MSF_TRIGGERSPECIAL_TOUCH) && !(po->flags & POF_NOSPECIALS))
 						P_ProcessSpecialSector(mo->player, mo->subsector->sector, po->lines[0]->backsector);
 
 					hitflags |= 1;
@@ -1091,7 +1091,7 @@ static void Polyobj_rotateLine(line_t *ld)
 }
 
 // Causes objects resting on top of the rotating polyobject to 'ride' with its movement.
-static void Polyobj_rotateThings(polyobj_t *po, vector2_t origin, angle_t delta, UINT8 turnthings)
+static void Polyobj_rotateThings(polyobj_t *po, vector2_t origin, angle_t delta, boolean turnplayers, boolean turnothers)
 {
 	static INT32 pomovecount = 10000;
 	INT32 x, y;
@@ -1153,7 +1153,7 @@ static void Polyobj_rotateThings(polyobj_t *po, vector2_t origin, angle_t delta,
 
 					Polyobj_slideThing(mo, newxoff, newyoff);
 
-					if (turnthings == 2 || (turnthings == 1 && !mo->player)) {
+					if ((turnplayers && mo->player) || (turnothers && !mo->player)) {
 						mo->angle += delta;
 						if (mo->player)
 							P_SetPlayerAngle(mo->player, (angle_t)(mo->player->angleturn << 16) + delta);
@@ -1165,7 +1165,7 @@ static void Polyobj_rotateThings(polyobj_t *po, vector2_t origin, angle_t delta,
 }
 
 // Rotates a polyobject around its start point.
-boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, boolean checkmobjs)
+boolean Polyobj_rotate(polyobj_t *po, angle_t delta, boolean turnplayers, boolean turnothers, boolean checkmobjs)
 {
 	size_t i;
 	angle_t angle;
@@ -1203,7 +1203,7 @@ boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, boolean c
 		for (i = 0; i < po->numLines; ++i)
 			hitflags |= Polyobj_clipThings(po, po->lines[i]);
 
-		Polyobj_rotateThings(po, origin, delta, turnthings);
+		Polyobj_rotateThings(po, origin, delta, turnplayers, turnothers);
 	}
 
 	if (hitflags & 2)
@@ -1411,7 +1411,7 @@ void Polyobj_MoveOnLoad(polyobj_t *po, angle_t angle, fixed_t x, fixed_t y)
 	fixed_t dx, dy;
 
 	// first, rotate to the saved angle
-	Polyobj_rotate(po, angle, false, false);
+	Polyobj_rotate(po, angle, false, false, false);
 
 	// determine component distances to translate
 	dx = x - po->spawnSpot.x;
@@ -1454,7 +1454,7 @@ void T_PolyObjRotate(polyrotate_t *th)
 
 	// rotate by 'speed' angle per frame
 	// if distance == -1, this polyobject rotates perpetually
-	if (Polyobj_rotate(po, th->speed, th->turnobjs, true) && th->distance != -1)
+	if (Polyobj_rotate(po, th->speed, th->turnobjs & PTF_PLAYERS, th->turnobjs & PTF_OTHERS, true) && th->distance != -1)
 	{
 		INT32 avel = abs(th->speed);
 
@@ -1856,7 +1856,7 @@ void T_PolyDoorSwing(polyswingdoor_t *th)
 
 	// rotate by 'speed' angle per frame
 	// if distance == -1, this polyobject rotates perpetually
-	if (Polyobj_rotate(po, th->speed, false, true) && th->distance != -1)
+	if (Polyobj_rotate(po, th->speed, false, false, true) && th->distance != -1)
 	{
 		INT32 avel = abs(th->speed);
 
@@ -1987,7 +1987,7 @@ void T_PolyObjRotDisplace(polyrotdisplace_t *th)
 
 	rotangle = FixedMul(th->rotscale, delta);
 
-	if (Polyobj_rotate(po, FixedAngle(rotangle), th->turnobjs, true))
+	if (Polyobj_rotate(po, FixedAngle(rotangle), th->turnobjs & PTF_PLAYERS, th->turnobjs & PTF_OTHERS, true))
 		th->oldHeights = newheights;
 }
 
@@ -2016,7 +2016,7 @@ boolean EV_DoPolyObjRotate(polyrotdata_t *prdata)
 		return false;
 
 	// check for override if this polyobj already has a thinker
-	if (po->thinker && !prdata->overRide)
+	if (po->thinker && !(prdata->flags & TMPR_OVERRIDE))
 		return false;
 
 	// create a new thinker
@@ -2031,10 +2031,10 @@ boolean EV_DoPolyObjRotate(polyrotdata_t *prdata)
 	// use Hexen-style byte angles for speed and distance
 	th->speed = Polyobj_AngSpeed(prdata->speed * prdata->direction);
 
-	if (prdata->distance == 360)    // 360 means perpetual
+	if (prdata->flags & TMPR_CONTINUOUS)
 		th->distance = -1;
-	else if (prdata->distance == 0) // 0 means 360 degrees
-		th->distance = 0xffffffff - 1;
+	else if (prdata->distance == 360)
+		th->distance = ANGLE_MAX - 1;
 	else
 		th->distance = FixedAngle(prdata->distance*FRACUNIT);
 
@@ -2049,7 +2049,11 @@ boolean EV_DoPolyObjRotate(polyrotdata_t *prdata)
 
 	oldpo = po;
 
-	th->turnobjs = prdata->turnobjs;
+	th->turnobjs = 0;
+	if (!(prdata->flags & TMPR_DONTROTATEOTHERS))
+		th->turnobjs |= PTF_OTHERS;
+	if (prdata->flags & TMPR_ROTATEPLAYERS)
+		th->turnobjs |= PTF_PLAYERS;
 
 	// apply action to mirroring polyobjects as well
 	start = 0;
diff --git a/src/p_polyobj.h b/src/p_polyobj.h
index 4cc1221dbacb6a149b23aa24b57c3557072e5ba3..e9d246168a5ea0a592ed071d445fad06775339e6 100644
--- a/src/p_polyobj.h
+++ b/src/p_polyobj.h
@@ -137,7 +137,7 @@ typedef struct polyrotate_s
 	INT32 polyObjNum;    // numeric id of polyobject (avoid C pointers here)
 	INT32 speed;         // speed of movement per frame
 	INT32 distance;      // distance to move
-	UINT8 turnobjs;      // turn objects? 0=no, 1=everything but players, 2=everything
+	UINT8 turnobjs;      // turn objects? PTF_ flags
 } polyrotate_t;
 
 typedef struct polymove_s
@@ -247,14 +247,27 @@ typedef struct polyfade_s
 // Line Activation Data Structures
 //
 
+typedef enum
+{
+	TMPR_DONTROTATEOTHERS = 1,
+	TMPR_ROTATEPLAYERS    = 1<<1,
+	TMPR_CONTINUOUS       = 1<<2,
+	TMPR_OVERRIDE         = 1<<3,
+} textmappolyrotate_t;
+
+typedef enum
+{
+	PTF_PLAYERS = 1,    // Turn players with movement
+	PTF_OTHERS = 1<<1, // Turn other mobjs with movement
+} polyturnflags_e;
+
 typedef struct polyrotdata_s
 {
 	INT32 polyObjNum;   // numeric id of polyobject to affect
 	INT32 direction;    // direction of rotation
 	INT32 speed;        // angular speed
 	INT32 distance;     // distance to move
-	UINT8 turnobjs;     // rotate objects being carried?
-	UINT8 overRide;     // if true, will override any action on the object
+	UINT8 flags;        // TMPR_ flags
 } polyrotdata_t;
 
 typedef struct polymovedata_s
@@ -281,6 +294,20 @@ typedef struct polywaypointdata_s
 	UINT8 flags;          // PWF_ flags
 } polywaypointdata_t;
 
+typedef enum
+{
+	TMPV_NOCHANGE  = 1,
+	TMPV_VISIBLE   = 1<<1,
+	TMPV_INVISIBLE = 1<<2,
+} textmappolyvisibility_t;
+
+typedef enum
+{
+	TMPT_NOCHANGE   = 1,
+	TMPT_TANGIBLE   = 1<<1,
+	TMPT_INTANGIBLE = 1<<2,
+} textmappolytangibility_t;
+
 // polyobject door types
 typedef enum
 {
@@ -322,6 +349,15 @@ typedef struct polyflagdata_s
 	fixed_t momx;
 } polyflagdata_t;
 
+typedef enum
+{
+	TMPF_RELATIVE        = 1,
+	TMPF_OVERRIDE        = 1<<1,
+	TMPF_TICBASED        = 1<<2,
+	TMPF_IGNORECOLLISION = 1<<3,
+	TMPF_GHOSTFADE       = 1<<4,
+} textmappolyfade_t;
+
 typedef struct polyfadedata_s
 {
 	INT32 polyObjNum;
@@ -337,7 +373,7 @@ typedef struct polyfadedata_s
 //
 
 boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y, boolean checkmobjs);
-boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, boolean checkmobjs);
+boolean Polyobj_rotate(polyobj_t *po, angle_t delta, boolean turnplayers, boolean turnothers, boolean checkmobjs);
 polyobj_t *Polyobj_GetForNum(INT32 id);
 void Polyobj_InitLevel(void);
 void Polyobj_MoveOnLoad(polyobj_t *po, angle_t angle, fixed_t x, fixed_t y);
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 99ec58bb97c74080ad171d40a65bbf804c25459c..668ca08f002ea983ea4538ab9a95a1d2a1a1fc36 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -850,6 +850,17 @@ static void P_NetUnArchiveWaypoints(void)
 #define SD_TAGLIST   0x01
 #define SD_COLORMAP  0x02
 #define SD_CRUMBLESTATE 0x04
+#define SD_FLOORLIGHT 0x08
+#define SD_CEILLIGHT 0x10
+#define SD_FLAG      0x20
+#define SD_SPECIALFLAG 0x40
+#define SD_DIFF4     0x80
+
+//diff4 flags
+#define SD_DAMAGETYPE 0x01
+#define SD_TRIGGERTAG 0x02
+#define SD_TRIGGERER 0x04
+#define SD_GRAVITY   0x08
 
 #define LD_FLAG     0x01
 #define LD_SPECIAL  0x02
@@ -988,11 +999,11 @@ static void ArchiveSectors(void)
 	size_t i, j;
 	const sector_t *ss = sectors;
 	const sector_t *spawnss = spawnsectors;
-	UINT8 diff, diff2, diff3;
+	UINT8 diff, diff2, diff3, diff4;
 
 	for (i = 0; i < numsectors; i++, ss++, spawnss++)
 	{
-		diff = diff2 = diff3 = 0;
+		diff = diff2 = diff3 = diff4 = 0;
 		if (ss->floorheight != spawnss->floorheight)
 			diff |= SD_FLOORHT;
 		if (ss->ceilingheight != spawnss->ceilingheight)
@@ -1031,9 +1042,29 @@ static void ArchiveSectors(void)
 		if (ss->crumblestate)
 			diff3 |= SD_CRUMBLESTATE;
 
+		if (ss->floorlightlevel != spawnss->floorlightlevel || ss->floorlightabsolute != spawnss->floorlightabsolute)
+			diff3 |= SD_FLOORLIGHT;
+		if (ss->ceilinglightlevel != spawnss->ceilinglightlevel || ss->ceilinglightabsolute != spawnss->ceilinglightabsolute)
+			diff3 |= SD_CEILLIGHT;
+		if (ss->flags != spawnss->flags)
+			diff3 |= SD_FLAG;
+		if (ss->specialflags != spawnss->specialflags)
+			diff3 |= SD_SPECIALFLAG;
+		if (ss->damagetype != spawnss->damagetype)
+			diff4 |= SD_DAMAGETYPE;
+		if (ss->triggertag != spawnss->triggertag)
+			diff4 |= SD_TRIGGERTAG;
+		if (ss->triggerer != spawnss->triggerer)
+			diff4 |= SD_TRIGGERER;
+		if (ss->gravity != spawnss->gravity)
+			diff4 |= SD_GRAVITY;
+
 		if (ss->ffloors && CheckFFloorDiff(ss))
 			diff |= SD_FFLOORS;
 
+		if (diff4)
+			diff3 |= SD_DIFF4;
+
 		if (diff3)
 			diff2 |= SD_DIFF3;
 
@@ -1048,6 +1079,8 @@ static void ArchiveSectors(void)
 				WRITEUINT8(save_p, diff2);
 			if (diff2 & SD_DIFF3)
 				WRITEUINT8(save_p, diff3);
+			if (diff3 & SD_DIFF4)
+				WRITEUINT8(save_p, diff4);
 			if (diff & SD_FLOORHT)
 				WRITEFIXED(save_p, ss->floorheight);
 			if (diff & SD_CEILHT)
@@ -1084,6 +1117,28 @@ static void ArchiveSectors(void)
 					// returns existing index if already added, or appends to net_colormaps and returns new index
 			if (diff3 & SD_CRUMBLESTATE)
 				WRITEINT32(save_p, ss->crumblestate);
+			if (diff3 & SD_FLOORLIGHT)
+			{
+				WRITEINT16(save_p, ss->floorlightlevel);
+				WRITEUINT8(save_p, ss->floorlightabsolute);
+			}
+			if (diff3 & SD_CEILLIGHT)
+			{
+				WRITEINT16(save_p, ss->ceilinglightlevel);
+				WRITEUINT8(save_p, ss->ceilinglightabsolute);
+			}
+			if (diff3 & SD_FLAG)
+				WRITEUINT32(save_p, ss->flags);
+			if (diff3 & SD_SPECIALFLAG)
+				WRITEUINT32(save_p, ss->specialflags);
+			if (diff4 & SD_DAMAGETYPE)
+				WRITEUINT8(save_p, ss->damagetype);
+			if (diff4 & SD_TRIGGERTAG)
+				WRITEINT16(save_p, ss->triggertag);
+			if (diff4 & SD_TRIGGERER)
+				WRITEUINT8(save_p, ss->triggerer);
+			if (diff4 & SD_GRAVITY)
+				WRITEFIXED(save_p, ss->gravity);
 			if (diff & SD_FFLOORS)
 				ArchiveFFloors(ss);
 		}
@@ -1095,7 +1150,7 @@ static void ArchiveSectors(void)
 static void UnArchiveSectors(void)
 {
 	UINT16 i, j;
-	UINT8 diff, diff2, diff3;
+	UINT8 diff, diff2, diff3, diff4;
 	for (;;)
 	{
 		i = READUINT16(save_p);
@@ -1115,6 +1170,10 @@ static void UnArchiveSectors(void)
 			diff3 = READUINT8(save_p);
 		else
 			diff3 = 0;
+		if (diff3 & SD_DIFF4)
+			diff4 = READUINT8(save_p);
+		else
+			diff4 = 0;
 
 		if (diff & SD_FLOORHT)
 			sectors[i].floorheight = READFIXED(save_p);
@@ -1175,6 +1234,31 @@ static void UnArchiveSectors(void)
 			sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save_p));
 		if (diff3 & SD_CRUMBLESTATE)
 			sectors[i].crumblestate = READINT32(save_p);
+		if (diff3 & SD_FLOORLIGHT)
+		{
+			sectors[i].floorlightlevel = READINT16(save_p);
+			sectors[i].floorlightabsolute = READUINT8(save_p);
+		}
+		if (diff3 & SD_CEILLIGHT)
+		{
+			sectors[i].ceilinglightlevel = READINT16(save_p);
+			sectors[i].ceilinglightabsolute = READUINT8(save_p);
+		}
+		if (diff3 & SD_FLAG)
+		{
+			sectors[i].flags = READUINT32(save_p);
+			CheckForReverseGravity |= (sectors[i].flags & MSF_GRAVITYFLIP);
+		}
+		if (diff3 & SD_SPECIALFLAG)
+			sectors[i].specialflags = READUINT32(save_p);
+		if (diff4 & SD_DAMAGETYPE)
+			sectors[i].damagetype = READUINT8(save_p);
+		if (diff4 & SD_TRIGGERTAG)
+			sectors[i].triggertag = READINT16(save_p);
+		if (diff4 & SD_TRIGGERER)
+			sectors[i].triggerer = READUINT8(save_p);
+		if (diff4 & SD_GRAVITY)
+			sectors[i].gravity = READFIXED(save_p);
 
 		if (diff & SD_FFLOORS)
 			UnArchiveFFloors(&sectors[i]);
@@ -1985,7 +2069,6 @@ static void SaveEachTimeThinker(const thinker_t *th, const UINT8 type)
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		WRITECHAR(save_p, ht->playersInArea[i]);
-		WRITECHAR(save_p, ht->playersOnArea[i]);
 	}
 	WRITECHAR(save_p, ht->triggerOnExit);
 }
@@ -2013,14 +2096,12 @@ static void SaveCeilingThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->bottomheight);
 	WRITEFIXED(save_p, ht->topheight);
 	WRITEFIXED(save_p, ht->speed);
-	WRITEFIXED(save_p, ht->oldspeed);
 	WRITEFIXED(save_p, ht->delay);
 	WRITEFIXED(save_p, ht->delaytimer);
 	WRITEUINT8(save_p, ht->crush);
 	WRITEINT32(save_p, ht->texture);
 	WRITEINT32(save_p, ht->direction);
-	WRITEINT32(save_p, ht->tag);
-	WRITEINT32(save_p, ht->olddirection);
+	WRITEINT16(save_p, ht->tag);
 	WRITEFIXED(save_p, ht->origspeed);
 	WRITEFIXED(save_p, ht->sourceline);
 }
@@ -2039,6 +2120,8 @@ static void SaveFloormoveThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->origspeed);
 	WRITEFIXED(save_p, ht->delay);
 	WRITEFIXED(save_p, ht->delaytimer);
+	WRITEINT16(save_p, ht->tag);
+	WRITEFIXED(save_p, ht->sourceline);
 }
 
 static void SaveLightflashThinker(const thinker_t *th, const UINT8 type)
@@ -2056,8 +2139,8 @@ static void SaveStrobeThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, type);
 	WRITEUINT32(save_p, SaveSector(ht->sector));
 	WRITEINT32(save_p, ht->count);
-	WRITEINT32(save_p, ht->minlight);
-	WRITEINT32(save_p, ht->maxlight);
+	WRITEINT16(save_p, ht->minlight);
+	WRITEINT16(save_p, ht->maxlight);
 	WRITEINT32(save_p, ht->darktime);
 	WRITEINT32(save_p, ht->brighttime);
 }
@@ -2067,10 +2150,10 @@ static void SaveGlowThinker(const thinker_t *th, const UINT8 type)
 	const glow_t *ht = (const void *)th;
 	WRITEUINT8(save_p, type);
 	WRITEUINT32(save_p, SaveSector(ht->sector));
-	WRITEINT32(save_p, ht->minlight);
-	WRITEINT32(save_p, ht->maxlight);
-	WRITEINT32(save_p, ht->direction);
-	WRITEINT32(save_p, ht->speed);
+	WRITEINT16(save_p, ht->minlight);
+	WRITEINT16(save_p, ht->maxlight);
+	WRITEINT16(save_p, ht->direction);
+	WRITEINT16(save_p, ht->speed);
 }
 
 static inline void SaveFireflickerThinker(const thinker_t *th, const UINT8 type)
@@ -2080,8 +2163,8 @@ static inline void SaveFireflickerThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT32(save_p, SaveSector(ht->sector));
 	WRITEINT32(save_p, ht->count);
 	WRITEINT32(save_p, ht->resetcount);
-	WRITEINT32(save_p, ht->maxlight);
-	WRITEINT32(save_p, ht->minlight);
+	WRITEINT16(save_p, ht->maxlight);
+	WRITEINT16(save_p, ht->minlight);
 }
 
 static void SaveElevatorThinker(const thinker_t *th, const UINT8 type)
@@ -2155,13 +2238,9 @@ static inline void SavePusherThinker(const thinker_t *th, const UINT8 type)
 	const pusher_t *ht = (const void *)th;
 	WRITEUINT8(save_p, type);
 	WRITEUINT8(save_p, ht->type);
-	WRITEINT32(save_p, ht->x_mag);
-	WRITEINT32(save_p, ht->y_mag);
-	WRITEINT32(save_p, ht->magnitude);
-	WRITEINT32(save_p, ht->radius);
-	WRITEINT32(save_p, ht->x);
-	WRITEINT32(save_p, ht->y);
-	WRITEINT32(save_p, ht->z);
+	WRITEFIXED(save_p, ht->x_mag);
+	WRITEFIXED(save_p, ht->y_mag);
+	WRITEFIXED(save_p, ht->z_mag);
 	WRITEINT32(save_p, ht->affectee);
 	WRITEUINT8(save_p, ht->roverpusher);
 	WRITEINT32(save_p, ht->referrer);
@@ -2259,18 +2338,30 @@ static void SavePlaneDisplaceThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->type);
 }
 
-static inline void SaveDynamicSlopeThinker(const thinker_t *th, const UINT8 type)
+static inline void SaveDynamicLineSlopeThinker(const thinker_t *th, const UINT8 type)
 {
-	const dynplanethink_t* ht = (const void*)th;
+	const dynlineplanethink_t* ht = (const void*)th;
 
 	WRITEUINT8(save_p, type);
 	WRITEUINT8(save_p, ht->type);
 	WRITEUINT32(save_p, SaveSlope(ht->slope));
 	WRITEUINT32(save_p, SaveLine(ht->sourceline));
 	WRITEFIXED(save_p, ht->extent);
+}
 
-	WRITEMEM(save_p, ht->tags, sizeof(ht->tags));
-    WRITEMEM(save_p, ht->vex, sizeof(ht->vex));
+static inline void SaveDynamicVertexSlopeThinker(const thinker_t *th, const UINT8 type)
+{
+	size_t i;
+	const dynvertexplanethink_t* ht = (const void*)th;
+
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveSlope(ht->slope));
+	for (i = 0; i < 3; i++)
+		WRITEUINT32(save_p, SaveSector(ht->secs[i]));
+	WRITEMEM(save_p, ht->vex, sizeof(ht->vex));
+	WRITEMEM(save_p, ht->origsecheights, sizeof(ht->origsecheights));
+	WRITEMEM(save_p, ht->origvecheights, sizeof(ht->origvecheights));
+	WRITEUINT8(save_p, ht->relative);
 }
 
 static inline void SavePolyrotatetThinker(const thinker_t *th, const UINT8 type)
@@ -2595,12 +2686,12 @@ static void P_NetArchiveThinkers(void)
 			}
 			else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeLine)
 			{
-				SaveDynamicSlopeThinker(th, tc_dynslopeline);
+				SaveDynamicLineSlopeThinker(th, tc_dynslopeline);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeVert)
 			{
-				SaveDynamicSlopeThinker(th, tc_dynslopevert);
+				SaveDynamicVertexSlopeThinker(th, tc_dynslopevert);
 				continue;
 			}
 #ifdef PARANOIA
@@ -2715,7 +2806,7 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 	{
 		UINT16 spawnpointnum = READUINT16(save_p);
 
-		if (mapthings[spawnpointnum].type == 1705 || mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case
+		if (mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case
 		{
 			P_SpawnHoop(&mapthings[spawnpointnum]);
 			return NULL;
@@ -3090,7 +3181,6 @@ static thinker_t* LoadEachTimeThinker(actionf_p1 thinker)
 	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;
@@ -3120,14 +3210,12 @@ static thinker_t* LoadCeilingThinker(actionf_p1 thinker)
 	ht->bottomheight = READFIXED(save_p);
 	ht->topheight = READFIXED(save_p);
 	ht->speed = READFIXED(save_p);
-	ht->oldspeed = READFIXED(save_p);
 	ht->delay = READFIXED(save_p);
 	ht->delaytimer = READFIXED(save_p);
 	ht->crush = READUINT8(save_p);
 	ht->texture = READINT32(save_p);
 	ht->direction = READINT32(save_p);
-	ht->tag = READINT32(save_p);
-	ht->olddirection = READINT32(save_p);
+	ht->tag = READINT16(save_p);
 	ht->origspeed = READFIXED(save_p);
 	ht->sourceline = READFIXED(save_p);
 	if (ht->sector)
@@ -3149,6 +3237,8 @@ static thinker_t* LoadFloormoveThinker(actionf_p1 thinker)
 	ht->origspeed = READFIXED(save_p);
 	ht->delay = READFIXED(save_p);
 	ht->delaytimer = READFIXED(save_p);
+	ht->tag = READINT16(save_p);
+	ht->sourceline = READFIXED(save_p);
 	if (ht->sector)
 		ht->sector->floordata = ht;
 	return &ht->thinker;
@@ -3172,8 +3262,8 @@ static thinker_t* LoadStrobeThinker(actionf_p1 thinker)
 	ht->thinker.function.acp1 = thinker;
 	ht->sector = LoadSector(READUINT32(save_p));
 	ht->count = READINT32(save_p);
-	ht->minlight = READINT32(save_p);
-	ht->maxlight = READINT32(save_p);
+	ht->minlight = READINT16(save_p);
+	ht->maxlight = READINT16(save_p);
 	ht->darktime = READINT32(save_p);
 	ht->brighttime = READINT32(save_p);
 	if (ht->sector)
@@ -3186,10 +3276,10 @@ static thinker_t* LoadGlowThinker(actionf_p1 thinker)
 	glow_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
 	ht->sector = LoadSector(READUINT32(save_p));
-	ht->minlight = READINT32(save_p);
-	ht->maxlight = READINT32(save_p);
-	ht->direction = READINT32(save_p);
-	ht->speed = READINT32(save_p);
+	ht->minlight = READINT16(save_p);
+	ht->maxlight = READINT16(save_p);
+	ht->direction = READINT16(save_p);
+	ht->speed = READINT16(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
 	return &ht->thinker;
@@ -3202,8 +3292,8 @@ static thinker_t* LoadFireflickerThinker(actionf_p1 thinker)
 	ht->sector = LoadSector(READUINT32(save_p));
 	ht->count = READINT32(save_p);
 	ht->resetcount = READINT32(save_p);
-	ht->maxlight = READINT32(save_p);
-	ht->minlight = READINT32(save_p);
+	ht->maxlight = READINT16(save_p);
+	ht->minlight = READINT16(save_p);
 	if (ht->sector)
 		ht->sector->lightingdata = ht;
 	return &ht->thinker;
@@ -3295,19 +3385,14 @@ static thinker_t* LoadPusherThinker(actionf_p1 thinker)
 	pusher_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
 	ht->type = READUINT8(save_p);
-	ht->x_mag = READINT32(save_p);
-	ht->y_mag = READINT32(save_p);
-	ht->magnitude = READINT32(save_p);
-	ht->radius = READINT32(save_p);
-	ht->x = READINT32(save_p);
-	ht->y = READINT32(save_p);
-	ht->z = READINT32(save_p);
+	ht->x_mag = READFIXED(save_p);
+	ht->y_mag = READFIXED(save_p);
+	ht->z_mag = READFIXED(save_p);
 	ht->affectee = READINT32(save_p);
 	ht->roverpusher = READUINT8(save_p);
 	ht->referrer = READINT32(save_p);
 	ht->exclusive = READINT32(save_p);
 	ht->slider = READINT32(save_p);
-	ht->source = P_GetPushThing(ht->affectee);
 	return &ht->thinker;
 }
 
@@ -3431,17 +3516,31 @@ static inline thinker_t* LoadPlaneDisplaceThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-static inline thinker_t* LoadDynamicSlopeThinker(actionf_p1 thinker)
+static inline thinker_t* LoadDynamicLineSlopeThinker(actionf_p1 thinker)
 {
-	dynplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL);
+	dynlineplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
 
 	ht->type = READUINT8(save_p);
 	ht->slope = LoadSlope(READUINT32(save_p));
 	ht->sourceline = LoadLine(READUINT32(save_p));
 	ht->extent = READFIXED(save_p);
-	READMEM(save_p, ht->tags, sizeof(ht->tags));
+	return &ht->thinker;
+}
+
+static inline thinker_t* LoadDynamicVertexSlopeThinker(actionf_p1 thinker)
+{
+	size_t i;
+	dynvertexplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+
+	ht->slope = LoadSlope(READUINT32(save_p));
+	for (i = 0; i < 3; i++)
+		ht->secs[i] = LoadSector(READUINT32(save_p));
 	READMEM(save_p, ht->vex, sizeof(ht->vex));
+	READMEM(save_p, ht->origsecheights, sizeof(ht->origsecheights));
+	READMEM(save_p, ht->origvecheights, sizeof(ht->origvecheights));
+	ht->relative = READUINT8(save_p);
 	return &ht->thinker;
 }
 
@@ -3754,11 +3853,11 @@ static void P_NetUnArchiveThinkers(void)
 					break;
 
 				case tc_dynslopeline:
-					th = LoadDynamicSlopeThinker((actionf_p1)T_DynamicSlopeLine);
+					th = LoadDynamicLineSlopeThinker((actionf_p1)T_DynamicSlopeLine);
 					break;
 
 				case tc_dynslopevert:
-					th = LoadDynamicSlopeThinker((actionf_p1)T_DynamicSlopeVert);
+					th = LoadDynamicVertexSlopeThinker((actionf_p1)T_DynamicSlopeVert);
 					break;
 
 				case tc_scroll:
diff --git a/src/p_setup.c b/src/p_setup.c
index b4675b27d3ebe26c185d54db178824d8fe3ebb95..ea099e13e1e486a764ca0be1f4ef8d02c0da538b 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -745,7 +745,7 @@ void P_ReloadRings(void)
 			mt->mobj = NULL;
 			P_SetBonusTime(P_SpawnMapThing(mt));
 		}
-		else if (mt->type >= 600 && mt->type <= 609) // Item patterns
+		else if (mt->type >= 600 && mt->type <= 611) // Item patterns
 		{
 			mt->mobj = NULL;
 			P_SpawnItemPattern(mt, true);
@@ -904,9 +904,9 @@ static void P_SpawnMapThings(boolean spawnemblems)
 
 		mt->mobj = NULL;
 
-		if (mt->type >= 600 && mt->type <= 609) // item patterns
+		if (mt->type >= 600 && mt->type <= 611) // item patterns
 			P_SpawnItemPattern(mt, false);
-		else if (mt->type == 1705 || mt->type == 1713) // hoops
+		else if (mt->type == 1713) // hoops
 			P_SpawnHoop(mt);
 		else // Everything else
 			P_SpawnMapThing(mt);
@@ -918,7 +918,7 @@ static void P_SpawnMapThings(boolean spawnemblems)
 }
 
 // Experimental groovy write function!
-void P_WriteThings(void)
+/*void P_WriteThings(void)
 {
 	size_t i, length;
 	mapthing_t *mt;
@@ -953,7 +953,7 @@ void P_WriteThings(void)
 	savebuf_p = NULL;
 
 	CONS_Printf(M_GetText("newthings%d.lmp saved.\n"), gamemap);
-}
+}*/
 
 //
 // MAP LOADING FUNCTIONS
@@ -1010,9 +1010,7 @@ static void P_InitializeSector(sector_t *ss)
 
 	ss->extra_colormap = NULL;
 
-	ss->gravity = NULL;
-	ss->verticalflip = false;
-	ss->flags = SF_FLIPSPECIAL_FLOOR;
+	ss->gravityptr = NULL;
 
 	ss->cullheight = NULL;
 
@@ -1054,8 +1052,21 @@ static void P_LoadSectors(UINT8 *data)
 
 		ss->floorpic_angle = ss->ceilingpic_angle = 0;
 
+		ss->floorlightlevel = ss->ceilinglightlevel = 0;
+		ss->floorlightabsolute = ss->ceilinglightabsolute = false;
+
 		ss->colormap_protected = false;
 
+		ss->gravity = FRACUNIT;
+
+		ss->flags = MSF_FLIPSPECIAL_FLOOR;
+		ss->specialflags = 0;
+		ss->damagetype = SD_NONE;
+		ss->triggertag = 0;
+		ss->triggerer = TO_PLAYER;
+
+		ss->friction = ORIG_FRICTION;
+
 		P_InitializeSector(ss);
 	}
 }
@@ -1214,7 +1225,7 @@ static void P_LoadSidedefs(UINT8 *data)
 		isfrontside = sd->line->sidenum[0] == i;
 
 		// Repeat count for midtexture
-		if (((sd->line->flags & (ML_TWOSIDED|ML_EFFECT5)) == (ML_TWOSIDED|ML_EFFECT5))
+		if (((sd->line->flags & (ML_TWOSIDED|ML_WRAPMIDTEX)) == (ML_TWOSIDED|ML_WRAPMIDTEX))
 			&& !(sd->special >= 300 && sd->special < 500)) // exempt linedef exec specials
 		{
 			sd->repeatcnt = (INT16)(((UINT16)textureoffset) >> 12);
@@ -1237,11 +1248,8 @@ static void P_LoadSidedefs(UINT8 *data)
 			case 455: // Fade colormaps! mazmazz 9/12/2018 (:flag_us:)
 				// SoM: R_CreateColormap will only create a colormap in software mode...
 				// Perhaps we should just call it instead of doing the calculations here.
-				if (!udmf)
-				{
-					sd->colormap_data = R_CreateColormapFromLinedef(msd->toptexture, msd->midtexture, msd->bottomtexture);
-					sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
-				}
+				sd->colormap_data = R_CreateColormapFromLinedef(msd->toptexture, msd->midtexture, msd->bottomtexture);
+				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
 				break;
 
 			case 413: // Change music
@@ -1283,7 +1291,6 @@ static void P_LoadSidedefs(UINT8 *data)
 			}
 
 			case 4: // Speed pad parameters
-			case 414: // Play SFX
 			{
 				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
 				if (msd->toptexture[0] != '-' || msd->toptexture[1] != '\0')
@@ -1296,17 +1303,23 @@ static void P_LoadSidedefs(UINT8 *data)
 				break;
 			}
 
+			case 414: // Play SFX
+			{
+				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
+				if (msd->toptexture[0] != '-' || msd->toptexture[1] != '\0')
+				{
+					char process[8 + 1];
+					M_Memcpy(process, msd->toptexture, 8);
+					process[8] = '\0';
+					sd->text = Z_Malloc(strlen(process) + 1, PU_LEVEL, NULL);
+					M_Memcpy(sd->text, process, strlen(process) + 1);
+				}
+				break;
+			}
+
 			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);
@@ -1326,8 +1339,16 @@ static void P_LoadSidedefs(UINT8 *data)
 			case 331: // Trigger linedef executor: Skin - Continuous
 			case 332: // Trigger linedef executor: Skin - Each time
 			case 333: // Trigger linedef executor: Skin - Once
+			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 443: // Calls a named Lua function
 			case 459: // Control text prompt (named tag)
+			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);
@@ -1420,9 +1441,9 @@ UINT32 vertexesPos[UINT16_MAX];
 UINT32 sectorsPos[UINT16_MAX];
 
 // Determine total amount of map data in TEXTMAP.
-static boolean TextmapCount(UINT8 *data, size_t size)
+static boolean TextmapCount(size_t size)
 {
-	char *tkn = M_GetToken((char *)data);
+	const char *tkn = M_TokenizerRead(0);
 	UINT8 brackets = 0;
 
 	nummapthings = 0;
@@ -1434,20 +1455,16 @@ static boolean TextmapCount(UINT8 *data, size_t size)
 	// Look for namespace at the beginning.
 	if (!fastcmp(tkn, "namespace"))
 	{
-		Z_Free(tkn);
 		CONS_Alert(CONS_ERROR, "No namespace at beginning of lump!\n");
 		return false;
 	}
-	Z_Free(tkn);
 
 	// Check if namespace is valid.
-	tkn = M_GetToken(NULL);
+	tkn = M_TokenizerRead(0);
 	if (!fastcmp(tkn, "srb2"))
 		CONS_Alert(CONS_WARNING, "Invalid namespace '%s', only 'srb2' is supported.\n", tkn);
-	Z_Free(tkn);
 
-	tkn = M_GetToken(NULL);
-	while (tkn && M_GetTokenPos() < size)
+	while ((tkn = M_TokenizerRead(0)) && M_TokenizerGetEndPos() < size)
 	{
 		// Avoid anything inside bracketed stuff, only look for external keywords.
 		if (brackets)
@@ -1459,24 +1476,19 @@ static boolean TextmapCount(UINT8 *data, size_t size)
 			brackets++;
 		// Check for valid fields.
 		else if (fastcmp(tkn, "thing"))
-			mapthingsPos[nummapthings++] = M_GetTokenPos();
+			mapthingsPos[nummapthings++] = M_TokenizerGetEndPos();
 		else if (fastcmp(tkn, "linedef"))
-			linesPos[numlines++] = M_GetTokenPos();
+			linesPos[numlines++] = M_TokenizerGetEndPos();
 		else if (fastcmp(tkn, "sidedef"))
-			sidesPos[numsides++] = M_GetTokenPos();
+			sidesPos[numsides++] = M_TokenizerGetEndPos();
 		else if (fastcmp(tkn, "vertex"))
-			vertexesPos[numvertexes++] = M_GetTokenPos();
+			vertexesPos[numvertexes++] = M_TokenizerGetEndPos();
 		else if (fastcmp(tkn, "sector"))
-			sectorsPos[numsectors++] = M_GetTokenPos();
+			sectorsPos[numsectors++] = M_TokenizerGetEndPos();
 		else
 			CONS_Alert(CONS_NOTICE, "Unknown field '%s'.\n", tkn);
-
-		Z_Free(tkn);
-		tkn = M_GetToken(NULL);
 	}
 
-	Z_Free(tkn);
-
 	if (brackets)
 	{
 		CONS_Alert(CONS_ERROR, "Unclosed brackets detected in textmap lump.\n");
@@ -1486,7 +1498,7 @@ static boolean TextmapCount(UINT8 *data, size_t size)
 	return true;
 }
 
-static void ParseTextmapVertexParameter(UINT32 i, char *param, char *val)
+static void ParseTextmapVertexParameter(UINT32 i, const char *param, const char *val)
 {
 	if (fastcmp(param, "x"))
 		vertexes[i].x = FLOAT_TO_FIXED(atof(val));
@@ -1533,7 +1545,7 @@ typedef struct textmap_plane_s {
 textmap_plane_t textmap_planefloor = {0, 0, 0, 0, 0};
 textmap_plane_t textmap_planeceiling = {0, 0, 0, 0, 0};
 
-static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
+static void ParseTextmapSectorParameter(UINT32 i, const char *param, const char *val)
 {
 	if (fastcmp(param, "heightfloor"))
 		sectors[i].floorheight = atol(val) << FRACBITS;
@@ -1545,13 +1557,19 @@ static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
 		sectors[i].ceilingpic = P_AddLevelFlat(val, foundflats);
 	else if (fastcmp(param, "lightlevel"))
 		sectors[i].lightlevel = atol(val);
-	else if (fastcmp(param, "special"))
-		sectors[i].special = atol(val);
+	else if (fastcmp(param, "lightfloor"))
+		sectors[i].floorlightlevel = atol(val);
+	else if (fastcmp(param, "lightfloorabsolute") && fastcmp("true", val))
+		sectors[i].floorlightabsolute = true;
+	else if (fastcmp(param, "lightceiling"))
+		sectors[i].ceilinglightlevel = atol(val);
+	else if (fastcmp(param, "lightceilingabsolute") && fastcmp("true", val))
+		sectors[i].ceilinglightabsolute = true;
 	else if (fastcmp(param, "id"))
 		Tag_FSet(&sectors[i].tags, atol(val));
 	else if (fastcmp(param, "moreids"))
 	{
-		char* id = val;
+		const char* id = val;
 		while (id)
 		{
 			Tag_Add(&sectors[i].tags, atol(id));
@@ -1653,9 +1671,94 @@ static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
 	}
 	else if (fastcmp(param, "colormapprotected") && fastcmp("true", val))
 		sectors[i].colormap_protected = true;
+	else if (fastcmp(param, "flipspecial_nofloor") && fastcmp("true", val))
+		sectors[i].flags &= ~MSF_FLIPSPECIAL_FLOOR;
+	else if (fastcmp(param, "flipspecial_ceiling") && fastcmp("true", val))
+		sectors[i].flags |= MSF_FLIPSPECIAL_CEILING;
+	else if (fastcmp(param, "triggerspecial_touch") && fastcmp("true", val))
+		sectors[i].flags |= MSF_TRIGGERSPECIAL_TOUCH;
+	else if (fastcmp(param, "triggerspecial_headbump") && fastcmp("true", val))
+		sectors[i].flags |= MSF_TRIGGERSPECIAL_HEADBUMP;
+	else if (fastcmp(param, "triggerline_plane") && fastcmp("true", val))
+		sectors[i].flags |= MSF_TRIGGERLINE_PLANE;
+	else if (fastcmp(param, "triggerline_mobj") && fastcmp("true", val))
+		sectors[i].flags |= MSF_TRIGGERLINE_MOBJ;
+	else if (fastcmp(param, "invertprecip") && fastcmp("true", val))
+		sectors[i].flags |= MSF_INVERTPRECIP;
+	else if (fastcmp(param, "gravityflip") && fastcmp("true", val))
+		sectors[i].flags |= MSF_GRAVITYFLIP;
+	else if (fastcmp(param, "heatwave") && fastcmp("true", val))
+		sectors[i].flags |= MSF_HEATWAVE;
+	else if (fastcmp(param, "noclipcamera") && fastcmp("true", val))
+		sectors[i].flags |= MSF_NOCLIPCAMERA;
+	else if (fastcmp(param, "outerspace") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_OUTERSPACE;
+	else if (fastcmp(param, "doublestepup") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_DOUBLESTEPUP;
+	else if (fastcmp(param, "nostepdown") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_NOSTEPDOWN;
+	else if (fastcmp(param, "speedpad") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_SPEEDPAD;
+	else if (fastcmp(param, "starpostactivator") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_STARPOSTACTIVATOR;
+	else if (fastcmp(param, "exit") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_EXIT;
+	else if (fastcmp(param, "specialstagepit") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_SPECIALSTAGEPIT;
+	else if (fastcmp(param, "returnflag") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_RETURNFLAG;
+	else if (fastcmp(param, "redteambase") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_REDTEAMBASE;
+	else if (fastcmp(param, "blueteambase") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_BLUETEAMBASE;
+	else if (fastcmp(param, "fan") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_FAN;
+	else if (fastcmp(param, "supertransform") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_SUPERTRANSFORM;
+	else if (fastcmp(param, "forcespin") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_FORCESPIN;
+	else if (fastcmp(param, "zoomtubestart") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_ZOOMTUBESTART;
+	else if (fastcmp(param, "zoomtubeend") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_ZOOMTUBEEND;
+	else if (fastcmp(param, "finishline") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_FINISHLINE;
+	else if (fastcmp(param, "ropehang") && fastcmp("true", val))
+		sectors[i].specialflags |= SSF_ROPEHANG;
+	else if (fastcmp(param, "friction"))
+		sectors[i].friction = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "gravity"))
+		sectors[i].gravity = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "damagetype"))
+	{
+		if (fastcmp(val, "Generic"))
+			sectors[i].damagetype = SD_GENERIC;
+		if (fastcmp(val, "Water"))
+			sectors[i].damagetype = SD_WATER;
+		if (fastcmp(val, "Fire"))
+			sectors[i].damagetype = SD_FIRE;
+		if (fastcmp(val, "Lava"))
+			sectors[i].damagetype = SD_LAVA;
+		if (fastcmp(val, "Electric"))
+			sectors[i].damagetype = SD_ELECTRIC;
+		if (fastcmp(val, "Spike"))
+			sectors[i].damagetype = SD_SPIKE;
+		if (fastcmp(val, "DeathPitTilt"))
+			sectors[i].damagetype = SD_DEATHPITTILT;
+		if (fastcmp(val, "DeathPitNoTilt"))
+			sectors[i].damagetype = SD_DEATHPITNOTILT;
+		if (fastcmp(val, "Instakill"))
+			sectors[i].damagetype = SD_INSTAKILL;
+		if (fastcmp(val, "SpecialStage"))
+			sectors[i].damagetype = SD_SPECIALSTAGE;
+	}
+	else if (fastcmp(param, "triggertag"))
+		sectors[i].triggertag = atol(val);
+	else if (fastcmp(param, "triggerer"))
+		sectors[i].triggerer = atol(val);
 }
 
-static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
+static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char *val)
 {
 	if (fastcmp(param, "offsetx"))
 		sides[i].textureoffset = atol(val)<<FRACBITS;
@@ -1673,13 +1776,13 @@ static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
 		sides[i].repeatcnt = atol(val);
 }
 
-static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
+static void ParseTextmapLinedefParameter(UINT32 i, const char *param, const char *val)
 {
 	if (fastcmp(param, "id"))
 		Tag_FSet(&lines[i].tags, atol(val));
 	else if (fastcmp(param, "moreids"))
 	{
-		char* id = val;
+		const char* id = val;
 		while (id)
 		{
 			Tag_Add(&lines[i].tags, atol(id));
@@ -1693,9 +1796,9 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 		P_SetLinedefV1(i, atol(val));
 	else if (fastcmp(param, "v2"))
 		P_SetLinedefV2(i, atol(val));
-	else if (strlen(param) == 7 && fastncmp(param, "arg", 3) && fastncmp(param + 4, "str", 3))
+	else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9)
 	{
-		size_t argnum = param[3] - '0';
+		size_t argnum = atol(param + 9);
 		if (argnum >= NUMLINESTRINGARGS)
 			return;
 		lines[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
@@ -1744,19 +1847,19 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 	else if (fastcmp(param, "dontpegbottom") && fastcmp("true", val))
 		lines[i].flags |= ML_DONTPEGBOTTOM;
 	else if (fastcmp(param, "skewtd") && fastcmp("true", val))
-		lines[i].flags |= ML_EFFECT1;
+		lines[i].flags |= ML_SKEWTD;
 	else if (fastcmp(param, "noclimb") && fastcmp("true", val))
 		lines[i].flags |= ML_NOCLIMB;
 	else if (fastcmp(param, "noskew") && fastcmp("true", val))
-		lines[i].flags |= ML_EFFECT2;
+		lines[i].flags |= ML_NOSKEW;
 	else if (fastcmp(param, "midpeg") && fastcmp("true", val))
-		lines[i].flags |= ML_EFFECT3;
+		lines[i].flags |= ML_MIDPEG;
 	else if (fastcmp(param, "midsolid") && fastcmp("true", val))
-		lines[i].flags |= ML_EFFECT4;
+		lines[i].flags |= ML_MIDSOLID;
 	else if (fastcmp(param, "wrapmidtex") && fastcmp("true", val))
-		lines[i].flags |= ML_EFFECT5;
-	else if (fastcmp(param, "effect6") && fastcmp("true", val))
-		lines[i].flags |= ML_EFFECT6;
+		lines[i].flags |= ML_WRAPMIDTEX;
+	/*else if (fastcmp(param, "effect6") && fastcmp("true", val))
+		lines[i].flags |= ML_EFFECT6;*/
 	else if (fastcmp(param, "nonet") && fastcmp("true", val))
 		lines[i].flags |= ML_NONET;
 	else if (fastcmp(param, "netonly") && fastcmp("true", val))
@@ -1767,13 +1870,13 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 		lines[i].flags |= ML_TFERLINE;
 }
 
-static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
+static void ParseTextmapThingParameter(UINT32 i, const char *param, const char *val)
 {
 	if (fastcmp(param, "id"))
 		Tag_FSet(&mapthings[i].tags, atol(val));
 	else if (fastcmp(param, "moreids"))
 	{
-		char* id = val;
+		const char* id = val;
 		while (id)
 		{
 			Tag_Add(&mapthings[i].tags, atol(id));
@@ -1798,18 +1901,12 @@ static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
 	else if (fastcmp(param, "scale") || fastcmp(param, "scalex") || fastcmp(param, "scaley"))
 		mapthings[i].scale = FLOAT_TO_FIXED(atof(val));
 	// Flags
-	else if (fastcmp(param, "extra") && fastcmp("true", val))
-		mapthings[i].options |= MTF_EXTRA;
 	else if (fastcmp(param, "flip") && fastcmp("true", val))
 		mapthings[i].options |= MTF_OBJECTFLIP;
-	else if (fastcmp(param, "objectspecial") && fastcmp("true", val))
-		mapthings[i].options |= MTF_OBJECTSPECIAL;
-	else if (fastcmp(param, "ambush") && fastcmp("true", val))
-		mapthings[i].options |= MTF_AMBUSH;
 
-	else if (strlen(param) == 7 && fastncmp(param, "arg", 3) && fastncmp(param + 4, "str", 3))
+	else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9)
 	{
-		size_t argnum = param[3] - '0';
+		size_t argnum = atol(param + 9);
 		if (argnum >= NUMMAPTHINGSTRINGARGS)
 			return;
 		mapthings[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
@@ -1830,32 +1927,25 @@ static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
   * \param Structure number (mapthings, sectors, ...).
   * \param Parser function pointer.
   */
-static void TextmapParse(UINT32 dataPos, size_t num, void (*parser)(UINT32, char *, char *))
+static void TextmapParse(UINT32 dataPos, size_t num, void (*parser)(UINT32, const char *, const char *))
 {
-	char *param, *val;
+	const char *param, *val;
 
-	M_SetTokenPos(dataPos);
-	param = M_GetToken(NULL);
+	M_TokenizerSetEndPos(dataPos);
+	param = M_TokenizerRead(0);
 	if (!fastcmp(param, "{"))
 	{
-		Z_Free(param);
 		CONS_Alert(CONS_WARNING, "Invalid UDMF data capsule!\n");
 		return;
 	}
-	Z_Free(param);
 
 	while (true)
 	{
-		param = M_GetToken(NULL);
+		param = M_TokenizerRead(0);
 		if (fastcmp(param, "}"))
-		{
-			Z_Free(param);
 			break;
-		}
-		val = M_GetToken(NULL);
+		val = M_TokenizerRead(1);
 		parser(num, param, val);
-		Z_Free(param);
-		Z_Free(val);
 	}
 }
 
@@ -1884,6 +1974,29 @@ static void TextmapFixFlatOffsets(sector_t *sec)
 	}
 }
 
+static void TextmapUnfixFlatOffsets(sector_t *sec)
+{
+	if (sec->floorpic_angle)
+	{
+		fixed_t pc = FINECOSINE(sec->floorpic_angle >> ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE(sec->floorpic_angle >> ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->floor_xoffs;
+		fixed_t yoffs = sec->floor_yoffs;
+		sec->floor_xoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+		sec->floor_yoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+	}
+
+	if (sec->ceilingpic_angle)
+	{
+		fixed_t pc = FINECOSINE(sec->ceilingpic_angle >> ANGLETOFINESHIFT);
+		fixed_t ps = FINESINE(sec->ceilingpic_angle >> ANGLETOFINESHIFT);
+		fixed_t xoffs = sec->ceiling_xoffs;
+		fixed_t yoffs = sec->ceiling_yoffs;
+		sec->ceiling_xoffs = (FixedMul(xoffs, ps) % MAXFLATSIZE) + (FixedMul(yoffs, pc) % MAXFLATSIZE);
+		sec->ceiling_yoffs = (FixedMul(xoffs, pc) % MAXFLATSIZE) - (FixedMul(yoffs, ps) % MAXFLATSIZE);
+	}
+}
+
 static INT32 P_ColorToRGBA(INT32 color, UINT8 alpha)
 {
 	UINT8 r = (color >> 16) & 0xFF;
@@ -1892,6 +2005,651 @@ static INT32 P_ColorToRGBA(INT32 color, UINT8 alpha)
 	return R_PutRgbaRGBA(r, g, b, alpha);
 }
 
+static INT32 P_RGBAToColor(INT32 rgba)
+{
+	UINT8 r = R_GetRgbaR(rgba);
+	UINT8 g = R_GetRgbaG(rgba);
+	UINT8 b = R_GetRgbaB(rgba);
+	return (r << 16) | (g << 8) | b;
+}
+
+typedef struct
+{
+	mapthing_t *teleport;
+	mapthing_t *altview;
+	mapthing_t *angleanchor;
+} sectorspecialthings_t;
+
+static void P_WriteTextmap(void)
+{
+	size_t i, j;
+	FILE *f;
+	char *filepath = va(pandf, srb2home, "TEXTMAP");
+	mtag_t firsttag;
+	mapthing_t *wmapthings;
+	vertex_t *wvertexes;
+	sector_t *wsectors;
+	line_t *wlines;
+	side_t *wsides;
+	mtag_t freetag;
+	sectorspecialthings_t *specialthings;
+
+	f = fopen(filepath, "w");
+	if (!f)
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("Couldn't save map file %s\n"), filepath);
+		return;
+	}
+
+	wmapthings = Z_Calloc(nummapthings * sizeof(*mapthings), PU_LEVEL, NULL);
+	wvertexes = Z_Calloc(numvertexes * sizeof(*vertexes), PU_LEVEL, NULL);
+	wsectors = Z_Calloc(numsectors * sizeof(*sectors), PU_LEVEL, NULL);
+	wlines = Z_Calloc(numlines * sizeof(*lines), PU_LEVEL, NULL);
+	wsides = Z_Calloc(numsides * sizeof(*sides), PU_LEVEL, NULL);
+	specialthings = Z_Calloc(numsectors * sizeof(*sectors), PU_LEVEL, NULL);
+
+	memcpy(wmapthings, mapthings, nummapthings * sizeof(*mapthings));
+	memcpy(wvertexes, vertexes, numvertexes * sizeof(*vertexes));
+	memcpy(wsectors, sectors, numsectors * sizeof(*sectors));
+	memcpy(wlines, lines, numlines * sizeof(*lines));
+	memcpy(wsides, sides, numsides * sizeof(*sides));
+
+	for (i = 0; i < nummapthings; i++)
+		if (mapthings[i].tags.count)
+			wmapthings[i].tags.tags = memcpy(Z_Malloc(mapthings[i].tags.count * sizeof(mtag_t), PU_LEVEL, NULL), mapthings[i].tags.tags, mapthings[i].tags.count * sizeof(mtag_t));
+
+	for (i = 0; i < numsectors; i++)
+		if (sectors[i].tags.count)
+			wsectors[i].tags.tags = memcpy(Z_Malloc(sectors[i].tags.count*sizeof(mtag_t), PU_LEVEL, NULL), sectors[i].tags.tags, sectors[i].tags.count*sizeof(mtag_t));
+
+	for (i = 0; i < numlines; i++)
+		if (lines[i].tags.count)
+			wlines[i].tags.tags = memcpy(Z_Malloc(lines[i].tags.count * sizeof(mtag_t), PU_LEVEL, NULL), lines[i].tags.tags, lines[i].tags.count * sizeof(mtag_t));
+
+	freetag = Tag_NextUnused(0);
+
+	for (i = 0; i < nummapthings; i++)
+	{
+		subsector_t *ss;
+		INT32 s;
+
+		if (wmapthings[i].type != 751 && wmapthings[i].type != 752 && wmapthings[i].type != 758)
+			continue;
+
+		ss = R_PointInSubsector(wmapthings[i].x << FRACBITS, wmapthings[i].y << FRACBITS);
+
+		if (!ss)
+			continue;
+
+		s = ss->sector - sectors;
+
+		switch (wmapthings[i].type)
+		{
+			case 751:
+				if (!specialthings[s].teleport)
+					specialthings[s].teleport = &wmapthings[i];
+				break;
+			case 752:
+				if (!specialthings[s].altview)
+					specialthings[s].altview = &wmapthings[i];
+				break;
+			case 758:
+				if (!specialthings[s].angleanchor)
+					specialthings[s].angleanchor = &wmapthings[i];
+				break;
+			default:
+				break;
+		}
+	}
+
+	for (i = 0; i < numlines; i++)
+	{
+		INT32 s;
+
+		switch (wlines[i].special)
+		{
+			case 1:
+				TAG_ITER_SECTORS(Tag_FGet(&wlines[i].tags), s)
+				{
+					CONS_Alert(CONS_WARNING, M_GetText("Linedef %d applies custom gravity to sector %d. Changes to this gravity at runtime will not be reflected in the converted map. Use linedef type 469 for this.\n"), i, s);
+					wsectors[s].gravity = FixedDiv(lines[i].frontsector->floorheight >> FRACBITS, 1000);
+				}
+				break;
+			case 2:
+				CONS_Alert(CONS_WARNING, M_GetText("Custom exit linedef %d detected. Changes to the next map at runtime will not be reflected in the converted map. Use linedef type 468 for this.\n"), i);
+				wlines[i].args[0] = lines[i].frontsector->floorheight >> FRACBITS;
+				wlines[i].args[2] = lines[i].frontsector->ceilingheight >> FRACBITS;
+				break;
+			case 5:
+			case 50:
+			case 51:
+				CONS_Alert(CONS_WARNING, M_GetText("Linedef %d has type %d, which is not supported in UDMF.\n"), i, wlines[i].special);
+				break;
+			case 61:
+				if (wlines[i].flags & ML_MIDSOLID)
+					continue;
+				if (!wlines[i].args[1])
+					continue;
+				CONS_Alert(CONS_WARNING, M_GetText("Linedef %d with crusher type 61 rises twice as fast on spawn. This behavior is not supported in UDMF.\n"), i);
+				break;
+			case 76:
+				if (freetag == (mtag_t)MAXTAGS)
+				{
+					CONS_Alert(CONS_WARNING, M_GetText("No unused tag found. Linedef %d with type 76 cannot be converted.\n"), i);
+					break;
+				}
+				TAG_ITER_SECTORS(wlines[i].args[0], s)
+					for (j = 0; (unsigned)j < wsectors[s].linecount; j++)
+					{
+						line_t *line = wsectors[s].lines[j] - lines + wlines;
+						if (line->special < 100 || line->special >= 300)
+							continue;
+						Tag_Add(&line->tags, freetag);
+					}
+				wlines[i].args[0] = freetag;
+				freetag = Tag_NextUnused(freetag);
+				break;
+			case 259:
+				if (wlines[i].args[3] & FF_QUICKSAND)
+					CONS_Alert(CONS_WARNING, M_GetText("Quicksand properties of custom FOF on linedef %d cannot be converted. Use linedef type 75 instead.\n"), i);
+				if (wlines[i].args[3] & FF_BUSTUP)
+					CONS_Alert(CONS_WARNING, M_GetText("Bustable properties of custom FOF on linedef %d cannot be converted. Use linedef type 74 instead.\n"), i);
+				break;
+			case 412:
+				if ((s = Tag_Iterate_Sectors(wlines[i].args[0], 0)) < 0)
+					break;
+				if (!specialthings[s].teleport)
+					break;
+				if (freetag == (mtag_t)MAXTAGS)
+				{
+					CONS_Alert(CONS_WARNING, M_GetText("No unused tag found. Linedef %d with type 412 cannot be converted.\n"), i);
+					break;
+				}
+				Tag_Add(&specialthings[s].teleport->tags, freetag);
+				wlines[i].args[0] = freetag;
+				freetag = Tag_NextUnused(freetag);
+				break;
+			case 422:
+				if ((s = Tag_Iterate_Sectors(wlines[i].args[0], 0)) < 0)
+					break;
+				if (!specialthings[s].altview)
+					break;
+				if (freetag == (mtag_t)MAXTAGS)
+				{
+					CONS_Alert(CONS_WARNING, M_GetText("No unused tag found. Linedef %d with type 422 cannot be converted.\n"), i);
+					break;
+				}
+				Tag_Add(&specialthings[s].altview->tags, freetag);
+				wlines[i].args[0] = freetag;
+				specialthings[s].altview->pitch = wlines[i].args[2];
+				freetag = Tag_NextUnused(freetag);
+				break;
+			case 447:
+				CONS_Alert(CONS_WARNING, M_GetText("Linedef %d has change colormap action, which cannot be converted automatically. Tag arg0 to a sector with the desired colormap.\n"), i);
+				if (wlines[i].flags & ML_TFERLINE)
+					CONS_Alert(CONS_WARNING, M_GetText("Linedef %d mixes front and back colormaps, which is not supported in UDMF. Copy one colormap to the target sector first, then mix in the second one.\n"), i);
+				break;
+			case 455:
+				CONS_Alert(CONS_WARNING, M_GetText("Linedef %d has fade colormap action, which cannot be converted automatically. Tag arg0 to a sector with the desired colormap.\n"), i);
+				if (wlines[i].flags & ML_TFERLINE)
+					CONS_Alert(CONS_WARNING, M_GetText("Linedef %d specifies starting colormap for the fade, which is not supported in UDMF. Change the colormap with linedef type 447 instead.\n"), i);
+				break;
+			case 457:
+				if ((s = Tag_Iterate_Sectors(wlines[i].args[0], 0)) < 0)
+					break;
+				if (!specialthings[s].angleanchor)
+					break;
+				if (freetag == (mtag_t)MAXTAGS)
+				{
+					CONS_Alert(CONS_WARNING, M_GetText("No unused tag found. Linedef %d with type 457 cannot be converted.\n"), i);
+					break;
+				}
+				Tag_Add(&specialthings[s].angleanchor->tags, freetag);
+				wlines[i].args[0] = freetag;
+				freetag = Tag_NextUnused(freetag);
+				break;
+			case 606:
+				if (wlines[i].args[0] == MTAG_GLOBAL)
+				{
+					sector_t *sec = wlines[i].frontsector - sectors + wsectors;
+					sec->extra_colormap = wsides[wlines[i].sidenum[0]].colormap_data;
+				}
+				else
+				{
+					TAG_ITER_SECTORS(wlines[i].args[0], s)
+					{
+						if (wsectors[s].colormap_protected)
+							continue;
+
+						wsectors[s].extra_colormap = wsides[wlines[i].sidenum[0]].colormap_data;
+						if (freetag == (mtag_t)MAXTAGS)
+						{
+							CONS_Alert(CONS_WARNING, M_GetText("No unused tag found. Linedef %d with type 606 cannot be converted.\n"), i);
+							break;
+						}
+						Tag_Add(&wsectors[s].tags, freetag);
+						wlines[i].args[1] = freetag;
+						freetag = Tag_NextUnused(freetag);
+						break;
+					}
+				}
+				break;
+			default:
+				break;
+		}
+
+		if (wlines[i].special >= 300 && wlines[i].special < 400 && wlines[i].flags & ML_WRAPMIDTEX)
+			CONS_Alert(CONS_WARNING, M_GetText("Linedef executor trigger linedef %d has disregard order flag, which is not supported in UDMF.\n"), i);
+	}
+
+	for (i = 0; i < numsectors; i++)
+	{
+		if (Tag_Find(&wsectors[i].tags, LE_CAPSULE0))
+			CONS_Alert(CONS_WARNING, M_GetText("Sector %d has reserved tag %d, which is not supported in UDMF. Use arg3 of the boss mapthing instead.\n"), i, LE_CAPSULE0);
+		if (Tag_Find(&wsectors[i].tags, LE_CAPSULE1))
+			CONS_Alert(CONS_WARNING, M_GetText("Sector %d has reserved tag %d, which is not supported in UDMF. Use arg3 of the boss mapthing instead.\n"), i, LE_CAPSULE1);
+		if (Tag_Find(&wsectors[i].tags, LE_CAPSULE2))
+			CONS_Alert(CONS_WARNING, M_GetText("Sector %d has reserved tag %d, which is not supported in UDMF. Use arg3 of the boss mapthing instead.\n"), i, LE_CAPSULE2);
+
+		switch (GETSECSPECIAL(wsectors[i].special, 1))
+		{
+			case 9:
+			case 10:
+				CONS_Alert(CONS_WARNING, M_GetText("Sector %d has ring drainer effect, which is not supported in UDMF. Use linedef type 462 instead.\n"), i);
+				break;
+			default:
+				break;
+		}
+
+		switch (GETSECSPECIAL(wsectors[i].special, 2))
+		{
+			case 6:
+				CONS_Alert(CONS_WARNING, M_GetText("Sector %d has emerald check trigger type, which is not supported in UDMF. Use linedef types 337-339 instead.\n"), i);
+				break;
+			case 7:
+				CONS_Alert(CONS_WARNING, M_GetText("Sector %d has NiGHTS mare trigger type, which is not supported in UDMF. Use linedef types 340-342 instead.\n"), i);
+				break;
+			case 9:
+				CONS_Alert(CONS_WARNING, M_GetText("Sector %d has Egg Capsule type, which is not supported in UDMF. Use linedef type 464 instead.\n"), i);
+				break;
+			default:
+				break;
+		}
+	}
+
+	fprintf(f, "namespace = \"srb2\";\n");
+	for (i = 0; i < nummapthings; i++)
+	{
+		fprintf(f, "thing // %d\n", i);
+		fprintf(f, "{\n");
+		firsttag = Tag_FGet(&wmapthings[i].tags);
+		if (firsttag != 0)
+			fprintf(f, "id = %d;\n", firsttag);
+		if (wmapthings[i].tags.count > 1)
+		{
+			fprintf(f, "moreids = \"");
+			for (j = 1; j < wmapthings[i].tags.count; j++)
+			{
+				if (j > 1)
+					fprintf(f, " ");
+				fprintf(f, "%d", wmapthings[i].tags.tags[j]);
+			}
+			fprintf(f, "\";\n");
+		}
+		fprintf(f, "x = %d;\n", wmapthings[i].x);
+		fprintf(f, "y = %d;\n", wmapthings[i].y);
+		if (wmapthings[i].z != 0)
+			fprintf(f, "height = %d;\n", wmapthings[i].z);
+		fprintf(f, "angle = %d;\n", wmapthings[i].angle);
+		if (wmapthings[i].pitch != 0)
+			fprintf(f, "pitch = %d;\n", wmapthings[i].pitch);
+		if (wmapthings[i].roll != 0)
+			fprintf(f, "roll = %d;\n", wmapthings[i].roll);
+		if (wmapthings[i].type != 0)
+			fprintf(f, "type = %d;\n", wmapthings[i].type);
+		if (wmapthings[i].scale != FRACUNIT)
+			fprintf(f, "scale = %f;\n", FIXED_TO_FLOAT(wmapthings[i].scale));
+		if (wmapthings[i].options & MTF_OBJECTFLIP)
+			fprintf(f, "flip = true;\n");
+		for (j = 0; j < NUMMAPTHINGARGS; j++)
+			if (wmapthings[i].args[j] != 0)
+				fprintf(f, "arg%d = %d;\n", j, wmapthings[i].args[j]);
+		for (j = 0; j < NUMMAPTHINGSTRINGARGS; j++)
+			if (mapthings[i].stringargs[j])
+				fprintf(f, "stringarg%d = \"%s\";\n", j, mapthings[i].stringargs[j]);
+		fprintf(f, "}\n");
+		fprintf(f, "\n");
+	}
+
+	for (i = 0; i < numvertexes; i++)
+	{
+		fprintf(f, "vertex // %d\n", i);
+		fprintf(f, "{\n");
+		fprintf(f, "x = %f;\n", FIXED_TO_FLOAT(wvertexes[i].x));
+		fprintf(f, "y = %f;\n", FIXED_TO_FLOAT(wvertexes[i].y));
+		if (wvertexes[i].floorzset)
+			fprintf(f, "zfloor = %f;\n", FIXED_TO_FLOAT(wvertexes[i].floorz));
+		if (wvertexes[i].ceilingzset)
+			fprintf(f, "zceiling = %f;\n", FIXED_TO_FLOAT(wvertexes[i].ceilingz));
+		fprintf(f, "}\n");
+		fprintf(f, "\n");
+	}
+
+	for (i = 0; i < numlines; i++)
+	{
+		fprintf(f, "linedef // %d\n", i);
+		fprintf(f, "{\n");
+		fprintf(f, "v1 = %d;\n", wlines[i].v1 - vertexes);
+		fprintf(f, "v2 = %d;\n", wlines[i].v2 - vertexes);
+		fprintf(f, "sidefront = %d;\n", wlines[i].sidenum[0]);
+		if (wlines[i].sidenum[1] != 0xffff)
+			fprintf(f, "sideback = %d;\n", wlines[i].sidenum[1]);
+		firsttag = Tag_FGet(&wlines[i].tags);
+		if (firsttag != 0)
+			fprintf(f, "id = %d;\n", firsttag);
+		if (wlines[i].tags.count > 1)
+		{
+			fprintf(f, "moreids = \"");
+			for (j = 1; j < wlines[i].tags.count; j++)
+			{
+				if (j > 1)
+					fprintf(f, " ");
+				fprintf(f, "%d", wlines[i].tags.tags[j]);
+			}
+			fprintf(f, "\";\n");
+		}
+		if (wlines[i].special != 0)
+			fprintf(f, "special = %d;\n", wlines[i].special);
+		for (j = 0; j < NUMLINEARGS; j++)
+			if (wlines[i].args[j] != 0)
+				fprintf(f, "arg%d = %d;\n", j, wlines[i].args[j]);
+		for (j = 0; j < NUMLINESTRINGARGS; j++)
+			if (lines[i].stringargs[j])
+				fprintf(f, "stringarg%d = \"%s\";\n", j, lines[i].stringargs[j]);
+		if (wlines[i].alpha != FRACUNIT)
+			fprintf(f, "alpha = %f;\n", FIXED_TO_FLOAT(wlines[i].alpha));
+		if (wlines[i].blendmode != AST_COPY)
+		{
+			switch (wlines[i].blendmode)
+			{
+				case AST_ADD:
+					fprintf(f, "renderstyle = \"add\";\n");
+					break;
+				case AST_SUBTRACT:
+					fprintf(f, "renderstyle = \"subtract\";\n");
+					break;
+				case AST_REVERSESUBTRACT:
+					fprintf(f, "renderstyle = \"reversesubtract\";\n");
+					break;
+				case AST_MODULATE:
+					fprintf(f, "renderstyle = \"modulate\";\n");
+					break;
+				case AST_FOG:
+					fprintf(f, "renderstyle = \"fog\";\n");
+					break;
+				default:
+					break;
+			}
+		}
+		if (wlines[i].executordelay != 0 && wlines[i].backsector)
+		{
+			CONS_Alert(CONS_WARNING, M_GetText("Linedef %d has an executor delay. Changes to the delay at runtime will not be reflected in the converted map. Use linedef type 465 for this.\n"), i);
+			fprintf(f, "executordelay = %d;\n", (wlines[i].backsector->ceilingheight >> FRACBITS) + (wlines[i].backsector->floorheight >> FRACBITS));
+		}
+		if (wlines[i].flags & ML_IMPASSIBLE)
+			fprintf(f, "blocking = true;\n");
+		if (wlines[i].flags & ML_BLOCKMONSTERS)
+			fprintf(f, "blockmonsters = true;\n");
+		if (wlines[i].flags & ML_TWOSIDED)
+			fprintf(f, "twosided = true;\n");
+		if (wlines[i].flags & ML_DONTPEGTOP)
+			fprintf(f, "dontpegtop = true;\n");
+		if (wlines[i].flags & ML_DONTPEGBOTTOM)
+			fprintf(f, "dontpegbottom = true;\n");
+		if (wlines[i].flags & ML_SKEWTD)
+			fprintf(f, "skewtd = true;\n");
+		if (wlines[i].flags & ML_NOCLIMB)
+			fprintf(f, "noclimb = true;\n");
+		if (wlines[i].flags & ML_NOSKEW)
+			fprintf(f, "noskew = true;\n");
+		if (wlines[i].flags & ML_MIDPEG)
+			fprintf(f, "midpeg = true;\n");
+		if (wlines[i].flags & ML_MIDSOLID)
+			fprintf(f, "midsolid = true;\n");
+		if (wlines[i].flags & ML_WRAPMIDTEX)
+			fprintf(f, "wrapmidtex = true;\n");
+		if (wlines[i].flags & ML_NONET)
+			fprintf(f, "nonet = true;\n");
+		if (wlines[i].flags & ML_NETONLY)
+			fprintf(f, "netonly = true;\n");
+		if (wlines[i].flags & ML_BOUNCY)
+			fprintf(f, "bouncy = true;\n");
+		if (wlines[i].flags & ML_TFERLINE)
+			fprintf(f, "transfer = true;\n");
+		fprintf(f, "}\n");
+		fprintf(f, "\n");
+	}
+
+	for (i = 0; i < numsides; i++)
+	{
+		fprintf(f, "sidedef // %d\n", i);
+		fprintf(f, "{\n");
+		fprintf(f, "sector = %d;\n", wsides[i].sector - sectors);
+		if (wsides[i].textureoffset != 0)
+			fprintf(f, "offsetx = %d;\n", wsides[i].textureoffset >> FRACBITS);
+		if (wsides[i].rowoffset != 0)
+			fprintf(f, "offsety = %d;\n", wsides[i].rowoffset >> FRACBITS);
+		if (wsides[i].toptexture > 0 && wsides[i].toptexture < numtextures)
+			fprintf(f, "texturetop = \"%.*s\";\n", 8, textures[wsides[i].toptexture]->name);
+		if (wsides[i].bottomtexture > 0 && wsides[i].bottomtexture < numtextures)
+			fprintf(f, "texturebottom = \"%.*s\";\n", 8, textures[wsides[i].bottomtexture]->name);
+		if (wsides[i].midtexture > 0 && wsides[i].midtexture < numtextures)
+			fprintf(f, "texturemiddle = \"%.*s\";\n", 8, textures[wsides[i].midtexture]->name);
+		if (wsides[i].repeatcnt != 0)
+			fprintf(f, "repeatcnt = %d;\n", wsides[i].repeatcnt);
+		fprintf(f, "}\n");
+		fprintf(f, "\n");
+	}
+
+	for (i = 0; i < numsectors; i++)
+	{
+		fprintf(f, "sector // %d\n", i);
+		fprintf(f, "{\n");
+		fprintf(f, "heightfloor = %d;\n", wsectors[i].floorheight >> FRACBITS);
+		fprintf(f, "heightceiling = %d;\n", wsectors[i].ceilingheight >> FRACBITS);
+		if (wsectors[i].floorpic != -1)
+			fprintf(f, "texturefloor = \"%s\";\n", levelflats[wsectors[i].floorpic].name);
+		if (wsectors[i].ceilingpic != -1)
+			fprintf(f, "textureceiling = \"%s\";\n", levelflats[wsectors[i].ceilingpic].name);
+		fprintf(f, "lightlevel = %d;\n", wsectors[i].lightlevel);
+		if (wsectors[i].floorlightlevel != 0)
+			fprintf(f, "lightfloor = %d;\n", wsectors[i].floorlightlevel);
+		if (wsectors[i].floorlightabsolute)
+			fprintf(f, "lightfloorabsolute = true;\n");
+		if (wsectors[i].ceilinglightlevel != 0)
+			fprintf(f, "lightceiling = %d;\n", wsectors[i].ceilinglightlevel);
+		if (wsectors[i].ceilinglightabsolute)
+			fprintf(f, "lightceilingabsolute = true;\n");
+		firsttag = Tag_FGet(&wsectors[i].tags);
+		if (firsttag != 0)
+			fprintf(f, "id = %d;\n", firsttag);
+		if (wsectors[i].tags.count > 1)
+		{
+			fprintf(f, "moreids = \"");
+			for (j = 1; j < wsectors[i].tags.count; j++)
+			{
+				if (j > 1)
+					fprintf(f, " ");
+				fprintf(f, "%d", wsectors[i].tags.tags[j]);
+			}
+			fprintf(f, "\";\n");
+		}
+		sector_t tempsec = wsectors[i];
+		TextmapUnfixFlatOffsets(&tempsec);
+		if (tempsec.floor_xoffs != 0)
+			fprintf(f, "xpanningfloor = %f;\n", FIXED_TO_FLOAT(tempsec.floor_xoffs));
+		if (tempsec.floor_yoffs != 0)
+			fprintf(f, "ypanningfloor = %f;\n", FIXED_TO_FLOAT(tempsec.floor_yoffs));
+		if (tempsec.ceiling_xoffs != 0)
+			fprintf(f, "xpanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceiling_xoffs));
+		if (tempsec.ceiling_yoffs != 0)
+			fprintf(f, "ypanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceiling_yoffs));
+		if (wsectors[i].floorpic_angle != 0)
+			fprintf(f, "rotationfloor = %f;\n", FIXED_TO_FLOAT(AngleFixed(wsectors[i].floorpic_angle)));
+		if (wsectors[i].ceilingpic_angle != 0)
+			fprintf(f, "rotationceiling = %f;\n", FIXED_TO_FLOAT(AngleFixed(wsectors[i].ceilingpic_angle)));
+        if (wsectors[i].extra_colormap)
+		{
+			INT32 lightcolor = P_RGBAToColor(wsectors[i].extra_colormap->rgba);
+			UINT8 lightalpha = R_GetRgbaA(wsectors[i].extra_colormap->rgba);
+			INT32 fadecolor = P_RGBAToColor(wsectors[i].extra_colormap->fadergba);
+			UINT8 fadealpha = R_GetRgbaA(wsectors[i].extra_colormap->fadergba);
+
+			if (lightcolor != 0)
+				fprintf(f, "lightcolor = %d;\n", lightcolor);
+			if (lightalpha != 25)
+				fprintf(f, "lightalpha = %d;\n", lightalpha);
+			if (fadecolor != 0)
+				fprintf(f, "fadecolor = %d;\n", fadecolor);
+			if (fadealpha != 25)
+				fprintf(f, "fadealpha = %d;\n", fadealpha);
+			if (wsectors[i].extra_colormap->fadestart != 0)
+				fprintf(f, "fadestart = %d;\n", wsectors[i].extra_colormap->fadestart);
+			if (wsectors[i].extra_colormap->fadeend != 31)
+				fprintf(f, "fadeend = %d;\n", wsectors[i].extra_colormap->fadeend);
+			if (wsectors[i].extra_colormap->flags & CMF_FOG)
+				fprintf(f, "colormapfog = true;\n");
+			if (wsectors[i].extra_colormap->flags & CMF_FADEFULLBRIGHTSPRITES)
+				fprintf(f, "colormapfadesprites = true;\n");
+		}
+		if (wsectors[i].colormap_protected)
+			fprintf(f, "colormapprotected = true;\n");
+		if (!(wsectors[i].flags & MSF_FLIPSPECIAL_FLOOR))
+			fprintf(f, "flipspecial_nofloor = true;\n");
+		if (wsectors[i].flags & MSF_FLIPSPECIAL_CEILING)
+			fprintf(f, "flipspecial_ceiling = true;\n");
+		if (wsectors[i].flags & MSF_TRIGGERSPECIAL_TOUCH)
+			fprintf(f, "triggerspecial_touch = true;\n");
+		if (wsectors[i].flags & MSF_TRIGGERSPECIAL_HEADBUMP)
+			fprintf(f, "triggerspecial_headbump = true;\n");
+		if (wsectors[i].flags & MSF_TRIGGERLINE_PLANE)
+			fprintf(f, "triggerline_plane = true;\n");
+		if (wsectors[i].flags & MSF_TRIGGERLINE_MOBJ)
+			fprintf(f, "triggerline_mobj = true;\n");
+		if (wsectors[i].flags & MSF_INVERTPRECIP)
+			fprintf(f, "invertprecip = true;\n");
+		if (wsectors[i].flags & MSF_GRAVITYFLIP)
+			fprintf(f, "gravityflip = true;\n");
+		if (wsectors[i].flags & MSF_HEATWAVE)
+			fprintf(f, "heatwave = true;\n");
+		if (wsectors[i].flags & MSF_NOCLIPCAMERA)
+			fprintf(f, "noclipcamera = true;\n");
+		if (wsectors[i].specialflags & SSF_OUTERSPACE)
+			fprintf(f, "outerspace = true;\n");
+		if (wsectors[i].specialflags & SSF_DOUBLESTEPUP)
+			fprintf(f, "doublestepup = true;\n");
+		if (wsectors[i].specialflags & SSF_NOSTEPDOWN)
+			fprintf(f, "nostepdown = true;\n");
+		if (wsectors[i].specialflags & SSF_SPEEDPAD)
+			fprintf(f, "speedpad = true;\n");
+		if (wsectors[i].specialflags & SSF_STARPOSTACTIVATOR)
+			fprintf(f, "starpostactivator = true;\n");
+		if (wsectors[i].specialflags & SSF_EXIT)
+			fprintf(f, "exit = true;\n");
+		if (wsectors[i].specialflags & SSF_SPECIALSTAGEPIT)
+			fprintf(f, "specialstagepit = true;\n");
+		if (wsectors[i].specialflags & SSF_RETURNFLAG)
+			fprintf(f, "returnflag = true;\n");
+		if (wsectors[i].specialflags & SSF_REDTEAMBASE)
+			fprintf(f, "redteambase = true;\n");
+		if (wsectors[i].specialflags & SSF_BLUETEAMBASE)
+			fprintf(f, "blueteambase = true;\n");
+		if (wsectors[i].specialflags & SSF_FAN)
+			fprintf(f, "fan = true;\n");
+		if (wsectors[i].specialflags & SSF_SUPERTRANSFORM)
+			fprintf(f, "supertransform = true;\n");
+		if (wsectors[i].specialflags & SSF_FORCESPIN)
+			fprintf(f, "forcespin = true;\n");
+		if (wsectors[i].specialflags & SSF_ZOOMTUBESTART)
+			fprintf(f, "zoomtubestart = true;\n");
+		if (wsectors[i].specialflags & SSF_ZOOMTUBEEND)
+			fprintf(f, "zoomtubeend = true;\n");
+		if (wsectors[i].specialflags & SSF_FINISHLINE)
+			fprintf(f, "finishline = true;\n");
+		if (wsectors[i].specialflags & SSF_ROPEHANG)
+			fprintf(f, "ropehang = true;\n");
+		if (wsectors[i].friction != ORIG_FRICTION)
+			fprintf(f, "friction = %f;\n", FIXED_TO_FLOAT(wsectors[i].friction));
+		if (wsectors[i].gravity != FRACUNIT)
+			fprintf(f, "gravity = %f;\n", FIXED_TO_FLOAT(wsectors[i].gravity));
+		if (wsectors[i].damagetype != SD_NONE)
+		{
+			switch (wsectors[i].damagetype)
+			{
+				case SD_GENERIC:
+					fprintf(f, "damagetype = \"Generic\";\n");
+					break;
+				case SD_WATER:
+					fprintf(f, "damagetype = \"Water\";\n");
+					break;
+				case SD_FIRE:
+					fprintf(f, "damagetype = \"Fire\";\n");
+					break;
+				case SD_LAVA:
+					fprintf(f, "damagetype = \"Lava\";\n");
+					break;
+				case SD_ELECTRIC:
+					fprintf(f, "damagetype = \"Electric\";\n");
+					break;
+				case SD_SPIKE:
+					fprintf(f, "damagetype = \"Spike\";\n");
+					break;
+				case SD_DEATHPITTILT:
+					fprintf(f, "damagetype = \"DeathPitTilt\";\n");
+					break;
+				case SD_DEATHPITNOTILT:
+					fprintf(f, "damagetype = \"DeathPitNoTilt\";\n");
+					break;
+				case SD_INSTAKILL:
+					fprintf(f, "damagetype = \"Instakill\";\n");
+					break;
+				case SD_SPECIALSTAGE:
+					fprintf(f, "damagetype = \"SpecialStage\";\n");
+					break;
+				default:
+					break;
+			}
+		}
+		if (wsectors[i].triggertag != 0)
+			fprintf(f, "triggertag = %d;\n", wsectors[i].triggertag);
+		if (wsectors[i].triggerer != 0)
+			fprintf(f, "triggerer = %d;\n", wsectors[i].triggerer);
+		fprintf(f, "}\n");
+		fprintf(f, "\n");
+	}
+
+	fclose(f);
+
+	for (i = 0; i < nummapthings; i++)
+		if (wmapthings[i].tags.count)
+			Z_Free(wmapthings[i].tags.tags);
+
+	for (i = 0; i < numsectors; i++)
+		if (wsectors[i].tags.count)
+			Z_Free(wsectors[i].tags.tags);
+
+	for (i = 0; i < numlines; i++)
+		if (wlines[i].tags.count)
+			Z_Free(wlines[i].tags.tags);
+
+	Z_Free(wmapthings);
+	Z_Free(wvertexes);
+	Z_Free(wsectors);
+	Z_Free(wlines);
+	Z_Free(wsides);
+	Z_Free(specialthings);
+}
+
 /** Loads the textmap data, after obtaining the elements count and allocating their respective space.
   */
 static void P_LoadTextmap(void)
@@ -1945,8 +2703,21 @@ static void P_LoadTextmap(void)
 
 		sc->floorpic_angle = sc->ceilingpic_angle = 0;
 
+		sc->floorlightlevel = sc->ceilinglightlevel = 0;
+		sc->floorlightabsolute = sc->ceilinglightabsolute = false;
+
 		sc->colormap_protected = false;
 
+		sc->gravity = FRACUNIT;
+
+		sc->flags = MSF_FLIPSPECIAL_FLOOR;
+		sc->specialflags = 0;
+		sc->damagetype = SD_NONE;
+		sc->triggertag = 0;
+		sc->triggerer = TO_PLAYER;
+
+		sc->friction = ORIG_FRICTION;
+
 		textmap_colormap.used = false;
 		textmap_colormap.lightcolor = 0;
 		textmap_colormap.lightalpha = 25;
@@ -2058,6 +2829,9 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 		ld->frontsector = sides[ld->sidenum[0]].sector; //e6y: Can't be -1 here
 		ld->backsector = ld->sidenum[1] != 0xffff ? sides[ld->sidenum[1]].sector : 0;
 
+		if (udmf)
+			continue;
+
 		switch (ld->special)
 		{
 		// Compile linedef 'text' from both sidedefs 'text' for appropriate specials.
@@ -2078,8 +2852,6 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 			break;
 		case 447: // Change colormap
 		case 455: // Fade colormap
-			if (udmf)
-				break;
 			if (ld->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
 			{
 				extracolormap_t *exc = R_CopyColormap(sides[ld->sidenum[0]].colormap_data, false);
@@ -2123,8 +2895,12 @@ static boolean P_LoadMapData(const virtres_t *virt)
 	if (udmf) // Count how many entries for each type we got in textmap.
 	{
 		virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
-		if (!TextmapCount(textmap->data, textmap->size))
+		M_TokenizerOpen((char *)textmap->data);
+		if (!TextmapCount(textmap->size))
+		{
+			M_TokenizerClose();
 			return false;
+		}
 	}
 	else
 	{
@@ -2178,7 +2954,10 @@ static boolean P_LoadMapData(const virtres_t *virt)
 
 	// Load map data.
 	if (udmf)
+	{
 		P_LoadTextmap();
+		M_TokenizerClose();
+	}
 	else
 	{
 		P_LoadVertices(virtvertexes->data);
@@ -2484,11 +3263,17 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 
 				linenum = (nodetype == NT_XGL3) ? READUINT32((*data)) : READUINT16((*data));
 				if (linenum != 0xFFFF && linenum >= numlines)
-					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), i, linenum);
 				segs[k].glseg = (linenum == 0xFFFF);
 				segs[k].linedef = (linenum == 0xFFFF) ? NULL : &lines[linenum];
 				segs[k].side = READUINT8((*data));
 			}
+			while (segs[subsectors[i].firstline].glseg)
+			{
+				subsectors[i].firstline++;
+				if (subsectors[i].firstline == k)
+					I_Error("P_LoadExtendedSubsectorsAndSegs: Subsector %d does not have any valid segs!", i);
+			}
 			break;
 
 		case NT_XNOD:
@@ -3117,7 +3902,7 @@ static void P_AddBinaryMapTags(void)
 			boolean matches_target_tag = target_tag && Tag_Find(&sectors[j].tags, target_tag);
 			size_t k;
 			for (k = 0; k < 4; k++) {
-				if (lines[i].flags & ML_EFFECT5) {
+				if (lines[i].flags & ML_WRAPMIDTEX) {
 					if (matches_target_tag || (offset_tags[k] && Tag_Find(&sectors[j].tags, offset_tags[k]))) {
 						Tag_Add(&sectors[j].tags, tag);
 						break;
@@ -3131,19 +3916,197 @@ static void P_AddBinaryMapTags(void)
 			}
 		}
 	}
-}
-
-//For maps in binary format, converts setup of specials to UDMF format.
-static void P_ConvertBinaryMap(void)
-{
-	size_t i;
 
-	for (i = 0; i < numlines; i++)
+	for (i = 0; i < nummapthings; i++)
 	{
-		mtag_t tag = Tag_FGet(&lines[i].tags);
-
+		switch (mapthings[i].type)
+		{
+		case 291:
+		case 322:
+		case 750:
+		case 760:
+		case 761:
+		case 762:
+			Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
+			break;
+		case 290:
+		case 292:
+		case 294:
+		case 780:
+			Tag_FSet(&mapthings[i].tags, mapthings[i].extrainfo);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+static void P_WriteConstant(INT32 constant, char **target)
+{
+	char buffer[12];
+	sprintf(buffer, "%d", constant);
+	*target = Z_Malloc(strlen(buffer) + 1, PU_LEVEL, NULL);
+	M_Memcpy(*target, buffer, strlen(buffer) + 1);
+}
+
+static line_t *P_FindPointPushLine(taglist_t *list)
+{
+	INT32 i, l;
+
+	for (i = 0; i < list->count; i++)
+	{
+		mtag_t tag = list->tags[i];
+		TAG_ITER_LINES(tag, l)
+		{
+			if (Tag_FGet(&lines[l].tags) != tag)
+				continue;
+
+			if (lines[l].special != 547)
+				continue;
+
+			return &lines[l];
+		}
+	}
+
+	return NULL;
+}
+
+static void P_SetBinaryFOFAlpha(line_t *line)
+{
+	if (sides[line->sidenum[0]].toptexture > 0)
+	{
+		line->args[1] = sides[line->sidenum[0]].toptexture;
+		if (sides[line->sidenum[0]].toptexture >= 1001)
+		{
+			line->args[2] = (sides[line->sidenum[0]].toptexture/1000);
+			line->args[1] %= 1000;
+		}
+	}
+	else
+	{
+		line->args[1] = 128;
+		line->args[2] = TMB_TRANSLUCENT;
+	}
+}
+
+static void P_ConvertBinaryLinedefTypes(void)
+{
+	size_t i;
+
+	for (i = 0; i < numlines; i++)
+	{
+		mtag_t tag = Tag_FGet(&lines[i].tags);
+
 		switch (lines[i].special)
 		{
+		case 2: //Custom exit
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[1] |= TMEF_SKIPTALLY;
+			if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[1] |= TMEF_EMERALDCHECK;
+			break;
+		case 3: //Zoom tube parameters
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[2] = !!(lines[i].flags & ML_MIDSOLID);
+			break;
+		case 4: //Speed pad parameters
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[1] |= TMSP_NOTELEPORT;
+			if (lines[i].flags & ML_WRAPMIDTEX)
+				lines[i].args[1] |= TMSP_FORCESPIN;
+			P_WriteConstant(sides[lines[i].sidenum[0]].toptexture ? sides[lines[i].sidenum[0]].toptexture : sfx_spdpad, &lines[i].stringargs[0]);
+			break;
+		case 7: //Sector flat alignment
+			lines[i].args[0] = tag;
+			if ((lines[i].flags & (ML_NETONLY|ML_NONET)) == (ML_NETONLY|ML_NONET))
+			{
+				CONS_Alert(CONS_WARNING, M_GetText("Flat alignment linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"), tag);
+				lines[i].special = 0;
+			}
+			else if (lines[i].flags & ML_NETONLY)
+				lines[i].args[1] = TMP_CEILING;
+			else if (lines[i].flags & ML_NONET)
+				lines[i].args[1] = TMP_FLOOR;
+			else
+				lines[i].args[1] = TMP_BOTH;
+			lines[i].flags &= ~(ML_NETONLY|ML_NONET);
+
+			if (lines[i].flags & ML_EFFECT6) // Set offset through x and y texture offsets
+			{
+				angle_t flatangle = InvAngle(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y));
+				fixed_t xoffs = sides[lines[i].sidenum[0]].textureoffset;
+				fixed_t yoffs = sides[lines[i].sidenum[0]].rowoffset;
+
+				//If no tag is given, apply to front sector
+				if (lines[i].args[0] == 0)
+					P_ApplyFlatAlignment(lines[i].frontsector, flatangle, xoffs, yoffs, lines[i].args[1] != TMP_CEILING, lines[i].args[1] != TMP_FLOOR);
+				else
+				{
+					INT32 s;
+					TAG_ITER_SECTORS(lines[i].args[0], s)
+						P_ApplyFlatAlignment(sectors + s, flatangle, xoffs, yoffs, lines[i].args[1] != TMP_CEILING, lines[i].args[1] != TMP_FLOOR);
+				}
+				lines[i].special = 0;
+			}
+			break;
+		case 8: //Special sector properties
+		{
+			INT32 s;
+
+			lines[i].args[0] = tag;
+			TAG_ITER_SECTORS(tag, s)
+			{
+				if (lines[i].flags & ML_NOCLIMB)
+				{
+					sectors[s].flags &= ~MSF_FLIPSPECIAL_FLOOR;
+					sectors[s].flags |= MSF_FLIPSPECIAL_CEILING;
+				}
+				else if (lines[i].flags & ML_MIDSOLID)
+					sectors[s].flags |= MSF_FLIPSPECIAL_BOTH;
+
+				if (lines[i].flags & ML_MIDPEG)
+					sectors[s].flags |= MSF_TRIGGERSPECIAL_TOUCH;
+				if (lines[i].flags & ML_NOSKEW)
+					sectors[s].flags |= MSF_TRIGGERSPECIAL_HEADBUMP;
+
+				if (lines[i].flags & ML_SKEWTD)
+					sectors[s].flags |= MSF_INVERTPRECIP;
+			}
+
+			if (GETSECSPECIAL(lines[i].frontsector->special, 4) != 12)
+				lines[i].special = 0;
+
+			break;
+		}
+		case 10: //Culling plane
+			lines[i].args[0] = tag;
+			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 11: //Rope hang parameters
+			lines[i].args[0] = (lines[i].flags & ML_NOCLIMB) ? 0 : sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[2] = !!(lines[i].flags & ML_SKEWTD);
+			break;
+		case 13: //Heat wave effect
+		{
+			INT32 s;
+
+			TAG_ITER_SECTORS(tag, s)
+				sectors[s].flags |= MSF_HEATWAVE;
+
+			break;
+		}
+		case 14: //Bustable block parameters
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[2] = !!(lines[i].flags & ML_SKEWTD);
+			P_WriteConstant(sides[lines[i].sidenum[0]].toptexture, &lines[i].stringargs[0]);
+			break;
+		case 16: //Minecart parameters
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			break;
 		case 20: //PolyObject first line
 		{
 			INT32 check = -1;
@@ -3178,15 +4141,15 @@ static void P_ConvertBinaryMap(void)
 						: ((lines[paramline].frontsector->floorheight >> FRACBITS) / 100);
 
 			//Flags
-			if (lines[paramline].flags & ML_EFFECT1)
+			if (lines[paramline].flags & ML_SKEWTD)
 				lines[i].args[3] |= TMPF_NOINSIDES;
-			if (lines[paramline].flags & ML_EFFECT2)
+			if (lines[paramline].flags & ML_NOSKEW)
 				lines[i].args[3] |= TMPF_INTANGIBLE;
-			if (lines[paramline].flags & ML_EFFECT3)
+			if (lines[paramline].flags & ML_MIDPEG)
 				lines[i].args[3] |= TMPF_PUSHABLESTOP;
-			if (lines[paramline].flags & ML_EFFECT4)
+			if (lines[paramline].flags & ML_MIDSOLID)
 				lines[i].args[3] &= ~TMPF_INVISIBLEPLANES;
-			/*if (lines[paramline].flags & ML_EFFECT5)
+			/*if (lines[paramline].flags & ML_WRAPMIDTEX)
 				lines[i].args[3] |= TMPF_DONTCLIPPLANES;*/
 			if (lines[paramline].flags & ML_EFFECT6)
 				lines[i].args[3] |= TMPF_SPLAT;
@@ -3195,56 +4158,1514 @@ static void P_ConvertBinaryMap(void)
 
 			break;
 		}
-		case 443: //Call Lua function
-			if (lines[i].text)
+		case 30: //Polyobject - waving flag
+			lines[i].args[0] = tag;
+			lines[i].args[1] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			break;
+		case 31: //Polyobject - displacement by front sector
+			lines[i].args[0] = tag;
+			lines[i].args[1] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+			break;
+		case 32: //Polyobject - angular displacement by front sector
+			lines[i].args[0] = tag;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset ? sides[lines[i].sidenum[0]].textureoffset >> FRACBITS : 128;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset ? sides[lines[i].sidenum[0]].rowoffset >> FRACBITS : 90;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[3] |= TMPR_DONTROTATEOTHERS;
+			else if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[3] |= TMPR_ROTATEPLAYERS;
+			break;
+		case 50: //Instantly lower floor on level load
+		case 51: //Instantly raise ceiling on level load
+			lines[i].args[0] = tag;
+			break;
+		case 52: //Continuously falling sector
+			lines[i].args[0] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 53: //Continuous floor/ceiling mover
+		case 54: //Continuous floor mover
+		case 55: //Continuous ceiling mover
+			lines[i].args[0] = tag;
+			lines[i].args[1] = (lines[i].special == 53) ? TMP_BOTH : lines[i].special - 54;
+			lines[i].args[2] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].args[3] = lines[i].args[2];
+			lines[i].args[4] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[5] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].special = 53;
+			break;
+		case 56: //Continuous two-speed floor/ceiling mover
+		case 57: //Continuous two-speed floor mover
+		case 58: //Continuous two-speed ceiling mover
+			lines[i].args[0] = tag;
+			lines[i].args[1] = (lines[i].special == 56) ? TMP_BOTH : lines[i].special - 57;
+			lines[i].args[2] = abs(lines[i].dx) >> FRACBITS;
+			lines[i].args[3] = abs(lines[i].dy) >> FRACBITS;
+			lines[i].args[4] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[5] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].special = 56;
+			break;
+		case 59: //Activate moving platform
+		case 60: //Activate moving platform (adjustable speed)
+			lines[i].args[0] = tag;
+			lines[i].args[1] = (lines[i].special == 60) ? P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS : 8;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[3] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[4] = (lines[i].flags & ML_NOCLIMB) ? 1 : 0;
+			lines[i].special = 60;
+			break;
+		case 61: //Crusher (Ceiling to floor)
+		case 62: //Crusher (Floor to ceiling)
+			lines[i].args[0] = tag;
+			lines[i].args[1] = lines[i].special - 61;
+			if (lines[i].flags & ML_MIDSOLID)
 			{
-				lines[i].stringargs[0] = Z_Malloc(strlen(lines[i].text) + 1, PU_LEVEL, NULL);
-				M_Memcpy(lines[i].stringargs[0], lines[i].text, strlen(lines[i].text) + 1);
+				lines[i].args[2] = abs(lines[i].dx) >> FRACBITS;
+				lines[i].args[3] = lines[i].args[2];
 			}
 			else
-				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", sizeu1(i));
+			{
+				lines[i].args[2] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> (FRACBITS + 1);
+				lines[i].args[3] = lines[i].args[2] / 4;
+			}
+			lines[i].special = 61;
 			break;
-		case 447: //Change colormap
-			lines[i].args[0] = Tag_FGet(&lines[i].tags);
-			if (lines[i].flags & ML_EFFECT3)
-				lines[i].args[2] |= TMCF_RELATIVE;
-			if (lines[i].flags & ML_EFFECT1)
-				lines[i].args[2] |= TMCF_SUBLIGHTR|TMCF_SUBFADER;
+		case 63: //Fake floor/ceiling planes
+			lines[i].args[0] = tag;
+			break;
+		case 64: //Appearing/disappearing FOF
+			lines[i].args[0] = (lines[i].flags & ML_BLOCKMONSTERS) ? 0 : tag;
+			lines[i].args[1] = (lines[i].flags & ML_BLOCKMONSTERS) ? tag : Tag_FGet(&lines[i].frontsector->tags);
+			lines[i].args[2] = lines[i].dx >> FRACBITS;
+			lines[i].args[3] = lines[i].dy >> FRACBITS;
+			lines[i].args[4] = lines[i].frontsector->floorheight >> FRACBITS;
+			lines[i].args[5] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 66: //Move floor by displacement
+		case 67: //Move ceiling by displacement
+		case 68: //Move floor and ceiling by displacement
+			lines[i].args[0] = tag;
+			lines[i].args[1] = lines[i].special - 66;
+			lines[i].args[2] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].special = 66;
+			break;
+		case 76: //Make FOF bouncy
+			lines[i].args[0] = tag;
+			lines[i].args[1] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			break;
+		case 100: //FOF: solid, opaque, shadowcasting
+		case 101: //FOF: solid, opaque, non-shadowcasting
+		case 102: //FOF: solid, translucent
+		case 103: //FOF: solid, sides only
+		case 104: //FOF: solid, no sides
+		case 105: //FOF: solid, invisible
+			lines[i].args[0] = tag;
+
+			//Alpha
+			if (lines[i].special == 102)
+			{
+				if (lines[i].flags & ML_NOCLIMB)
+					lines[i].args[3] |= TMFA_INSIDES;
+				P_SetBinaryFOFAlpha(&lines[i]);
+
+				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
+				if (lines[i].args[1] == 256)
+					lines[i].args[3] |= TMFA_SPLAT;
+			}
+			else
+				lines[i].args[1] = 255;
+
+			//Appearance
+			if (lines[i].special == 105)
+				lines[i].args[3] |= TMFA_NOPLANES|TMFA_NOSIDES;
+			else if (lines[i].special == 104)
+				lines[i].args[3] |= TMFA_NOSIDES;
+			else if (lines[i].special == 103)
+				lines[i].args[3] |= TMFA_NOPLANES;
+			if (lines[i].special != 100 && (lines[i].special != 104 || !(lines[i].flags & ML_NOCLIMB)))
+				lines[i].args[3] |= TMFA_NOSHADE;
+			if (lines[i].flags & ML_EFFECT6)
+				lines[i].args[3] |= TMFA_SPLAT;
+
+			//Tangibility
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[4] |= TMFT_DONTBLOCKOTHERS;
+			if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[4] |= TMFT_DONTBLOCKPLAYER;
+
+			lines[i].special = 100;
+			break;
+		case 120: //FOF: water, opaque
+		case 121: //FOF: water, translucent
+		case 122: //FOF: water, opaque, no sides
+		case 123: //FOF: water, translucent, no sides
+		case 124: //FOF: goo water, translucent
+		case 125: //FOF: goo water, translucent, no sides
+			lines[i].args[0] = tag;
+
+			//Alpha
+			if (lines[i].special == 120 || lines[i].special == 122)
+				lines[i].args[1] = 255;
+			else
+			{
+				P_SetBinaryFOFAlpha(&lines[i]);
+
+				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
+				if (lines[i].args[1] == 256)
+					lines[i].args[3] |= TMFW_SPLAT;
+			}
+
+			//No sides?
+			if (lines[i].special == 122 || lines[i].special == 123 || lines[i].special == 125)
+				lines[i].args[3] |= TMFW_NOSIDES;
+
+			//Flags
 			if (lines[i].flags & ML_NOCLIMB)
-				lines[i].args[2] |= TMCF_SUBLIGHTG|TMCF_SUBFADEG;
-			if (lines[i].flags & ML_EFFECT2)
-				lines[i].args[2] |= TMCF_SUBLIGHTB|TMCF_SUBFADEB;
+				lines[i].args[3] |= TMFW_DOUBLESHADOW;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[3] |= TMFW_COLORMAPONLY;
+			if (!(lines[i].flags & ML_WRAPMIDTEX))
+				lines[i].args[3] |= TMFW_NORIPPLE;
+
+			//Goo?
+			if (lines[i].special >= 124)
+				lines[i].args[3] |= TMFW_GOOWATER;
+
+			//Splat rendering?
+			if (lines[i].flags & ML_EFFECT6)
+				lines[i].args[3] |= TMFW_SPLAT;
+
+			lines[i].special = 120;
 			break;
-		case 455: //Fade colormap
-		{
-			INT32 speed = (INT32)((((lines[i].flags & ML_DONTPEGBOTTOM) || !sides[lines[i].sidenum[0]].rowoffset) && lines[i].sidenum[1] != 0xFFFF) ?
-				abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS)
-				: abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS));
+		case 140: //FOF: intangible from bottom, opaque
+		case 141: //FOF: intangible from bottom, translucent
+		case 142: //FOF: intangible from bottom, translucent, no sides
+		case 143: //FOF: intangible from top, opaque
+		case 144: //FOF: intangible from top, translucent
+		case 145: //FOF: intangible from top, translucent, no sides
+		case 146: //FOF: only tangible from sides
+			lines[i].args[0] = tag;
 
-			lines[i].args[0] = Tag_FGet(&lines[i].tags);
-			if (lines[i].flags & ML_EFFECT4)
-				lines[i].args[2] = speed;
+			//Alpha
+			if (lines[i].special == 141 || lines[i].special == 142 || lines[i].special == 144 || lines[i].special == 145)
+			{
+				if (lines[i].flags & ML_NOCLIMB)
+					lines[i].args[3] |= TMFA_INSIDES;
+				P_SetBinaryFOFAlpha(&lines[i]);
+
+				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
+				if (lines[i].args[1] == 256)
+					lines[i].args[3] |= TMFA_SPLAT;
+			}
 			else
-				lines[i].args[2] = (256 + speed - 1)/speed;
-			if (lines[i].flags & ML_EFFECT3)
-				lines[i].args[3] |= TMCF_RELATIVE;
-			if (lines[i].flags & ML_EFFECT1)
-				lines[i].args[3] |= TMCF_SUBLIGHTR|TMCF_SUBFADER;
+				lines[i].args[1] = 255;
+
+			//Appearance
+			if (lines[i].special == 142 || lines[i].special == 145)
+				lines[i].args[3] |= TMFA_NOSIDES;
+			else if (lines[i].special == 146)
+				lines[i].args[3] |= TMFA_NOPLANES;
+			if (lines[i].special != 146 && (lines[i].flags & ML_NOCLIMB))
+				lines[i].args[3] |= TMFA_NOSHADE;
+			if (lines[i].flags & ML_EFFECT6)
+				lines[i].args[3] |= TMFA_SPLAT;
+
+			//Tangibility
+			if (lines[i].special <= 142)
+				lines[i].args[4] |= TMFT_INTANGIBLEBOTTOM;
+			else if (lines[i].special <= 145)
+				lines[i].args[4] |= TMFT_INTANGIBLETOP;
+			else
+				lines[i].args[4] |= TMFT_INTANGIBLEBOTTOM|TMFT_INTANGIBLETOP;
+
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[4] |= TMFT_DONTBLOCKOTHERS;
+			if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[4] |= TMFT_DONTBLOCKPLAYER;
+
+			lines[i].special = 100;
+			break;
+		case 150: //FOF: Air bobbing
+		case 151: //FOF: Air bobbing (adjustable)
+		case 152: //FOF: Reverse air bobbing (adjustable)
+		case 153: //FOF: Dynamically sinking platform
+			lines[i].args[0] = tag;
+			lines[i].args[1] = (lines[i].special == 150) ? 16 : (P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS);
+
+			//Flags
+			if (lines[i].special == 152)
+				lines[i].args[2] |= TMFB_REVERSE;
 			if (lines[i].flags & ML_NOCLIMB)
-				lines[i].args[3] |= TMCF_SUBLIGHTG|TMCF_SUBFADEG;
-			if (lines[i].flags & ML_EFFECT2)
-				lines[i].args[3] |= TMCF_SUBLIGHTB|TMCF_SUBFADEB;
-			if (lines[i].flags & ML_BOUNCY)
-				lines[i].args[3] |= TMCF_FROMBLACK;
-			if (lines[i].flags & ML_EFFECT5)
-				lines[i].args[3] |= TMCF_OVERRIDE;
+				lines[i].args[2] |= TMFB_SPINDASH;
+			if (lines[i].special == 153)
+				lines[i].args[2] |= TMFB_DYNAMIC;
+
+			lines[i].special = 150;
+			break;
+		case 160: //FOF: Water bobbing
+			lines[i].args[0] = tag;
+			break;
+		case 170: //FOF: Crumbling, respawn
+		case 171: //FOF: Crumbling, no respawn
+		case 172: //FOF: Crumbling, respawn, intangible from bottom
+		case 173: //FOF: Crumbling, no respawn, intangible from bottom
+		case 174: //FOF: Crumbling, respawn, intangible from bottom, translucent
+		case 175: //FOF: Crumbling, no respawn, intangible from bottom, translucent
+		case 176: //FOF: Crumbling, respawn, floating, bobbing
+		case 177: //FOF: Crumbling, no respawn, floating, bobbing
+		case 178: //FOF: Crumbling, respawn, floating
+		case 179: //FOF: Crumbling, no respawn, floating
+		case 180: //FOF: Crumbling, respawn, air bobbing
+			lines[i].args[0] = tag;
+
+			//Alpha
+			if (lines[i].special >= 174 && lines[i].special <= 175)
+			{
+				P_SetBinaryFOFAlpha(&lines[i]);
+
+				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
+				if (lines[i].args[1] == 256)
+					lines[i].args[4] |= TMFC_SPLAT;
+			}
+			else
+				lines[i].args[1] = 255;
+
+			if (lines[i].special >= 172 && lines[i].special <= 175)
+			{
+				lines[i].args[3] |= TMFT_INTANGIBLEBOTTOM;
+				if (lines[i].flags & ML_NOCLIMB)
+					lines[i].args[4] |= TMFC_NOSHADE;
+			}
+
+			if (lines[i].special % 2 == 1)
+				lines[i].args[4] |= TMFC_NORETURN;
+			if (lines[i].special == 176 || lines[i].special == 177 || lines[i].special == 180)
+				lines[i].args[4] |= TMFC_AIRBOB;
+			if (lines[i].special >= 176 && lines[i].special <= 179)
+				lines[i].args[4] |= TMFC_FLOATBOB;
+			if (lines[i].flags & ML_EFFECT6)
+				lines[i].args[4] |= TMFC_SPLAT;
+
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[3] |= TMFT_DONTBLOCKOTHERS;
+			if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[3] |= TMFT_DONTBLOCKPLAYER;
+
+			lines[i].special = 170;
+			break;
+		case 190: // FOF: Rising, solid, opaque, shadowcasting
+		case 191: // FOF: Rising, solid, opaque, non-shadowcasting
+		case 192: // FOF: Rising, solid, translucent
+		case 193: // FOF: Rising, solid, invisible
+		case 194: // FOF: Rising, intangible from bottom, opaque
+		case 195: // FOF: Rising, intangible from bottom, translucent
+			lines[i].args[0] = tag;
+
+			//Translucency
+			if (lines[i].special == 192 || lines[i].special == 195)
+			{
+				P_SetBinaryFOFAlpha(&lines[i]);
+
+				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
+				if (lines[i].args[1] == 256)
+					lines[i].args[3] |= TMFA_SPLAT;
+			}
+			else
+				lines[i].args[1] = 255;
+
+			//Appearance
+			if (lines[i].special == 193)
+				lines[i].args[3] |= TMFA_NOPLANES|TMFA_NOSIDES;
+			if (lines[i].special >= 194)
+				lines[i].args[3] |= TMFA_INSIDES;
+			if (lines[i].special != 190 && (lines[i].special <= 193 || lines[i].flags & ML_NOCLIMB))
+				lines[i].args[3] |= TMFA_NOSHADE;
+			if (lines[i].flags & ML_EFFECT6)
+				lines[i].args[3] |= TMFA_SPLAT;
+
+			//Tangibility
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[4] |= TMFT_DONTBLOCKOTHERS;
+			if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[4] |= TMFT_DONTBLOCKPLAYER;
+			if (lines[i].special >= 194)
+				lines[i].args[4] |= TMFT_INTANGIBLEBOTTOM;
+
+			//Speed
+			lines[i].args[5] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+
+			//Flags
+			if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[6] |= TMFR_REVERSE;
+			if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[6] |= TMFR_SPINDASH;
+
+			lines[i].special = 190;
+			break;
+		case 200: //FOF: Light block
+		case 201: //FOF: Half light block
+			lines[i].args[0] = tag;
+			if (lines[i].special == 201)
+				lines[i].args[1] = 1;
+			lines[i].special = 200;
+			break;
+		case 202: //FOF: Fog block
+		case 223: //FOF: Intangible, invisible
+			lines[i].args[0] = tag;
+			break;
+		case 220: //FOF: Intangible, opaque
+		case 221: //FOF: Intangible, translucent
+		case 222: //FOF: Intangible, sides only
+			lines[i].args[0] = tag;
+
+			//Alpha
+			if (lines[i].special == 221)
+			{
+				P_SetBinaryFOFAlpha(&lines[i]);
+
+				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
+				if (lines[i].args[1] == 256)
+					lines[i].args[3] |= TMFA_SPLAT;
+			}
+			else
+				lines[i].args[1] = 255;
+
+			//Appearance
+			if (lines[i].special == 222)
+				lines[i].args[3] |= TMFA_NOPLANES;
+			if (lines[i].special == 221)
+				lines[i].args[3] |= TMFA_INSIDES;
+			if (lines[i].special != 220 && !(lines[i].flags & ML_NOCLIMB))
+				lines[i].args[3] |= TMFA_NOSHADE;
+			if (lines[i].flags & ML_EFFECT6)
+				lines[i].args[3] |= TMFA_SPLAT;
+
+			lines[i].special = 220;
+            break;
+		case 250: //FOF: Mario block
+			lines[i].args[0] = tag;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[1] |= TMFM_BRICK;
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[1] |= TMFM_INVISIBLE;
+			break;
+		case 251: //FOF: Thwomp block
+			lines[i].args[0] = tag;
+			if (lines[i].flags & ML_WRAPMIDTEX) //Custom speeds
+			{
+				lines[i].args[1] = lines[i].dy >> FRACBITS;
+				lines[i].args[2] = lines[i].dx >> FRACBITS;
+			}
+			else
+			{
+				lines[i].args[1] = 80;
+				lines[i].args[2] = 16;
+			}
+			if (lines[i].flags & ML_MIDSOLID)
+				P_WriteConstant(sides[lines[i].sidenum[0]].textureoffset >> FRACBITS, &lines[i].stringargs[0]);
+			break;
+		case 252: //FOF: Shatter block
+		case 253: //FOF: Shatter block, translucent
+		case 254: //FOF: Bustable block
+		case 255: //FOF: Spin-bustable block
+		case 256: //FOF: Spin-bustable block, translucent
+			lines[i].args[0] = tag;
+
+			//Alpha
+			if (lines[i].special == 253 || lines[i].special == 256)
+			{
+				P_SetBinaryFOFAlpha(&lines[i]);
+
+				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
+				if (lines[i].args[1] == 256)
+					lines[i].args[4] |= TMFB_SPLAT;
+			}
+			else
+				lines[i].args[1] = 255;
+
+			//Bustable type
+			if (lines[i].special <= 253)
+				lines[i].args[3] = TMFB_TOUCH;
+			else if (lines[i].special >= 255)
+				lines[i].args[3] = TMFB_SPIN;
+			else if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[3] = TMFB_STRONG;
+			else
+				lines[i].args[3] = TMFB_REGULAR;
+
+			//Flags
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[4] |= TMFB_PUSHABLES;
+			if (lines[i].flags & ML_WRAPMIDTEX)
+			{
+				lines[i].args[4] |= TMFB_EXECUTOR;
+				lines[i].args[5] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			}
+			if (lines[i].special == 252 && lines[i].flags & ML_NOCLIMB)
+				lines[i].args[4] |= TMFB_ONLYBOTTOM;
+			if (lines[i].flags & ML_EFFECT6)
+				lines[i].args[4] |= TMFB_SPLAT;
+
+			lines[i].special = 254;
+			break;
+		case 257: //FOF: Quicksand
+			lines[i].args[0] = tag;
+			if (!(lines[i].flags & ML_WRAPMIDTEX))
+				lines[i].args[1] = 1; //No ripple effect
+			lines[i].args[2] = lines[i].dx >> FRACBITS; //Sinking speed
+			lines[i].args[3] = lines[i].dy >> FRACBITS; //Friction
+			break;
+		case 258: //FOF: Laser
+			lines[i].args[0] = tag;
+
+			//Alpha
+			P_SetBinaryFOFAlpha(&lines[i]);
+
+			//Flags
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[3] |= TMFL_NOBOSSES;
+			//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
+			if (lines[i].flags & ML_EFFECT6 || lines[i].args[1] == 256)
+				lines[i].args[3] |= TMFL_SPLAT;
+
+			break;
+		case 259: //Custom FOF
+			if (lines[i].sidenum[1] == 0xffff)
+				I_Error("Custom FOF (tag %d) found without a linedef back side!", tag);
+
+			lines[i].args[0] = tag;
+			lines[i].args[3] = sides[lines[i].sidenum[1]].toptexture;
+			if (lines[i].flags & ML_EFFECT6)
+				lines[i].args[3] |= FF_SPLAT;
+			lines[i].args[4] = sides[lines[i].sidenum[1]].midtexture;
+			if (lines[i].args[3] & FF_TRANSLUCENT)
+			{
+				P_SetBinaryFOFAlpha(&lines[i]);
+
+				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
+				if (lines[i].args[1] == 256)
+					lines[i].args[3] |= FF_SPLAT;
+			}
+			else
+				lines[i].args[1] = 255;
+			break;
+		case 300: //Trigger linedef executor - Continuous
+		case 301: //Trigger linedef executor - Each time
+		case 302: //Trigger linedef executor - Once
+			if (lines[i].special == 302)
+				lines[i].args[0] = TMT_ONCE;
+			else if (lines[i].special == 301)
+				lines[i].args[0] = (lines[i].flags & ML_BOUNCY) ? TMT_EACHTIMEENTERANDEXIT : TMT_EACHTIMEENTER;
+			else
+				lines[i].args[0] = TMT_CONTINUOUS;
+			lines[i].special = 300;
+			break;
+		case 303: //Ring count - Continuous
+		case 304: //Ring count - Once
+			lines[i].args[0] = (lines[i].special == 304) ? TMT_ONCE : TMT_CONTINUOUS;
+			lines[i].args[1] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[2] = TMC_LTE;
+			else if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[2] = TMC_GTE;
+			else
+				lines[i].args[2] = TMC_EQUAL;
+			lines[i].args[3] = !!(lines[i].flags & ML_MIDSOLID);
+			lines[i].special = 303;
+			break;
+		case 305: //Character ability - Continuous
+		case 306: //Character ability - Each time
+		case 307: //Character ability - Once
+			if (lines[i].special == 307)
+				lines[i].args[0] = TMT_ONCE;
+			else if (lines[i].special == 306)
+				lines[i].args[0] = (lines[i].flags & ML_BOUNCY) ? TMT_EACHTIMEENTERANDEXIT : TMT_EACHTIMEENTER;
+			else
+				lines[i].args[0] = TMT_CONTINUOUS;
+			lines[i].args[1] = (P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS) / 10;
+			lines[i].special = 305;
+			break;
+		case 308: //Race only - once
+			lines[i].args[0] = TMT_ONCE;
+			lines[i].args[1] = GTR_RACE;
+			lines[i].args[2] = TMF_HASANY;
+			break;
+		case 309: //CTF red team - continuous
+		case 310: //CTF red team - each time
+		case 311: //CTF blue team - continuous
+		case 312: //CTF blue team - each time
+			if (lines[i].special % 2 == 0)
+				lines[i].args[0] = (lines[i].flags & ML_BOUNCY) ? TMT_EACHTIMEENTERANDEXIT : TMT_EACHTIMEENTER;
+			else
+				lines[i].args[0] = TMT_CONTINUOUS;
+			lines[i].args[1] = (lines[i].special > 310) ? TMT_BLUE : TMT_RED;
+			lines[i].special = 309;
+			break;
+		case 313: //No more enemies - once
+			lines[i].args[0] = tag;
+			break;
+		case 314: //Number of pushables - Continuous
+		case 315: //Number of pushables - Once
+			lines[i].args[0] = (lines[i].special == 315) ? TMT_ONCE : TMT_CONTINUOUS;
+			lines[i].args[1] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[2] = TMC_GTE;
+			else if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[2] = TMC_LTE;
+			else
+				lines[i].args[2] = TMC_EQUAL;
+			lines[i].special = 314;
+			break;
+		case 317: //Condition set trigger - Continuous
+		case 318: //Condition set trigger - Once
+			lines[i].args[0] = (lines[i].special == 318) ? TMT_ONCE : TMT_CONTINUOUS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].special = 317;
+			break;
+		case 319: //Unlockable trigger - Continuous
+		case 320: //Unlockable trigger - Once
+			lines[i].args[0] = (lines[i].special == 320) ? TMT_ONCE : TMT_CONTINUOUS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].special = 319;
+			break;
+		case 321: //Trigger after X calls - Continuous
+		case 322: //Trigger after X calls - Each time
+			if (lines[i].special % 2 == 0)
+				lines[i].args[0] = (lines[i].flags & ML_BOUNCY) ? TMXT_EACHTIMEENTERANDEXIT : TMXT_EACHTIMEENTER;
+			else
+				lines[i].args[0] = TMXT_CONTINUOUS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			if (lines[i].flags & ML_NOCLIMB)
+			{
+				lines[i].args[2] = 1;
+				lines[i].args[3] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			}
+			else
+				lines[i].args[2] = lines[i].args[3] = 0;
+			lines[i].special = 321;
+			break;
+		case 323: //NiGHTSerize - Each time
+		case 324: //NiGHTSerize - Once
+		case 325: //DeNiGHTSerize - Each time
+		case 326: //DeNiGHTSerize - Once
+		case 327: //NiGHTS lap - Each time
+		case 328: //NiGHTS lap - Once
+		case 329: //Ideya capture touch - Each time
+		case 330: //Ideya capture touch - Once
+			lines[i].args[0] = (lines[i].special + 1) % 2;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[3] = TMC_LTE;
+			else if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[3] = TMC_GTE;
+			else
+				lines[i].args[3] = TMC_EQUAL;
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[4] = TMC_LTE;
+			else if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[4] = TMC_GTE;
+			else
+				lines[i].args[4] = TMC_EQUAL;
+			if (lines[i].flags & ML_DONTPEGBOTTOM)
+				lines[i].args[5] = TMNP_SLOWEST;
+			else if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[5] = TMNP_TRIGGERER;
+			else
+				lines[i].args[5] = TMNP_FASTEST;
+			if (lines[i].special % 2 == 0)
+				lines[i].special--;
+			if (lines[i].special == 323)
+			{
+				if (lines[i].flags & ML_TFERLINE)
+					lines[i].args[6] = TMN_FROMNONIGHTS;
+				else if (lines[i].flags & ML_DONTPEGTOP)
+					lines[i].args[6] = TMN_FROMNIGHTS;
+				else
+					lines[i].args[6] = TMN_ALWAYS;
+
+				if (lines[i].flags & ML_MIDPEG)
+					lines[i].args[7] |= TMN_BONUSLAPS;
+				if (lines[i].flags & ML_BOUNCY)
+					lines[i].args[7] |= TMN_LEVELCOMPLETION;
+			}
+			else if (lines[i].special == 325)
+			{
+				if (lines[i].flags & ML_TFERLINE)
+					lines[i].args[6] = TMD_NOBODYNIGHTS;
+				else if (lines[i].flags & ML_DONTPEGTOP)
+					lines[i].args[6] = TMD_SOMEBODYNIGHTS;
+				else
+					lines[i].args[6] = TMD_ALWAYS;
+
+				lines[i].args[7] = !!(lines[i].flags & ML_MIDPEG);
+			}
+			else if (lines[i].special == 327)
+				lines[i].args[6] = !!(lines[i].flags & ML_MIDPEG);
+			else
+			{
+				if (lines[i].flags & ML_DONTPEGTOP)
+					lines[i].args[6] = TMS_ALWAYS;
+				else if (lines[i].flags & ML_BOUNCY)
+					lines[i].args[6] = TMS_IFNOTENOUGH;
+				else
+					lines[i].args[6] = TMS_IFENOUGH;
+
+				if (lines[i].flags & ML_MIDPEG)
+					lines[i].args[7] |= TMI_BONUSLAPS;
+				if (lines[i].flags & ML_TFERLINE)
+					lines[i].args[7] |= TMI_ENTER;
+			}
+			break;
+		case 331: // Player skin - continuous
+		case 332: // Player skin - each time
+		case 333: // Player skin - once
+			if (lines[i].special == 303)
+				lines[i].args[0] = TMT_ONCE;
+			else if (lines[i].special == 302)
+				lines[i].args[0] = (lines[i].flags & ML_BOUNCY) ? TMT_EACHTIMEENTERANDEXIT : TMT_EACHTIMEENTER;
+			else
+				lines[i].args[0] = TMT_CONTINUOUS;
+			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
+			if (lines[i].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(lines[i].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], lines[i].text, strlen(lines[i].text) + 1);
+			}
+			lines[i].special = 331;
+			break;
+		case 334: // Object dye - continuous
+		case 335: // Object dye - each time
+		case 336: // Object dye - once
+			if (lines[i].special == 336)
+				lines[i].args[0] = TMT_ONCE;
+			else if (lines[i].special == 335)
+				lines[i].args[0] = (lines[i].flags & ML_BOUNCY) ? TMT_EACHTIMEENTERANDEXIT : TMT_EACHTIMEENTER;
+			else
+				lines[i].args[0] = TMT_CONTINUOUS;
+			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
+			if (sides[lines[i].sidenum[0]].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
+			}
+			lines[i].special = 334;
+			break;
+		case 337: //Emerald check - continuous
+		case 338: //Emerald check - each time
+		case 339: //Emerald check - once
+			if (lines[i].special == 339)
+				lines[i].args[0] = TMT_ONCE;
+			else if (lines[i].special == 338)
+				lines[i].args[0] = (lines[i].flags & ML_BOUNCY) ? TMT_EACHTIMEENTERANDEXIT : TMT_EACHTIMEENTER;
+			else
+				lines[i].args[0] = TMT_CONTINUOUS;
+			lines[i].args[1] = EMERALD1|EMERALD2|EMERALD3|EMERALD4|EMERALD5|EMERALD6|EMERALD7;
+			lines[i].args[2] = TMF_HASALL;
+			lines[i].special = 337;
+			break;
+		case 340: //NiGHTS mare - continuous
+		case 341: //NiGHTS mare - each time
+		case 342: //NiGHTS mare - once
+			if (lines[i].special == 342)
+				lines[i].args[0] = TMT_ONCE;
+			else if (lines[i].special == 341)
+				lines[i].args[0] = (lines[i].flags & ML_BOUNCY) ? TMT_EACHTIMEENTERANDEXIT : TMT_EACHTIMEENTER;
+			else
+				lines[i].args[0] = TMT_CONTINUOUS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[2] = TMC_LTE;
+			else if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[2] = TMC_GTE;
+			else
+				lines[i].args[2] = TMC_EQUAL;
+			lines[i].special = 340;
+			break;
+		case 400: //Set tagged sector's floor height/texture
+		case 401: //Set tagged sector's ceiling height/texture
+			lines[i].args[0] = tag;
+			lines[i].args[1] = lines[i].special - 400;
+			lines[i].args[2] = !(lines[i].flags & ML_NOCLIMB);
+			lines[i].special = 400;
+			break;
+		case 402: //Copy light level
+			lines[i].args[0] = tag;
+			lines[i].args[1] = 0;
+			break;
+		case 403: //Move tagged sector's floor
+		case 404: //Move tagged sector's ceiling
+			lines[i].args[0] = tag;
+			lines[i].args[1] = lines[i].special - 403;
+			lines[i].args[2] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].args[3] = (lines[i].flags & ML_BLOCKMONSTERS) ? sides[lines[i].sidenum[0]].textureoffset >> FRACBITS : 0;
+			lines[i].args[4] = !!(lines[i].flags & ML_NOCLIMB);
+			lines[i].special = 403;
+			break;
+		case 405: //Move floor according to front texture offsets
+		case 407: //Move ceiling according to front texture offsets
+			lines[i].args[0] = tag;
+			lines[i].args[1] = (lines[i].special == 405) ? TMP_FLOOR : TMP_CEILING;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[3] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[4] = !!(lines[i].flags & ML_NOCLIMB);
+			lines[i].special = 405;
+			break;
+		case 408: //Set flats
+			lines[i].args[0] = tag;
+			if ((lines[i].flags & (ML_NOCLIMB|ML_MIDSOLID)) == (ML_NOCLIMB|ML_MIDSOLID))
+			{
+				CONS_Alert(CONS_WARNING, M_GetText("Set flats linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"), tag);
+				lines[i].special = 0;
+			}
+			else if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[1] = TMP_CEILING;
+			else if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[1] = TMP_FLOOR;
+			else
+				lines[i].args[1] = TMP_BOTH;
+			break;
+		case 409: //Change tagged sector's tag
+			lines[i].args[0] = tag;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[2] = TMT_ADD;
+			else if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[2] = TMT_REMOVE;
+			else
+				lines[i].args[2] = TMT_REPLACEFIRST;
+			break;
+		case 410: //Change front sector's tag
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[1] = TMT_ADD;
+			else if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[1] = TMT_REMOVE;
+			else
+				lines[i].args[1] = TMT_REPLACEFIRST;
+			break;
+		case 411: //Stop plane movement
+			lines[i].args[0] = tag;
+			break;
+		case 412: //Teleporter
+			lines[i].args[0] = tag;
+			if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[1] |= TMT_SILENT;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[1] |= TMT_KEEPANGLE;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[1] |= TMT_KEEPMOMENTUM;
+			if (lines[i].flags & ML_MIDPEG)
+				lines[i].args[1] |= TMT_RELATIVE;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[3] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[4] = lines[i].frontsector->ceilingheight >> FRACBITS;
+			break;
+		case 413: //Change music
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[0] |= TMM_ALLPLAYERS;
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[0] |= TMM_OFFSET;
+			if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[0] |= TMM_FADE;
+			if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[0] |= TMM_NORELOAD;
+			if (lines[i].flags & ML_BOUNCY)
+				lines[i].args[0] |= TMM_FORCERESET;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[0] |= TMM_NOLOOP;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].midtexture;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[3] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[4] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : 0;
+			lines[i].args[5] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].rowoffset >> FRACBITS : -1;
+			lines[i].args[6] = sides[lines[i].sidenum[0]].bottomtexture;
+			if (sides[lines[i].sidenum[0]].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
+			}
+			break;
+		case 414: //Play sound effect
+			lines[i].args[2] = tag;
+			if (tag != 0)
+			{
+				if (lines[i].flags & ML_WRAPMIDTEX)
+				{
+					lines[i].args[0] = TMSS_TAGGEDSECTOR;
+					lines[i].args[1] = TMSL_EVERYONE;
+				}
+				else
+				{
+					lines[i].args[0] = TMSS_NOWHERE;
+					lines[i].args[1] = TMSL_TAGGEDSECTOR;
+				}
+			}
+			else
+			{
+				if (lines[i].flags & ML_NOCLIMB)
+				{
+					lines[i].args[0] = TMSS_NOWHERE;
+					lines[i].args[1] = TMSL_TRIGGERER;
+				}
+				else if (lines[i].flags & ML_MIDSOLID)
+				{
+					lines[i].args[0] = TMSS_NOWHERE;
+					lines[i].args[1] = TMSL_EVERYONE;
+				}
+				else if (lines[i].flags & ML_BLOCKMONSTERS)
+				{
+					lines[i].args[0] = TMSS_TRIGGERSECTOR;
+					lines[i].args[1] = TMSL_EVERYONE;
+				}
+				else
+				{
+					lines[i].args[0] = TMSS_TRIGGERMOBJ;
+					lines[i].args[1] = TMSL_EVERYONE;
+				}
+			}
+			if (sides[lines[i].sidenum[0]].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
+			}
+			break;
+		case 415: //Run script
+		{
+			INT32 scrnum;
+
+			lines[i].stringargs[0] = Z_Malloc(9, PU_LEVEL, NULL);
+			strcpy(lines[i].stringargs[0], G_BuildMapName(gamemap));
+			lines[i].stringargs[0][0] = 'S';
+			lines[i].stringargs[0][1] = 'C';
+			lines[i].stringargs[0][2] = 'R';
+
+			scrnum = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			if (scrnum < 0 || scrnum > 999)
+			{
+				scrnum = 0;
+				lines[i].stringargs[0][5] = lines[i].stringargs[0][6] = lines[i].stringargs[0][7] = '0';
+			}
+			else
+			{
+				lines[i].stringargs[0][5] = (char)('0' + (char)((scrnum / 100)));
+				lines[i].stringargs[0][6] = (char)('0' + (char)((scrnum % 100) / 10));
+				lines[i].stringargs[0][7] = (char)('0' + (char)(scrnum % 10));
+			}
+			lines[i].stringargs[0][8] = '\0';
+			break;
+		}
+		case 416: //Start adjustable flickering light
+		case 417: //Start adjustable pulsating light
+		case 602: //Adjustable pulsating light
+		case 603: //Adjustable flickering light
+			lines[i].args[0] = tag;
+			lines[i].args[1] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].args[2] = lines[i].frontsector->lightlevel;
+			if ((lines[i].flags & ML_NOCLIMB) && lines[i].backsector)
+				lines[i].args[4] = lines[i].backsector->lightlevel;
+			else
+				lines[i].args[3] = 1;
+			break;
+		case 418: //Start adjustable blinking light (unsynchronized)
+		case 419: //Start adjustable blinking light (synchronized)
+		case 604: //Adjustable blinking light (unsynchronized)
+		case 605: //Adjustable blinking light (synchronized)
+			lines[i].args[0] = tag;
+			lines[i].args[1] = abs(lines[i].dx) >> FRACBITS;
+			lines[i].args[2] = abs(lines[i].dy) >> FRACBITS;
+			lines[i].args[3] = lines[i].frontsector->lightlevel;
+			if ((lines[i].flags & ML_NOCLIMB) && lines[i].backsector)
+				lines[i].args[5] = lines[i].backsector->lightlevel;
+			else
+				lines[i].args[4] |= TMB_USETARGET;
+			if (lines[i].special % 2 == 1)
+			{
+				lines[i].args[4] |= TMB_SYNC;
+				lines[i].special--;
+			}
+			break;
+		case 420: //Fade light level
+			lines[i].args[0] = tag;
+			if (lines[i].flags & ML_DONTPEGBOTTOM)
+			{
+				lines[i].args[1] = max(sides[lines[i].sidenum[0]].textureoffset >> FRACBITS, 0);
+				// failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
+				// to be consistent with other light and fade specials
+				lines[i].args[2] = ((lines[i].sidenum[1] != 0xFFFF && !(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS)) ?
+					max(min(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS, 255), 0)
+					: max(min(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS, 255), 0));
+			}
+			else
+			{
+				lines[i].args[1] = lines[i].frontsector->lightlevel;
+				lines[i].args[2] = abs(P_AproxDistance(lines[i].dx, lines[i].dy)) >> FRACBITS;
+			}
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[3] |= TMF_TICBASED;
+			if (lines[i].flags & ML_WRAPMIDTEX)
+				lines[i].args[3] |= TMF_OVERRIDE;
+			break;
+		case 421: //Stop lighting effect
+			lines[i].args[0] = tag;
+			break;
+		case 422: //Switch to cut-away view
+			lines[i].args[0] = tag;
+			lines[i].args[1] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].args[2] = (lines[i].flags & ML_NOCLIMB) ? sides[lines[i].sidenum[0]].textureoffset >> FRACBITS : 0;
+			break;
+		case 423: //Change sky
+		case 424: //Change weather
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 425: //Change object state
+			if (sides[lines[i].sidenum[0]].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
+			}
+			break;
+		case 426: //Stop object
+			lines[i].args[0] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 427: //Award score
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			break;
+		case 428: //Start platform movement
+			lines[i].args[0] = tag;
+			lines[i].args[1] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[3] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[4] = (lines[i].flags & ML_NOCLIMB) ? 1 : 0;
+			break;
+		case 429: //Crush ceiling once
+		case 430: //Crush floor once
+		case 431: //Crush floor and ceiling once
+			lines[i].args[0] = tag;
+			lines[i].args[1] = (lines[i].special == 429) ? TMP_CEILING : ((lines[i].special == 430) ? TMP_FLOOR : TMP_BOTH);
+			if (lines[i].special == 430 || lines[i].flags & ML_MIDSOLID)
+			{
+				lines[i].args[2] = abs(lines[i].dx) >> FRACBITS;
+				lines[i].args[3] = lines[i].args[2];
+			}
+			else
+			{
+				lines[i].args[2] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> (FRACBITS + 1);
+				lines[i].args[3] = lines[i].args[2] / 4;
+			}
+			lines[i].special = 429;
+			break;
+		case 432: //Enable/disable 2D mode
+			lines[i].args[0] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 433: //Enable/disable gravity flip
+			lines[i].args[0] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 434: //Award power-up
+			if (sides[lines[i].sidenum[0]].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
+			}
+			if (lines[i].sidenum[1] != 0xffff && lines[i].flags & ML_BLOCKMONSTERS) // read power from back sidedef
+			{
+				lines[i].stringargs[1] = Z_Malloc(strlen(sides[lines[i].sidenum[1]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[1], sides[lines[i].sidenum[1]].text, strlen(sides[lines[i].sidenum[1]].text) + 1);
+			}
+			else
+				P_WriteConstant((lines[i].flags & ML_NOCLIMB) ? -1 : (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS), &lines[i].stringargs[1]);
+			break;
+		case 435: //Change plane scroller direction
+			lines[i].args[0] = tag;
+			lines[i].args[1] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+			break;
+		case 436: //Shatter FOF
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			break;
+		case 437: //Disable player control
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 438: //Change object size
+			lines[i].args[0] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			break;
+		case 439: //Change tagged linedef's textures
+			lines[i].args[0] = tag;
+			lines[i].args[1] = TMSD_FRONTBACK;
+			lines[i].args[2] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 441: //Condition set trigger
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			break;
+		case 442: //Change object type state
+			lines[i].args[0] = tag;
+			if (sides[lines[i].sidenum[0]].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
+			}
+			if (lines[i].sidenum[1] == 0xffff)
+				lines[i].args[1] = 1;
+			else
+			{
+				lines[i].args[1] = 0;
+				if (sides[lines[i].sidenum[1]].text)
+				{
+					lines[i].stringargs[1] = Z_Malloc(strlen(sides[lines[i].sidenum[1]].text) + 1, PU_LEVEL, NULL);
+					M_Memcpy(lines[i].stringargs[1], sides[lines[i].sidenum[1]].text, strlen(sides[lines[i].sidenum[1]].text) + 1);
+				}
+			}
+			break;
+		case 443: //Call Lua function
+			if (lines[i].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(lines[i].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], lines[i].text, strlen(lines[i].text) + 1);
+			}
+			else
+				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", sizeu1(i));
+			break;
+		case 444: //Earthquake
+			lines[i].args[0] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			break;
+		case 445: //Make FOF disappear/reappear
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[2] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 446: //Make FOF crumble
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[2] |= TMFR_NORETURN;
+			if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[2] |= TMFR_CHECKFLAG;
+			break;
+		case 447: //Change colormap
+			lines[i].args[0] = tag;
+			if (lines[i].flags & ML_MIDPEG)
+				lines[i].args[2] |= TMCF_RELATIVE;
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[2] |= TMCF_SUBLIGHTR|TMCF_SUBFADER;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[2] |= TMCF_SUBLIGHTG|TMCF_SUBFADEG;
+			if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[2] |= TMCF_SUBLIGHTB|TMCF_SUBFADEB;
+			break;
+		case 448: //Change skybox
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			if ((lines[i].flags & (ML_MIDSOLID|ML_BLOCKMONSTERS)) == ML_MIDSOLID) // Solid Midtexture is on but Block Enemies is off?
+			{
+				CONS_Alert(CONS_WARNING,
+					M_GetText("Skybox switch linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
+					tag);
+				lines[i].special = 0;
+				break;
+			}
+			else if ((lines[i].flags & (ML_MIDSOLID|ML_BLOCKMONSTERS)) == (ML_MIDSOLID|ML_BLOCKMONSTERS))
+				lines[i].args[2] = TMS_CENTERPOINT;
+			else if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[2] = TMS_BOTH;
+			else
+				lines[i].args[2] = TMS_VIEWPOINT;
+			lines[i].args[3] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 449: //Enable bosses with parameters
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 450: //Execute linedef executor (specific tag)
+			lines[i].args[0] = tag;
+			break;
+		case 451: //Execute linedef executor (random tag in range)
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			break;
+		case 452: //Set FOF translucency
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[2] = lines[i].sidenum[1] != 0xffff ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS);
+			if (lines[i].flags & ML_MIDPEG)
+				lines[i].args[3] |= TMST_RELATIVE;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[3] |= TMST_DONTDOTRANSLUCENT;
+			break;
+		case 453: //Fade FOF
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[2] = lines[i].sidenum[1] != 0xffff ? (sides[lines[i].sidenum[1]].textureoffset >> FRACBITS) : (lines[i].dx >> FRACBITS);
+			lines[i].args[3] = lines[i].sidenum[1] != 0xffff ? (sides[lines[i].sidenum[1]].rowoffset >> FRACBITS) : (abs(lines[i].dy) >> FRACBITS);
+			if (lines[i].flags & ML_MIDPEG)
+				lines[i].args[4] |= TMFT_RELATIVE;
+			if (lines[i].flags & ML_WRAPMIDTEX)
+				lines[i].args[4] |= TMFT_OVERRIDE;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[4] |= TMFT_TICBASED;
+			if (lines[i].flags & ML_BOUNCY)
+				lines[i].args[4] |= TMFT_IGNORECOLLISION;
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[4] |= TMFT_GHOSTFADE;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[4] |= TMFT_DONTDOTRANSLUCENT;
+			if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[4] |= TMFT_DONTDOEXISTS;
+			if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[4] |= (TMFT_DONTDOLIGHTING|TMFT_DONTDOCOLORMAP);
+			if (lines[i].flags & ML_TFERLINE)
+				lines[i].args[4] |= TMFT_USEEXACTALPHA;
+			break;
+		case 454: //Stop fading FOF
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[2] = !!(lines[i].flags & ML_BLOCKMONSTERS);
+			break;
+		case 455: //Fade colormap
+		{
+			INT32 speed = (INT32)((((lines[i].flags & ML_DONTPEGBOTTOM) || !sides[lines[i].sidenum[0]].rowoffset) && lines[i].sidenum[1] != 0xFFFF) ?
+				abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS)
+				: abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS));
+
+			lines[i].args[0] = tag;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[2] = speed;
+			else
+				lines[i].args[2] = (256 + speed - 1)/speed;
+			if (lines[i].flags & ML_MIDPEG)
+				lines[i].args[3] |= TMCF_RELATIVE;
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[3] |= TMCF_SUBLIGHTR|TMCF_SUBFADER;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[3] |= TMCF_SUBLIGHTG|TMCF_SUBFADEG;
+			if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[3] |= TMCF_SUBLIGHTB|TMCF_SUBFADEB;
+			if (lines[i].flags & ML_BOUNCY)
+				lines[i].args[3] |= TMCF_FROMBLACK;
+			if (lines[i].flags & ML_WRAPMIDTEX)
+				lines[i].args[3] |= TMCF_OVERRIDE;
+			break;
+		}
+		case 456: //Stop fading colormap
+			lines[i].args[0] = tag;
+			break;
+		case 457: //Track object's angle
+			lines[i].args[0] = tag;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[3] = (lines[i].sidenum[1] != 0xffff) ? sides[lines[i].sidenum[1]].rowoffset >> FRACBITS : 0;
+			lines[i].args[4] = !!(lines[i].flags & ML_NOSKEW);
+			break;
+		case 459: //Control text prompt
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			if (lines[i].flags & ML_BLOCKMONSTERS)
+				lines[i].args[2] |= TMP_CLOSE;
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[2] |= TMP_RUNPOSTEXEC;
+			if (lines[i].flags & ML_TFERLINE)
+				lines[i].args[2] |= TMP_CALLBYNAME;
+			if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[2] |= TMP_KEEPCONTROLS;
+			if (lines[i].flags & ML_MIDPEG)
+				lines[i].args[2] |= TMP_KEEPREALTIME;
+			/*if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[2] |= TMP_ALLPLAYERS;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[2] |= TMP_FREEZETHINKERS;*/
+			lines[i].args[3] = (lines[i].sidenum[1] != 0xFFFF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : tag;
+			if (sides[lines[i].sidenum[0]].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
+			}
+			break;
+		case 460: //Award rings
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			break;
+		case 461: //Spawn object
+			lines[i].args[0] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[2] = lines[i].frontsector->floorheight >> FRACBITS;
+			lines[i].args[3] = (lines[i].flags & ML_SKEWTD) ? AngleFixed(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y)) >> FRACBITS : 0;
+			if (lines[i].flags & ML_NOCLIMB)
+			{
+				if (lines[i].sidenum[1] != 0xffff) // Make sure the linedef has a back side
+				{
+					lines[i].args[4] = 1;
+					lines[i].args[5] = sides[lines[i].sidenum[1]].textureoffset >> FRACBITS;
+					lines[i].args[6] = sides[lines[i].sidenum[1]].rowoffset >> FRACBITS;
+					lines[i].args[7] = lines[i].frontsector->ceilingheight >> FRACBITS;
+				}
+				else
+				{
+					CONS_Alert(CONS_WARNING, "Linedef Type %d - Spawn Object: Linedef is set for random range but has no back side.\n", lines[i].special);
+					lines[i].args[4] = 0;
+				}
+			}
+			else
+				lines[i].args[4] = 0;
+			if (sides[lines[i].sidenum[0]].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
+			}
+			break;
+		case 463: //Dye object
+			if (sides[lines[i].sidenum[0]].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(sides[lines[i].sidenum[0]].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], sides[lines[i].sidenum[0]].text, strlen(sides[lines[i].sidenum[0]].text) + 1);
+			}
+			break;
+		case 464: //Trigger egg capsule
+			lines[i].args[0] = tag;
+			lines[i].args[1] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 466: //Set level failure state
+			lines[i].args[0] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
+		case 467: //Set light level
+			lines[i].args[0] = tag;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[2] = TML_SECTOR;
+			lines[i].args[3] = !!(lines[i].flags & ML_MIDPEG);
+			break;
+		case 480: //Polyobject - door slide
+		case 481: //Polyobject - door move
+			lines[i].args[0] = tag;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			if (lines[i].sidenum[1] != 0xffff)
+				lines[i].args[3] = sides[lines[i].sidenum[1]].textureoffset >> FRACBITS;
+			break;
+		case 482: //Polyobject - move
+		case 483: //Polyobject - move, override
+			lines[i].args[0] = tag;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			lines[i].args[3] = lines[i].special == 483;
+			lines[i].special = 482;
+			break;
+		case 484: //Polyobject - rotate right
+		case 485: //Polyobject - rotate right, override
+		case 486: //Polyobject - rotate left
+		case 487: //Polyobject - rotate left, override
+			lines[i].args[0] = tag;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			if (lines[i].args[2] == 360)
+				lines[i].args[3] |= TMPR_CONTINUOUS;
+			else if (lines[i].args[2] == 0)
+				lines[i].args[2] = 360;
+			if (lines[i].special < 486)
+				lines[i].args[2] *= -1;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[3] |= TMPR_DONTROTATEOTHERS;
+			else if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[3] |= TMPR_ROTATEPLAYERS;
+			if (lines[i].special % 2 == 1)
+				lines[i].args[3] |= TMPR_OVERRIDE;
+			lines[i].special = 484;
+			break;
+		case 488: //Polyobject - move by waypoints
+			lines[i].args[0] = tag;
+			lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			if (lines[i].flags & ML_MIDPEG)
+				lines[i].args[3] = PWR_WRAP;
+			else if (lines[i].flags & ML_NOSKEW)
+				lines[i].args[3] = PWR_COMEBACK;
+			else
+				lines[i].args[3] = PWR_STOP;
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[4] |= PWF_REVERSE;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[4] |= PWF_LOOP;
+			break;
+		case 489: //Polyobject - turn invisible, intangible
+		case 490: //Polyobject - turn visible, tangible
+			lines[i].args[0] = tag;
+			lines[i].args[1] = 491 - lines[i].special;
+			if (!(lines[i].flags & ML_NOCLIMB))
+				lines[i].args[2] = lines[i].args[1];
+			lines[i].special = 489;
+			break;
+		case 491: //Polyobject - set translucency
+			lines[i].args[0] = tag;
+			// If Front X Offset is specified, use that. Else, use floorheight.
+			lines[i].args[1] = (sides[lines[i].sidenum[0]].textureoffset ? sides[lines[i].sidenum[0]].textureoffset : lines[i].frontsector->floorheight) >> FRACBITS;
+			// If DONTPEGBOTTOM, specify raw translucency value. Else, take it out of 1000.
+			if (!(lines[i].flags & ML_DONTPEGBOTTOM))
+				lines[i].args[1] /= 100;
+			lines[i].args[2] = !!(lines[i].flags & ML_MIDPEG);
+			break;
+		case 492: //Polyobject - fade translucency
+			lines[i].args[0] = tag;
+			// If Front X Offset is specified, use that. Else, use floorheight.
+			lines[i].args[1] = (sides[lines[i].sidenum[0]].textureoffset ? sides[lines[i].sidenum[0]].textureoffset : lines[i].frontsector->floorheight) >> FRACBITS;
+			// If DONTPEGBOTTOM, specify raw translucency value. Else, take it out of 1000.
+			if (!(lines[i].flags & ML_DONTPEGBOTTOM))
+				lines[i].args[1] /= 100;
+			// allow Back Y Offset to be consistent with other fade specials
+			lines[i].args[2] = (lines[i].sidenum[1] != 0xffff && !sides[lines[i].sidenum[0]].rowoffset) ?
+				abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS)
+				: abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS);
+			if (lines[i].flags & ML_MIDPEG)
+				lines[i].args[3] |= TMPF_RELATIVE;
+			if (lines[i].flags & ML_WRAPMIDTEX)
+				lines[i].args[3] |= TMPF_OVERRIDE;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[3] |= TMPF_TICBASED;
+			if (lines[i].flags & ML_BOUNCY)
+				lines[i].args[3] |= TMPF_IGNORECOLLISION;
+			if (lines[i].flags & ML_SKEWTD)
+				lines[i].args[3] |= TMPF_GHOSTFADE;
+			break;
+		case 500: //Scroll front wall left
+		case 501: //Scroll front wall right
+			lines[i].args[0] = 0;
+			lines[i].args[1] = (lines[i].special == 500) ? -1 : 1;
+			lines[i].args[2] = 0;
+			lines[i].special = 500;
+			break;
+		case 502: //Scroll tagged wall
+		case 503: //Scroll tagged wall (accelerative)
+		case 504: //Scroll tagged wall (displacement)
+			lines[i].args[0] = tag;
+			if (lines[i].flags & ML_MIDPEG)
+			{
+				if (lines[i].sidenum[1] == 0xffff)
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Line special %d (line #%s) missing back side!\n", lines[i].special, sizeu1(i));
+					lines[i].special = 0;
+					break;
+				}
+				lines[i].args[1] = 1;
+			}
+			else
+				lines[i].args[1] = 0;
+			if (lines[i].flags & ML_NOSKEW)
+			{
+				lines[i].args[2] = lines[i].dx >> (FRACBITS + SCROLL_SHIFT);
+				lines[i].args[3] = lines[i].dy >> (FRACBITS + SCROLL_SHIFT);
+			}
+			else
+			{
+				lines[i].args[2] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+				lines[i].args[3] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			}
+			lines[i].args[4] = lines[i].special - 502;
+			lines[i].special = 502;
+			break;
+		case 505: //Scroll front wall by front side offsets
+		case 506: //Scroll front wall by back side offsets
+		case 507: //Scroll back wall by front side offsets
+		case 508: //Scroll back wall by back side offsets
+			lines[i].args[0] = lines[i].special >= 507;
+			if (lines[i].special % 2 == 0)
+			{
+				if (lines[i].sidenum[1] == 0xffff)
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Line special %d (line #%s) missing back side!\n", lines[i].special, sizeu1(i));
+					lines[i].special = 0;
+					break;
+				}
+				lines[i].args[1] = sides[lines[i].sidenum[1]].textureoffset >> FRACBITS;
+				lines[i].args[2] = sides[lines[i].sidenum[1]].rowoffset >> FRACBITS;
+			}
+			else
+			{
+				lines[i].args[1] = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+				lines[i].args[2] = sides[lines[i].sidenum[0]].rowoffset >> FRACBITS;
+			}
+			lines[i].special = 500;
+			break;
+		case 510: //Scroll floor texture
+		case 511: //Scroll floor texture (accelerative)
+		case 512: //Scroll floor texture (displacement)
+		case 513: //Scroll ceiling texture
+		case 514: //Scroll ceiling texture (accelerative)
+		case 515: //Scroll ceiling texture (displacement)
+		case 520: //Carry objects on floor
+		case 521: //Carry objects on floor (accelerative)
+		case 522: //Carry objects on floor (displacement)
+		case 523: //Carry objects on ceiling
+		case 524: //Carry objects on ceiling (accelerative)
+		case 525: //Carry objects on ceiling (displacement)
+		case 530: //Scroll floor texture and carry objects
+		case 531: //Scroll floor texture and carry objects (accelerative)
+		case 532: //Scroll floor texture and carry objects (displacement)
+		case 533: //Scroll ceiling texture and carry objects
+		case 534: //Scroll ceiling texture and carry objects (accelerative)
+		case 535: //Scroll ceiling texture and carry objects (displacement)
+			lines[i].args[0] = tag;
+			lines[i].args[1] = ((lines[i].special % 10) < 3) ? TMP_FLOOR : TMP_CEILING;
+			lines[i].args[2] = ((lines[i].special - 510)/10 + 1) % 3;
+			lines[i].args[3] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+			lines[i].args[4] = (lines[i].special % 10) % 3;
+			if (lines[i].args[2] != TMS_SCROLLONLY && !(lines[i].flags & ML_NOCLIMB))
+				lines[i].args[4] |= TMST_NONEXCLUSIVE;
+			lines[i].special = 510;
+			break;
+		case 540: //Floor friction
+		{
+			INT32 s;
+			fixed_t strength; // friction value of sector
+			fixed_t friction; // friction value to be applied during movement
+
+			strength = sides[lines[i].sidenum[0]].textureoffset >> FRACBITS;
+			if (strength > 0) // sludge
+				strength = strength*2; // otherwise, the maximum sludginess value is +967...
+
+			// The following might seem odd. At the time of movement,
+			// the move distance is multiplied by 'friction/0x10000', so a
+			// higher friction value actually means 'less friction'.
+			friction = ORIG_FRICTION - (0x1EB8*strength)/0x80; // ORIG_FRICTION is 0xE800
+
+			TAG_ITER_SECTORS(tag, s)
+				sectors[s].friction = friction;
+			break;
+		}
+		case 541: //Wind
+		case 542: //Upwards wind
+		case 543: //Downwards wind
+		case 544: //Current
+		case 545: //Upwards current
+		case 546: //Downwards current
+			lines[i].args[0] = tag;
+			switch ((lines[i].special - 541) % 3)
+			{
+				case 0:
+					lines[i].args[1] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+					break;
+				case 1:
+					lines[i].args[2] = R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+					break;
+				case 2:
+					lines[i].args[2] = -R_PointToDist2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y) >> FRACBITS;
+					break;
+			}
+			lines[i].args[3] = (lines[i].special >= 544) ? p_current : p_wind;
+			if (lines[i].flags & ML_MIDSOLID)
+				lines[i].args[4] |= TMPF_SLIDE;
+			if (!(lines[i].flags & ML_NOCLIMB))
+				lines[i].args[4] |= TMPF_NONEXCLUSIVE;
+			lines[i].special = 541;
 			break;
-		}
-		case 456: //Stop fading colormap
-			lines[i].args[0] = Tag_FGet(&lines[i].tags);
+		case 600: //Floor lighting
+		case 601: //Ceiling lighting
+			lines[i].args[0] = tag;
+			lines[i].args[1] = (lines[i].special == 601) ? TMP_CEILING : TMP_FLOOR;
+			lines[i].special = 600;
 			break;
 		case 606: //Colormap
-			lines[i].args[0] = Tag_FGet(&lines[i].tags);
+			lines[i].args[0] = tag;
 			break;
 		case 700: //Slope front sector floor
 		case 701: //Slope front sector ceiling
@@ -3267,12 +5688,8 @@ static void P_ConvertBinaryMap(void)
 				lines[i].args[2] |= TMSL_NOPHYSICS;
 			if (lines[i].flags & ML_NONET)
 				lines[i].args[2] |= TMSL_DYNAMIC;
-
 			if (lines[i].flags & ML_TFERLINE)
-			{
-					lines[i].args[4] |= backfloor ? TMSC_BACKTOFRONTFLOOR : (frontfloor ? TMSC_FRONTTOBACKFLOOR : 0);
-					lines[i].args[4] |= backceil ? TMSC_BACKTOFRONTCEILING : (frontceil ? TMSC_FRONTTOBACKCEILING : 0);
-			}
+				lines[i].args[2] |= TMSL_COPY;
 
 			lines[i].special = 700;
 			break;
@@ -3355,6 +5772,9 @@ static void P_ConvertBinaryMap(void)
 				lines[i].args[4] |= TMSC_BACKTOFRONTCEILING;
 			lines[i].special = 720;
 			break;
+		case 799: //Set dynamic slope vertex to front sector height
+			lines[i].args[0] = !!(lines[i].flags & ML_NOCLIMB);
+			break;
 		case 909: //Fog wall
 			lines[i].blendmode = AST_FOG;
 			break;
@@ -3391,25 +5811,529 @@ static void P_ConvertBinaryMap(void)
 				lines[i].executordelay = 1;
 		}
 	}
+}
+
+static void P_ConvertBinarySectorTypes(void)
+{
+	size_t i;
+
+	for (i = 0; i < numsectors; i++)
+	{
+		mtag_t tag = Tag_FGet(&sectors[i].tags);
+
+		switch(GETSECSPECIAL(sectors[i].special, 1))
+		{
+			case 1: //Damage
+				sectors[i].damagetype = SD_GENERIC;
+				break;
+			case 2: //Damage (Water)
+				sectors[i].damagetype = SD_WATER;
+				break;
+			case 3: //Damage (Fire)
+			{
+				size_t j;
+				boolean isLava = false;
+
+				for (j = 0; j < sectors[i].linecount; j++)
+				{
+					line_t *line = sectors[i].lines[j];
+
+					if (line->frontsector != &sectors[i])
+						continue;
+
+					if (line->flags & ML_BLOCKMONSTERS)
+						continue;
+
+					if (line->special == 120 || (line->special == 259 && (line->args[2] & FF_SWIMMABLE)))
+					{
+						isLava = true;
+						break;
+					}
+				}
+				sectors[i].damagetype = isLava ? SD_LAVA : SD_FIRE;
+				break;
+			}
+			case 4: //Damage (Electric)
+				sectors[i].damagetype = SD_ELECTRIC;
+				break;
+			case 5: //Spikes
+				sectors[i].damagetype = SD_SPIKE;
+				break;
+			case 6: //Death pit (camera tilt)
+				sectors[i].damagetype = SD_DEATHPITTILT;
+				break;
+			case 7: //Death pit (no camera tilt)
+				sectors[i].damagetype = SD_DEATHPITNOTILT;
+				break;
+			case 8: //Instakill
+				sectors[i].damagetype = SD_INSTAKILL;
+				break;
+			case 11: //Special stage damage
+				sectors[i].damagetype = SD_SPECIALSTAGE;
+				break;
+			case 12: //Space countdown
+				sectors[i].specialflags |= SSF_OUTERSPACE;
+				break;
+			case 13: //Ramp sector
+				sectors[i].specialflags |= SSF_DOUBLESTEPUP;
+				break;
+			case 14: //Non-ramp sector
+				sectors[i].specialflags |= SSF_NOSTEPDOWN;
+				break;
+			default:
+				break;
+		}
+
+		switch(GETSECSPECIAL(sectors[i].special, 2))
+		{
+			case 1: //Trigger linedef executor (pushable objects)
+				sectors[i].triggertag = tag;
+				sectors[i].flags |= MSF_TRIGGERLINE_PLANE;
+				sectors[i].triggerer = TO_MOBJ;
+				break;
+			case 2: //Trigger linedef executor (Anywhere in sector, all players)
+				sectors[i].triggertag = tag;
+				sectors[i].flags &= ~MSF_TRIGGERLINE_PLANE;
+				sectors[i].triggerer = TO_ALLPLAYERS;
+				break;
+			case 3: //Trigger linedef executor (Floor touch, all players)
+				sectors[i].triggertag = tag;
+				sectors[i].flags |= MSF_TRIGGERLINE_PLANE;
+				sectors[i].triggerer = TO_ALLPLAYERS;
+				break;
+			case 4: //Trigger linedef executor (Anywhere in sector)
+				sectors[i].triggertag = tag;
+				sectors[i].flags &= ~MSF_TRIGGERLINE_PLANE;
+				sectors[i].triggerer = TO_PLAYER;
+				break;
+			case 5: //Trigger linedef executor (Floor touch)
+				sectors[i].triggertag = tag;
+				sectors[i].flags |= MSF_TRIGGERLINE_PLANE;
+				sectors[i].triggerer = TO_PLAYER;
+				break;
+			case 6: //Trigger linedef executor (Emerald check)
+				sectors[i].triggertag = tag;
+				sectors[i].flags &= ~MSF_TRIGGERLINE_PLANE;
+				sectors[i].triggerer = TO_PLAYEREMERALDS;
+				break;
+			case 7: //Trigger linedef executor (NiGHTS mare)
+				sectors[i].triggertag = tag;
+				sectors[i].flags &= ~MSF_TRIGGERLINE_PLANE;
+				sectors[i].triggerer = TO_PLAYERNIGHTS;
+				break;
+			case 8: //Check for linedef executor on FOFs
+				sectors[i].flags |= MSF_TRIGGERLINE_MOBJ;
+				break;
+			default:
+				break;
+		}
+
+		switch(GETSECSPECIAL(sectors[i].special, 3))
+		{
+			case 5: //Speed pad
+				sectors[i].specialflags |= SSF_SPEEDPAD;
+				break;
+			default:
+				break;
+		}
+
+		switch(GETSECSPECIAL(sectors[i].special, 4))
+		{
+			case 1: //Star post activator
+				sectors[i].specialflags |= SSF_STARPOSTACTIVATOR;
+				break;
+			case 2: //Exit/Special Stage pit/Return flag
+				sectors[i].specialflags |= SSF_EXIT|SSF_SPECIALSTAGEPIT|SSF_RETURNFLAG;
+				break;
+			case 3: //Red team base
+				sectors[i].specialflags |= SSF_REDTEAMBASE;
+				break;
+			case 4: //Blue team base
+				sectors[i].specialflags |= SSF_BLUETEAMBASE;
+				break;
+			case 5: //Fan sector
+				sectors[i].specialflags |= SSF_FAN;
+				break;
+			case 6: //Super Sonic transform
+				sectors[i].specialflags |= SSF_SUPERTRANSFORM;
+				break;
+			case 7: //Force spin
+				sectors[i].specialflags |= SSF_FORCESPIN;
+				break;
+			case 8: //Zoom tube start
+				sectors[i].specialflags |= SSF_ZOOMTUBESTART;
+				break;
+			case 9: //Zoom tube end
+				sectors[i].specialflags |= SSF_ZOOMTUBEEND;
+				break;
+			case 10: //Circuit finish line
+				sectors[i].specialflags |= SSF_FINISHLINE;
+				break;
+			case 11: //Rope hang
+				sectors[i].specialflags |= SSF_ROPEHANG;
+				break;
+			case 12: //Intangible to the camera
+				sectors[i].flags |= MSF_NOCLIPCAMERA;
+				break;
+			default:
+				break;
+		}
+	}
+}
+
+static void P_ConvertBinaryThingTypes(void)
+{
+	size_t i;
+	mobjtype_t mobjtypeofthing[4096] = {0};
+	mobjtype_t mobjtype;
+
+	for (i = 0; i < NUMMOBJTYPES; i++)
+	{
+		if (mobjinfo[i].doomednum < 0 || mobjinfo[i].doomednum >= 4096)
+			continue;
+
+		mobjtypeofthing[mobjinfo[i].doomednum] = (mobjtype_t)i;
+	}
 
 	for (i = 0; i < nummapthings; i++)
 	{
+		mobjtype = mobjtypeofthing[mapthings[i].type];
+		if (mobjtype)
+		{
+			if (mobjinfo[mobjtype].flags & MF_BOSS)
+			{
+				INT32 paramoffset = mapthings[i].extrainfo*LE_PARAMWIDTH;
+				mapthings[i].args[0] = mapthings[i].extrainfo;
+				mapthings[i].args[1] = !!(mapthings[i].options & MTF_OBJECTSPECIAL);
+				mapthings[i].args[2] = LE_BOSSDEAD + paramoffset;
+				mapthings[i].args[3] = LE_ALLBOSSESDEAD + paramoffset;
+				mapthings[i].args[4] = LE_PINCHPHASE + paramoffset;
+			}
+			if (mobjinfo[mobjtype].flags & MF_NIGHTSITEM)
+			{
+				if (mapthings[i].options & MTF_OBJECTSPECIAL)
+					mapthings[i].args[0] |= TMNI_BONUSONLY;
+				if (mapthings[i].options & MTF_AMBUSH)
+					mapthings[i].args[0] |= TMNI_REVEAL;
+			}
+			if (mobjinfo[mobjtype].flags & MF_PUSHABLE)
+			{
+				if ((mapthings[i].options & (MTF_OBJECTSPECIAL|MTF_AMBUSH)) == (MTF_OBJECTSPECIAL|MTF_AMBUSH))
+					mapthings[i].args[0] = TMP_CLASSIC;
+				else if (mapthings[i].options & MTF_OBJECTSPECIAL)
+					mapthings[i].args[0] = TMP_SLIDE;
+				else if (mapthings[i].options & MTF_AMBUSH)
+					mapthings[i].args[0] = TMP_IMMOVABLE;
+				else
+					mapthings[i].args[0] = TMP_NORMAL;
+			}
+			if ((mobjinfo[mobjtype].flags & MF_SPRING) && mobjinfo[mobjtype].painchance == 3)
+				mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			if (mobjinfo[mobjtype].flags & MF_MONITOR)
+			{
+				if ((mapthings[i].options & MTF_EXTRA) && mapthings[i].angle & 16384)
+					mapthings[i].args[0] = mapthings[i].angle & 16383;
+
+				if (mobjinfo[mobjtype].speed != 0)
+				{
+					if (mapthings[i].options & MTF_OBJECTSPECIAL)
+						mapthings[i].args[1] = TMMR_STRONG;
+					else if (mapthings[i].options & MTF_AMBUSH)
+						mapthings[i].args[1] = TMMR_WEAK;
+					else
+						mapthings[i].args[1] = TMMR_SAME;
+				}
+			}
+		}
+
+		if (mapthings[i].type >= 1 && mapthings[i].type <= 35) //Player starts
+		{
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			continue;
+		}
+		else if (mapthings[i].type >= 2200 && mapthings[i].type <= 2217) //Flickies
+		{
+			mapthings[i].args[0] = mapthings[i].angle;
+			if (mapthings[i].options & MTF_EXTRA)
+				mapthings[i].args[1] |= TMFF_AIMLESS;
+			if (mapthings[i].options & MTF_OBJECTSPECIAL)
+				mapthings[i].args[1] |= TMFF_STATIONARY;
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[1] |= TMFF_HOP;
+			if (mapthings[i].type == 2207)
+				mapthings[i].args[2] = mapthings[i].extrainfo;
+			continue;
+		}
+
 		switch (mapthings[i].type)
 		{
-		case 750:
-			Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
+		case 102: //SDURF
+		case 1805: //Puma
+			mapthings[i].args[0] = mapthings[i].angle;
 			break;
-		case 760:
-		case 761:
-			Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
+		case 110: //THZ Turret
+			mapthings[i].args[0] = LE_TURRET;
 			break;
-		case 762:
+		case 111: //Pop-up Turret
+			mapthings[i].args[0] = mapthings[i].angle;
+			break;
+		case 103: //Buzz (Gold)
+		case 104: //Buzz (Red)
+		case 105: //Jetty-syn Bomber
+		case 106: //Jetty-syn Gunner
+		case 117: //Robo-Hood
+		case 126: //Crushstacean
+		case 128: //Bumblebore
+		case 132: //Cacolantern
+		case 138: //Banpyura
+		case 1602: //Pian
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 119: //Egg Guard
+			if ((mapthings[i].options & (MTF_EXTRA|MTF_OBJECTSPECIAL)) == MTF_OBJECTSPECIAL)
+				mapthings[i].args[0] = TMGD_LEFT;
+			else if ((mapthings[i].options & (MTF_EXTRA|MTF_OBJECTSPECIAL)) == MTF_EXTRA)
+				mapthings[i].args[0] = TMGD_RIGHT;
+			else
+				mapthings[i].args[0] = TMGD_BACK;
+			mapthings[i].args[1] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 127: //Hive Elemental
+			mapthings[i].args[0] = mapthings[i].extrainfo;
+			break;
+		case 135: //Pterabyte Spawner
+			mapthings[i].args[0] = mapthings[i].extrainfo + 1;
+			break;
+		case 136: //Pyre Fly
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 201: //Egg Slimer
+			mapthings[i].args[5] = !(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 203: //Egg Colosseum
+			mapthings[i].args[5] = LE_BOSS4DROP + mapthings[i].extrainfo * LE_PARAMWIDTH;
+			break;
+		case 204: //Fang
+			mapthings[i].args[4] = LE_BOSS4DROP + mapthings[i].extrainfo*LE_PARAMWIDTH;
+			if (mapthings[i].options & MTF_EXTRA)
+				mapthings[i].args[5] |= TMF_GRAYSCALE;
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[5] |= TMF_SKIPINTRO;
+			break;
+		case 206: //Brak Eggman (Old)
+			mapthings[i].args[5] = LE_BRAKPLATFORM + mapthings[i].extrainfo*LE_PARAMWIDTH;
+			break;
+		case 207: //Metal Sonic (Race)
+		case 2104: //Amy Cameo
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_EXTRA);
+			break;
+		case 208: //Metal Sonic (Battle)
+			mapthings[i].args[5] = !!(mapthings[i].options & MTF_EXTRA);
+			break;
+		case 209: //Brak Eggman
+			mapthings[i].args[5] = LE_BRAKVILEATACK + mapthings[i].extrainfo*LE_PARAMWIDTH;
+			if (mapthings[i].options & MTF_EXTRA)
+				mapthings[i].args[6] |= TMB_NODEATHFLING;
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[6] |= TMB_BARRIER;
+			break;
+		case 292: //Boss waypoint
+			mapthings[i].args[0] = mapthings[i].angle;
+			mapthings[i].args[1] = mapthings[i].options & 7;
+			break;
+		case 294: //Fang waypoint
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 300: //Ring
+		case 301: //Bounce ring
+		case 302: //Rail ring
+		case 303: //Infinity ring
+		case 304: //Automatic ring
+		case 305: //Explosion ring
+		case 306: //Scatter ring
+		case 307: //Grenade ring
+		case 308: //Red team ring
+		case 309: //Blue team ring
+		case 312: //Emerald token
+		case 320: //Emerald hunt location
+		case 321: //Match chaos emerald spawn
+		case 322: //Emblem
+		case 330: //Bounce ring panel
+		case 331: //Rail ring panel
+		case 332: //Automatic ring panel
+		case 333: //Explosion ring panel
+		case 334: //Scatter ring panel
+		case 335: //Grenade ring panel
+		case 520: //Bomb sphere
+		case 521: //Spikeball
+		case 1706: //Blue sphere
+		case 1800: //Coin
+			mapthings[i].args[0] = !(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 409: //Extra life monitor
+			mapthings[i].args[2] = !(mapthings[i].options & (MTF_AMBUSH|MTF_OBJECTSPECIAL));
+			break;
+		case 500: //Air bubble patch
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 502: //Star post
+			if (mapthings[i].extrainfo)
+				// Allow thing Parameter to define star post num too!
+				// For starposts above param 15 (the 16th), add 360 to the angle like before and start parameter from 1 (NOT 0)!
+				// So the 16th starpost is angle=0 param=15, the 17th would be angle=360 param=1.
+				// This seems more intuitive for mappers to use, since most SP maps won't have over 16 consecutive star posts.
+				mapthings[i].args[0] = mapthings[i].extrainfo + (mapthings[i].angle/360) * 15;
+			else
+				// Old behavior if Parameter is 0; add 360 to the angle for each consecutive star post.
+				mapthings[i].args[0] = (mapthings[i].angle/360);
+			mapthings[i].args[1] = !!(mapthings[i].options & MTF_OBJECTSPECIAL);
+			break;
+		case 522: //Wall spike
+			if (mapthings[i].options & MTF_OBJECTSPECIAL)
+			{
+				mapthings[i].args[0] = mobjinfo[MT_WALLSPIKE].speed + mapthings[i].angle/360;
+				mapthings[i].args[1] = (16 - mapthings[i].extrainfo) * mapthings[i].args[0]/16;
+				if (mapthings[i].options & MTF_EXTRA)
+					mapthings[i].args[2] |= TMSF_RETRACTED;
+			}
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[2] |= TMSF_INTANGIBLE;
+			break;
+		case 523: //Spike
+			if (mapthings[i].options & MTF_OBJECTSPECIAL)
+			{
+				mapthings[i].args[0] = mobjinfo[MT_SPIKE].speed + mapthings[i].angle;
+				mapthings[i].args[1] = (16 - mapthings[i].extrainfo) * mapthings[i].args[0]/16;
+				if (mapthings[i].options & MTF_EXTRA)
+					mapthings[i].args[2] |= TMSF_RETRACTED;
+			}
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[2] |= TMSF_INTANGIBLE;
+			break;
+		case 540: //Fan
+			mapthings[i].args[0] = mapthings[i].angle;
+			if (mapthings[i].options & MTF_OBJECTSPECIAL)
+				mapthings[i].args[1] |= TMF_INVISIBLE;
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[1] |= TMF_NODISTANCECHECK;
+			break;
+		case 541: //Gas jet
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 543: //Balloon
+			if (mapthings[i].angle > 0)
+				P_WriteConstant(((mapthings[i].angle - 1) % (numskincolors - 1)) + 1, &mapthings[i].stringargs[0]);
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 555: //Diagonal yellow spring
+		case 556: //Diagonal red spring
+		case 557: //Diagonal blue spring
+			if (mapthings[i].options & MTF_OBJECTSPECIAL)
+				mapthings[i].args[0] |= TMDS_NOGRAVITY;
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[0] |= TMDS_ROTATEEXTRA;
+			break;
+		case 558: //Horizontal yellow spring
+		case 559: //Horizontal red spring
+		case 560: //Horizontal blue spring
+			mapthings[i].args[0] = !(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 700: //Water ambience A
+		case 701: //Water ambience B
+		case 702: //Water ambience C
+		case 703: //Water ambience D
+		case 704: //Water ambience E
+		case 705: //Water ambience F
+		case 706: //Water ambience G
+		case 707: //Water ambience H
+			mapthings[i].args[0] = 35;
+			P_WriteConstant(sfx_amwtr1 + mapthings[i].type - 700, &mapthings[i].stringargs[0]);
+			mapthings[i].type = 700;
+			break;
+		case 708: //Disco ambience
+			mapthings[i].args[0] = 512;
+			P_WriteConstant(sfx_ambint, &mapthings[i].stringargs[0]);
+			mapthings[i].type = 700;
+			break;
+		case 709: //Volcano ambience
+			mapthings[i].args[0] = 220;
+			P_WriteConstant(sfx_ambin2, &mapthings[i].stringargs[0]);
+			mapthings[i].type = 700;
+			break;
+		case 710: //Machine ambience
+			mapthings[i].args[0] = 24;
+			P_WriteConstant(sfx_ambmac, &mapthings[i].stringargs[0]);
+			mapthings[i].type = 700;
+			break;
+		case 750: //Slope vertex
+			mapthings[i].args[0] = mapthings[i].extrainfo;
+			break;
+		case 753: //Zoom tube waypoint
+			mapthings[i].args[0] = mapthings[i].angle >> 8;
+			mapthings[i].args[1] = mapthings[i].angle & 255;
+			break;
+		case 754: //Push point
+		case 755: //Pull point
+		{
+			subsector_t *ss = R_PointInSubsector(mapthings[i].x << FRACBITS, mapthings[i].y << FRACBITS);
+			sector_t *s;
+			line_t *line;
+
+			if (!ss)
+			{
+				CONS_Debug(DBG_GAMELOGIC, "Push/pull point: Placed outside of map bounds!\n");
+				break;
+			}
+
+			s = ss->sector;
+			line = P_FindPointPushLine(&s->tags);
+
+			if (!line)
+			{
+				CONS_Debug(DBG_GAMELOGIC, "Push/pull point: Unable to find line of type 547 tagged to sector %s!\n", sizeu1((size_t)(s - sectors)));
+				break;
+			}
+
+			mapthings[i].args[0] = mapthings[i].angle;
+			mapthings[i].args[1] = P_AproxDistance(line->dx >> FRACBITS, line->dy >> FRACBITS);
+			if (mapthings[i].type == 755)
+				mapthings[i].args[1] *= -1;
+			if (mapthings[i].options & MTF_OBJECTSPECIAL)
+				mapthings[i].args[2] |= TMPP_NOZFADE;
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[2] |= TMPP_PUSHZ;
+			if (!(line->flags & ML_NOCLIMB))
+				mapthings[i].args[2] |= TMPP_NONEXCLUSIVE;
+			mapthings[i].type = 754;
+			break;
+		}
+		case 756: //Blast linedef executor
+			mapthings[i].args[0] = mapthings[i].angle;
+			break;
+		case 757: //Fan particle generator
+		{
+			INT32 j = Tag_FindLineSpecial(15, mapthings[i].angle);
+
+			if (j == -1)
+			{
+				CONS_Debug(DBG_GAMELOGIC, "Particle generator (mapthing #%d) needs to be tagged to a #15 parameter line (trying to find tag %d).\n", i, mapthings[i].angle);
+				break;
+			}
+			mapthings[i].args[0] = mapthings[i].z;
+			mapthings[i].args[1] = R_PointToDist2(lines[j].v1->x, lines[j].v1->y, lines[j].v2->x, lines[j].v2->y) >> FRACBITS;
+			mapthings[i].args[2] = sides[lines[j].sidenum[0]].textureoffset >> FRACBITS;
+			mapthings[i].args[3] = sides[lines[j].sidenum[0]].rowoffset >> FRACBITS;
+			mapthings[i].args[4] = lines[j].backsector ? sides[lines[j].sidenum[1]].textureoffset >> FRACBITS : 0;
+			mapthings[i].args[6] = mapthings[i].angle;
+			if (sides[lines[j].sidenum[0]].toptexture)
+				P_WriteConstant(sides[lines[j].sidenum[0]].toptexture, &mapthings[i].stringargs[0]);
+			break;
+		}
+		case 762: //PolyObject spawn point (crush)
 		{
 			INT32 check = -1;
 			INT32 firstline = -1;
-			mtag_t tag = mapthings[i].angle;
-
-			Tag_FSet(&mapthings[i].tags, tag);
+			mtag_t tag = Tag_FGet(&mapthings[i].tags);
 
 			TAG_ITER_LINES(tag, check)
 			{
@@ -3426,8 +6350,201 @@ static void P_ConvertBinaryMap(void)
 			mapthings[i].type = 761;
 			break;
 		}
-		case 780:
-			Tag_FSet(&mapthings[i].tags, mapthings[i].extrainfo);
+		case 780: //Skybox
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_OBJECTSPECIAL);
+			break;
+		case 799: //Tutorial plant
+			mapthings[i].args[0] = mapthings[i].extrainfo;
+			break;
+		case 1002: //Dripping water
+			mapthings[i].args[0] = mapthings[i].angle;
+			break;
+		case 1007: //Kelp
+		case 1008: //Stalagmite (DSZ1)
+		case 1011: //Stalagmite (DSZ2)
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_OBJECTSPECIAL);
+			break;
+		case 1102: //Eggman Statue
+			mapthings[i].args[1] = !!(mapthings[i].options & MTF_EXTRA);
+			break;
+		case 1104: //Mace spawnpoint
+		case 1105: //Chain with maces spawnpoint
+		case 1106: //Chained spring spawnpoint
+		case 1107: //Chain spawnpoint
+		case 1109: //Firebar spawnpoint
+		case 1110: //Custom mace spawnpoint
+		{
+			mtag_t tag = (mtag_t)mapthings[i].angle;
+			INT32 j = Tag_FindLineSpecial(9, tag);
+
+			if (j == -1)
+			{
+				CONS_Debug(DBG_GAMELOGIC, "Chain/mace setup: Unable to find parameter line 9 (tag %d)!\n", tag);
+				break;
+			}
+
+			mapthings[i].angle = lines[j].frontsector->ceilingheight >> FRACBITS;
+			mapthings[i].pitch = lines[j].frontsector->floorheight >> FRACBITS;
+			mapthings[i].args[0] = lines[j].dx >> FRACBITS;
+			mapthings[i].args[1] = mapthings[i].extrainfo;
+			mapthings[i].args[3] = lines[j].dy >> FRACBITS;
+			mapthings[i].args[4] = sides[lines[j].sidenum[0]].textureoffset >> FRACBITS;
+			mapthings[i].args[7] = -sides[lines[j].sidenum[0]].rowoffset >> FRACBITS;
+			if (lines[j].backsector)
+			{
+				mapthings[i].roll = lines[j].backsector->ceilingheight >> FRACBITS;
+				mapthings[i].args[2] = sides[lines[j].sidenum[1]].rowoffset >> FRACBITS;
+				mapthings[i].args[5] = lines[j].backsector->floorheight >> FRACBITS;
+				mapthings[i].args[6] = sides[lines[j].sidenum[1]].textureoffset >> FRACBITS;
+			}
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[8] |= TMM_DOUBLESIZE;
+			if (mapthings[i].options & MTF_OBJECTSPECIAL)
+				mapthings[i].args[8] |= TMM_SILENT;
+			if (lines[j].flags & ML_NOCLIMB)
+				mapthings[i].args[8] |= TMM_ALLOWYAWCONTROL;
+			if (lines[j].flags & ML_SKEWTD)
+				mapthings[i].args[8] |= TMM_SWING;
+			if (lines[j].flags & ML_NOSKEW)
+				mapthings[i].args[8] |= TMM_MACELINKS;
+			if (lines[j].flags & ML_MIDPEG)
+				mapthings[i].args[8] |= TMM_CENTERLINK;
+			if (lines[j].flags & ML_MIDSOLID)
+				mapthings[i].args[8] |= TMM_CLIP;
+			if (lines[j].flags & ML_WRAPMIDTEX)
+				mapthings[i].args[8] |= TMM_ALWAYSTHINK;
+			if (mapthings[i].type == 1110)
+			{
+				P_WriteConstant(sides[lines[j].sidenum[0]].toptexture, &mapthings[i].stringargs[0]);
+				P_WriteConstant(lines[j].backsector ? sides[lines[j].sidenum[1]].toptexture : MT_NULL, &mapthings[i].stringargs[1]);
+			}
+			break;
+		}
+		case 1101: //Torch
+		case 1119: //Candle
+		case 1120: //Candle pricket
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_EXTRA);
+			break;
+		case 1121: //Flame holder
+			if (mapthings[i].options & MTF_OBJECTSPECIAL)
+				mapthings[i].args[0] |= TMFH_NOFLAME;
+			if (mapthings[i].options & MTF_EXTRA)
+				mapthings[i].args[0] |= TMFH_CORONA;
+			break;
+		case 1127: //Spectator EggRobo
+			if (mapthings[i].options & MTF_AMBUSH)
+				mapthings[i].args[0] = TMED_LEFT;
+			else if (mapthings[i].options & MTF_OBJECTSPECIAL)
+				mapthings[i].args[0] = TMED_RIGHT;
+			else
+				mapthings[i].args[0] = TMED_NONE;
+			break;
+		case 1200: //Tumbleweed (Big)
+		case 1201: //Tumbleweed (Small)
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 1202: //Rock spawner
+		{
+			mtag_t tag = (mtag_t)mapthings[i].angle;
+			INT32 j = Tag_FindLineSpecial(12, tag);
+
+			if (j == -1)
+			{
+				CONS_Debug(DBG_GAMELOGIC, "Rock spawner: Unable to find parameter line 12 (tag %d)!\n", tag);
+				break;
+			}
+			mapthings[i].angle = AngleFixed(R_PointToAngle2(lines[j].v2->x, lines[j].v2->y, lines[j].v1->x, lines[j].v1->y)) >> FRACBITS;
+			mapthings[i].args[0] = P_AproxDistance(lines[j].dx, lines[j].dy) >> FRACBITS;
+			mapthings[i].args[1] = sides[lines[j].sidenum[0]].textureoffset >> FRACBITS;
+			mapthings[i].args[2] = !!(lines[j].flags & ML_NOCLIMB);
+			P_WriteConstant(MT_ROCKCRUMBLE1 + (sides[lines[j].sidenum[0]].rowoffset >> FRACBITS), &mapthings[i].stringargs[0]);
+			break;
+		}
+		case 1221: //Minecart saloon door
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 1229: //Minecart switch point
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 1300: //Flame jet (horizontal)
+		case 1301: //Flame jet (vertical)
+			mapthings[i].args[0] = (mapthings[i].angle >> 13)*TICRATE/2;
+			mapthings[i].args[1] = ((mapthings[i].angle >> 10) & 7)*TICRATE/2;
+			mapthings[i].args[2] = 80 - 5*mapthings[i].extrainfo;
+			mapthings[i].args[3] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 1304: //Lavafall
+			mapthings[i].args[0] = mapthings[i].angle;
+			mapthings[i].args[1] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 1305: //Rollout Rock
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 1500: //Glaregoyle
+		case 1501: //Glaregoyle (Up)
+		case 1502: //Glaregoyle (Down)
+		case 1503: //Glaregoyle (Long)
+			if (mapthings[i].angle >= 360)
+				mapthings[i].args[1] = 7*(mapthings[i].angle/360) + 1;
+			break;
+		case 1700: //Axis
+			mapthings[i].args[2] = mapthings[i].angle & 16383;
+			mapthings[i].args[3] = !!(mapthings[i].angle & 16384);
+			/* FALLTHRU */
+		case 1701: //Axis transfer
+		case 1702: //Axis transfer line
+			mapthings[i].args[0] = mapthings[i].extrainfo;
+			mapthings[i].args[1] = mapthings[i].options;
+			break;
+		case 1703: //Ideya drone
+			mapthings[i].args[0] = mapthings[i].angle & 0xFFF;
+			mapthings[i].args[1] = mapthings[i].extrainfo*32;
+			mapthings[i].args[2] = ((mapthings[i].angle & 0xF000) >> 12)*32;
+			if ((mapthings[i].options & (MTF_OBJECTSPECIAL|MTF_EXTRA)) == (MTF_OBJECTSPECIAL|MTF_EXTRA))
+				mapthings[i].args[3] = TMDA_BOTTOM;
+			else if ((mapthings[i].options & (MTF_OBJECTSPECIAL|MTF_EXTRA)) == MTF_OBJECTSPECIAL)
+				mapthings[i].args[3] = TMDA_TOP;
+			else if ((mapthings[i].options & (MTF_OBJECTSPECIAL|MTF_EXTRA)) == MTF_EXTRA)
+				mapthings[i].args[3] = TMDA_MIDDLE;
+			else
+				mapthings[i].args[3] = TMDA_BOTTOMOFFSET;
+			mapthings[i].args[4] = !!(mapthings[i].options & MTF_AMBUSH);
+			break;
+		case 1704: //NiGHTS bumper
+			mapthings[i].pitch = 30 * (((mapthings[i].options & 15) + 9) % 12);
+			mapthings[i].options &= ~0xF;
+			break;
+		case 1705: //Hoop
+		case 1713: //Hoop (Customizable)
+		{
+			UINT16 oldangle = mapthings[i].angle;
+			mapthings[i].angle = ((oldangle >> 8)*360)/256;
+			mapthings[i].pitch = ((oldangle & 255)*360)/256;
+			mapthings[i].args[0] = (mapthings[i].type == 1705) ? 96 : (mapthings[i].options & 0xF)*16 + 32;
+			mapthings[i].options &= ~0xF;
+			mapthings[i].type = 1713;
+			break;
+		}
+		case 1710: //Ideya capture
+			mapthings[i].args[0] = mapthings[i].extrainfo;
+			mapthings[i].args[1] = mapthings[i].angle;
+			break;
+		case 1714: //Ideya anchor point
+			mapthings[i].args[0] = mapthings[i].extrainfo;
+			break;
+		case 1806: //King Bowser
+			mapthings[i].args[0] = LE_KOOPA;
+			break;
+		case 1807: //Axe
+			mapthings[i].args[0] = LE_AXE;
+			break;
+		case 2000: //Smashing spikeball
+			mapthings[i].args[0] = mapthings[i].angle;
+			break;
+		case 2006: //Jack-o'-lantern 1
+		case 2007: //Jack-o'-lantern 2
+		case 2008: //Jack-o'-lantern 3
+			mapthings[i].args[0] = !!(mapthings[i].options & MTF_EXTRA);
 			break;
 		default:
 			break;
@@ -3435,6 +6552,52 @@ static void P_ConvertBinaryMap(void)
 	}
 }
 
+static void P_ConvertBinaryLinedefFlags(void)
+{
+	size_t i;
+
+	for (i = 0; i < numlines; i++)
+	{
+		if (!!(lines[i].flags & ML_DONTPEGBOTTOM) ^ !!(lines[i].flags & ML_MIDPEG))
+			lines[i].flags |= ML_MIDPEG;
+		else
+			lines[i].flags &= ~ML_MIDPEG;
+
+		if (lines[i].special >= 100 && lines[i].special < 300)
+		{
+			if (lines[i].flags & ML_DONTPEGTOP)
+				lines[i].flags |= ML_SKEWTD;
+			else
+				lines[i].flags &= ~ML_SKEWTD;
+
+			if ((lines[i].flags & ML_TFERLINE) && lines[i].frontsector)
+			{
+				size_t j;
+
+				for (j = 0; j < lines[i].frontsector->linecount; j++)
+				{
+					if (lines[i].frontsector->lines[j]->flags & ML_DONTPEGTOP)
+						lines[i].frontsector->lines[j]->flags |= ML_SKEWTD;
+					else
+						lines[i].frontsector->lines[j]->flags &= ~ML_SKEWTD;
+				}
+			}
+		}
+
+	}
+}
+
+//For maps in binary format, converts setup of specials to UDMF format.
+static void P_ConvertBinaryMap(void)
+{
+	P_ConvertBinaryLinedefTypes();
+	P_ConvertBinarySectorTypes();
+	P_ConvertBinaryThingTypes();
+	P_ConvertBinaryLinedefFlags();
+	if (M_CheckParm("-writetextmap"))
+		P_WriteTextmap();
+}
+
 /** Compute MD5 message digest for bytes read from memory source
   *
   * The resulting message digest number will be written into the 16 bytes
diff --git a/src/p_setup.h b/src/p_setup.h
index d0c47a521e8407aa22782a92c6e898a7bcafb266..36d19f66dbbf9ce96aa582298e2535effb530b30 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -107,7 +107,7 @@ boolean P_AddFolder(const char *folderpath);
 boolean P_RunSOC(const char *socfilename);
 void P_LoadSoundsRange(UINT16 wadnum, UINT16 first, UINT16 num);
 void P_LoadMusicsRange(UINT16 wadnum, UINT16 first, UINT16 num);
-void P_WriteThings(void);
+//void P_WriteThings(void);
 size_t P_PrecacheLevelFlats(void);
 void P_AllocMapHeader(INT16 i);
 
diff --git a/src/p_slopes.c b/src/p_slopes.c
index ffbfef2d300f5c2645b54c7dc62fa8c9dc8668b5..7fa51452ec5ec220c834c6ba79afbe91a6ae6bbc 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -121,7 +121,7 @@ static void ReconfigureViaConstants (pslope_t *slope, const fixed_t a, const fix
 }
 
 /// Recalculate dynamic slopes.
-void T_DynamicSlopeLine (dynplanethink_t* th)
+void T_DynamicSlopeLine (dynlineplanethink_t* th)
 {
 	pslope_t* slope = th->slope;
 	line_t* srcline = th->sourceline;
@@ -161,47 +161,56 @@ void T_DynamicSlopeLine (dynplanethink_t* th)
 }
 
 /// Mapthing-defined
-void T_DynamicSlopeVert (dynplanethink_t* th)
+void T_DynamicSlopeVert (dynvertexplanethink_t* th)
 {
-	pslope_t* slope = th->slope;
-
 	size_t i;
-	INT32 l;
 
-	for (i = 0; i < 3; i++) {
-		l = Tag_FindLineSpecial(799, th->tags[i]);
-		if (l != -1) {
-			th->vex[i].z = lines[l].frontsector->floorheight;
-		}
+	for (i = 0; i < 3; i++)
+	{
+		if (th->relative & (1 << i))
+			th->vex[i].z = th->origvecheights[i] + (th->secs[i]->floorheight - th->origsecheights[i]);
 		else
-			th->vex[i].z = 0;
+			th->vex[i].z = th->secs[i]->floorheight;
 	}
 
-	ReconfigureViaVertexes(slope, th->vex[0], th->vex[1], th->vex[2]);
+	ReconfigureViaVertexes(th->slope, th->vex[0], th->vex[1], th->vex[2]);
 }
 
-static inline void P_AddDynSlopeThinker (pslope_t* slope, dynplanetype_t type, line_t* sourceline, fixed_t extent, const INT16 tags[3], const vector3_t vx[3])
+static inline void P_AddDynLineSlopeThinker (pslope_t* slope, dynplanetype_t type, line_t* sourceline, fixed_t extent)
 {
-	dynplanethink_t* th = Z_Calloc(sizeof (*th), PU_LEVSPEC, NULL);
-	switch (type)
-	{
-	case DP_VERTEX:
-		th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeVert;
-		memcpy(th->tags, tags, sizeof(th->tags));
-		memcpy(th->vex, vx, sizeof(th->vex));
-		break;
-	default:
-		th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeLine;
-		th->sourceline = sourceline;
-		th->extent = extent;
-	}
-
+	dynlineplanethink_t* th = Z_Calloc(sizeof (*th), PU_LEVSPEC, NULL);
+	th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeLine;
 	th->slope = slope;
 	th->type = type;
-
+	th->sourceline = sourceline;
+	th->extent = extent;
 	P_AddThinker(THINK_DYNSLOPE, &th->thinker);
 }
 
+static inline void P_AddDynVertexSlopeThinker (pslope_t* slope, const INT16 tags[3], const vector3_t vx[3])
+{
+	dynvertexplanethink_t* th = Z_Calloc(sizeof (*th), PU_LEVSPEC, NULL);
+	size_t i;
+	INT32 l;
+	th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeVert;
+	th->slope = slope;
+
+	for (i = 0; i < 3; i++) {
+		l = Tag_FindLineSpecial(799, tags[i]);
+		if (l == -1)
+		{
+			Z_Free(th);
+			return;
+		}
+		th->secs[i] = lines[l].frontsector;
+		th->vex[i] = vx[i];
+		th->origsecheights[i] = lines[l].frontsector->floorheight;
+		th->origvecheights[i] = vx[i].z;
+		if (lines[l].args[0])
+			th->relative |= 1<<i;
+	}
+	P_AddThinker(THINK_DYNSLOPE, &th->thinker);
+}
 
 /// Create a new slope and add it to the slope list.
 static inline pslope_t* Slope_Add (const UINT8 flags)
@@ -268,6 +277,27 @@ static fixed_t GetExtent(sector_t *sector, line_t *line)
 	return fardist;
 }
 
+static boolean P_CopySlope(pslope_t** toslope, pslope_t* fromslope)
+{
+	if (*toslope || !fromslope)
+		return true;
+
+	*toslope = fromslope;
+	return true;
+}
+
+static void P_UpdateHasSlope(sector_t *sec)
+{
+	size_t i;
+
+	sec->hasslope = true;
+
+	// if this is an FOF control sector, make sure any target sectors also are marked as having slopes
+	if (sec->numattached)
+		for (i = 0; i < sec->numattached; i++)
+			sectors[sec->attached[i]].hasslope = true;
+}
+
 /// Creates one or more slopes based on the given line type and front/back sectors.
 static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 {
@@ -358,7 +388,7 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 			P_CalculateSlopeNormal(fslope);
 
 			if (spawnthinker && (flags & SL_DYNAMIC))
-				P_AddDynSlopeThinker(fslope, DP_FRONTFLOOR, line, extent, NULL, NULL);
+				P_AddDynLineSlopeThinker(fslope, DP_FRONTFLOOR, line, extent);
 		}
 		if(frontceil)
 		{
@@ -375,7 +405,7 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 			P_CalculateSlopeNormal(cslope);
 
 			if (spawnthinker && (flags & SL_DYNAMIC))
-				P_AddDynSlopeThinker(cslope, DP_FRONTCEIL, line, extent, NULL, NULL);
+				P_AddDynLineSlopeThinker(cslope, DP_FRONTCEIL, line, extent);
 		}
 	}
 	if(backfloor || backceil)
@@ -415,7 +445,7 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 			P_CalculateSlopeNormal(fslope);
 
 			if (spawnthinker && (flags & SL_DYNAMIC))
-				P_AddDynSlopeThinker(fslope, DP_BACKFLOOR, line, extent, NULL, NULL);
+				P_AddDynLineSlopeThinker(fslope, DP_BACKFLOOR, line, extent);
 		}
 		if(backceil)
 		{
@@ -432,9 +462,26 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 			P_CalculateSlopeNormal(cslope);
 
 			if (spawnthinker && (flags & SL_DYNAMIC))
-				P_AddDynSlopeThinker(cslope, DP_BACKCEIL, line, extent, NULL, NULL);
+				P_AddDynLineSlopeThinker(cslope, DP_BACKCEIL, line, extent);
 		}
 	}
+
+	if (line->args[2] & TMSL_COPY)
+	{
+		if (frontfloor)
+			P_CopySlope(&line->backsector->f_slope, line->frontsector->f_slope);
+		if (backfloor)
+			P_CopySlope(&line->frontsector->f_slope, line->backsector->f_slope);
+		if (frontceil)
+			P_CopySlope(&line->backsector->c_slope, line->frontsector->c_slope);
+		if (backceil)
+			P_CopySlope(&line->frontsector->c_slope, line->backsector->c_slope);
+
+		if (backfloor || backceil)
+			P_UpdateHasSlope(line->frontsector);
+		if (frontfloor || frontceil)
+			P_UpdateHasSlope(line->backsector);
+	}
 }
 
 /// Creates a new slope from three mapthings with the specified IDs
@@ -469,14 +516,14 @@ static pslope_t *MakeViaMapthings(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flag
 		vx[i].x = mt->x << FRACBITS;
 		vx[i].y = mt->y << FRACBITS;
 		vx[i].z = mt->z << FRACBITS;
-		if (!mt->extrainfo)
+		if (!mt->args[0])
 			vx[i].z += R_PointInSubsector(vx[i].x, vx[i].y)->sector->floorheight;
 	}
 
 	ReconfigureViaVertexes(ret, vx[0], vx[1], vx[2]);
 
 	if (spawnthinker && (flags & SL_DYNAMIC))
-		P_AddDynSlopeThinker(ret, DP_VERTEX, NULL, 0, tags, vx);
+		P_AddDynVertexSlopeThinker(ret, tags, vx);
 
 	return ret;
 }
@@ -591,27 +638,6 @@ static boolean P_SetSlopeFromTag(sector_t *sec, INT32 tag, boolean ceiling)
 	return false;
 }
 
-static boolean P_CopySlope(pslope_t **toslope, pslope_t *fromslope)
-{
-	if (*toslope || !fromslope)
-		return true;
-
-	*toslope = fromslope;
-	return true;
-}
-
-static void P_UpdateHasSlope(sector_t *sec)
-{
-	size_t i;
-
-	sec->hasslope = true;
-
-	// if this is an FOF control sector, make sure any target sectors also are marked as having slopes
-	if (sec->numattached)
-		for (i = 0; i < sec->numattached; i++)
-			sectors[sec->attached[i]].hasslope = true;
-}
-
 //
 // P_CopySectorSlope
 //
@@ -701,9 +727,6 @@ void P_SpawnSlopes(const boolean fromsave) {
 	for (i = 0; i < numlines; i++)
 		switch (lines[i].special)
 		{
-			case 700:
-				if (lines[i].flags & ML_TFERLINE) P_CopySectorSlope(&lines[i]);
-				break;
 			case 720:
 				P_CopySectorSlope(&lines[i]);
 			default:
diff --git a/src/p_slopes.h b/src/p_slopes.h
index d1a053d28fb92d1a0767e566af8c39b86eeabb08..f4b0535e73abddde35486c9079b5c5ef7455a824 100644
--- a/src/p_slopes.h
+++ b/src/p_slopes.h
@@ -44,7 +44,8 @@ typedef enum
 typedef enum
 {
 	TMSL_NOPHYSICS = 1,
-	TMSL_DYNAMIC = 2,
+	TMSL_DYNAMIC   = 1<<1,
+	TMSL_COPY      = 1<<2,
 } textmapslopeflags_t;
 
 void P_LinkSlopeThinkers (void);
@@ -95,26 +96,29 @@ typedef enum {
 	DP_FRONTCEIL,
 	DP_BACKFLOOR,
 	DP_BACKCEIL,
-	DP_VERTEX
 } dynplanetype_t;
 
 /// Permit slopes to be dynamically altered through a thinker.
 typedef struct
 {
 	thinker_t thinker;
-
-	pslope_t* slope;
+	pslope_t *slope;
 	dynplanetype_t type;
-
-	// Used by line slopes.
-	line_t* sourceline;
+	line_t *sourceline;
 	fixed_t extent;
+} dynlineplanethink_t;
 
-	// Used by mapthing vertex slopes.
-	INT16 tags[3];
+typedef struct
+{
+	thinker_t thinker;
+	pslope_t *slope;
+	sector_t *secs[3];
 	vector3_t vex[3];
-} dynplanethink_t;
+	fixed_t origsecheights[3];
+	fixed_t origvecheights[3];
+	UINT8 relative;
+} dynvertexplanethink_t;
 
-void T_DynamicSlopeLine (dynplanethink_t* th);
-void T_DynamicSlopeVert (dynplanethink_t* th);
+void T_DynamicSlopeLine (dynlineplanethink_t* th);
+void T_DynamicSlopeVert (dynvertexplanethink_t* th);
 #endif // #ifndef P_SLOPES_H__
diff --git a/src/p_spec.c b/src/p_spec.c
index 3fe77d7a2e8fa378fabc9615b89d826229c923e0..69e1e3925b35a9e8004b74b1130db6dd0a265592 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -15,6 +15,7 @@
 ///        utility functions, etc.
 ///        Line Tag handling. Line and Sector triggers.
 
+#include "dehacked.h"
 #include "doomdef.h"
 #include "g_game.h"
 #include "p_local.h"
@@ -50,9 +51,6 @@ mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpoint
 mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs
 mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
 
-// Amount (dx, dy) vector linedef is shifted right to get scroll amount
-#define SCROLL_SHIFT 5
-
 /** Animated texture descriptor
   * This keeps track of an animated texture or an animated flat.
   * \sa P_UpdateSpecials, P_InitPicAnims, animdef_t
@@ -100,7 +98,7 @@ typedef struct
 static void P_SpawnScrollers(void);
 static void P_SpawnFriction(void);
 static void P_SpawnPushers(void);
-static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *source, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider); //SoM: 3/9/2000
+static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, fixed_t z_mag, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider); //SoM: 3/9/2000
 static void Add_MasterDisappearer(tic_t appeartime, tic_t disappeartime, tic_t offset, INT32 line, INT32 sourceline);
 static void P_ResetFakeFloorFader(ffloor_t *rover, fade_t *data, boolean finalize);
 #define P_RemoveFakeFloorFader(l) P_ResetFakeFloorFader(l, NULL, false);
@@ -117,7 +115,7 @@ static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, ext
 static void P_AddBlockThinker(sector_t *sec, 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_AddFakeFloorsByLine(size_t line, INT32 alpha, UINT8 blendmode, 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_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee, UINT8 reverse);
@@ -993,30 +991,22 @@ static boolean PolyDoor(line_t *line)
 {
 	polydoordata_t pdd;
 
-	pdd.polyObjNum = Tag_FGet(&line->tags); // polyobject id
+	pdd.polyObjNum = line->args[0]; // polyobject id
 
 	switch(line->special)
 	{
 		case 480: // Polyobj_DoorSlide
 			pdd.doorType = POLY_DOOR_SLIDE;
-			pdd.speed    = sides[line->sidenum[0]].textureoffset / 8;
+			pdd.speed    = line->args[1] << (FRACBITS - 3);
 			pdd.angle    = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y); // angle of motion
-			pdd.distance = sides[line->sidenum[0]].rowoffset;
-
-			if (line->sidenum[1] != 0xffff)
-				pdd.delay = sides[line->sidenum[1]].textureoffset >> FRACBITS; // delay in tics
-			else
-				pdd.delay = 0;
+			pdd.distance = line->args[2] << FRACBITS;
+			pdd.delay    = line->args[3]; // delay in tics
 			break;
 		case 481: // Polyobj_DoorSwing
 			pdd.doorType = POLY_DOOR_SWING;
-			pdd.speed    = sides[line->sidenum[0]].textureoffset >> FRACBITS; // angular speed
-			pdd.distance = sides[line->sidenum[0]].rowoffset >> FRACBITS; // angular distance
-
-			if (line->sidenum[1] != 0xffff)
-				pdd.delay = sides[line->sidenum[1]].textureoffset >> FRACBITS; // delay in tics
-			else
-				pdd.delay = 0;
+			pdd.speed    = line->args[1]; // angular speed
+			pdd.distance = line->args[2]; // angular distance
+			pdd.delay    = line->args[3]; // delay in tics
 			break;
 		default:
 			return 0; // ???
@@ -1025,31 +1015,29 @@ static boolean PolyDoor(line_t *line)
 	return EV_DoPolyDoor(&pdd);
 }
 
-// Parses arguments for parameterized polyobject move specials
+// Parses arguments for parameterized polyobject move special
 static boolean PolyMove(line_t *line)
 {
 	polymovedata_t pmd;
 
-	pmd.polyObjNum = Tag_FGet(&line->tags);
-	pmd.speed      = sides[line->sidenum[0]].textureoffset / 8;
+	pmd.polyObjNum = line->args[0];
+	pmd.speed      = line->args[1] << (FRACBITS - 3);
 	pmd.angle      = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y);
-	pmd.distance   = sides[line->sidenum[0]].rowoffset;
+	pmd.distance   = line->args[2] << FRACBITS;
 
-	pmd.overRide = (line->special == 483); // Polyobj_OR_Move
+	pmd.overRide = !!line->args[3]; // Polyobj_OR_Move
 
 	return EV_DoPolyObjMove(&pmd);
 }
 
-// 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)
+static void PolySetVisibilityTangibility(line_t *line)
 {
-	INT32 polyObjNum = Tag_FGet(&line->tags);
-	polyobj_t *po;
+	INT32 polyObjNum = line->args[0];
+	polyobj_t* po;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
 	{
-		CONS_Debug(DBG_POLYOBJ, "PolyInvisible: bad polyobj %d\n", polyObjNum);
+		CONS_Debug(DBG_POLYOBJ, "PolySetVisibilityTangibility: bad polyobj %d\n", polyObjNum);
 		return;
 	}
 
@@ -1057,49 +1045,32 @@ static void PolyInvisible(line_t *line)
 	if (po->isBad)
 		return;
 
-	if (!(line->flags & ML_NOCLIMB))
-		po->flags &= ~POF_SOLID;
-
-	po->flags |= POF_NOSPECIALS;
-	po->flags &= ~POF_RENDERALL;
-}
-
-// 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 = Tag_FGet(&line->tags);
-	polyobj_t *po;
-
-	if (!(po = Polyobj_GetForNum(polyObjNum)))
+	if (line->args[1] == TMPV_VISIBLE)
 	{
-		CONS_Debug(DBG_POLYOBJ, "PolyVisible: bad polyobj %d\n", polyObjNum);
-		return;
+		po->flags &= ~POF_NOSPECIALS;
+		po->flags |= (po->spawnflags & POF_RENDERALL);
+	}
+	else if (line->args[1] == TMPV_INVISIBLE)
+	{
+		po->flags |= POF_NOSPECIALS;
+		po->flags &= ~POF_RENDERALL;
 	}
 
-	// don't allow line actions to affect bad polyobjects
-	if (po->isBad)
-		return;
-
-	if (!(line->flags & ML_NOCLIMB))
+	if (line->args[2] == TMPT_TANGIBLE)
 		po->flags |= POF_SOLID;
-
-	po->flags &= ~POF_NOSPECIALS;
-	po->flags |= (po->spawnflags & POF_RENDERALL);
+	else if (line->args[2] == TMPT_INTANGIBLE)
+		po->flags &= ~POF_SOLID;
 }
 
-
 // Sets the translucency of a polyobject
-// Frontsector floor / 100 = translevel
 static void PolyTranslucency(line_t *line)
 {
-	INT32 polyObjNum = Tag_FGet(&line->tags);
+	INT32 polyObjNum = line->args[0];
 	polyobj_t *po;
-	INT32 value;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
 	{
-		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyObjWaypoint: bad polyobj %d\n", polyObjNum);
+		CONS_Debug(DBG_POLYOBJ, "PolyTranslucency: bad polyobj %d\n", polyObjNum);
 		return;
 	}
 
@@ -1107,17 +1078,10 @@ static void PolyTranslucency(line_t *line)
 	if (po->isBad)
 		return;
 
-	// 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 += value;
+	if (lines->args[2]) // relative calc
+		po->translucency += line->args[1];
 	else
-		po->translucency = value;
+		po->translucency = line->args[1];
 
 	po->translucency = max(min(po->translucency, NUMTRANSMAPS), 0);
 }
@@ -1125,10 +1089,9 @@ static void PolyTranslucency(line_t *line)
 // Makes a polyobject translucency fade and applies tangibility
 static boolean PolyFade(line_t *line)
 {
-	INT32 polyObjNum = Tag_FGet(&line->tags);
+	INT32 polyObjNum = line->args[0];
 	polyobj_t *po;
 	polyfadedata_t pfd;
-	INT32 value;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
 	{
@@ -1141,7 +1104,7 @@ static boolean PolyFade(line_t *line)
 		return 0;
 
 	// Prevent continuous execs from interfering on an existing fade
-	if (!(line->flags & ML_EFFECT5)
+	if (!(line->args[3] & TMPF_OVERRIDE)
 		&& po->thinker
 		&& po->thinker->function.acp1 == (actionf_p1)T_PolyObjFade)
 	{
@@ -1151,17 +1114,10 @@ static boolean PolyFade(line_t *line)
 
 	pfd.polyObjNum = polyObjNum;
 
-	// 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 = po->translucency + value;
+	if (line->args[3] & TMPF_RELATIVE) // relative calc
+		pfd.destvalue = po->translucency + line->args[1];
 	else
-		pfd.destvalue = value;
+		pfd.destvalue = line->args[1];
 
 	pfd.destvalue = max(min(pfd.destvalue, NUMTRANSMAPS), 0);
 
@@ -1169,15 +1125,11 @@ static boolean PolyFade(line_t *line)
 	if (po->translucency == pfd.destvalue)
 		return 1;
 
-	pfd.docollision = !(line->flags & ML_BOUNCY);         // do not handle collision flags
-	pfd.doghostfade = (line->flags & ML_EFFECT1);         // do ghost fade (no collision flags during fade)
-	pfd.ticbased = (line->flags & ML_EFFECT4);            // Speed = Tic Duration
-
-	// allow Back Y Offset to be consistent with other fade specials
-	pfd.speed = (line->sidenum[1] != 0xFFFF && !sides[line->sidenum[0]].rowoffset) ?
-		abs(sides[line->sidenum[1]].rowoffset>>FRACBITS)
-		: abs(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+	pfd.docollision = !(line->args[3] & TMPF_IGNORECOLLISION); // do not handle collision flags
+	pfd.doghostfade = (line->args[3] & TMPF_GHOSTFADE);        // do ghost fade (no collision flags during fade)
+	pfd.ticbased = (line->args[3] & TMPF_TICBASED);            // Speed = Tic Duration
 
+	pfd.speed = line->args[2];
 
 	return EV_DoPolyObjFade(&pfd);
 }
@@ -1187,49 +1139,25 @@ static boolean PolyWaypoint(line_t *line)
 {
 	polywaypointdata_t pwd;
 
-	pwd.polyObjNum = Tag_FGet(&line->tags);
-	pwd.speed      = sides[line->sidenum[0]].textureoffset / 8;
-	pwd.sequence   = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Sequence #
-
-	// Behavior after reaching the last waypoint?
-	if (line->flags & ML_EFFECT3)
-		pwd.returnbehavior = PWR_WRAP; // Wrap back to first waypoint
-	else if (line->flags & ML_EFFECT2)
-		pwd.returnbehavior = PWR_COMEBACK; // Go through sequence in reverse
-	else
-		pwd.returnbehavior = PWR_STOP; // Stop
-
-	// Flags
-	pwd.flags = 0;
-	if (line->flags & ML_EFFECT1)
-		pwd.flags |= PWF_REVERSE;
-	if (line->flags & ML_EFFECT4)
-		pwd.flags |= PWF_LOOP;
+	pwd.polyObjNum     = line->args[0];
+	pwd.speed          = line->args[1] << (FRACBITS - 3);
+	pwd.sequence       = line->args[2];
+	pwd.returnbehavior = line->args[3];
+	pwd.flags          = line->args[4];
 
 	return EV_DoPolyObjWaypoint(&pwd);
 }
 
-// Parses arguments for parameterized polyobject rotate specials
+// Parses arguments for parameterized polyobject rotate special
 static boolean PolyRotate(line_t *line)
 {
 	polyrotdata_t prd;
 
-	prd.polyObjNum = Tag_FGet(&line->tags);
-	prd.speed      = sides[line->sidenum[0]].textureoffset >> FRACBITS; // angular speed
-	prd.distance   = sides[line->sidenum[0]].rowoffset >> FRACBITS; // angular distance
-
-	// Polyobj_(OR_)RotateRight have dir == -1
-	prd.direction = (line->special == 484 || line->special == 485) ? -1 : 1;
-
-	// Polyobj_OR types have override set to true
-	prd.overRide  = (line->special == 485 || line->special == 487);
-
-	if (line->flags & ML_NOCLIMB)
-		prd.turnobjs = 0;
-	else if (line->flags & ML_EFFECT4)
-		prd.turnobjs = 2;
-	else
-		prd.turnobjs = 1;
+	prd.polyObjNum = line->args[0];
+	prd.speed      = line->args[1]; // angular speed
+	prd.distance   = abs(line->args[2]); // angular distance
+	prd.direction  = (line->args[2] < 0) ? -1 : 1;
+	prd.flags      = line->args[3];
 
 	return EV_DoPolyObjRotate(&prd);
 }
@@ -1239,10 +1167,10 @@ static boolean PolyFlag(line_t *line)
 {
 	polyflagdata_t pfd;
 
-	pfd.polyObjNum = Tag_FGet(&line->tags);
-	pfd.speed = P_AproxDistance(line->dx, line->dy) >> FRACBITS;
+	pfd.polyObjNum = line->args[0];
+	pfd.speed = line->args[1];
 	pfd.angle = line->angle >> ANGLETOFINESHIFT;
-	pfd.momx = sides[line->sidenum[0]].textureoffset >> FRACBITS;
+	pfd.momx = line->args[2];
 
 	return EV_DoPolyObjFlag(&pfd);
 }
@@ -1251,12 +1179,14 @@ static boolean PolyFlag(line_t *line)
 static boolean PolyDisplace(line_t *line)
 {
 	polydisplacedata_t pdd;
+	fixed_t length = R_PointToDist2(line->v2->x, line->v2->y, line->v1->x, line->v1->y);
+	fixed_t speed = line->args[1] << FRACBITS;
 
-	pdd.polyObjNum = Tag_FGet(&line->tags);
+	pdd.polyObjNum = line->args[0];
 
 	pdd.controlSector = line->frontsector;
-	pdd.dx = line->dx>>8;
-	pdd.dy = line->dy>>8;
+	pdd.dx = FixedMul(FixedDiv(line->dx, length), speed) >> 8;
+	pdd.dy = FixedMul(FixedDiv(line->dy, length), speed) >> 8;
 
 	return EV_DoPolyObjDisplace(&pdd);
 }
@@ -1268,22 +1198,16 @@ static boolean PolyRotDisplace(line_t *line)
 	polyrotdisplacedata_t pdd;
 	fixed_t anginter, distinter;
 
-	pdd.polyObjNum = Tag_FGet(&line->tags);
+	pdd.polyObjNum = line->args[0];
 	pdd.controlSector = line->frontsector;
 
 	// Rotate 'anginter' interval for each 'distinter' interval from the control sector.
-	// Use default values if not provided as fallback.
-	anginter	= sides[line->sidenum[0]].rowoffset ? sides[line->sidenum[0]].rowoffset : 90*FRACUNIT;
-	distinter	= sides[line->sidenum[0]].textureoffset ? sides[line->sidenum[0]].textureoffset : 128*FRACUNIT;
+	anginter	= line->args[2] << FRACBITS;
+	distinter	= line->args[1] << FRACBITS;
 	pdd.rotscale = FixedDiv(anginter, distinter);
 
 	// Same behavior as other rotators when carrying things.
-	if (line->flags & ML_NOCLIMB)
-		pdd.turnobjs = 0;
-	else if (line->flags & ML_EFFECT4)
-		pdd.turnobjs = 2;
-	else
-		pdd.turnobjs = 1;
+	pdd.turnobjs = line->args[3];
 
 	return EV_DoPolyObjRotDisplace(&pdd);
 }
@@ -1298,7 +1222,7 @@ void P_RunNightserizeExecutors(mobj_t *actor)
 
 	for (i = 0; i < numlines; i++)
 	{
-		if (lines[i].special == 323 || lines[i].special == 324)
+		if (lines[i].special == 323)
 			P_RunTriggerLinedef(&lines[i], actor, NULL);
 	}
 }
@@ -1312,7 +1236,7 @@ void P_RunDeNightserizeExecutors(mobj_t *actor)
 
 	for (i = 0; i < numlines; i++)
 	{
-		if (lines[i].special == 325 || lines[i].special == 326)
+		if (lines[i].special == 325)
 			P_RunTriggerLinedef(&lines[i], actor, NULL);
 	}
 }
@@ -1326,7 +1250,7 @@ void P_RunNightsLapExecutors(mobj_t *actor)
 
 	for (i = 0; i < numlines; i++)
 	{
-		if (lines[i].special == 327 || lines[i].special == 328)
+		if (lines[i].special == 327)
 			P_RunTriggerLinedef(&lines[i], actor, NULL);
 	}
 }
@@ -1340,13 +1264,19 @@ void P_RunNightsCapsuleTouchExecutors(mobj_t *actor, boolean entering, boolean e
 
 	for (i = 0; i < numlines; i++)
 	{
-		if ((lines[i].special == 329 || lines[i].special == 330)
-			&& ((entering && (lines[i].flags & ML_TFERLINE))
-				|| (!entering && !(lines[i].flags & ML_TFERLINE)))
-			&& ((lines[i].flags & ML_DONTPEGTOP)
-				|| (enoughspheres && !(lines[i].flags & ML_BOUNCY))
-				|| (!enoughspheres && (lines[i].flags & ML_BOUNCY))))
-			P_RunTriggerLinedef(&lines[i], actor, NULL);
+		if (lines[i].special != 329)
+			continue;
+
+		if (!!(lines[i].args[7] & TMI_ENTER) != entering)
+			continue;
+
+		if (lines[i].args[6] == TMS_IFENOUGH && !enoughspheres)
+			continue;
+
+		if (lines[i].args[6] == TMS_IFNOTENOUGH && enoughspheres)
+			continue;
+
+		P_RunTriggerLinedef(&lines[i], actor, NULL);
 	}
 }
 
@@ -1426,27 +1356,42 @@ static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
 	INT16 specialtype = triggerline->special;
 	size_t i;
 
-	UINT8 inputmare = max(0, min(255, sides[triggerline->sidenum[0]].textureoffset>>FRACBITS));
-	UINT8 inputlap = max(0, min(255, sides[triggerline->sidenum[0]].rowoffset>>FRACBITS));
+	UINT8 inputmare = max(0, min(255, triggerline->args[1]));
+	UINT8 inputlap = max(0, min(255, triggerline->args[2]));
 
-	boolean ltemare = triggerline->flags & ML_NOCLIMB;
-	boolean gtemare = triggerline->flags & ML_BLOCKMONSTERS;
-	boolean ltelap = triggerline->flags & ML_EFFECT1;
-	boolean gtelap = triggerline->flags & ML_EFFECT2;
+	textmapcomparison_t marecomp = triggerline->args[3];
+	textmapcomparison_t lapcomp = triggerline->args[4];
+	textmapnightsplayer_t checkplayer = triggerline->args[5];
 
-	boolean lapfrombonustime = triggerline->flags & ML_EFFECT3;
-	boolean perglobalinverse = triggerline->flags & ML_DONTPEGBOTTOM;
-	boolean perglobal = !(triggerline->flags & ML_EFFECT4) && !perglobalinverse;
+	boolean lapfrombonustime;
 
-	boolean donomares = triggerline->flags & ML_BOUNCY; // nightserize: run at end of level (no mares)
-	boolean fromnonights = triggerline->flags & ML_TFERLINE; // nightserize: from non-nights // denightserize: all players no nights
-	boolean fromnights = triggerline->flags & ML_DONTPEGTOP; // nightserize: from nights // denightserize: >0 players are nights
+	boolean donomares = (specialtype == 323) && (triggerline->args[7] & TMN_LEVELCOMPLETION); // nightserize: run at end of level (no mares)
 
 	UINT8 currentmare = UINT8_MAX;
 	UINT8 currentlap = UINT8_MAX;
 
+	// Set lapfrombonustime
+	switch (specialtype)
+	{
+		case 323:
+			lapfrombonustime = !!(triggerline->args[7] & TMN_BONUSLAPS);
+			break;
+		case 325:
+			lapfrombonustime = !!(triggerline->args[7]);
+			break;
+		case 327:
+			lapfrombonustime = !!(triggerline->args[6]);
+			break;
+		case 329:
+			lapfrombonustime = !!(triggerline->args[7] & TMI_BONUSLAPS);
+			break;
+		default:
+			lapfrombonustime = false;
+			break;
+	}
+
 	// Do early returns for Nightserize
-	if (specialtype >= 323 && specialtype <= 324)
+	if (specialtype == 323)
 	{
 		// run only when no mares are found
 		if (donomares && P_FindLowestMare() != UINT8_MAX)
@@ -1457,7 +1402,7 @@ static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
 			return false;
 
 		// run only if player is nightserizing from non-nights
-		if (fromnonights)
+		if (triggerline->args[6] == TMN_FROMNONIGHTS)
 		{
 			if (!actor->player)
 				return false;
@@ -1465,7 +1410,7 @@ static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
 				return false;
 		}
 		// run only if player is nightserizing from nights
-		else if (fromnights)
+		else if (triggerline->args[6] == TMN_FROMNIGHTS)
 		{
 			if (!actor->player)
 				return false;
@@ -1475,8 +1420,8 @@ static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
 	}
 
 	// Get current mare and lap (and check early return for DeNightserize)
-	if (perglobal || perglobalinverse
-		|| (specialtype >= 325 && specialtype <= 326 && (fromnonights || fromnights)))
+	if (checkplayer != TMNP_TRIGGERER
+		|| (specialtype == 325 && triggerline->args[6] != TMD_ALWAYS))
 	{
 		UINT8 playersarenights = 0;
 
@@ -1487,19 +1432,19 @@ static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
 				continue;
 
 			// denightserize: run only if all players are not nights
-			if (specialtype >= 325 && specialtype <= 326 && fromnonights
+			if (specialtype == 325 && triggerline->args[6] == TMD_NOBODYNIGHTS
 				&& players[i].powers[pw_carry] == CR_NIGHTSMODE)
 				return false;
 
 			// count number of nights players for denightserize return
-			if (specialtype >= 325 && specialtype <= 326 && fromnights
+			if (specialtype == 325 && triggerline->args[6] == TMD_SOMEBODYNIGHTS
 				&& players[i].powers[pw_carry] == CR_NIGHTSMODE)
 				playersarenights++;
 
 			lap = lapfrombonustime ? players[i].marebonuslap : players[i].marelap;
 
 			// get highest mare/lap of players
-			if (perglobal)
+			if (checkplayer == TMNP_FASTEST)
 			{
 				if (players[i].mare > currentmare || currentmare == UINT8_MAX)
 				{
@@ -1511,7 +1456,7 @@ static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
 					currentlap = lap;
 			}
 			// get lowest mare/lap of players
-			else if (perglobalinverse)
+			else if (checkplayer == TMNP_SLOWEST)
 			{
 				if (players[i].mare < currentmare || currentmare == UINT8_MAX)
 				{
@@ -1525,12 +1470,12 @@ static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
 		}
 
 		// denightserize: run only if >0 players are nights
-		if (specialtype >= 325 && specialtype <= 326 && fromnights
+		if (specialtype == 325 && triggerline->args[6] == TMD_SOMEBODYNIGHTS
 			&& playersarenights < 1)
 			return false;
 	}
 	// get current mare/lap from triggering player
-	else if (!perglobal && !perglobalinverse)
+	else if (checkplayer == TMNP_TRIGGERER)
 	{
 		if (!actor->player)
 			return false;
@@ -1542,280 +1487,170 @@ static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
 		return false; // special case: player->marebonuslap is 0 until passing through on bonus time. Don't trigger lines looking for inputlap 0.
 
 	// Compare current mare/lap to input mare/lap based on rules
-	if (!(specialtype >= 323 && specialtype <= 324 && donomares) // don't return false if donomares and we got this far
-		&& ((ltemare && currentmare > inputmare)
-		|| (gtemare && currentmare < inputmare)
-		|| (!ltemare && !gtemare && currentmare != inputmare)
-		|| (ltelap && currentlap > inputlap)
-		|| (gtelap && currentlap < inputlap)
-		|| (!ltelap && !gtelap && currentlap != inputlap))
+	if (!donomares // don't return false if donomares and we got this far
+		&& ((marecomp == TMC_LTE && currentmare > inputmare)
+		|| (marecomp == TMC_GTE && currentmare < inputmare)
+		|| (marecomp == TMC_EQUAL && currentmare != inputmare)
+		|| (lapcomp == TMC_LTE && currentlap > inputlap)
+		|| (lapcomp == TMC_GTE && currentlap < inputlap)
+		|| (lapcomp == TMC_EQUAL && currentlap != inputlap))
 		)
 		return false;
 
 	return true;
 }
 
-/** Used by P_LinedefExecute to check a trigger linedef's conditions
-  * The linedef executor specials in the trigger linedef's sector are run if all conditions are met.
-  * Return false cancels P_LinedefExecute, this happens if a condition is not met.
-  *
-  * \param triggerline Trigger linedef to check conditions for; should NEVER be NULL.
-  * \param actor Object initiating the action; should not be NULL.
-  * \param caller Sector in which the action was started. May be NULL.
-  * \sa P_ProcessLineSpecial, P_LinedefExecute
-  */
-boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller)
+static boolean P_CheckPlayerMareOld(line_t *triggerline)
 {
-	sector_t *ctlsector;
-	fixed_t dist = P_AproxDistance(triggerline->dx, triggerline->dy)>>FRACBITS;
-	size_t i, linecnt, sectori;
-	INT16 specialtype = triggerline->special;
+	UINT8 mare;
+	INT32 targetmare = P_AproxDistance(triggerline->dx, triggerline->dy) >> FRACBITS;
 
-	/////////////////////////////////////////////////
-	// Distance-checking/sector trigger conditions //
-	/////////////////////////////////////////////////
+	if (!(maptol & TOL_NIGHTS))
+		return false;
 
-	// Linetypes 303 and 304 require a specific
-	// number, or minimum or maximum, of rings.
-	if (specialtype == 303 || specialtype == 304)
-	{
-		fixed_t rings = 0;
+	mare = P_FindLowestMare();
 
-		// With the passuse flag, count all player's
-		// rings.
-		if (triggerline->flags & ML_EFFECT4)
-		{
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i] || players[i].spectator)
-					continue;
+	if (triggerline->flags & ML_NOCLIMB)
+		return mare <= targetmare;
 
-				if (!players[i].mo || ((maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings) <= 0)
-					continue;
+	if (triggerline->flags & ML_BLOCKMONSTERS)
+		return mare >= targetmare;
 
-				rings += (maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings;
-			}
-		}
-		else
-		{
-			if (!(actor && actor->player))
-				return false; // no player to count rings from here, sorry
+	return mare == targetmare;
+}
 
-			rings = (maptol & TOL_NIGHTS) ? actor->player->spheres : actor->player->rings;
-		}
+static boolean P_CheckPlayerMare(line_t *triggerline)
+{
+	UINT8 mare;
+	INT32 targetmare = triggerline->args[1];
 
-		if (triggerline->flags & ML_NOCLIMB)
-		{
-			if (rings > dist)
-				return false;
-		}
-		else if (triggerline->flags & ML_BLOCKMONSTERS)
-		{
-			if (rings < dist)
-				return false;
-		}
-		else
-		{
-			if (rings != dist)
-				return false;
-		}
-	}
-	else if (specialtype >= 314 && specialtype <= 315)
+	if (!(maptol & TOL_NIGHTS))
+		return false;
+
+	mare = P_FindLowestMare();
+
+	switch (triggerline->args[2])
 	{
-		msecnode_t *node;
-		mobj_t *mo;
-		INT32 numpush = 0;
-		INT32 numneeded = dist;
+		case TMC_EQUAL:
+		default:
+			return mare == targetmare;
+		case TMC_GTE:
+			return mare >= targetmare;
+		case TMC_LTE:
+			return mare <= targetmare;
+	}
+}
 
-		if (!caller)
-			return false; // we need a calling sector to find pushables in, silly!
+static boolean P_CheckPlayerRings(line_t *triggerline, mobj_t *actor)
+{
+	INT32 rings = 0;
+	INT32 targetrings = triggerline->args[1];
+	size_t i;
 
-		// Count the pushables in this sector
-		node = caller->touching_thinglist; // things touching this sector
-		while (node)
+	// Count all players' rings.
+	if (triggerline->args[3])
+	{
+		for (i = 0; i < MAXPLAYERS; i++)
 		{
-			mo = node->m_thing;
-			if ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse))
-				numpush++;
-			node = node->m_thinglist_next;
-		}
+			if (!playeringame[i] || players[i].spectator)
+				continue;
 
-		if (triggerline->flags & ML_NOCLIMB) // Need at least or more
-		{
-			if (numpush < numneeded)
-				return false;
-		}
-		else if (triggerline->flags & ML_EFFECT4) // Need less than
-		{
-			if (numpush >= numneeded)
-				return false;
-		}
-		else // Need exact
-		{
-			if (numpush != numneeded)
-				return false;
+			if (!players[i].mo || ((maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings) <= 0)
+				continue;
+
+			rings += (maptol & TOL_NIGHTS) ? players[i].spheres : players[i].rings;
 		}
 	}
-	else if (caller)
+	else
 	{
-		if (GETSECSPECIAL(caller->special, 2) == 6)
-		{
-			if (!(ALL7EMERALDS(emeralds)))
-				return false;
-		}
-		else if (GETSECSPECIAL(caller->special, 2) == 7)
-		{
-			UINT8 mare;
-
-			if (!(maptol & TOL_NIGHTS))
-				return false;
-
-			mare = P_FindLowestMare();
+		if (!(actor && actor->player))
+			return false; // no player to count rings from here, sorry
 
-			if (triggerline->flags & ML_NOCLIMB)
-			{
-				if (!(mare <= dist))
-					return false;
-			}
-			else if (triggerline->flags & ML_BLOCKMONSTERS)
-			{
-				if (!(mare >= dist))
-					return false;
-			}
-			else
-			{
-				if (!(mare == dist))
-					return false;
-			}
-		}
-		// If we were not triggered by a sector type especially for the purpose,
-		// a Linedef Executor linedef trigger is not handling sector triggers properly, return.
+		rings = (maptol & TOL_NIGHTS) ? actor->player->spheres : actor->player->rings;
+	}
 
-		else if ((!GETSECSPECIAL(caller->special, 2) || GETSECSPECIAL(caller->special, 2) > 7) && (specialtype > 322))
-		{
-			CONS_Alert(CONS_WARNING,
-				M_GetText("Linedef executor trigger isn't handling sector triggers properly!\nspecialtype = %d, if you are not a dev, report this warning instance\nalong with the wad that caused it!\n"),
-				specialtype);
-			return false;
-		}
+	switch (triggerline->args[2])
+	{
+		case TMC_EQUAL:
+		default:
+			return rings == targetrings;
+		case TMC_GTE:
+			return rings >= targetrings;
+		case TMC_LTE:
+			return rings <= targetrings;
 	}
+}
 
-	//////////////////////////////////////
-	// Miscellaneous trigger conditions //
-	//////////////////////////////////////
+static boolean P_CheckPushables(line_t *triggerline, sector_t *caller)
+{
+	msecnode_t *node;
+	mobj_t *mo;
+	INT32 numpushables = 0;
+	INT32 targetpushables = triggerline->args[1];
 
-	switch (specialtype)
-	{
-		case 305: // continuous
-		case 306: // each time
-		case 307: // once
-			if (!(actor && actor->player && actor->player->charability == dist/10))
-				return false;
-			break;
-		case 309: // continuous
-		case 310: // each time
-			// Only red team members can activate this.
-			if (!(actor && actor->player && actor->player->ctfteam == 1))
-				return false;
-			break;
-		case 311: // continuous
-		case 312: // each time
-			// Only blue team members can activate this.
-			if (!(actor && actor->player && actor->player->ctfteam == 2))
-				return false;
-			break;
-		case 317: // continuous
-		case 318: // once
-			{ // Unlockable triggers required
-				INT32 trigid = (INT32)(sides[triggerline->sidenum[0]].textureoffset>>FRACBITS);
+	if (!caller)
+		return false; // we need a calling sector to find pushables in, silly!
 
-				if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
-					return false;
-				else if (trigid < 0 || trigid > 31) // limited by 32 bit variable
-				{
-					CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid);
-					return false;
-				}
-				else if (!(unlocktriggers & (1 << trigid)))
-					return false;
-			}
-			break;
-		case 319: // continuous
-		case 320: // once
-			{ // An unlockable itself must be unlocked!
-				INT32 unlockid = (INT32)(sides[triggerline->sidenum[0]].textureoffset>>FRACBITS);
+	// Count the pushables in this sector
+	for (node = caller->touching_thinglist; node; node = node->m_thinglist_next)
+	{
+		mo = node->m_thing;
+		if ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse))
+			numpushables++;
+	}
 
-				if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
-					return false;
-				else if (unlockid < 0 || unlockid >= MAXUNLOCKABLES) // limited by unlockable count
-				{
-					CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
-					return false;
-				}
-				else if (!(unlockables[unlockid-1].unlocked))
-					return false;
-			}
-			break;
-		case 321: // continuous
-		case 322: // each time
-			// decrement calls left before triggering
-			if (triggerline->callcount > 0)
-			{
-				if (--triggerline->callcount > 0)
-					return false;
-			}
-			break;
-		case 323: // nightserize - each time
-		case 324: // nightserize - once
-		case 325: // denightserize - each time
-		case 326: // denightserize - once
-		case 327: // nights lap - each time
-		case 328: // nights lap - once
-		case 329: // nights egg capsule touch - each time
-		case 330: // nights egg capsule touch - once
-			if (!P_CheckNightsTriggerLine(triggerline, actor))
-				return false;
-			break;
-		case 331: // continuous
-		case 332: // each time
-		case 333: // once
-			if (!(actor && actor->player && ((stricmp(triggerline->text, skins[actor->player->skin].name) == 0) ^ ((triggerline->flags & ML_NOCLIMB) == ML_NOCLIMB))))
-				return false;
-			break;
-		case 334: // object dye - continuous
-		case 335: // object dye - each time
-		case 336: // object dye - once
-			{
-				INT32 triggercolor = (INT32)sides[triggerline->sidenum[0]].toptexture;
-				UINT16 color = (actor->player ? actor->player->powers[pw_dye] : actor->color);
-				boolean invert = (triggerline->flags & ML_NOCLIMB ? true : false);
+	switch (triggerline->args[2])
+	{
+		case TMC_EQUAL:
+		default:
+			return numpushables == targetpushables;
+		case TMC_GTE:
+			return numpushables >= targetpushables;
+		case TMC_LTE:
+			return numpushables <= targetpushables;
+	}
+}
 
-				if (invert ^ (triggercolor != color))
-					return false;
-			}
+static boolean P_CheckEmeralds(INT32 checktype, UINT16 target)
+{
+	switch (checktype)
+	{
+		case TMF_HASALL:
 		default:
-			break;
+			return (emeralds & target) == target;
+		case TMF_HASANY:
+			return !!(emeralds & target);
+		case TMF_HASEXACTLY:
+			return emeralds == target;
+		case TMF_DOESNTHAVEALL:
+			return (emeralds & target) != target;
+		case TMF_DOESNTHAVEANY:
+			return !(emeralds & target);
 	}
+}
 
-	/////////////////////////////////
-	// Processing linedef specials //
-	/////////////////////////////////
+static void P_ActivateLinedefExecutor(line_t *line, mobj_t *actor, sector_t *caller)
+{
+	if (line->special < 400 || line->special >= 500)
+		return;
+
+	if (line->executordelay)
+		P_AddExecutorDelay(line, actor, caller);
+	else
+		P_ProcessLineSpecial(line, actor, caller);
+}
 
-	ctlsector = triggerline->frontsector;
-	sectori = (size_t)(ctlsector - sectors);
-	linecnt = ctlsector->linecount;
+static boolean P_ActivateLinedefExecutorsInSector(line_t *triggerline, mobj_t *actor, sector_t *caller)
+{
+	sector_t *ctlsector = triggerline->frontsector;
+	size_t sectori = (size_t)(ctlsector - sectors);
+	size_t linecnt = ctlsector->linecount;
+	size_t i;
 
-	if (triggerline->flags & ML_EFFECT5) // disregard order for efficiency
+	if (!udmf && triggerline->flags & ML_WRAPMIDTEX) // disregard order for efficiency
 	{
 		for (i = 0; i < linecnt; i++)
-			if (ctlsector->lines[i]->special >= 400
-				&& ctlsector->lines[i]->special < 500)
-			{
-				if (ctlsector->lines[i]->executordelay)
-					P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
-				else
-					P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
-			}
+			P_ActivateLinedefExecutor(ctlsector->lines[i], actor, caller);
 	}
 	else // walk around the sector in a defined order
 	{
@@ -1896,91 +1731,335 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			if (i == masterlineindex)
 				break;
 
-			if (ctlsector->lines[i]->special >= 400
-				&& ctlsector->lines[i]->special < 500)
-			{
-				if (ctlsector->lines[i]->executordelay)
-					P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
-				else
-					P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
-			}
+			P_ActivateLinedefExecutor(ctlsector->lines[i], actor, caller);
 		}
 	}
 
-	// "Trigger on X calls" linedefs reset if noclimb is set
-	if ((specialtype == 321 || specialtype == 322) && triggerline->flags & ML_NOCLIMB)
-		triggerline->callcount = sides[triggerline->sidenum[0]].textureoffset>>FRACBITS;
-	else
-	// These special types work only once
-	if (specialtype == 302  // Once
-	 || specialtype == 304  // Ring count - Once
-	 || specialtype == 307  // Character ability - Once
-	 || specialtype == 308  // Race only - Once
-	 || specialtype == 313  // No More Enemies - Once
-	 || specialtype == 315  // No of pushables - Once
-	 || specialtype == 318  // Unlockable trigger - Once
-	 || specialtype == 320  // Unlockable - Once
-	 || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time
-	 || specialtype == 324 // Nightserize - Once
-	 || specialtype == 326 // DeNightserize - Once
-	 || 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
-
 	return true;
 }
 
-/** Runs a linedef executor.
-  * Can be called by:
-  *   - a player moving into a special sector or FOF.
-  *   - a pushable object moving into a special sector or FOF.
-  *   - a ceiling or floor movement from a previous linedef executor finishing.
-  *   - any object in a state with the A_LinedefExecute() action.
+/** Used by P_LinedefExecute to check a trigger linedef's conditions
+  * The linedef executor specials in the trigger linedef's sector are run if all conditions are met.
+  * Return false cancels P_LinedefExecute, this happens if a condition is not met.
   *
-  * \param tag Tag of the linedef executor to run.
+  * \param triggerline Trigger linedef to check conditions for; should NEVER be NULL.
   * \param actor Object initiating the action; should not be NULL.
   * \param caller Sector in which the action was started. May be NULL.
-  * \sa P_ProcessLineSpecial, P_RunTriggerLinedef
-  * \author Graue <graue@oceanbase.org>
+  * \sa P_ProcessLineSpecial, P_LinedefExecute
   */
-void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
+boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller)
 {
-	size_t masterline;
-
-	CONS_Debug(DBG_GAMELOGIC, "P_LinedefExecute: Executing trigger linedefs of tag %d\n", tag);
+	INT16 specialtype = triggerline->special;
 
-	I_Assert(!actor || !P_MobjWasRemoved(actor)); // If actor is there, it must be valid.
+	////////////////////////
+	// Trigger conditions //
+	////////////////////////
 
-	for (masterline = 0; masterline < numlines; masterline++)
+	if (caller && !udmf)
 	{
-		if (Tag_FGet(&lines[masterline].tags) != tag)
-			continue;
-
-		// "No More Enemies" and "Level Load" take care of themselves.
-		if (lines[masterline].special == 313
-		 || lines[masterline].special == 399
-		 // Each-time executors handle themselves, too
-		 || lines[masterline].special == 301 // Each time
-		 || lines[masterline].special == 306 // Character ability - Each time
-		 || lines[masterline].special == 310 // CTF Red team - Each time
-		 || lines[masterline].special == 312 // CTF Blue team - Each time
-		 || lines[masterline].special == 322 // Trigger on X calls - Each Time
-		 || lines[masterline].special == 332 // Skin - Each time
-		 || lines[masterline].special == 335)// Dye - Each time
-			continue;
+		if (caller->triggerer == TO_PLAYEREMERALDS)
+		{
+			CONS_Alert(CONS_WARNING, M_GetText("Deprecated emerald check sector type detected. Please use linedef types 337-339 instead.\n"));
+			if (!(ALL7EMERALDS(emeralds)))
+				return false;
+		}
+		else if (caller->triggerer == TO_PLAYERNIGHTS)
+		{
+			CONS_Alert(CONS_WARNING, M_GetText("Deprecated NiGHTS mare sector type detected. Please use linedef types 340-342 instead.\n"));
+			if (!P_CheckPlayerMareOld(triggerline))
+				return false;
+		}
+	}
 
-		if (lines[masterline].special < 300
+	switch (specialtype)
+	{
+		case 303:
+			if (!P_CheckPlayerRings(triggerline, actor))
+				return false;
+			break;
+		case 305:
+			if (!(actor && actor->player && actor->player->charability == triggerline->args[1]))
+				return false;
+			break;
+		case 309:
+			// Only red/blue team members can activate this.
+			if (!(actor && actor->player))
+				return false;
+			if (actor->player->ctfteam != ((triggerline->args[1] == TMT_RED) ? 1 : 2))
+				return false;
+			break;
+		case 314:
+			if (!P_CheckPushables(triggerline, caller))
+				return false;
+			break;
+		case 317:
+			{ // Unlockable triggers required
+				INT32 trigid = triggerline->args[1];
+
+				if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
+					return false;
+				else if (trigid < 0 || trigid > 31) // limited by 32 bit variable
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid);
+					return false;
+				}
+				else if (!(unlocktriggers & (1 << trigid)))
+					return false;
+			}
+			break;
+		case 319:
+			{ // An unlockable itself must be unlocked!
+				INT32 unlockid = triggerline->args[1];
+
+				if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
+					return false;
+				else if (unlockid < 0 || unlockid >= MAXUNLOCKABLES) // limited by unlockable count
+				{
+					CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
+					return false;
+				}
+				else if (!(unlockables[unlockid-1].unlocked))
+					return false;
+			}
+			break;
+		case 321:
+			// decrement calls left before triggering
+			if (triggerline->callcount > 0)
+			{
+				if (--triggerline->callcount > 0)
+					return false;
+			}
+			break;
+		case 323: // nightserize
+		case 325: // denightserize
+		case 327: // nights lap
+		case 329: // nights egg capsule touch
+			if (!P_CheckNightsTriggerLine(triggerline, actor))
+				return false;
+			break;
+		case 331:
+			if (!(actor && actor->player))
+				return false;
+			if (!triggerline->stringargs[0])
+				return false;
+			if (!(stricmp(triggerline->stringargs[0], skins[actor->player->skin].name) == 0) ^ !!(triggerline->args[1]))
+				return false;
+			break;
+		case 334: // object dye
+			{
+				INT32 triggercolor = triggerline->stringargs[0] ? get_number(triggerline->stringargs[0]) : SKINCOLOR_NONE;
+				UINT16 color = (actor->player ? actor->player->powers[pw_dye] : actor->color);
+
+				if (!!(triggerline->args[1]) ^ (triggercolor != color))
+					return false;
+			}
+			break;
+		case 337: // emerald check
+			if (!P_CheckEmeralds(triggerline->args[2], (UINT16)triggerline->args[1]))
+				return false;
+			break;
+		case 340: // NiGHTS mare
+			if (!P_CheckPlayerMare(triggerline))
+				return false;
+			break;
+		default:
+			break;
+	}
+
+	/////////////////////////////////
+	// Processing linedef specials //
+	/////////////////////////////////
+
+	if (!P_ActivateLinedefExecutorsInSector(triggerline, actor, caller))
+		return false;
+
+	// "Trigger on X calls" linedefs reset if args[2] is set
+	if (specialtype == 321 && triggerline->args[2])
+		triggerline->callcount = triggerline->args[3];
+	else
+	{
+		// These special types work only once
+		if (specialtype == 313  // No more enemies
+			|| specialtype == 321 // Trigger on X calls
+			|| specialtype == 399) // Level Load
+			triggerline->special = 0;
+		else if ((specialtype == 323 // Nightserize
+			|| specialtype == 325 // DeNightserize
+			|| specialtype == 327 // Nights lap
+			|| specialtype == 329) // Nights bonus time
+			&& triggerline->args[0])
+			triggerline->special = 0;
+		else if ((specialtype == 300 // Basic
+			|| specialtype == 303 // Ring count
+			|| specialtype == 305 // Character ability
+			|| specialtype == 308 // Gametype
+			|| specialtype == 309 // CTF team
+			|| specialtype == 314 // No of pushables
+			|| specialtype == 317 // Unlockable trigger
+			|| specialtype == 319 // Unlockable
+			|| specialtype == 331 // Player skin
+			|| specialtype == 334 // Object dye
+			|| specialtype == 337) // Emerald check
+			&& triggerline->args[0] == TMT_ONCE)
+			triggerline->special = 0;
+	}
+
+	return true;
+}
+
+/** Runs a linedef executor.
+  * Can be called by:
+  *   - a player moving into a special sector or FOF.
+  *   - a pushable object moving into a special sector or FOF.
+  *   - a ceiling or floor movement from a previous linedef executor finishing.
+  *   - any object in a state with the A_LinedefExecute() action.
+  *
+  * \param tag Tag of the linedef executor to run.
+  * \param actor Object initiating the action; should not be NULL.
+  * \param caller Sector in which the action was started. May be NULL.
+  * \sa P_ProcessLineSpecial, P_RunTriggerLinedef
+  * \author Graue <graue@oceanbase.org>
+  */
+void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
+{
+	INT32 masterline;
+
+	CONS_Debug(DBG_GAMELOGIC, "P_LinedefExecute: Executing trigger linedefs of tag %d\n", tag);
+
+	I_Assert(!actor || !P_MobjWasRemoved(actor)); // If actor is there, it must be valid.
+
+	TAG_ITER_LINES(tag, masterline)
+	{
+		if (lines[masterline].special < 300
 			|| lines[masterline].special > 399)
 			continue;
 
+		// "No More Enemies" and "Level Load" take care of themselves.
+		if (lines[masterline].special == 313  || lines[masterline].special == 399)
+			continue;
+
+		// Each-time executors handle themselves, too
+		if ((lines[masterline].special == 300 // Basic
+			|| lines[masterline].special == 303 // Ring count
+			|| lines[masterline].special == 305 // Character ability
+			|| lines[masterline].special == 308 // Gametype
+			|| lines[masterline].special == 309 // CTF team
+			|| lines[masterline].special == 314 // Number of pushables
+			|| lines[masterline].special == 317 // Condition set trigger
+			|| lines[masterline].special == 319 // Unlockable trigger
+			|| lines[masterline].special == 331 // Player skin
+			|| lines[masterline].special == 334 // Object dye
+			|| lines[masterline].special == 337) // Emerald check
+			&& lines[masterline].args[0] > TMT_EACHTIMEMASK)
+			continue;
+
+		if (lines[masterline].special == 321 && lines[masterline].args[0] > TMXT_EACHTIMEMASK) // Trigger after X calls
+			continue;
+
 		if (!P_RunTriggerLinedef(&lines[masterline], actor, caller))
 			return; // cancel P_LinedefExecute if function returns false
 	}
 }
 
+static void P_PlaySFX(INT32 sfxnum, mobj_t *mo, sector_t *callsec, INT16 tag, textmapsoundsource_t source, textmapsoundlistener_t listener)
+{
+	if (sfxnum == sfx_None)
+		return; // Do nothing!
+
+	if (sfxnum < sfx_None || sfxnum >= NUMSFX)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "Line type 414 Executor: sfx number %d is invalid!\n", sfxnum);
+		return;
+	}
+
+	// Check if you can hear the sound
+	switch (listener)
+	{
+		case TMSL_TRIGGERER: // only play sound if displayplayer
+			if (!mo)
+				return;
+
+			if (!mo->player)
+				return;
+
+			if (mo->player != &players[displayplayer] && mo->player != &players[secondarydisplayplayer])
+				return;
+
+			break;
+		case TMSL_TAGGEDSECTOR: // only play if touching tagged sectors
+		{
+			UINT8 i = 0;
+			mobj_t *camobj = players[displayplayer].mo;
+			ffloor_t *rover;
+			boolean foundit = false;
+
+			for (i = 0; i < 2; camobj = players[secondarydisplayplayer].mo, i++)
+			{
+				if (!camobj)
+					continue;
+
+				if (foundit || Tag_Find(&camobj->subsector->sector->tags, tag))
+				{
+					foundit = true;
+					break;
+				}
+
+				// Only trigger if mobj is touching the tag
+				for (rover = camobj->subsector->sector->ffloors; rover; rover = rover->next)
+				{
+					if (!Tag_Find(&rover->master->frontsector->tags, tag))
+						continue;
+
+					if (camobj->z > P_GetSpecialTopZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
+						continue;
+
+					if (camobj->z + camobj->height < P_GetSpecialBottomZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
+						continue;
+
+					foundit = true;
+					break;
+				}
+			}
+
+			if (!foundit)
+				return;
+
+			break;
+		}
+		case TMSL_EVERYONE: // no additional check
+		default:
+			break;
+	}
+
+	// Play the sound from the specified source
+	switch (source)
+	{
+		case TMSS_TRIGGERMOBJ: // play the sound from mobj that triggered it
+			if (mo)
+				S_StartSound(mo, sfxnum);
+			break;
+		case TMSS_TRIGGERSECTOR: // play the sound from calling sector's soundorg
+			if (callsec)
+				S_StartSound(&callsec->soundorg, sfxnum);
+			else if (mo)
+				S_StartSound(&mo->subsector->sector->soundorg, sfxnum);
+			break;
+		case TMSS_NOWHERE: // play the sound from nowhere
+			S_StartSound(NULL, sfxnum);
+			break;
+		case TMSS_TAGGEDSECTOR: // play the sound from tagged sectors' soundorgs
+		{
+			INT32 secnum;
+
+			TAG_ITER_SECTORS(tag, secnum)
+				S_StartSound(&sectors[secnum].soundorg, sfxnum);
+			break;
+		}
+		default:
+			break;
+	}
+}
+
 static boolean is_rain_type (INT32 weathernum)
 {
 	switch (weathernum)
@@ -2163,6 +2242,39 @@ static mobj_t *P_GetObjectTypeInSectorNum(mobjtype_t type, size_t s)
 	return NULL;
 }
 
+static mobj_t* P_FindObjectTypeFromTag(mobjtype_t type, mtag_t tag)
+{
+	if (udmf)
+	{
+		INT32 mtnum;
+		mobj_t *mo;
+
+		TAG_ITER_THINGS(tag, mtnum)
+		{
+			mo = mapthings[mtnum].mobj;
+
+			if (!mo)
+				continue;
+
+			if (mo->type != type)
+				continue;
+
+			return mo;
+		}
+
+		return NULL;
+	}
+	else
+	{
+		INT32 secnum;
+
+		if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
+			return NULL;
+
+		return P_GetObjectTypeInSectorNum(type, secnum);
+	}
+}
+
 /** Processes the line special triggered by an object.
   *
   * \param line Line with the special command on it.
@@ -2181,7 +2293,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 {
 	INT32 secnum = -1;
 	mobj_t *bot = NULL;
-	mtag_t tag = Tag_FGet(&line->tags);
 
 	I_Assert(!mo || !P_MobjWasRemoved(mo)); // If mo is there, mo must be valid!
 
@@ -2191,90 +2302,128 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 	// note: only commands with linedef types >= 400 && < 500 can be used
 	switch (line->special)
 	{
-		case 400: // Set tagged sector's floor height/pic
-			EV_DoFloor(line, instantMoveFloorByFrontSector);
+		case 400: // Set tagged sector's heights/flats
+			if (line->args[1] != TMP_CEILING)
+				EV_DoFloor(line->args[0], line, instantMoveFloorByFrontSector);
+			if (line->args[1] != TMP_FLOOR)
+				EV_DoCeiling(line->args[0], line, instantMoveCeilingByFrontSector);
 			break;
 
-		case 401: // Set tagged sector's ceiling height/pic
-			EV_DoCeiling(line, instantMoveCeilingByFrontSector);
-			break;
-
-		case 402: // Set tagged sector's light level
+		case 402: // Copy light level to tagged sectors
 			{
 				INT16 newlightlevel;
+				INT16 newfloorlightlevel, newceilinglightlevel;
+				boolean newfloorlightabsolute, newceilinglightabsolute;
 				INT32 newfloorlightsec, newceilinglightsec;
 
 				newlightlevel = line->frontsector->lightlevel;
+				newfloorlightlevel = line->frontsector->floorlightlevel;
+				newfloorlightabsolute = line->frontsector->floorlightabsolute;
+				newceilinglightlevel = line->frontsector->ceilinglightlevel;
+				newceilinglightabsolute = line->frontsector->ceilinglightabsolute;
 				newfloorlightsec = line->frontsector->floorlightsec;
 				newceilinglightsec = line->frontsector->ceilinglightsec;
 
-				// act on all sectors with the same tag as the triggering linedef
-				TAG_ITER_SECTORS(tag, secnum)
+				TAG_ITER_SECTORS(line->args[0], secnum)
 				{
 					if (sectors[secnum].lightingdata)
 					{
 						// Stop the lighting madness going on in this sector!
-						P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
+						P_RemoveThinker(&((thinkerdata_t *)sectors[secnum].lightingdata)->thinker);
 						sectors[secnum].lightingdata = NULL;
-
-						// No, it's not an elevator_t, but any struct with a thinker_t named
-						// 'thinker' at the beginning will do here. (We don't know what it
-						// actually is: could be lightlevel_t, fireflicker_t, glow_t, etc.)
 					}
 
-					sectors[secnum].lightlevel = newlightlevel;
-					sectors[secnum].floorlightsec = newfloorlightsec;
-					sectors[secnum].ceilinglightsec = newceilinglightsec;
+					if (!(line->args[1] & TMLC_NOSECTOR))
+						sectors[secnum].lightlevel = newlightlevel;
+					if (!(line->args[1] & TMLC_NOFLOOR))
+					{
+						sectors[secnum].floorlightlevel = newfloorlightlevel;
+						sectors[secnum].floorlightabsolute = newfloorlightabsolute;
+						sectors[secnum].floorlightsec = newfloorlightsec;
+					}
+					if (!(line->args[1] & TMLC_NOCEILING))
+					{
+						sectors[secnum].ceilinglightlevel = newceilinglightlevel;
+						sectors[secnum].ceilinglightabsolute = newceilinglightabsolute;
+						sectors[secnum].ceilinglightsec = newceilinglightsec;
+					}
 				}
 			}
 			break;
 
-		case 403: // Move floor, linelen = speed, frontsector floor = dest height
-			EV_DoFloor(line, moveFloorByFrontSector);
-			break;
-
-		case 404: // Move ceiling, linelen = speed, frontsector ceiling = dest height
-			EV_DoCeiling(line, moveCeilingByFrontSector);
-			break;
-
-		case 405: // Move floor by front side texture offsets, offset x = speed, offset y = amount to raise/lower
-			EV_DoFloor(line, moveFloorByFrontTexture);
-			break;
-
-		case 407: // Move ceiling by front side texture offsets, offset x = speed, offset y = amount to raise/lower
-			EV_DoCeiling(line, moveCeilingByFrontTexture);
-			break;
-
-/*		case 405: // Lower floor by line, dx = speed, dy = amount to lower
-			EV_DoFloor(line, lowerFloorByLine);
+		case 403: // Move planes by front sector
+			if (line->args[1] != TMP_CEILING)
+				EV_DoFloor(line->args[0], line, moveFloorByFrontSector);
+			if (line->args[1] != TMP_FLOOR)
+				EV_DoCeiling(line->args[0], line, moveCeilingByFrontSector);
 			break;
 
-		case 406: // Raise floor by line, dx = speed, dy = amount to raise
-			EV_DoFloor(line, raiseFloorByLine);
+		case 405: // Move planes by distance
+			if (line->args[1] != TMP_CEILING)
+				EV_DoFloor(line->args[0], line, moveFloorByDistance);
+			if (line->args[1] != TMP_FLOOR)
+				EV_DoCeiling(line->args[0], line, moveCeilingByDistance);
 			break;
 
-		case 407: // Lower ceiling by line, dx = speed, dy = amount to lower
-			EV_DoCeiling(line, lowerCeilingByLine);
+		case 408: // Set flats
+		{
+			TAG_ITER_SECTORS(line->args[0], secnum)
+			{
+				if (line->args[1] != TMP_CEILING)
+					sectors[secnum].floorpic = line->frontsector->floorpic;
+				if (line->args[1] != TMP_FLOOR)
+					sectors[secnum].ceilingpic = line->frontsector->ceilingpic;
+			}
 			break;
-
-		case 408: // Raise ceiling by line, dx = speed, dy = amount to raise
-			EV_DoCeiling(line, raiseCeilingByLine);
-			break;*/
+		}
 
 		case 409: // Change tagged sectors' tag
 		// (formerly "Change calling sectors' tag", but behavior was changed)
 		{
-			TAG_ITER_SECTORS(tag, secnum)
-				Tag_SectorFSet(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
+			mtag_t newtag = line->args[1];
+
+			TAG_ITER_SECTORS(line->args[0], secnum)
+			{
+				switch (line->args[2])
+				{
+					case TMT_ADD:
+						Tag_SectorAdd(secnum, newtag);
+						break;
+					case TMT_REMOVE:
+						Tag_SectorRemove(secnum, newtag);
+						break;
+					case TMT_REPLACEFIRST:
+					default:
+						Tag_SectorFSet(secnum, newtag);
+						break;
+				}
+			}
 			break;
 		}
 
 		case 410: // Change front sector's tag
-			Tag_SectorFSet((UINT32)(line->frontsector - sectors), (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
+		{
+			mtag_t newtag = line->args[1];
+			secnum = (UINT32)(line->frontsector - sectors);
+
+			switch (line->args[2])
+			{
+				case TMT_ADD:
+					Tag_SectorAdd(secnum, newtag);
+					break;
+				case TMT_REMOVE:
+					Tag_SectorRemove(secnum, newtag);
+					break;
+				case TMT_REPLACEFIRST:
+				default:
+					Tag_SectorFSet(secnum, newtag);
+					break;
+			}
 			break;
+		}
 
 		case 411: // Stop floor/ceiling movement in tagged sector(s)
-			TAG_ITER_SECTORS(tag, secnum)
+			TAG_ITER_SECTORS(line->args[0], secnum)
 			{
 				if (sectors[secnum].floordata)
 				{
@@ -2308,13 +2457,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if (!mo) // nothing to teleport
 					return;
 
-				if (line->flags & ML_EFFECT3) // Relative silent teleport
+				if (line->args[1] & TMT_RELATIVE) // Relative silent teleport
 				{
 					fixed_t x, y, z;
 
-					x = sides[line->sidenum[0]].textureoffset;
-					y = sides[line->sidenum[0]].rowoffset;
-					z = line->frontsector->ceilingheight;
+					x = line->args[2] << FRACBITS;
+					y = line->args[3] << FRACBITS;
+					z = line->args[4] << FRACBITS;
 
 					P_UnsetThingPosition(mo);
 					mo->x += x;
@@ -2344,41 +2493,40 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 				else
 				{
-					if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
-						return;
+					angle_t angle;
+					boolean silent, keepmomentum;
 
-					dest = P_GetObjectTypeInSectorNum(MT_TELEPORTMAN, secnum);
+					dest = P_FindObjectTypeFromTag(MT_TELEPORTMAN, line->args[0]);
 					if (!dest)
 						return;
 
+					angle = (line->args[1] & TMT_KEEPANGLE) ? mo->angle : dest->angle;
+					silent = !!(line->args[1] & TMT_SILENT);
+					keepmomentum = !!(line->args[1] & TMT_KEEPMOMENTUM);
+
 					if (bot)
-						P_Teleport(bot, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ?  mo->angle : dest->angle, (line->flags & ML_BLOCKMONSTERS) == 0, (line->flags & ML_EFFECT4) == ML_EFFECT4);
-					if (line->flags & ML_BLOCKMONSTERS)
-						P_Teleport(mo, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ?  mo->angle : dest->angle, false, (line->flags & ML_EFFECT4) == ML_EFFECT4);
-					else
-					{
-						P_Teleport(mo, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ?  mo->angle : dest->angle, true, (line->flags & ML_EFFECT4) == ML_EFFECT4);
-						// Play the 'bowrwoosh!' sound
-						S_StartSound(dest, sfx_mixup);
-					}
+						P_Teleport(bot, dest->x, dest->y, dest->z, angle, !silent, keepmomentum);
+					P_Teleport(mo, dest->x, dest->y, dest->z, angle, !silent, keepmomentum);
+					if (!silent)
+						S_StartSound(dest, sfx_mixup); // Play the 'bowrwoosh!' sound
 				}
 			}
 			break;
 
 		case 413: // Change music
-			// console player only unless NOCLIMB is set
-			if ((line->flags & ML_NOCLIMB) || (mo && mo->player && P_IsLocalPlayer(mo->player)) || titlemapinaction)
+			// console player only unless TMM_ALLPLAYERS is set
+			if ((line->args[0] & TMM_ALLPLAYERS) || (mo && mo->player && P_IsLocalPlayer(mo->player)) || titlemapinaction)
 			{
-				boolean musicsame = (!sides[line->sidenum[0]].text[0] || !strnicmp(sides[line->sidenum[0]].text, S_MusicName(), 7));
-				UINT16 tracknum = (UINT16)max(sides[line->sidenum[0]].bottomtexture, 0);
-				INT32 position = (INT32)max(sides[line->sidenum[0]].midtexture, 0);
-				UINT32 prefadems = (UINT32)max(sides[line->sidenum[0]].textureoffset >> FRACBITS, 0);
-				UINT32 postfadems = (UINT32)max(sides[line->sidenum[0]].rowoffset >> FRACBITS, 0);
-				UINT8 fadetarget = (UINT8)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].textureoffset >> FRACBITS : 0, 0);
-				INT16 fadesource = (INT16)max((line->sidenum[1] != 0xffff) ? sides[line->sidenum[1]].rowoffset >> FRACBITS : -1, -1);
+				boolean musicsame = (!line->stringargs[0] || !line->stringargs[0][0] || !strnicmp(line->stringargs[0], S_MusicName(), 7));
+				UINT16 tracknum = (UINT16)max(line->args[6], 0);
+				INT32 position = (INT32)max(line->args[1], 0);
+				UINT32 prefadems = (UINT32)max(line->args[2], 0);
+				UINT32 postfadems = (UINT32)max(line->args[3], 0);
+				UINT8 fadetarget = (UINT8)max(line->args[4], 0);
+				INT16 fadesource = (INT16)max(line->args[5], -1);
 
 				// Seek offset from current song position
-				if (line->flags & ML_EFFECT1)
+				if (line->args[0] & TMM_OFFSET)
 				{
 					// adjust for loop point if subtracting
 					if (position < 0 && S_GetMusicLength() &&
@@ -2390,7 +2538,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 
 				// Fade current music to target volume (if music won't be changed)
-				if ((line->flags & ML_EFFECT2) && fadetarget && musicsame)
+				if ((line->args[0] & TMM_FADE) && fadetarget && musicsame)
 				{
 					// 0 fadesource means fade from current volume.
 					// meaning that we can't specify volume 0 as the source volume -- this starts at 1.
@@ -2408,22 +2556,25 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				// Change the music and apply position/fade operations
 				else
 				{
-					strncpy(mapmusname, sides[line->sidenum[0]].text, 7);
+					if (!line->stringargs[0])
+						break;
+
+					strncpy(mapmusname, line->stringargs[0], 7);
 					mapmusname[6] = 0;
 
 					mapmusflags = tracknum & MUSIC_TRACKMASK;
-					if (!(line->flags & ML_BLOCKMONSTERS))
+					if (!(line->args[0] & TMM_NORELOAD))
 						mapmusflags |= MUSIC_RELOADRESET;
-					if (line->flags & ML_BOUNCY)
+					if (line->args[0] & TMM_FORCERESET)
 						mapmusflags |= MUSIC_FORCERESET;
 
 					mapmusposition = position;
 
-					S_ChangeMusicEx(mapmusname, mapmusflags, !(line->flags & ML_EFFECT4), position,
-						!(line->flags & ML_EFFECT2) ? prefadems : 0,
-						!(line->flags & ML_EFFECT2) ? postfadems : 0);
+					S_ChangeMusicEx(mapmusname, mapmusflags, !(line->args[0] & TMM_NOLOOP), position,
+						!(line->args[0] & TMM_FADE) ? prefadems : 0,
+						!(line->args[0] & TMM_FADE) ? postfadems : 0);
 
-					if ((line->flags & ML_EFFECT2) && fadetarget)
+					if ((line->args[0] & TMM_FADE) && fadetarget)
 					{
 						if (!postfadems)
 							S_SetInternalMusicVolume(fadetarget);
@@ -2432,302 +2583,55 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					}
 				}
 
-				// Except, you can use the ML_BLOCKMONSTERS flag to change this behavior.
+				// Except, you can use the TMM_NORELOAD flag to change this behavior.
 				// if (mapmusflags & MUSIC_RELOADRESET) then it will reset the music in G_PlayerReborn.
 			}
 			break;
 
 		case 414: // Play SFX
+			P_PlaySFX(line->stringargs[0] ? get_number(line->stringargs[0]) : sfx_None, mo, callsec, line->args[2], line->args[0], line->args[1]);
+			break;
+
+		case 415: // Run a script
+			if (cv_runscripts.value)
 			{
-				INT32 sfxnum;
+				lumpnum_t lumpnum = W_CheckNumForName(line->stringargs[0]);
 
-				sfxnum = sides[line->sidenum[0]].toptexture;
+				if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0)
+					CONS_Debug(DBG_SETUP, "Line type 415 Executor: script lump %s not found/not valid.\n", line->stringargs[0]);
+				else
+					COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
+			}
+			break;
 
-				if (sfxnum == sfx_None)
-					return; // Do nothing!
-				if (sfxnum < sfx_None || sfxnum >= NUMSFX)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "Line type 414 Executor: sfx number %d is invalid!\n", sfxnum);
-					return;
-				}
-
-				if (tag != 0) // Do special stuff only if a non-zero linedef tag is set
-				{
-					// Play sounds from tagged sectors' origins.
-					if (line->flags & ML_EFFECT5) // Repeat Midtexture
-					{
-						// Additionally play the sound from tagged sectors' soundorgs
-						sector_t *sec;
-
-						TAG_ITER_SECTORS(tag, secnum)
-						{
-							sec = &sectors[secnum];
-							S_StartSound(&sec->soundorg, sfxnum);
-						}
-					}
-
-					// Play the sound without origin for anyone, as long as they're inside tagged areas.
-					else
-					{
-						UINT8 i = 0;
-						mobj_t* camobj = players[displayplayer].mo;
-						ffloor_t *rover;
-						boolean foundit = false;
-
-						for (i = 0; i < 2; camobj = players[secondarydisplayplayer].mo, i++)
-						{
-							if (!camobj)
-								continue;
-
-							if (foundit || Tag_Find(&camobj->subsector->sector->tags, tag))
-							{
-								foundit = true;
-								break;
-							}
-
-							// Only trigger if mobj is touching the tag
-							for(rover = camobj->subsector->sector->ffloors; rover; rover = rover->next)
-							{
-								if (!Tag_Find(&rover->master->frontsector->tags, tag))
-									continue;
-
-								if (camobj->z > P_GetSpecialTopZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
-									continue;
-
-								if (camobj->z + camobj->height < P_GetSpecialBottomZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
-									continue;
-
-								foundit = true;
-								break;
-							}
-						}
-
-						if (foundit)
-							S_StartSound(NULL, sfxnum);
-					}
-				}
-				else
-				{
-					if (line->flags & ML_NOCLIMB)
-					{
-						// play the sound from nowhere, but only if display player triggered it
-						if (mo && mo->player && (mo->player == &players[displayplayer] || mo->player == &players[secondarydisplayplayer]))
-							S_StartSound(NULL, sfxnum);
-					}
-					else if (line->flags & ML_EFFECT4)
-					{
-						// play the sound from nowhere
-						S_StartSound(NULL, sfxnum);
-					}
-					else if (line->flags & ML_BLOCKMONSTERS)
-					{
-						// play the sound from calling sector's soundorg
-						if (callsec)
-							S_StartSound(&callsec->soundorg, sfxnum);
-						else if (mo)
-							S_StartSound(&mo->subsector->sector->soundorg, sfxnum);
-					}
-					else if (mo)
-					{
-						// play the sound from mobj that triggered it
-						S_StartSound(mo, sfxnum);
-					}
-				}
-			}
-			break;
-
-		case 415: // Run a script
-			if (cv_runscripts.value)
-			{
-				INT32 scrnum;
-				lumpnum_t lumpnum;
-				char newname[9];
-
-				strcpy(newname, G_BuildMapName(gamemap));
-				newname[0] = 'S';
-				newname[1] = 'C';
-				newname[2] = 'R';
-
-				scrnum = sides[line->sidenum[0]].textureoffset>>FRACBITS;
-				if (scrnum < 0 || scrnum > 999)
-				{
-					scrnum = 0;
-					newname[5] = newname[6] = newname[7] = '0';
-				}
-				else
-				{
-					newname[5] = (char)('0' + (char)((scrnum/100)));
-					newname[6] = (char)('0' + (char)((scrnum%100)/10));
-					newname[7] = (char)('0' + (char)(scrnum%10));
-				}
-				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));
-			}
-			break;
-
-		case 416: // Spawn adjustable fire flicker
-			TAG_ITER_SECTORS(tag, secnum)
-			{
-				if (line->flags & ML_NOCLIMB && line->backsector)
-				{
-					// Use front sector for min light level, back sector for max.
-					// This is tricky because P_SpawnAdjustableFireFlicker expects
-					// the maxsector (second argument) to also be the target
-					// sector, so we have to do some light level twiddling.
-					fireflicker_t *flick;
-					INT16 reallightlevel = sectors[secnum].lightlevel;
-					sectors[secnum].lightlevel = line->backsector->lightlevel;
-
-					flick = P_SpawnAdjustableFireFlicker(line->frontsector, &sectors[secnum],
-						P_AproxDistance(line->dx, line->dy)>>FRACBITS);
-
-					// Make sure the starting light level is in range.
-					if (reallightlevel < flick->minlight)
-						reallightlevel = (INT16)flick->minlight;
-					else if (reallightlevel > flick->maxlight)
-						reallightlevel = (INT16)flick->maxlight;
-
-					sectors[secnum].lightlevel = reallightlevel;
-				}
-				else
-				{
-					// Use front sector for min, target sector for max,
-					// the same way linetype 61 does it.
-					P_SpawnAdjustableFireFlicker(line->frontsector, &sectors[secnum],
-						P_AproxDistance(line->dx, line->dy)>>FRACBITS);
-				}
-			}
-			break;
+		case 416: // Spawn adjustable fire flicker
+			TAG_ITER_SECTORS(line->args[0], secnum)
+				P_SpawnAdjustableFireFlicker(&sectors[secnum], line->args[2],
+					line->args[3] ? sectors[secnum].lightlevel : line->args[4], line->args[1]);
+			break;
 
 		case 417: // Spawn adjustable glowing light
-			TAG_ITER_SECTORS(tag, secnum)
-			{
-				if (line->flags & ML_NOCLIMB && line->backsector)
-				{
-					// Use front sector for min light level, back sector for max.
-					// This is tricky because P_SpawnAdjustableGlowingLight expects
-					// the maxsector (second argument) to also be the target
-					// sector, so we have to do some light level twiddling.
-					glow_t *glow;
-					INT16 reallightlevel = sectors[secnum].lightlevel;
-					sectors[secnum].lightlevel = line->backsector->lightlevel;
-
-					glow = P_SpawnAdjustableGlowingLight(line->frontsector, &sectors[secnum],
-						P_AproxDistance(line->dx, line->dy)>>FRACBITS);
-
-					// Make sure the starting light level is in range.
-					if (reallightlevel < glow->minlight)
-						reallightlevel = (INT16)glow->minlight;
-					else if (reallightlevel > glow->maxlight)
-						reallightlevel = (INT16)glow->maxlight;
-
-					sectors[secnum].lightlevel = reallightlevel;
-				}
-				else
-				{
-					// Use front sector for min, target sector for max,
-					// the same way linetype 602 does it.
-					P_SpawnAdjustableGlowingLight(line->frontsector, &sectors[secnum],
-						P_AproxDistance(line->dx, line->dy)>>FRACBITS);
-				}
-			}
-			break;
-
-		case 418: // Spawn adjustable strobe flash (unsynchronized)
-			TAG_ITER_SECTORS(tag, secnum)
-			{
-				if (line->flags & ML_NOCLIMB && line->backsector)
-				{
-					// Use front sector for min light level, back sector for max.
-					// This is tricky because P_SpawnAdjustableGlowingLight expects
-					// the maxsector (second argument) to also be the target
-					// sector, so we have to do some light level twiddling.
-					strobe_t *flash;
-					INT16 reallightlevel = sectors[secnum].lightlevel;
-					sectors[secnum].lightlevel = line->backsector->lightlevel;
-
-					flash = P_SpawnAdjustableStrobeFlash(line->frontsector, &sectors[secnum],
-						abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, false);
-
-					// Make sure the starting light level is in range.
-					if (reallightlevel < flash->minlight)
-						reallightlevel = (INT16)flash->minlight;
-					else if (reallightlevel > flash->maxlight)
-						reallightlevel = (INT16)flash->maxlight;
-
-					sectors[secnum].lightlevel = reallightlevel;
-				}
-				else
-				{
-					// Use front sector for min, target sector for max,
-					// the same way linetype 602 does it.
-					P_SpawnAdjustableStrobeFlash(line->frontsector, &sectors[secnum],
-						abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, false);
-				}
-			}
+			TAG_ITER_SECTORS(line->args[0], secnum)
+				P_SpawnAdjustableGlowingLight(&sectors[secnum], line->args[2],
+					line->args[3] ? sectors[secnum].lightlevel : line->args[4], line->args[1]);
 			break;
 
-		case 419: // Spawn adjustable strobe flash (synchronized)
-			TAG_ITER_SECTORS(tag, secnum)
-			{
-				if (line->flags & ML_NOCLIMB && line->backsector)
-				{
-					// Use front sector for min light level, back sector for max.
-					// This is tricky because P_SpawnAdjustableGlowingLight expects
-					// the maxsector (second argument) to also be the target
-					// sector, so we have to do some light level twiddling.
-					strobe_t *flash;
-					INT16 reallightlevel = sectors[secnum].lightlevel;
-					sectors[secnum].lightlevel = line->backsector->lightlevel;
-
-					flash = P_SpawnAdjustableStrobeFlash(line->frontsector, &sectors[secnum],
-						abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, true);
-
-					// Make sure the starting light level is in range.
-					if (reallightlevel < flash->minlight)
-						reallightlevel = (INT16)flash->minlight;
-					else if (reallightlevel > flash->maxlight)
-						reallightlevel = (INT16)flash->maxlight;
-
-					sectors[secnum].lightlevel = reallightlevel;
-				}
-				else
-				{
-					// Use front sector for min, target sector for max,
-					// the same way linetype 602 does it.
-					P_SpawnAdjustableStrobeFlash(line->frontsector, &sectors[secnum],
-						abs(line->dx)>>FRACBITS, abs(line->dy)>>FRACBITS, true);
-				}
-			}
+		case 418: // Spawn adjustable strobe flash
+			TAG_ITER_SECTORS(line->args[0], secnum)
+				P_SpawnAdjustableStrobeFlash(&sectors[secnum], line->args[3],
+					(line->args[4] & TMB_USETARGET) ? sectors[secnum].lightlevel : line->args[5],
+					line->args[1], line->args[2], line->args[4] & TMB_SYNC);
 			break;
 
 		case 420: // Fade light levels in tagged sectors to new value
-			P_FadeLight(tag,
-				(line->flags & ML_DONTPEGBOTTOM) ? max(sides[line->sidenum[0]].textureoffset>>FRACBITS, 0) : line->frontsector->lightlevel,
-				// failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
-				// to be consistent with other light and fade specials
-				(line->flags & ML_DONTPEGBOTTOM) ?
-					((line->sidenum[1] != 0xFFFF && !(sides[line->sidenum[0]].rowoffset>>FRACBITS)) ?
-						max(min(sides[line->sidenum[1]].rowoffset>>FRACBITS, 255), 0)
-						: max(min(sides[line->sidenum[0]].rowoffset>>FRACBITS, 255), 0))
-					: abs(P_AproxDistance(line->dx, line->dy))>>FRACBITS,
-				(line->flags & ML_EFFECT4),
-				(line->flags & ML_EFFECT5));
+			P_FadeLight(line->args[0], line->args[1], line->args[2], line->args[3] & TMF_TICBASED, line->args[3] & TMF_OVERRIDE, line->args[3] & TMF_RELATIVE);
 			break;
 
 		case 421: // Stop lighting effect in tagged sectors
-			TAG_ITER_SECTORS(tag, secnum)
+			TAG_ITER_SECTORS(line->args[0], secnum)
 				if (sectors[secnum].lightingdata)
 				{
-					P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
+					P_RemoveThinker(&((thinkerdata_t *)sectors[secnum].lightingdata)->thinker);
 					sectors[secnum].lightingdata = NULL;
 				}
 			break;
@@ -2735,15 +2639,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 422: // Cut away to another view
 			{
 				mobj_t *altview;
+				INT32 aim;
 
 				if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens
 					return;
 
-				if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
-					return;
-
-				altview = P_GetObjectTypeInSectorNum(MT_ALTVIEWMAN, secnum);
-				if (!altview)
+				altview = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, line->args[0]);
+				if (!altview || !altview->spawnpoint)
 					return;
 
 				// If titlemap, set the camera ref for title's thinker
@@ -2753,59 +2655,50 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				else
 				{
 					P_SetTarget(&mo->player->awayviewmobj, altview);
-					mo->player->awayviewtics = P_AproxDistance(line->dx, line->dy)>>FRACBITS;
+					mo->player->awayviewtics = line->args[1];
 				}
 
-
-				if (line->flags & ML_NOCLIMB) // lets you specify a vertical angle
-				{
-					INT32 aim;
-
-					aim = sides[line->sidenum[0]].textureoffset>>FRACBITS;
-					aim = (aim + 360) % 360;
-					aim *= (ANGLE_90>>8);
-					aim /= 90;
-					aim <<= 8;
-					if (titlemapinaction)
-						titlemapcameraref->cusval = (angle_t)aim;
-					else
-						mo->player->awayviewaiming = (angle_t)aim;
-				}
+				aim = udmf ? altview->spawnpoint->pitch : line->args[2];
+				aim = (aim + 360) % 360;
+				aim *= (ANGLE_90>>8);
+				aim /= 90;
+				aim <<= 8;
+				if (titlemapinaction)
+					titlemapcameraref->cusval = (angle_t)aim;
 				else
-				{
-					// straight ahead
-					if (!titlemapinaction)
-						mo->player->awayviewaiming = 0;
-					// don't do cusval cause that's annoying
-				}
+					mo->player->awayviewaiming = (angle_t)aim;
 			}
 			break;
 
 		case 423: // Change Sky
-			if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || (line->flags & ML_NOCLIMB))
-				P_SetupLevelSky(sides[line->sidenum[0]].textureoffset>>FRACBITS, (line->flags & ML_NOCLIMB));
+			if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || line->args[1])
+				P_SetupLevelSky(line->args[0], line->args[1]);
 			break;
 
 		case 424: // Change Weather
-			if (line->flags & ML_NOCLIMB)
+			if (line->args[1])
 			{
-				globalweather = (UINT8)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
+				globalweather = (UINT8)(line->args[0]);
 				P_SwitchWeather(globalweather);
 			}
 			else if (mo && mo->player && P_IsLocalPlayer(mo->player))
-				P_SwitchWeather(sides[line->sidenum[0]].textureoffset>>FRACBITS);
+				P_SwitchWeather(line->args[0]);
 			break;
 
 		case 425: // Calls P_SetMobjState on calling mobj
 			if (mo && !mo->player)
-				P_SetMobjState(mo, sides[line->sidenum[0]].toptexture); //P_AproxDistance(line->dx, line->dy)>>FRACBITS);
+			{
+				statenum_t state = line->stringargs[0] ? get_number(line->stringargs[0]) : S_NULL;
+				if (state >= 0 && state < NUMSTATES)
+					P_SetMobjState(mo, state);
+			}
 			break;
 
 		case 426: // Moves the mobj to its sector's soundorg and on the floor, and stops it
 			if (!mo)
 				return;
 
-			if (line->flags & ML_NOCLIMB)
+			if (line->args[0])
 			{
 				P_UnsetThingPosition(mo);
 				mo->x = mo->subsector->sector->soundorg.x;
@@ -2826,7 +2719,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				// Reset bot too.
 				if (bot) {
-					if (line->flags & ML_NOCLIMB)
+					if (line->args[0])
 						P_TeleportMove(bot, mo->x, mo->y, mo->z);
 					bot->momx = bot->momy = bot->momz = 1;
 					bot->pmomz = 0;
@@ -2840,29 +2733,26 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 427: // Awards points if the mobj is a player
 			if (mo && mo->player)
-				P_AddPlayerScore(mo->player, sides[line->sidenum[0]].textureoffset>>FRACBITS);
+				P_AddPlayerScore(mo->player, line->args[0]);
 			break;
 
 		case 428: // Start floating platform movement
-			EV_DoElevator(line, elevateContinuous, true);
-			break;
-
-		case 429: // Crush Ceiling Down Once
-			EV_DoCrush(line, crushCeilOnce);
-			break;
-
-		case 430: // Crush Floor Up Once
-			EV_DoFloor(line, crushFloorOnce);
+			EV_DoElevator(line->args[0], line, elevateContinuous);
 			break;
 
-		case 431: // Crush Floor & Ceiling to middle Once
-			EV_DoCrush(line, crushBothOnce);
+		case 429: // Crush planes once
+			if (line->args[1] == TMP_FLOOR)
+				EV_DoFloor(line->args[0], line, crushFloorOnce);
+			else if (line->args[1] == TMP_CEILING)
+				EV_DoCrush(line->args[0], line, crushCeilOnce);
+			else
+				EV_DoCrush(line->args[0], line, crushBothOnce);
 			break;
 
-		case 432: // Enable 2D Mode (Disable if noclimb)
+		case 432: // Enable/Disable 2D Mode
 			if (mo && mo->player)
 			{
-				if (line->flags & ML_NOCLIMB)
+				if (line->args[0])
 					mo->flags2 &= ~MF2_TWOD;
 				else
 					mo->flags2 |= MF2_TWOD;
@@ -2876,8 +2766,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
-		case 433: // Flip gravity (Flop gravity if noclimb) Works on pushables, too!
-			if (line->flags & ML_NOCLIMB)
+		case 433: // Flip/flop gravity. Works on pushables, too!
+			if (line->args[0])
 				mo->flags2 &= ~MF2_OBJECTFLIP;
 			else
 				mo->flags2 |= MF2_OBJECTFLIP;
@@ -2888,15 +2778,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 434: // Custom Power
 			if (mo && mo->player)
 			{
-				powertype_t power = sides[line->sidenum[0]].toptexture; //(line->dx>>FRACBITS)-1;
-				UINT16 value;
-
-				if (line->sidenum[1] != 0xffff && line->flags & ML_BLOCKMONSTERS) // read power from back sidedef
-					value = sides[line->sidenum[1]].toptexture;
-				else if (line->flags & ML_NOCLIMB) // 'Infinite'
+				powertype_t power = line->stringargs[0] ? get_number(line->stringargs[0]) : 0;
+				INT32 value = line->stringargs[1] ? get_number(line->stringargs[1]) : 0;
+				if (value == -1) // 'Infinite'
 					value = UINT16_MAX;
-				else
-					value = sides[line->sidenum[0]].textureoffset>>FRACBITS;
 
 				P_SetPower(mo->player, power, value);
 
@@ -2910,25 +2795,30 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				scroll_t *scroller;
 				thinker_t *th;
 
+				fixed_t length = R_PointToDist2(line->v2->x, line->v2->y, line->v1->x, line->v1->y);
+				fixed_t speed = line->args[1] << FRACBITS;
+				fixed_t dx = FixedMul(FixedMul(FixedDiv(line->dx, length), speed) >> SCROLL_SHIFT, CARRYFACTOR);
+				fixed_t dy = FixedMul(FixedMul(FixedDiv(line->dy, length), speed) >> SCROLL_SHIFT, CARRYFACTOR);
+
 				for (th = thlist[THINK_MAIN].next; th != &thlist[THINK_MAIN]; th = th->next)
 				{
 					if (th->function.acp1 != (actionf_p1)T_Scroll)
 						continue;
 
 					scroller = (scroll_t *)th;
-					if (!Tag_Find(&sectors[scroller->affectee].tags, tag))
+					if (!Tag_Find(&sectors[scroller->affectee].tags, line->args[0]))
 						continue;
 
-					scroller->dx = FixedMul(line->dx>>SCROLL_SHIFT, CARRYFACTOR);
-					scroller->dy = FixedMul(line->dy>>SCROLL_SHIFT, CARRYFACTOR);
+					scroller->dx = dx;
+					scroller->dy = dy;
 				}
 			}
 			break;
 
 		case 436: // Shatter block remotely
 			{
-				INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
-				INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+				INT16 sectag = (INT16)(line->args[0]);
+				INT16 foftag = (INT16)(line->args[1]);
 				sector_t *sec; // Sector that the FOF is visible in
 				ffloor_t *rover; // FOF that we are going to crumble
 				boolean foundrover = false; // for debug, "Can't find a FOF" message
@@ -2965,10 +2855,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 437: // Disable Player Controls
 			if (mo && mo->player)
 			{
-				UINT16 fractime = (UINT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
+				UINT16 fractime = (UINT16)(line->args[0]);
 				if (fractime < 1)
 					fractime = 1; //instantly wears off upon leaving
-				if (line->flags & ML_NOCLIMB)
+				if (line->args[1])
 					fractime |= 1<<15; //more crazy &ing, as if music stuff wasn't enough
 				mo->player->powers[pw_nocontrol] = fractime;
 				if (bot)
@@ -2979,7 +2869,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 438: // Set player scale
 			if (mo)
 			{
-				mo->destscale = FixedDiv(P_AproxDistance(line->dx, line->dy), 100<<FRACBITS);
+				mo->destscale = FixedDiv(line->args[0]<<FRACBITS, 100<<FRACBITS);
 				if (mo->destscale < FRACUNIT/100)
 					mo->destscale = FRACUNIT/100;
 				if (mo->player && bot)
@@ -2991,30 +2881,33 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			{
 				size_t linenum;
 				side_t *set = &sides[line->sidenum[0]], *this;
-				boolean always = !(line->flags & ML_NOCLIMB); // If noclimb: Only change mid texture if mid texture already exists on tagged lines, etc.
+				boolean always = !(line->args[2]); // If args[2] is set: Only change mid texture if mid texture already exists on tagged lines, etc.
 
 				for (linenum = 0; linenum < numlines; linenum++)
 				{
 					if (lines[linenum].special == 439)
 						continue; // Don't override other set texture lines!
 
-					if (!Tag_Find(&lines[linenum].tags, tag))
+					if (!Tag_Find(&lines[linenum].tags, line->args[0]))
 						continue; // Find tagged lines
 
 					// Front side
-					this = &sides[lines[linenum].sidenum[0]];
-					if (always || this->toptexture) this->toptexture = set->toptexture;
-					if (always || this->midtexture) this->midtexture = set->midtexture;
-					if (always || this->bottomtexture) this->bottomtexture = set->bottomtexture;
-
-					if (lines[linenum].sidenum[1] == 0xffff)
-						continue; // One-sided stops here.
+					if (line->args[1] != TMSD_BACK)
+					{
+						this = &sides[lines[linenum].sidenum[0]];
+						if (always || this->toptexture) this->toptexture = set->toptexture;
+						if (always || this->midtexture) this->midtexture = set->midtexture;
+						if (always || this->bottomtexture) this->bottomtexture = set->bottomtexture;
+					}
 
 					// Back side
-					this = &sides[lines[linenum].sidenum[1]];
-					if (always || this->toptexture) this->toptexture = set->toptexture;
-					if (always || this->midtexture) this->midtexture = set->midtexture;
-					if (always || this->bottomtexture) this->bottomtexture = set->bottomtexture;
+					if (line->args[1] != TMSD_FRONT && lines[linenum].sidenum[1] != 0xffff)
+					{
+						this = &sides[lines[linenum].sidenum[1]];
+						if (always || this->toptexture) this->toptexture = set->toptexture;
+						if (always || this->midtexture) this->midtexture = set->midtexture;
+						if (always || this->bottomtexture) this->bottomtexture = set->bottomtexture;
+					}
 				}
 			}
 			break;
@@ -3027,7 +2920,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 441: // Trigger unlockable
 			if ((!modifiedgame || savemoddata) && !(netgame || multiplayer))
 			{
-				INT32 trigid = (INT32)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
+				INT32 trigid = line->args[0];
 
 				if (trigid < 0 || trigid > 31) // limited by 32 bit variable
 					CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", line->sidenum[0], trigid);
@@ -3050,37 +2943,37 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 442: // Calls P_SetMobjState on mobjs of a given type in the tagged sectors
 		{
-			const mobjtype_t type = (mobjtype_t)sides[line->sidenum[0]].toptexture;
+			const mobjtype_t type = line->stringargs[0] ? get_number(line->stringargs[0]) : MT_NULL;
 			statenum_t state = NUMSTATES;
-			sector_t *sec;
 			mobj_t *thing;
 
-			if (line->sidenum[1] != 0xffff)
-				state = (statenum_t)sides[line->sidenum[1]].toptexture;
+			if (type < 0 || type >= NUMMOBJTYPES)
+				break;
 
-			TAG_ITER_SECTORS(tag, secnum)
+			if (!line->args[1])
+			{
+				state = line->stringargs[1] ? get_number(line->stringargs[1]) : S_NULL;
+
+				if (state < 0 || state >= NUMSTATES)
+					break;
+			}
+
+			TAG_ITER_SECTORS(line->args[0], secnum)
 			{
 				boolean tryagain;
-				sec = sectors + secnum;
 				do {
 					tryagain = false;
-					for (thing = sec->thinglist; thing; thing = thing->snext)
-						if (thing->type == type)
-						{
-							if (state != NUMSTATES)
-							{
-								if (!P_SetMobjState(thing, state)) // set state to specific state
-								{ // mobj was removed
-									tryagain = true; // snext is corrupt, we'll have to start over.
-									break;
-								}
-							}
-							else if (!P_SetMobjState(thing, thing->state->nextstate)) // set state to nextstate
-							{ // mobj was removed
-								tryagain = true; // snext is corrupt, we'll have to start over.
-								break;
-							}
+					for (thing = sectors[secnum].thinglist; thing; thing = thing->snext)
+					{
+						if (thing->type != type)
+							continue;
+
+						if (!P_SetMobjState(thing, line->args[1] ? thing->state->nextstate : state))
+						{ // mobj was removed
+							tryagain = true; // snext is corrupt, we'll have to start over.
+							break;
 						}
+					}
 				} while (tryagain);
 			}
 			break;
@@ -3090,14 +2983,14 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			if (line->stringargs[0])
 				LUA_HookLinedefExecute(line, mo, callsec);
 			else
-				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in arg0str)\n", sizeu1(line-lines));
+				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in stringarg0)\n", sizeu1(line-lines));
 			break;
 
 		case 444: // Earthquake camera
 		{
-			quake.intensity = sides[line->sidenum[0]].textureoffset;
-			quake.radius = sides[line->sidenum[0]].rowoffset;
-			quake.time = P_AproxDistance(line->dx, line->dy)>>FRACBITS;
+			quake.intensity = line->args[1] << FRACBITS;
+			quake.radius = line->args[2] << FRACBITS;
+			quake.time = line->args[0];
 
 			quake.epicenter = NULL; /// \todo
 
@@ -3109,10 +3002,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 		}
 
-		case 445: // Force block disappear remotely (reappear if noclimb)
+		case 445: // Force block disappear remotely (reappear if args[2] is set)
 			{
-				INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
-				INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+				INT16 sectag = (INT16)(line->args[0]);
+				INT16 foftag = (INT16)(line->args[1]);
 				sector_t *sec; // Sector that the FOF is visible (or not visible) in
 				ffloor_t *rover; // FOF to vanish/un-vanish
 				boolean foundrover = false; // for debug, "Can't find a FOF" message
@@ -3137,7 +3030,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							oldflags = rover->flags;
 
 							// Abracadabra!
-							if (line->flags & ML_NOCLIMB)
+							if (line->args[2])
 								rover->flags |= FF_EXISTS;
 							else
 								rover->flags &= ~FF_EXISTS;
@@ -3162,8 +3055,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 446: // Make block fall remotely (acts like FF_CRUMBLE)
 			{
-				INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
-				INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+				INT16 sectag = (INT16)(line->args[0]);
+				INT16 foftag = (INT16)(line->args[1]);
 				sector_t *sec; // Sector that the FOF is visible in
 				ffloor_t *rover; // FOF that we are going to make fall down
 				boolean foundrover = false; // for debug, "Can't find a FOF" message
@@ -3173,7 +3066,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if (mo) // NULL check
 					player = mo->player;
 
-				if (line->flags & ML_NOCLIMB) // don't respawn!
+				if (line->args[2] & TMFR_NORETURN) // don't respawn!
 					respawn = false;
 
 				TAG_ITER_SECTORS(sectag, secnum)
@@ -3192,8 +3085,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						{
 							foundrover = true;
 
-							if (line->flags & ML_BLOCKMONSTERS) // FOF flags determine respawn ability instead?
-								respawn = !(rover->flags & FF_NORETURN) ^ !!(line->flags & ML_NOCLIMB); // no climb inverts
+							if (line->args[2] & TMFR_CHECKFLAG) // FOF flags determine respawn ability instead?
+								respawn = !(rover->flags & FF_NORETURN) ^ !!(line->args[2] & TMFR_NORETURN); // TMFR_NORETURN inverts
 
 							EV_StartCrumble(rover->master->frontsector, rover, (rover->flags & FF_FLOATBOB), player, rover->alpha, respawn);
 						}
@@ -3275,57 +3168,48 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 		}
 		case 448: // Change skybox viewpoint/centerpoint
-			if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || (line->flags & ML_NOCLIMB))
+			if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || line->args[3])
 			{
-				INT32 viewid = sides[line->sidenum[0]].textureoffset>>FRACBITS;
-				INT32 centerid = sides[line->sidenum[0]].rowoffset>>FRACBITS;
+				INT32 viewid = line->args[0];
+				INT32 centerid = line->args[1];
 
-				if ((line->flags & (ML_EFFECT4|ML_BLOCKMONSTERS)) == ML_EFFECT4) // Solid Midtexture is on but Block Enemies is off?
+				// set viewpoint mobj
+				if (line->args[2] != TMS_CENTERPOINT)
 				{
-					CONS_Alert(CONS_WARNING,
-					M_GetText("Skybox switch linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
-					tag);
+					if (viewid >= 0 && viewid < 16)
+						skyboxmo[0] = skyboxviewpnts[viewid];
+					else
+						skyboxmo[0] = NULL;
 				}
-				else
-				{
-					// set viewpoint mobj
-					if (!(line->flags & ML_EFFECT4)) // Solid Midtexture turns off viewpoint setting
-					{
-						if (viewid >= 0 && viewid < 16)
-							skyboxmo[0] = skyboxviewpnts[viewid];
-						else
-							skyboxmo[0] = NULL;
-					}
 
-					// set centerpoint mobj
-					if (line->flags & ML_BLOCKMONSTERS) // Block Enemies turns ON centerpoint setting
-					{
-						if (centerid >= 0 && centerid < 16)
-							skyboxmo[1] = skyboxcenterpnts[centerid];
-						else
-							skyboxmo[1] = NULL;
-					}
+				// set centerpoint mobj
+				if (line->args[2] != TMS_VIEWPOINT)
+				{
+					if (centerid >= 0 && centerid < 16)
+						skyboxmo[1] = skyboxcenterpnts[centerid];
+					else
+						skyboxmo[1] = NULL;
 				}
 
 				CONS_Debug(DBG_GAMELOGIC, "Line type 448 Executor: viewid = %d, centerid = %d, viewpoint? = %s, centerpoint? = %s\n",
 						viewid,
 						centerid,
-						((line->flags & ML_EFFECT4) ? "no" : "yes"),
-						((line->flags & ML_BLOCKMONSTERS) ? "yes" : "no"));
+						((line->args[2] == TMS_CENTERPOINT) ? "no" : "yes"),
+						((line->args[2] == TMS_VIEWPOINT) ? "no" : "yes"));
 			}
 			break;
 
 		case 449: // Enable bosses with parameter
 		{
-			INT32 bossid = sides[line->sidenum[0]].textureoffset>>FRACBITS;
+			INT32 bossid = line->args[0];
 			if (bossid & ~15) // if any bits other than first 16 are set
 			{
 				CONS_Alert(CONS_WARNING,
-					M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
-					tag);
+					M_GetText("Boss enable linedef has an invalid boss ID (%d).\nConsider changing it or removing it entirely.\n"),
+					bossid);
 				break;
 			}
-			if (line->flags & ML_NOCLIMB)
+			if (line->args[1])
 			{
 				bossdisabled |= (1<<bossid);
 				CONS_Debug(DBG_GAMELOGIC, "Line type 449 Executor: bossid disabled = %d", bossid);
@@ -3339,13 +3223,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		}
 
 		case 450: // Execute Linedef Executor - for recursion
-			P_LinedefExecute(tag, mo, NULL);
+			P_LinedefExecute(line->args[0], mo, NULL);
 			break;
 
 		case 451: // Execute Random Linedef Executor
 		{
-			INT32 rvalue1 = sides[line->sidenum[0]].textureoffset>>FRACBITS;
-			INT32 rvalue2 = sides[line->sidenum[0]].rowoffset>>FRACBITS;
+			INT32 rvalue1 = line->args[0];
+			INT32 rvalue2 = line->args[1];
 			INT32 result;
 
 			if (rvalue1 <= rvalue2)
@@ -3359,10 +3243,9 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 452: // Set FOF alpha
 		{
-			INT16 destvalue = line->sidenum[1] != 0xffff ?
-				(INT16)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : (INT16)(P_AproxDistance(line->dx, line->dy)>>FRACBITS);
-			INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
-			INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+			INT16 destvalue = (INT16)(line->args[2]);
+			INT16 sectag = (INT16)(line->args[0]);
+			INT16 foftag = (INT16)(line->args[1]);
 			sector_t *sec; // Sector that the FOF is visible in
 			ffloor_t *rover; // FOF that we are going to operate
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
@@ -3386,7 +3269,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						// If fading an invisible FOF whose render flags we did not yet set,
 						// initialize its alpha to 1
 						// for relative alpha calc
-						if (!(line->flags & ML_NOCLIMB) &&      // do translucent
+						if (!(line->args[3] & TMST_DONTDOTRANSLUCENT) &&      // do translucent
 							(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
 							!(rover->spawnflags & FF_RENDERSIDES) &&
 							!(rover->spawnflags & FF_RENDERPLANES) &&
@@ -3396,16 +3279,16 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						P_RemoveFakeFloorFader(rover);
 						P_FadeFakeFloor(rover,
 							rover->alpha,
-							max(1, min(256, (line->flags & ML_EFFECT3) ? rover->alpha + destvalue : destvalue)),
-							0,                                  // set alpha immediately
-							false, NULL,                        // tic-based logic
-							false,                              // do not handle FF_EXISTS
-							!(line->flags & ML_NOCLIMB),        // handle FF_TRANSLUCENT
-							false,                              // do not handle lighting
-							false,                              // do not handle colormap
-							false,                              // do not handle collision
-							false,                              // do not do ghost fade (no collision during fade)
-							true);                               // use exact alpha values (for opengl)
+							max(1, min(256, (line->args[3] & TMST_RELATIVE) ? rover->alpha + destvalue : destvalue)),
+							0,                                         // set alpha immediately
+							false, NULL,                               // tic-based logic
+							false,                                     // do not handle FF_EXISTS
+							!(line->args[3] & TMST_DONTDOTRANSLUCENT), // handle FF_TRANSLUCENT
+							false,                                     // do not handle lighting
+							false,                                     // do not handle colormap
+							false,                                     // do not handle collision
+							false,                                     // do not do ghost fade (no collision during fade)
+							true);                                     // use exact alpha values (for opengl)
 					}
 				}
 
@@ -3420,12 +3303,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 453: // Fade FOF
 		{
-			INT16 destvalue = line->sidenum[1] != 0xffff ?
-				(INT16)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : (INT16)(line->dx>>FRACBITS);
-			INT16 speed = line->sidenum[1] != 0xffff ?
-				(INT16)(abs(sides[line->sidenum[1]].rowoffset>>FRACBITS)) : (INT16)(abs(line->dy)>>FRACBITS);
-			INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
-			INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+			INT16 destvalue = (INT16)(line->args[2]);
+			INT16 speed = (INT16)(line->args[3]);
+			INT16 sectag = (INT16)(line->args[0]);
+			INT16 foftag = (INT16)(line->args[1]);
 			sector_t *sec; // Sector that the FOF is visible in
 			ffloor_t *rover; // FOF that we are going to operate
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
@@ -3448,7 +3329,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						foundrover = true;
 
 						// Prevent continuous execs from interfering on an existing fade
-						if (!(line->flags & ML_EFFECT5)
+						if (!(line->args[4] & TMFT_OVERRIDE)
 							&& rover->fadingdata)
 							//&& ((fade_t*)rover->fadingdata)->timer > (ticbased ? 2 : speed*2))
 						{
@@ -3460,21 +3341,21 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							P_AddFakeFloorFader(rover, secnum, j,
 								destvalue,
 								speed,
-								(line->flags & ML_EFFECT4),         // tic-based logic
-								(line->flags & ML_EFFECT3),         // Relative destvalue
-								!(line->flags & ML_BLOCKMONSTERS),  // do not handle FF_EXISTS
-								!(line->flags & ML_NOCLIMB),        // do not handle FF_TRANSLUCENT
-								!(line->flags & ML_EFFECT2),        // do not handle lighting
-								!(line->flags & ML_EFFECT2),        // do not handle colormap (ran out of flags)
-								!(line->flags & ML_BOUNCY),         // do not handle collision
-								(line->flags & ML_EFFECT1),         // do ghost fade (no collision during fade)
-								(line->flags & ML_TFERLINE));       // use exact alpha values (for opengl)
+								(line->args[4] & TMFT_TICBASED),           // tic-based logic
+								(line->args[4] & TMFT_RELATIVE),           // Relative destvalue
+								!(line->args[4] & TMFT_DONTDOEXISTS),      // do not handle FF_EXISTS
+								!(line->args[4] & TMFT_DONTDOTRANSLUCENT), // do not handle FF_TRANSLUCENT
+								!(line->args[4] & TMFT_DONTDOLIGHTING),    // do not handle lighting
+								!(line->args[4] & TMFT_DONTDOCOLORMAP),    // do not handle colormap
+								!(line->args[4] & TMFT_IGNORECOLLISION),   // do not handle collision
+								(line->args[4] & TMFT_GHOSTFADE),          // do ghost fade (no collision during fade)
+								(line->args[4] & TMFT_USEEXACTALPHA));     // use exact alpha values (for opengl)
 						else
 						{
 							// If fading an invisible FOF whose render flags we did not yet set,
 							// initialize its alpha to 1
 							// for relative alpha calc
-							if (!(line->flags & ML_NOCLIMB) &&      // do translucent
+							if (!(line->args[4] & TMFT_DONTDOTRANSLUCENT) &&      // do translucent
 								(rover->spawnflags & FF_NOSHADE) && // do not include light blocks, which don't set FF_NOSHADE
 								!(rover->spawnflags & FF_RENDERSIDES) &&
 								!(rover->spawnflags & FF_RENDERPLANES) &&
@@ -3484,16 +3365,16 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							P_RemoveFakeFloorFader(rover);
 							P_FadeFakeFloor(rover,
 								rover->alpha,
-								max(1, min(256, (line->flags & ML_EFFECT3) ? rover->alpha + destvalue : destvalue)),
-								0,                                  // set alpha immediately
-								false, NULL,                        // tic-based logic
-								!(line->flags & ML_BLOCKMONSTERS),  // do not handle FF_EXISTS
-								!(line->flags & ML_NOCLIMB),        // do not handle FF_TRANSLUCENT
-								!(line->flags & ML_EFFECT2),        // do not handle lighting
-								!(line->flags & ML_EFFECT2),        // do not handle colormap (ran out of flags)
-								!(line->flags & ML_BOUNCY),         // do not handle collision
-								(line->flags & ML_EFFECT1),         // do ghost fade (no collision during fade)
-								(line->flags & ML_TFERLINE));       // use exact alpha values (for opengl)
+								max(1, min(256, (line->args[4] & TMFT_RELATIVE) ? rover->alpha + destvalue : destvalue)),
+								0,                                         // set alpha immediately
+								false, NULL,                               // tic-based logic
+								!(line->args[4] & TMFT_DONTDOEXISTS),      // do not handle FF_EXISTS
+								!(line->args[4] & TMFT_DONTDOTRANSLUCENT), // do not handle FF_TRANSLUCENT
+								!(line->args[4] & TMFT_DONTDOLIGHTING),    // do not handle lighting
+								!(line->args[4] & TMFT_DONTDOCOLORMAP),    // do not handle colormap
+								!(line->args[4] & TMFT_IGNORECOLLISION),   // do not handle collision
+								(line->args[4] & TMFT_GHOSTFADE),          // do ghost fade (no collision during fade)
+								(line->args[4] & TMFT_USEEXACTALPHA));     // use exact alpha values (for opengl)
 						}
 					}
 					j++;
@@ -3510,8 +3391,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 454: // Stop fading FOF
 		{
-			INT16 sectag = (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
-			INT16 foftag = (INT16)(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+			INT16 sectag = (INT16)(line->args[0]);
+			INT16 foftag = (INT16)(line->args[1]);
 			sector_t *sec; // Sector that the FOF is visible in
 			ffloor_t *rover; // FOF that we are going to operate
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
@@ -3533,7 +3414,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						foundrover = true;
 
 						P_ResetFakeFloorFader(rover, NULL,
-							!(line->flags & ML_BLOCKMONSTERS)); // do not finalize collision flags
+							!(line->args[2])); // do not finalize collision flags
 					}
 				}
 
@@ -3654,17 +3535,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 457: // Track mobj angle to point
 			if (mo)
 			{
-				INT32 failureangle = FixedAngle((min(max(abs(sides[line->sidenum[0]].textureoffset>>FRACBITS), 0), 360))*FRACUNIT);
-				INT32 failuredelay = abs(sides[line->sidenum[0]].rowoffset>>FRACBITS);
-				INT32 failureexectag = line->sidenum[1] != 0xffff ?
-					(INT32)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : 0;
-				boolean persist = (line->flags & ML_EFFECT2);
+				INT32 failureangle = FixedAngle((min(max(abs(line->args[1]), 0), 360))*FRACUNIT);
+				INT32 failuredelay = abs(line->args[2]);
+				INT32 failureexectag = line->args[3];
+				boolean persist = !!(line->args[4]);
 				mobj_t *anchormo;
 
-				if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
-					return;
-
-				anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
+				anchormo = P_FindObjectTypeFromTag(MT_ANGLEMAN, line->args[0]);
 				if (!anchormo)
 					return;
 
@@ -3687,27 +3564,27 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 459: // Control Text Prompt
-			// console player only unless NOCLIMB is set
+			// console player only
 			if (mo && mo->player && P_IsLocalPlayer(mo->player) && (!bot || bot != mo))
 			{
-				INT32 promptnum = max(0, (sides[line->sidenum[0]].textureoffset>>FRACBITS)-1);
-				INT32 pagenum = max(0, (sides[line->sidenum[0]].rowoffset>>FRACBITS)-1);
-				INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : tag);
-
-				boolean closetextprompt = (line->flags & ML_BLOCKMONSTERS);
-				//boolean allplayers = (line->flags & ML_NOCLIMB);
-				boolean runpostexec = (line->flags & ML_EFFECT1);
-				boolean blockcontrols = !(line->flags & ML_EFFECT2);
-				boolean freezerealtime = !(line->flags & ML_EFFECT3);
-				//boolean freezethinkers = (line->flags & ML_EFFECT4);
-				boolean callbynamedtag = (line->flags & ML_TFERLINE);
+				INT32 promptnum = max(0, line->args[0] - 1);
+				INT32 pagenum = max(0, line->args[1] - 1);
+				INT32 postexectag = abs(line->args[3]);
+
+				boolean closetextprompt = (line->args[2] & TMP_CLOSE);
+				//boolean allplayers = (line->args[2] & TMP_ALLPLAYERS);
+				boolean runpostexec = (line->args[2] & TMP_RUNPOSTEXEC);
+				boolean blockcontrols = !(line->args[2] & TMP_KEEPCONTROLS);
+				boolean freezerealtime = !(line->args[2] & TMP_KEEPREALTIME);
+				//boolean freezethinkers = (line->args[2] & TMP_FREEZETHINKERS);
+				boolean callbynamedtag = (line->args[2] & TMP_CALLBYNAME);
 
 				if (closetextprompt)
 					F_EndTextPrompt(false, false);
 				else
 				{
-					if (callbynamedtag && sides[line->sidenum[0]].text && sides[line->sidenum[0]].text[0])
-						F_GetPromptPageByNamedTag(sides[line->sidenum[0]].text, &promptnum, &pagenum);
+					if (callbynamedtag && line->stringargs[0] && line->stringargs[0][0])
+						F_GetPromptPageByNamedTag(line->stringargs[0], &promptnum, &pagenum);
 					F_StartTextPrompt(promptnum, pagenum, mo, runpostexec ? postexectag : 0, blockcontrols, freezerealtime);
 				}
 			}
@@ -3715,8 +3592,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 460: // Award rings
 			{
-				INT16 rings = (sides[line->sidenum[0]].textureoffset>>FRACBITS);
-				INT32 delay = (sides[line->sidenum[0]].rowoffset>>FRACBITS);
+				INT16 rings = line->args[0];
+				INT32 delay = line->args[1];
 				if (mo && mo->player)
 				{
 					if (delay <= 0 || !(leveltime % delay))
@@ -3727,34 +3604,28 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 461: // Spawns an object on the map based on texture offsets
 			{
-				const mobjtype_t type = (mobjtype_t)(sides[line->sidenum[0]].toptexture);
+				const mobjtype_t type = line->stringargs[0] ? get_number(line->stringargs[0]) : MT_NULL;
 				mobj_t *mobj;
 
 				fixed_t x, y, z;
-				x = sides[line->sidenum[0]].textureoffset;
-				y = sides[line->sidenum[0]].rowoffset;
-				z = line->frontsector->floorheight;
 
-				if (line->flags & ML_NOCLIMB) // If noclimb is set, spawn randomly within a range
+				if (line->args[4]) // If args[4] is set, spawn randomly within a range
 				{
-					if (line->sidenum[1] != 0xffff) // Make sure the linedef has a back side
-					{
-						x = P_RandomRange(sides[line->sidenum[0]].textureoffset>>FRACBITS, sides[line->sidenum[1]].textureoffset>>FRACBITS)<<FRACBITS;
-						y = P_RandomRange(sides[line->sidenum[0]].rowoffset>>FRACBITS, sides[line->sidenum[1]].rowoffset>>FRACBITS)<<FRACBITS;
-						z = P_RandomRange(line->frontsector->floorheight>>FRACBITS, line->frontsector->ceilingheight>>FRACBITS)<<FRACBITS;
-					}
-					else
-					{
-						CONS_Alert(CONS_WARNING,"Linedef Type %d - Spawn Object: Linedef is set for random range but has no back side.\n", line->special);
-						break;
-					}
+					x = P_RandomRange(line->args[0], line->args[5])<<FRACBITS;
+					y = P_RandomRange(line->args[1], line->args[6])<<FRACBITS;
+					z = P_RandomRange(line->args[2], line->args[7])<<FRACBITS;
+				}
+				else
+				{
+					x = line->args[0] << FRACBITS;
+					y = line->args[1] << FRACBITS;
+					z = line->args[2] << FRACBITS;
 				}
 
 				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);
+					mobj->angle = FixedAngle(line->args[3] << FRACBITS);
 					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
@@ -3782,10 +3653,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 463: // Dye object
 			{
-				INT32 color = sides[line->sidenum[0]].toptexture;
-
 				if (mo)
 				{
+					INT32 color = line->stringargs[0] ? get_number(line->stringargs[0]) : SKINCOLOR_NONE;
+
 					if (color < 0 || color >= numskincolors)
 						return;
 
@@ -3798,31 +3669,28 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 464: // Trigger Egg Capsule
 			{
-				thinker_t *th;
+				INT32 mtnum;
 				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)
+				TAG_ITER_THINGS(line->args[0], mtnum)
 				{
-					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-						continue;
-
-					mo2 = (mobj_t *)th;
+					mo2 = mapthings[mtnum].mobj;
 
-					if (mo2->type != MT_EGGTRAP)
+					if (!mo2)
 						continue;
 
-					if (!mo2->spawnpoint)
+					if (mo2->type != MT_EGGTRAP)
 						continue;
 
-					if (mo2->spawnpoint->angle != tag)
+					if (mo2->thinker.function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 						continue;
 
 					P_KillMobj(mo2, NULL, mo, 0);
 				}
 
-				if (!(line->flags & ML_NOCLIMB))
+				if (!(line->args[1]))
 				{
 					INT32 i;
 
@@ -3856,7 +3724,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 466: // Set level failure state
 			{
-				if (line->flags & ML_NOCLIMB)
+				if (line->args[1])
 				{
 					stagefailed = false;
 					CONS_Debug(DBG_GAMELOGIC, "Stage can be completed successfully!\n");
@@ -3869,28 +3737,106 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
+		case 467: // Set light level
+			TAG_ITER_SECTORS(line->args[0], secnum)
+			{
+				if (sectors[secnum].lightingdata)
+				{
+					// Stop any lighting effects going on in the sector
+					P_RemoveThinker(&((thinkerdata_t *)sectors[secnum].lightingdata)->thinker);
+					sectors[secnum].lightingdata = NULL;
+				}
+
+				if (line->args[2] == TML_FLOOR)
+				{
+					if (line->args[3])
+						sectors[secnum].floorlightlevel += line->args[1];
+					else
+						sectors[secnum].floorlightlevel = line->args[1];
+				}
+				else if (line->args[2] == TML_CEILING)
+				{
+					if (line->args[3])
+						sectors[secnum].ceilinglightlevel += line->args[1];
+					else
+						sectors[secnum].ceilinglightlevel = line->args[1];
+				}
+				else
+				{
+					if (line->args[3])
+						sectors[secnum].lightlevel += line->args[1];
+					else
+						sectors[secnum].lightlevel = line->args[1];
+					sectors[secnum].lightlevel = max(0, min(255, sectors[secnum].lightlevel));
+				}
+			}
+			break;
+
+		case 468: // Change linedef executor argument
+		{
+			INT32 linenum;
+
+			if (!udmf)
+				break;
+
+			if (line->args[1] < 0 || line->args[1] >= NUMLINEARGS)
+			{
+				CONS_Debug(DBG_GAMELOGIC, "Linedef type 468: Invalid linedef arg %d\n", line->args[1]);
+				break;
+			}
+
+			TAG_ITER_LINES(line->args[0], linenum)
+			{
+				if (line->args[3])
+					lines[linenum].args[line->args[1]] += line->args[2];
+				else
+					lines[linenum].args[line->args[1]] = line->args[2];
+			}
+		}
+		break;
+
+		case 469: // Change sector gravity
+		{
+			fixed_t gravityvalue;
+
+			if (!udmf)
+				break;
+
+			if (!line->stringargs[0])
+				break;
+
+			gravityvalue = FloatToFixed(atof(line->stringargs[0]));
+
+			TAG_ITER_SECTORS(line->args[0], secnum)
+			{
+				if (line->args[1])
+					sectors[secnum].gravity = FixedMul(sectors[secnum].gravity, gravityvalue);
+				else
+					sectors[secnum].gravity = gravityvalue;
+
+				if (line->args[2] == TMF_ADD)
+					sectors[secnum].flags |= MSF_GRAVITYFLIP;
+				else if (line->args[2] == TMF_REMOVE)
+					sectors[secnum].flags &= ~MSF_GRAVITYFLIP;
+			}
+		}
+		break;
+
 		case 480: // Polyobj_DoorSlide
 		case 481: // Polyobj_DoorSwing
 			PolyDoor(line);
 			break;
 		case 482: // Polyobj_Move
-		case 483: // Polyobj_OR_Move
 			PolyMove(line);
 			break;
 		case 484: // Polyobj_RotateRight
-		case 485: // Polyobj_OR_RotateRight
-		case 486: // Polyobj_RotateLeft
-		case 487: // Polyobj_OR_RotateLeft
 			PolyRotate(line);
 			break;
 		case 488: // Polyobj_Waypoint
 			PolyWaypoint(line);
 			break;
 		case 489:
-			PolyInvisible(line);
-			break;
-		case 490:
-			PolyVisible(line);
+			PolySetVisibilityTangibility(line);
 			break;
 		case 491:
 			PolyTranslucency(line);
@@ -3981,7 +3927,7 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 {
 	thinker_t *think;
 	mobj_t *mo;
-	INT32 specialnum = (flag == MT_REDFLAG) ? 3 : 4;
+	sectorspecialflags_t specialflag = (flag == MT_REDFLAG) ? SSF_REDTEAMBASE : SSF_BLUETEAMBASE;
 
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
@@ -3993,7 +3939,7 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 		if (mo->type != flag)
 			continue;
 
-		if (GETSECSPECIAL(mo->subsector->sector->special, 4) == specialnum)
+		if (mo->subsector->sector->specialflags & specialflag)
 			return true;
 		else if (mo->subsector->sector->ffloors) // Check the 3D floors
 		{
@@ -4004,7 +3950,7 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 				if (!(rover->flags & FF_EXISTS))
 					continue;
 
-				if (GETSECSPECIAL(rover->master->frontsector->special, 4) != specialnum)
+				if (!(rover->master->frontsector->specialflags & specialflag))
 					continue;
 
 				if (!(mo->z <= P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector)
@@ -4018,1050 +3964,1171 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 	return false;
 }
 
-//
-// P_PlayerTouchingSectorSpecial
-//
-// Replaces the old player->specialsector.
-// This allows a player to touch more than
-// one sector at a time, if necessary.
-//
-// Returns a pointer to the first sector of
-// the particular type that it finds.
-// Returns NULL if it doesn't find it.
-//
-sector_t *P_PlayerTouchingSectorSpecial(player_t *player, INT32 section, INT32 number)
+static boolean P_IsMobjTouchingPlane(mobj_t *mo, sector_t *sec, fixed_t floorz, fixed_t ceilingz)
 {
-	msecnode_t *node;
-	ffloor_t *rover;
+	boolean floorallowed = ((sec->flags & MSF_FLIPSPECIAL_FLOOR) && ((sec->flags & MSF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == floorz));
+	boolean ceilingallowed = ((sec->flags & MSF_FLIPSPECIAL_CEILING) && ((sec->flags &  MSF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == ceilingz));
+	return (floorallowed || ceilingallowed);
+}
 
-	if (!player->mo)
-		return NULL;
+boolean P_IsMobjTouchingSectorPlane(mobj_t *mo, sector_t *sec)
+{
+	return P_IsMobjTouchingPlane(mo, sec, P_GetSpecialBottomZ(mo, sec, sec), P_GetSpecialTopZ(mo, sec, sec));
+}
 
-	// Check default case first
-	if (GETSECSPECIAL(player->mo->subsector->sector->special, section) == number)
-		return player->mo->subsector->sector;
+boolean P_IsMobjTouching3DFloor(mobj_t *mo, ffloor_t *ffloor, sector_t *sec)
+{
+	fixed_t topheight = P_GetSpecialTopZ(mo, sectors + ffloor->secnum, sec);
+	fixed_t bottomheight = P_GetSpecialBottomZ(mo, sectors + ffloor->secnum, sec);
 
-	// Hmm.. maybe there's a FOF that has it...
-	for (rover = player->mo->subsector->sector->ffloors; rover; rover = rover->next)
+	if (((ffloor->flags & FF_BLOCKPLAYER) && mo->player)
+		|| ((ffloor->flags & FF_BLOCKOTHERS) && !mo->player))
+	{
+		// Solid 3D floor: Mobj must touch the top or bottom
+		return P_IsMobjTouchingPlane(mo, ffloor->master->frontsector, topheight, bottomheight);
+	}
+	else
 	{
-		fixed_t topheight, bottomheight;
+		// Water or intangible 3D floor: Mobj must be inside
+		return mo->z <= topheight && (mo->z + mo->height) >= bottomheight;
+	}
+}
 
-		if (GETSECSPECIAL(rover->master->frontsector->special, section) != number)
-			continue;
+boolean P_IsMobjTouchingPolyobj(mobj_t *mo, polyobj_t *po, sector_t *polysec)
+{
+	if (!(po->flags & POF_TESTHEIGHT)) // Don't do height checking
+		return true;
 
-		if (!(rover->flags & FF_EXISTS))
-			continue;
+	if (po->flags & POF_SOLID)
+	{
+		// Solid polyobject: Player must touch the top or bottom
+		return P_IsMobjTouchingPlane(mo, polysec, polysec->ceilingheight, polysec->floorheight);
+	}
+	else
+	{
+		// Water or intangible polyobject: Player must be inside
+		return mo->z <= polysec->ceilingheight && (mo->z + mo->height) >= polysec->floorheight;
+	}
+}
 
-		topheight = P_GetSpecialTopZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
-		bottomheight = P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
+static sector_t *P_MobjTouching3DFloorSpecial(mobj_t *mo, sector_t *sector, INT32 section, INT32 number)
+{
+	ffloor_t *rover;
 
-		// Check the 3D floor's type...
-		if (rover->flags & FF_BLOCKPLAYER)
-		{
-			boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == topheight));
-			boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == bottomheight));
-			// Thing must be on top of the floor to be affected...
-			if (!(floorallowed || ceilingallowed))
-				continue;
-		}
-		else
-		{
-			// Water and DEATH FOG!!! heh
-			if (player->mo->z > topheight || (player->mo->z + player->mo->height) < bottomheight)
-				continue;
-		}
+	for (rover = sector->ffloors; rover; rover = rover->next)
+	{
+		if (GETSECSPECIAL(rover->master->frontsector->special, section) != number)
+			continue;
+
+		if (!(rover->flags & FF_EXISTS))
+			continue;
 
-		// This FOF has the special we're looking for!
-		return rover->master->frontsector;
+		if (!P_IsMobjTouching3DFloor(mo, rover, sector))
+			continue;
+
+		// This FOF has the special we're looking for, but are we allowed to touch it?
+		if (sector == mo->subsector->sector
+			|| (rover->master->frontsector->flags & MSF_TRIGGERSPECIAL_TOUCH))
+			return rover->master->frontsector;
 	}
 
-	for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+	return NULL;
+}
+
+static sector_t *P_MobjTouching3DFloorSpecialFlag(mobj_t *mo, sector_t *sector, sectorspecialflags_t flag)
+{
+	ffloor_t *rover;
+
+	for (rover = sector->ffloors; rover; rover = rover->next)
+	{
+		if (!(rover->master->frontsector->specialflags & flag))
+			continue;
+
+		if (!(rover->flags & FF_EXISTS))
+			continue;
+
+		if (!P_IsMobjTouching3DFloor(mo, rover, sector))
+			continue;
+
+		// This FOF has the special we're looking for, but are we allowed to touch it?
+		if (sector == mo->subsector->sector
+			|| (rover->master->frontsector->flags & MSF_TRIGGERSPECIAL_TOUCH))
+			return rover->master->frontsector;
+	}
+
+	return NULL;
+}
+
+static sector_t *P_MobjTouchingPolyobjSpecial(mobj_t *mo, INT32 section, INT32 number)
+{
+	polyobj_t *po;
+	sector_t *polysec;
+	boolean touching = false;
+	boolean inside = false;
+
+	for (po = mo->subsector->polyList; po; po = (polyobj_t *)(po->link.next))
+	{
+		if (po->flags & POF_NOSPECIALS)
+			continue;
+
+		polysec = po->lines[0]->backsector;
+
+		if (GETSECSPECIAL(polysec->special, section) != number)
+			continue;
+
+		touching = (polysec->flags & MSF_TRIGGERSPECIAL_TOUCH) && P_MobjTouchingPolyobj(po, mo);
+		inside = P_MobjInsidePolyobj(po, mo);
+
+		if (!(inside || touching))
+			continue;
+
+		if (!P_IsMobjTouchingPolyobj(mo, po, polysec))
+			continue;
+
+		return polysec;
+	}
+
+	return NULL;
+}
+
+static sector_t *P_MobjTouchingPolyobjSpecialFlag(mobj_t *mo, sectorspecialflags_t flag)
+{
+	polyobj_t *po;
+	sector_t *polysec;
+	boolean touching = false;
+	boolean inside = false;
+
+	for (po = mo->subsector->polyList; po; po = (polyobj_t *)(po->link.next))
+	{
+		if (po->flags & POF_NOSPECIALS)
+			continue;
+
+		polysec = po->lines[0]->backsector;
+
+		if (!(polysec->specialflags & flag))
+			continue;
+
+		touching = (polysec->flags & MSF_TRIGGERSPECIAL_TOUCH) && P_MobjTouchingPolyobj(po, mo);
+		inside = P_MobjInsidePolyobj(po, mo);
+
+		if (!(inside || touching))
+			continue;
+
+		if (!P_IsMobjTouchingPolyobj(mo, po, polysec))
+			continue;
+
+		return polysec;
+	}
+
+	return NULL;
+}
+
+sector_t *P_MobjTouchingSectorSpecial(mobj_t *mo, INT32 section, INT32 number)
+{
+	msecnode_t *node;
+	sector_t *result;
+
+	result = P_MobjTouching3DFloorSpecial(mo, mo->subsector->sector, section, number);
+	if (result)
+		return result;
+
+	result = P_MobjTouchingPolyobjSpecial(mo, section, number);
+	if (result)
+		return result;
+
+	if (GETSECSPECIAL(mo->subsector->sector->special, section) == number)
+		return mo->subsector->sector;
+
+	for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
 	{
+		if (node->m_sector == mo->subsector->sector) // Don't duplicate
+			continue;
+
+		result = P_MobjTouching3DFloorSpecial(mo, node->m_sector, section, number);
+		if (result)
+			return result;
+
+		if (!(node->m_sector->flags & MSF_TRIGGERSPECIAL_TOUCH))
+			continue;
+
 		if (GETSECSPECIAL(node->m_sector->special, section) == number)
-		{
-			// This sector has the special we're looking for, but
-			// are we allowed to touch it?
-			if (node->m_sector == player->mo->subsector->sector
-				|| (node->m_sector->flags & SF_TRIGGERSPECIAL_TOUCH))
-				return node->m_sector;
-		}
+			return node->m_sector;
+	}
 
-		// Hmm.. maybe there's a FOF that has it...
-		for (rover = node->m_sector->ffloors; rover; rover = rover->next)
-		{
-			fixed_t topheight, bottomheight;
+	return NULL;
+}
 
-			if (GETSECSPECIAL(rover->master->frontsector->special, section) != number)
-				continue;
+sector_t *P_MobjTouchingSectorSpecialFlag(mobj_t *mo, sectorspecialflags_t flag)
+{
+	msecnode_t *node;
+	sector_t *result;
 
-			if (!(rover->flags & FF_EXISTS))
-				continue;
+	result = P_MobjTouching3DFloorSpecialFlag(mo, mo->subsector->sector, flag);
+	if (result)
+		return result;
 
-			topheight = P_GetSpecialTopZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
-			bottomheight = P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, player->mo->subsector->sector);
+	result = P_MobjTouchingPolyobjSpecialFlag(mo, flag);
+	if (result)
+		return result;
 
-			// Check the 3D floor's type...
-			if (rover->flags & FF_BLOCKPLAYER)
-			{
-				boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == topheight));
-				boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == bottomheight));
-				// Thing must be on top of the floor to be affected...
-				if (!(floorallowed || ceilingallowed))
-					continue;
-			}
-			else
-			{
-				// Water and DEATH FOG!!! heh
-				if (player->mo->z > topheight || (player->mo->z + player->mo->height) < bottomheight)
-					continue;
-			}
+	if (mo->subsector->sector->specialflags & flag)
+		return mo->subsector->sector;
 
-			// This FOF has the special we're looking for, but are we allowed to touch it?
-			if (node->m_sector == player->mo->subsector->sector
-				|| (rover->master->frontsector->flags & SF_TRIGGERSPECIAL_TOUCH))
-				return rover->master->frontsector;
-		}
+	for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+	{
+		if (node->m_sector == mo->subsector->sector) // Don't duplicate
+			continue;
+
+		result = P_MobjTouching3DFloorSpecialFlag(mo, node->m_sector, flag);
+		if (result)
+			return result;
+
+		if (!(node->m_sector->flags & MSF_TRIGGERSPECIAL_TOUCH))
+			continue;
+
+		if (node->m_sector->specialflags & flag)
+			return node->m_sector;
 	}
 
 	return NULL;
 }
 
 //
-// P_ThingIsOnThe3DFloor
+// P_PlayerTouchingSectorSpecial
 //
-// This checks whether the mobj is on/in the FOF we want it to be at
-// Needed for the "All players" trigger sector specials only
+// Replaces the old player->specialsector.
+// This allows a player to touch more than
+// one sector at a time, if necessary.
+//
+// Returns a pointer to the first sector of
+// the particular type that it finds.
+// Returns NULL if it doesn't find it.
 //
-static boolean P_ThingIsOnThe3DFloor(mobj_t *mo, sector_t *sector, sector_t *targetsec)
+sector_t *P_PlayerTouchingSectorSpecial(player_t *player, INT32 section, INT32 number)
+{
+	if (!player->mo)
+		return NULL;
+
+	return P_MobjTouchingSectorSpecial(player->mo, section, number);
+}
+
+sector_t *P_PlayerTouchingSectorSpecialFlag(player_t *player, sectorspecialflags_t flag)
+{
+	if (!player->mo)
+		return NULL;
+
+	return P_MobjTouchingSectorSpecialFlag(player->mo, flag);
+}
+
+static sector_t *P_CheckPlayer3DFloorTrigger(player_t *player, sector_t *sector, line_t *sourceline)
 {
 	ffloor_t *rover;
-	fixed_t top, bottom;
 
-	if (!mo->player) // should NEVER happen
+	for (rover = sector->ffloors; rover; rover = rover->next)
+	{
+		if (!rover->master->frontsector->triggertag)
+			continue;
+
+		if (rover->master->frontsector->triggerer == TO_MOBJ)
+			continue;
+
+		if (!(rover->flags & FF_EXISTS))
+			continue;
+
+		if (!Tag_Find(&sourceline->tags, rover->master->frontsector->triggertag))
+			continue;
+
+		if (!P_IsMobjTouching3DFloor(player->mo, rover, sector))
+			continue;
+
+		// This FOF has the special we're looking for, but are we allowed to touch it?
+		if (sector == player->mo->subsector->sector
+			|| (rover->master->frontsector->flags & MSF_TRIGGERSPECIAL_TOUCH))
+			return rover->master->frontsector;
+	}
+
+	return NULL;
+}
+
+static sector_t *P_CheckPlayerPolyobjTrigger(player_t *player, line_t *sourceline)
+{
+	polyobj_t *po;
+	sector_t *polysec;
+	boolean touching = false;
+	boolean inside = false;
+
+	for (po = player->mo->subsector->polyList; po; po = (polyobj_t *)(po->link.next))
+	{
+		if (po->flags & POF_NOSPECIALS)
+			continue;
+
+		polysec = po->lines[0]->backsector;
+
+		if (!polysec->triggertag)
+			continue;
+
+		if (polysec->triggerer == TO_MOBJ)
+			continue;
+
+		if (!Tag_Find(&sourceline->tags, polysec->triggertag))
+			continue;
+
+		touching = (polysec->flags & MSF_TRIGGERSPECIAL_TOUCH) && P_MobjTouchingPolyobj(po, player->mo);
+		inside = P_MobjInsidePolyobj(po, player->mo);
+
+		if (!(inside || touching))
+			continue;
+
+		if (!P_IsMobjTouchingPolyobj(player->mo, po, polysec))
+			continue;
+
+		return polysec;
+	}
+
+	return NULL;
+}
+
+static boolean P_CheckPlayerSectorTrigger(player_t *player, sector_t *sector, line_t *sourceline)
+{
+	if (!sector->triggertag)
+		return false;
+
+	if (sector->triggerer == TO_MOBJ)
 		return false;
 
-	if (!targetsec->ffloors) // also should NEVER happen
+	if (!Tag_Find(&sourceline->tags, sector->triggertag))
 		return false;
 
-	for (rover = targetsec->ffloors; rover; rover = rover->next)
+	if (!(sector->flags & MSF_TRIGGERLINE_PLANE))
+		return true; // Don't require plane touch
+
+	return P_IsMobjTouchingSectorPlane(player->mo, sector);
+
+}
+
+sector_t *P_FindPlayerTrigger(player_t *player, line_t *sourceline)
+{
+	sector_t *originalsector;
+	sector_t *loopsector;
+	msecnode_t *node;
+	sector_t *caller;
+
+	if (!player->mo)
+		return NULL;
+
+	originalsector = player->mo->subsector->sector;
+
+	caller = P_CheckPlayer3DFloorTrigger(player, originalsector, sourceline); // Handle FOFs first.
+
+	if (caller)
+		return caller;
+
+	// Allow sector specials to be applied to polyobjects!
+	caller = P_CheckPlayerPolyobjTrigger(player, sourceline);
+
+	if (caller)
+		return caller;
+
+	if (P_CheckPlayerSectorTrigger(player, originalsector, sourceline))
+		return originalsector;
+
+	// Iterate through touching_sectorlist for SF_TRIGGERSPECIAL_TOUCH
+	for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
 	{
-		if (rover->master->frontsector != sector)
+		loopsector = node->m_sector;
+
+		if (loopsector == originalsector) // Don't duplicate
 			continue;
 
-		// we're assuming the FOF existed when the first player touched it
-		//if (!(rover->flags & FF_EXISTS))
-		//	return false;
+		// Check 3D floors...
+		caller = P_CheckPlayer3DFloorTrigger(player, loopsector, sourceline); // Handle FOFs first.
 
-		top = P_GetSpecialTopZ(mo, sector, targetsec);
-		bottom = P_GetSpecialBottomZ(mo, sector, targetsec);
+		if (caller)
+			return caller;
 
-		// Check the 3D floor's type...
-		if (rover->flags & FF_BLOCKPLAYER)
-		{
-			boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == top));
-			boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == bottom));
-			// Thing must be on top of the floor to be affected...
-			if (!(floorallowed || ceilingallowed))
-				continue;
-		}
-		else
-		{
-			// Water and intangible FOFs
-			if (mo->z > top || (mo->z + mo->height) < bottom)
-				return false;
-		}
+		if (!(loopsector->flags & MSF_TRIGGERSPECIAL_TOUCH))
+			continue;
 
-		return true;
+		if (P_CheckPlayerSectorTrigger(player, loopsector, sourceline))
+			return loopsector;
 	}
 
 	return false;
 }
 
-//
-// P_MobjReadyToTrigger
-//
-// Is player standing on the sector's "ground"?
-//
-static boolean P_MobjReadyToTrigger(mobj_t *mo, sector_t *sec)
+boolean P_IsPlayerValid(size_t playernum)
 {
-	boolean floorallowed = ((sec->flags & SF_FLIPSPECIAL_FLOOR) && ((sec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == P_GetSpecialBottomZ(mo, sec, sec)));
-	boolean ceilingallowed = ((sec->flags & SF_FLIPSPECIAL_CEILING) && ((sec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == P_GetSpecialTopZ(mo, sec, sec)));
-	// Thing must be on top of the floor to be affected...
-	return (floorallowed || ceilingallowed);
+	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;
 }
 
-/** Applies a sector special to a player.
-  *
-  * \param player       Player in the sector.
-  * \param sector       Sector with the special.
-  * \param roversector  If !NULL, sector is actually an FOF; otherwise, sector
-  *                     is being physically contacted by the player.
-  * \todo Split up into multiple functions.
-  * \sa P_PlayerInSpecialSector, P_PlayerOnSpecial3DFloor
-  */
-void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *roversector)
+boolean P_CanPlayerTrigger(size_t playernum)
 {
-	INT32 i = 0;
-	INT32 section1, section2, section3, section4;
-	INT32 special;
-	mtag_t sectag = Tag_FGet(&sector->tags);
+	return P_IsPlayerValid(playernum) && !players[playernum].bot;
+}
 
-	section1 = GETSECSPECIAL(sector->special, 1);
-	section2 = GETSECSPECIAL(sector->special, 2);
-	section3 = GETSECSPECIAL(sector->special, 3);
-	section4 = GETSECSPECIAL(sector->special, 4);
+/// \todo check continues for proper splitscreen support?
+static boolean P_DoAllPlayersTrigger(mtag_t triggertag)
+{
+	INT32 i;
+	line_t dummyline;
+	dummyline.tags.count = 1;
+	dummyline.tags.tags = &triggertag;
 
-	// Ignore spectators
-	if (player->spectator)
-		return;
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!P_CanPlayerTrigger(i))
+			continue;
+		if (!P_FindPlayerTrigger(&players[i], &dummyline))
+			return false;
+	}
 
-	// Ignore dead players.
-	// If this strange phenomenon could be potentially used in levels,
-	// TODO: modify this to accommodate for it.
-	if (player->playerstate != PST_LIVE)
+	return true;
+}
+
+static void P_ProcessEggCapsule(player_t *player, sector_t *sector)
+{
+	thinker_t *th;
+	mobj_t *mo2;
+	INT32 i;
+
+	if (player->bot || sector->ceilingdata || sector->floordata)
 		return;
 
-	// Conveyor stuff
-	if (section3 == 2 || section3 == 4)
-		player->onconveyor = section3;
+	// 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;
+		P_KillMobj(mo2, NULL, player->mo, 0);
+	}
 
-	special = section1;
+	// clear the special so you can't push the button twice.
+	sector->special = 0;
 
-	// Process Section 1
-	switch (special)
-	{
-		case 1: // Damage (Generic)
-			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
-				P_DamageMobj(player->mo, NULL, NULL, 1, 0);
-			break;
-		case 2: // Damage (Water)
-			if ((roversector || P_MobjReadyToTrigger(player->mo, sector)) && (player->powers[pw_underwater] || player->powers[pw_carry] == CR_NIGHTSMODE))
-				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_WATER);
-			break;
-		case 3: // Damage (Fire)
-			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
-				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_FIRE);
-			break;
-		case 4: // Damage (Electrical)
-			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
-				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_ELECTRIC);
-			break;
-		case 5: // Spikes
-			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)
-			if (roversector || P_MobjReadyToTrigger(player->mo, sector))
-			{
-				if (player->quittime)
-					G_MovePlayerToSpawnOrStarpost(player - players);
-				else
-					P_DamageMobj(player->mo, NULL, NULL, 1, DMG_DEATHPIT);
-			}
-			break;
-		case 8: // Instant Kill
-			if (player->quittime)
-				G_MovePlayerToSpawnOrStarpost(player - players);
-			else
-				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
-			break;
-		case 9: // Ring Drainer (Floor Touch)
-		case 10: // Ring Drainer (No Floor Touch)
-			if (leveltime % (TICRATE/2) == 0 && player->rings > 0)
-			{
-				player->rings--;
-				S_StartSound(player->mo, sfx_antiri);
-			}
-			break;
-		case 11: // Special Stage Damage
-			if (player->exiting || player->bot) // Don't do anything for bots or players who have just finished
-				break;
+	// Move the button down
+	EV_DoElevator(LE_CAPSULE0, NULL, elevateDown);
 
-			if (!(player->powers[pw_shield] || player->spheres > 0)) // Don't do anything if no shield or spheres anyway
-				break;
+	// Open the top FOF
+	EV_DoFloor(LE_CAPSULE1, NULL, raiseFloorToNearestFast);
+	// Open the bottom FOF
+	EV_DoCeiling(LE_CAPSULE2, NULL, lowerToLowestFast);
 
-			P_SpecialStageDamage(player, NULL, NULL);
-			break;
-		case 12: // Space Countdown
-			if (!(player->powers[pw_shield] & SH_PROTECTWATER) && !player->powers[pw_spacetime])
-				player->powers[pw_spacetime] = spacetimetics + 1;
-			break;
-		case 13: // Ramp Sector (Increase step-up/down)
-		case 14: // Non-Ramp Sector (Don't step-down)
-		case 15: // Bouncy Sector (FOF Control Only)
-			break;
+	// Mark all players with the time to exit thingy!
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+		P_DoPlayerExit(&players[i]);
 	}
+}
+
+static void P_ProcessSpeedPad(player_t *player, sector_t *sector, sector_t *roversector, mtag_t sectag)
+{
+	INT32 lineindex = -1;
+	angle_t lineangle;
+	fixed_t linespeed;
+	fixed_t sfxnum;
+	size_t i;
 
-	special = section2;
+	if (player->powers[pw_flashing] != 0 && player->powers[pw_flashing] < TICRATE/2)
+		return;
 
-	// Process Section 2
-	switch (special)
+	// Try for lines facing the sector itself, with tag 0.
+	for (i = 0; i < sector->linecount; i++)
 	{
-		case 1: // Trigger Linedef Exec (Pushable Objects)
-			break;
-		case 2: // Linedef executor requires all players present+doesn't require touching floor
-		case 3: // Linedef executor requires all players present
-			/// \todo check continues for proper splitscreen support?
-			for (i = 0; i < MAXPLAYERS; i++)
-			{
-				if (!playeringame[i])
-					continue;
-				if (!players[i].mo)
-					continue;
-				if (players[i].spectator)
-					continue;
-				if (players[i].bot)
-					continue;
-				if (G_CoopGametype() && players[i].lives <= 0)
-					continue;
-				if (roversector)
-				{
-					if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
-					{
-						msecnode_t *node;
-						for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
-						{
-							if (P_ThingIsOnThe3DFloor(players[i].mo, sector, node->m_sector))
-								break;
-						}
-						if (!node)
-							goto DoneSection2;
-					}
-					else if (players[i].mo->subsector && !P_ThingIsOnThe3DFloor(players[i].mo, sector, players[i].mo->subsector->sector)) // this function handles basically everything for us lmao
-						goto DoneSection2;
-				}
-				else
-				{
-					if (players[i].mo->subsector->sector == sector)
-						;
-					else if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
-					{
-						msecnode_t *node;
-						for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
-						{
-							if (node->m_sector == sector)
-								break;
-						}
-						if (!node)
-							goto DoneSection2;
-					}
-					else
-						goto DoneSection2;
+		line_t *li = sector->lines[i];
 
-					if (special == 3 && !P_MobjReadyToTrigger(players[i].mo, sector))
-						goto DoneSection2;
-				}
-			}
-			/* FALLTHRU */
-		case 4: // Linedef executor that doesn't require touching floor
-		case 5: // Linedef executor
-		case 6: // Linedef executor (7 Emeralds)
-		case 7: // Linedef executor (NiGHTS Mare)
-			if (!player->bot)
-				P_LinedefExecute(sectag, player->mo, sector);
-			break;
-		case 8: // Tells pushable things to check FOFs
-			break;
-		case 9: // Egg trap capsule
-		{
-			thinker_t *th;
-			mobj_t *mo2;
-			line_t junk;
+		if (li->frontsector != sector)
+			continue;
 
-			if (player->bot || sector->ceilingdata || sector->floordata)
-				return;
+		if (li->special != 4)
+			continue;
 
-			// 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;
-				P_KillMobj(mo2, NULL, player->mo, 0);
-			}
+		if (!Tag_Find(&li->tags, 0))
+			continue;
 
-			// clear the special so you can't push the button twice.
-			sector->special = 0;
+		lineindex = li - lines;
+		break;
+	}
 
-			// Initialize my junk
-			junk.tags.tags = NULL;
-			junk.tags.count = 0;
+	// Nothing found? Look via tag.
+	if (lineindex == -1)
+		lineindex = Tag_FindLineSpecial(4, sectag);
 
-			// Move the button down
-			Tag_FSet(&junk.tags, LE_CAPSULE0);
-			EV_DoElevator(&junk, elevateDown, false);
+	if (lineindex == -1)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad missing line special #4.\n");
+		return;
+	}
 
-			// Open the top FOF
-			Tag_FSet(&junk.tags, LE_CAPSULE1);
-			EV_DoFloor(&junk, raiseFloorToNearestFast);
-			// Open the bottom FOF
-			Tag_FSet(&junk.tags, LE_CAPSULE2);
-			EV_DoCeiling(&junk, lowerToLowestFast);
+	lineangle = R_PointToAngle2(lines[lineindex].v1->x, lines[lineindex].v1->y, lines[lineindex].v2->x, lines[lineindex].v2->y);
+	linespeed = lines[lineindex].args[0] << FRACBITS;
 
-			// 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 10: // Special Stage Time/Rings
-		case 11: // Custom Gravity
-			break;
-		case 12: // Lua sector special
-			break;
+	if (linespeed == 0)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sectag);
+		return;
 	}
-DoneSection2:
 
-	special = section3;
+	player->mo->angle = player->drawangle = lineangle;
 
-	// Process Section 3
-	switch (special)
-	{
-		case 1: // Unused
-		case 2: // Wind/Current
-		case 3: // Unused
-		case 4: // Conveyor Belt
-			break;
+	if (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG)
+		P_SetPlayerAngle(player, player->mo->angle);
 
-		case 5: // Speed pad
-			if (player->powers[pw_flashing] != 0 && player->powers[pw_flashing] < TICRATE/2)
-				break;
+	if (!(lines[lineindex].args[1] & TMSP_NOTELEPORT))
+	{
+		P_UnsetThingPosition(player->mo);
+		if (roversector) // make FOF speed pads work
+		{
+			player->mo->x = roversector->soundorg.x;
+			player->mo->y = roversector->soundorg.y;
+		}
+		else
+		{
+			player->mo->x = sector->soundorg.x;
+			player->mo->y = sector->soundorg.y;
+		}
+		P_SetThingPosition(player->mo);
+	}
 
-			i = Tag_FindLineSpecial(4, sectag);
+	P_InstaThrust(player->mo, player->mo->angle, linespeed);
 
-			if (i != -1)
-			{
-				angle_t lineangle;
-				fixed_t linespeed;
-				fixed_t sfxnum;
+	if (lines[lineindex].args[1] & TMSP_FORCESPIN) // Roll!
+	{
+		if (!(player->pflags & PF_SPINNING))
+			player->pflags |= PF_SPINNING;
 
-				lineangle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y);
-				linespeed = sides[lines[i].sidenum[0]].textureoffset;
+		P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+	}
 
-				if (linespeed == 0)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sectag);
-					break;
-				}
+	player->powers[pw_flashing] = TICRATE/3;
 
-				player->mo->angle = player->drawangle = lineangle;
+	sfxnum = lines[lineindex].stringargs[0] ? get_number(lines[lineindex].stringargs[0]) : sfx_spdpad;
 
-				if (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG)
-					P_SetPlayerAngle(player, player->mo->angle);
+	if (!sfxnum)
+		sfxnum = sfx_spdpad;
 
-				if (!(lines[i].flags & ML_EFFECT4))
-				{
-					P_UnsetThingPosition(player->mo);
-					if (roversector) // make FOF speed pads work
-					{
-						player->mo->x = roversector->soundorg.x;
-						player->mo->y = roversector->soundorg.y;
-					}
-					else
-					{
-						player->mo->x = sector->soundorg.x;
-						player->mo->y = sector->soundorg.y;
-					}
-					P_SetThingPosition(player->mo);
-				}
+	S_StartSound(player->mo, sfxnum);
+}
 
-				P_InstaThrust(player->mo, player->mo->angle, linespeed);
+static void P_ProcessSpecialStagePit(player_t* player)
+{
+	if (!(gametyperules & GTR_ALLOWEXIT))
+		return;
 
-				if (lines[i].flags & ML_EFFECT5) // Roll!
-				{
-					if (!(player->pflags & PF_SPINNING))
-						player->pflags |= PF_SPINNING;
+	if (player->bot)
+		return;
 
-					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-				}
+	if (!G_IsSpecialStage(gamemap))
+		return;
 
-				player->powers[pw_flashing] = TICRATE/3;
+	if (maptol & TOL_NIGHTS)
+		return;
 
-				sfxnum = sides[lines[i].sidenum[0]].toptexture;
+	if (player->nightstime <= 6)
+		return;
 
-				if (!sfxnum)
-					sfxnum = sfx_spdpad;
+	player->nightstime = 6; // Just let P_Ticker take care of the rest.
+}
 
-				S_StartSound(player->mo, sfxnum);
-			}
-			break;
+static void P_ProcessExitSector(player_t *player, mtag_t sectag)
+{
+	INT32 lineindex;
 
-		case 6: // Unused
-		case 7: // Unused
-		case 8: // Unused
-		case 9: // Unused
-		case 10: // Unused
-		case 11: // Unused
-		case 12: // Unused
-		case 13: // Unused
-		case 14: // Unused
-		case 15: // Unused
-			break;
-	}
+	if (!(gametyperules & GTR_ALLOWEXIT))
+		return;
 
-	special = section4;
+	if (player->bot)
+		return;
 
-	// Process Section 4
-	switch (special)
-	{
-		case 1: // Starpost Activator
-		{
-			mobj_t *post = P_GetObjectTypeInSectorNum(MT_STARPOST, sector - sectors);
+	// Exit (for FOF exits; others are handled in P_PlayerThink in p_user.c)
+	P_DoPlayerFinish(player);
 
-			if (!post)
-				break;
+	P_SetupSignExit(player);
 
-			P_TouchStarPost(post, player, false);
-			break;
-		}
+	if (!G_CoopGametype())
+		return;
 
-		case 2: // Special stage GOAL sector / Exit Sector / CTF Flag Return
-			if (player->bot || !(gametyperules & GTR_ALLOWEXIT))
-				break;
-			if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && player->nightstime > 6)
-			{
-				player->nightstime = 6; // Just let P_Ticker take care of the rest.
-				return;
-			}
+	// Custom exit!
+	// important: use sectag on next line instead of player->mo->subsector->tag
+	// this part is different from in P_PlayerThink, this is what was causing
+	// FOF custom exits not to work.
+	lineindex = Tag_FindLineSpecial(2, sectag);
 
-			// Exit (for FOF exits; others are handled in P_PlayerThink in p_user.c)
-			{
-				INT32 lineindex;
+	if (lineindex == -1)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "ERROR: Exit sector missing line special #2.\n");
+		return;
+	}
 
-				P_DoPlayerFinish(player);
+	// Special goodies depending on emeralds collected
+	if ((lines[lineindex].args[1] & TMEF_EMERALDCHECK) && ALL7EMERALDS(emeralds))
+		nextmapoverride = (INT16)(udmf ? lines[lineindex].args[2] : lines[lineindex].frontsector->ceilingheight>>FRACBITS);
+	else
+		nextmapoverride = (INT16)(udmf ? lines[lineindex].args[0] : lines[lineindex].frontsector->floorheight>>FRACBITS);
 
-				P_SetupSignExit(player);
-				// important: use sector->tag on next line instead of player->mo->subsector->tag
-				// this part is different from in P_PlayerThink, this is what was causing
-				// FOF custom exits not to work.
-				lineindex = Tag_FindLineSpecial(2, sectag);
+	if (lines[lineindex].args[1] & TMEF_SKIPTALLY)
+		skipstats = 1;
+}
 
-				if (G_CoopGametype() && lineindex != -1) // Custom exit!
-				{
-					// Special goodies with the block monsters flag depending on emeralds collected
-					if ((lines[lineindex].flags & ML_BLOCKMONSTERS) && ALL7EMERALDS(emeralds))
-						nextmapoverride = (INT16)(lines[lineindex].frontsector->ceilingheight>>FRACBITS);
-					else
-						nextmapoverride = (INT16)(lines[lineindex].frontsector->floorheight>>FRACBITS);
+static void P_ProcessTeamBase(player_t *player, boolean redteam)
+{
+	mobj_t *mo;
 
-					if (lines[lineindex].flags & ML_NOCLIMB)
-						skipstats = 1;
-				}
-			}
-			break;
+	if (!(gametyperules & GTR_TEAMFLAGS))
+		return;
 
-		case 3: // Red Team's Base
-			if ((gametyperules & GTR_TEAMFLAGS) && P_IsObjectOnGround(player->mo))
-			{
-				if (player->ctfteam == 1 && (player->gotflag & GF_BLUEFLAG))
-				{
-					mobj_t *mo;
+	if (!P_IsObjectOnGround(player->mo))
+		return;
 
-					// Make sure the red team still has their own
-					// flag at their base so they can score.
-					if (!P_IsFlagAtBase(MT_REDFLAG))
-						break;
+	if (player->ctfteam != (redteam ? 1 : 2))
+		return;
 
-					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
-					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("\205%s\200\\CAPTURED THE \204BLUE FLAG\200.\\\\\\\\"), player_names[player-players]));
-
-					if (splitscreen || players[consoleplayer].ctfteam == 1)
-						S_StartSound(NULL, sfx_flgcap);
-					else if (players[consoleplayer].ctfteam == 2)
-						S_StartSound(NULL, sfx_lose);
-
-					mo = P_SpawnMobj(player->mo->x,player->mo->y,player->mo->z,MT_BLUEFLAG);
-					player->gotflag &= ~GF_BLUEFLAG;
-					mo->flags &= ~MF_SPECIAL;
-					mo->fuse = TICRATE;
-					mo->spawnpoint = bflagpoint;
-					mo->flags2 |= MF2_JUSTATTACKED;
-					redscore += 1;
-					P_AddPlayerScore(player, 250);
-				}
-			}
-			break;
+	if (!(player->gotflag & (redteam ? GF_BLUEFLAG : GF_REDFLAG)))
+		return;
 
-		case 4: // Blue Team's Base
-			if ((gametyperules & GTR_TEAMFLAGS) && P_IsObjectOnGround(player->mo))
-			{
-				if (player->ctfteam == 2 && (player->gotflag & GF_REDFLAG))
-				{
-					mobj_t *mo;
+	// Make sure the team still has their own
+	// flag at their base so they can score.
+	if (!P_IsFlagAtBase(redteam ? MT_BLUEFLAG : MT_REDFLAG))
+		return;
 
-					// Make sure the blue team still has their own
-					// flag at their base so they can score.
-					if (!P_IsFlagAtBase(MT_BLUEFLAG))
-						break;
+	HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
+	HU_SetCEchoDuration(5);
+	HU_DoCEcho(va(M_GetText("%s%s\200\\CAPTURED THE %s%s FLAG\200.\\\\\\\\"), redteam ? "\205" : "\204", player_names[player-players], redteam ? "\204" : "\205", redteam ? "BLUE" : "RED"));
+
+	if (splitscreen || players[consoleplayer].ctfteam == (redteam ? 1 : 2))
+		S_StartSound(NULL, sfx_flgcap);
+	else if (players[consoleplayer].ctfteam == (redteam ? 2 : 1))
+		S_StartSound(NULL, sfx_lose);
+
+	mo = P_SpawnMobj(player->mo->x,player->mo->y,player->mo->z, redteam ? MT_BLUEFLAG : MT_REDFLAG);
+	player->gotflag &= ~(redteam ? GF_BLUEFLAG : GF_REDFLAG);
+	mo->flags &= ~MF_SPECIAL;
+	mo->fuse = TICRATE;
+	mo->spawnpoint = redteam ? bflagpoint : rflagpoint;
+	mo->flags2 |= MF2_JUSTATTACKED;
+	if (redteam)
+		redscore += 1;
+	else
+		bluescore += 1;
+	P_AddPlayerScore(player, 250);
+}
 
-					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
-					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("\204%s\200\\CAPTURED THE \205RED FLAG\200.\\\\\\\\"), player_names[player-players]));
-
-					if (splitscreen || players[consoleplayer].ctfteam == 2)
-						S_StartSound(NULL, sfx_flgcap);
-					else if (players[consoleplayer].ctfteam == 1)
-						S_StartSound(NULL, sfx_lose);
-
-					mo = P_SpawnMobj(player->mo->x,player->mo->y,player->mo->z,MT_REDFLAG);
-					player->gotflag &= ~GF_REDFLAG;
-					mo->flags &= ~MF_SPECIAL;
-					mo->fuse = TICRATE;
-					mo->spawnpoint = rflagpoint;
-					mo->flags2 |= MF2_JUSTATTACKED;
-					bluescore += 1;
-					P_AddPlayerScore(player, 250);
-				}
-			}
-			break;
+static void P_ProcessZoomTube(player_t *player, mtag_t sectag, boolean end)
+{
+	INT32 sequence;
+	fixed_t speed;
+	INT32 lineindex;
+	mobj_t *waypoint = NULL;
+	angle_t an;
 
-		case 5: // Fan sector
-			player->mo->momz += mobjinfo[MT_FAN].mass/4;
+	if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && player->powers[pw_carry] == CR_ZOOMTUBE)
+		return;
 
-			if (player->mo->momz > mobjinfo[MT_FAN].mass)
-				player->mo->momz = mobjinfo[MT_FAN].mass;
+	if (player->powers[pw_ignorelatch] & (1<<15))
+		return;
 
-			P_ResetPlayer(player);
-			if (player->panim != PA_FALL)
-				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
-			break;
+	// Find line #3 tagged to this sector
+	lineindex = Tag_FindLineSpecial(3, sectag);
 
-		case 6: // Super Sonic transformer
-			if (player->mo->health > 0 && !player->bot && (player->charflags & SF_SUPER) && !player->powers[pw_super] && ALL7EMERALDS(emeralds))
-				P_DoSuperTransformation(player, true);
-			break;
+	if (lineindex == -1)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "ERROR: Zoom tube missing line special #3.\n");
+		return;
+	}
 
-		case 7: // Make player spin
-			if (!(player->pflags & PF_SPINNING))
-			{
-				player->pflags |= PF_SPINNING;
-				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-				S_StartAttackSound(player->mo, sfx_spin);
+	// Grab speed and sequence values
+	speed = abs(lines[lineindex].args[0])<<(FRACBITS-3);
+	if (end)
+		speed *= -1;
+	sequence = abs(lines[lineindex].args[1]);
 
-				if (abs(player->rmomx) < FixedMul(5*FRACUNIT, player->mo->scale)
-				&& abs(player->rmomy) < FixedMul(5*FRACUNIT, player->mo->scale))
-					P_InstaThrust(player->mo, player->mo->angle, FixedMul(10*FRACUNIT, player->mo->scale));
-			}
-			break;
+	if (speed == 0)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
+		return;
+	}
 
-		case 8: // Zoom Tube Start
-			{
-				INT32 sequence;
-				fixed_t speed;
-				INT32 lineindex;
-				mobj_t *waypoint = NULL;
-				angle_t an;
+	waypoint = end ? P_GetLastWaypoint(sequence) : P_GetFirstWaypoint(sequence);
 
-				if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && player->powers[pw_carry] == CR_ZOOMTUBE)
-					break;
+	if (!waypoint)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "ERROR: %s WAYPOINT IN SEQUENCE %d NOT FOUND.\n", end ? "LAST" : "FIRST", sequence);
+		return;
+	}
 
-				if (player->powers[pw_ignorelatch] & (1<<15))
-					break;
+	CONS_Debug(DBG_GAMELOGIC, "Waypoint %d found in sequence %d - speed = %d\n", waypoint->health, sequence, speed);
 
-				// Find line #3 tagged to this sector
-				lineindex = Tag_FindLineSpecial(3, sectag);
+	an = R_PointToAngle2(player->mo->x, player->mo->y, waypoint->x, waypoint->y) - player->mo->angle;
 
-				if (lineindex == -1)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Sector special %d missing line special #3.\n", sector->special);
-					break;
-				}
+	if (an > ANGLE_90 && an < ANGLE_270 && !(lines[lineindex].args[2]))
+		return; // behind back
 
-				// Grab speed and sequence values
-				speed = abs(sides[lines[lineindex].sidenum[0]].textureoffset)/8;
-				sequence = abs(sides[lines[lineindex].sidenum[0]].rowoffset)>>FRACBITS;
+	P_SetTarget(&player->mo->tracer, waypoint);
+	player->powers[pw_carry] = CR_ZOOMTUBE;
+	player->speed = speed;
+	player->pflags |= PF_SPINNING;
+	player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
+	player->climbing = 0;
 
-				if (speed == 0)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
-					break;
-				}
+	if (player->mo->state-states != S_PLAY_ROLL)
+	{
+		P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+		S_StartSound(player->mo, sfx_spin);
+	}
+}
 
-				waypoint = P_GetFirstWaypoint(sequence);
+static void P_ProcessFinishLine(player_t *player)
+{
+	if ((gametyperules & (GTR_RACE|GTR_LIVES)) != GTR_RACE)
+		return;
 
-				if (!waypoint)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: FIRST WAYPOINT IN SEQUENCE %d NOT FOUND.\n", sequence);
-					break;
-				}
-				else
-				{
-					CONS_Debug(DBG_GAMELOGIC, "Waypoint %d found in sequence %d - speed = %d\n", waypoint->health, sequence, speed);
-				}
+	if (player->exiting)
+		return;
 
-				an = R_PointToAngle2(player->mo->x, player->mo->y, waypoint->x, waypoint->y) - player->mo->angle;
+	if (player->starpostnum == numstarposts) // Must have touched all the starposts
+	{
+		player->laps++;
 
-				if (an > ANGLE_90 && an < ANGLE_270 && !(lines[lineindex].flags & ML_EFFECT4))
-					break; // behind back
+		if (player->powers[pw_carry] == CR_NIGHTSMODE)
+			player->drillmeter += 48*20;
 
-				P_SetTarget(&player->mo->tracer, waypoint);
-				player->powers[pw_carry] = CR_ZOOMTUBE;
-				player->speed = speed;
-				player->pflags |= PF_SPINNING;
-				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
-				player->climbing = 0;
+		if (player->laps >= (UINT8)cv_numlaps.value)
+			CONS_Printf(M_GetText("%s has finished the race.\n"), player_names[player-players]);
+		else if (player->laps == (UINT8)cv_numlaps.value-1)
+			CONS_Printf(M_GetText("%s started the \205final lap\200!\n"), player_names[player-players]);
+		else
+			CONS_Printf(M_GetText("%s started lap %u\n"), player_names[player-players], (UINT32)player->laps+1);
 
-				if (player->mo->state-states != S_PLAY_ROLL)
-				{
-					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-					S_StartSound(player->mo, sfx_spin);
-				}
-			}
-			break;
+		// Reset starposts (checkpoints) info
+		player->starpostscale = player->starpostangle = player->starposttime = player->starpostnum = 0;
+		player->starpostx = player->starposty = player->starpostz = 0;
+		P_ResetStarposts();
 
-		case 9: // Zoom Tube End
-			{
-				INT32 sequence;
-				fixed_t speed;
-				INT32 lineindex;
-				mobj_t *waypoint = NULL;
-				angle_t an;
+		// Play the starpost sound for 'consistency'
+		S_StartSound(player->mo, sfx_strpst);
+	}
+	else if (player->starpostnum)
+	{
+		// blatant reuse of a variable that's normally unused in circuit
+		if (!player->tossdelay)
+			S_StartSound(player->mo, sfx_lose);
+		player->tossdelay = 3;
+	}
 
-				if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && player->powers[pw_carry] == CR_ZOOMTUBE)
-					break;
+	if (player->laps >= (unsigned)cv_numlaps.value)
+	{
+		if (P_IsLocalPlayer(player))
+		{
+			HU_SetCEchoFlags(0);
+			HU_SetCEchoDuration(5);
+			HU_DoCEcho("FINISHED!");
+		}
 
-				if (player->powers[pw_ignorelatch] & (1<<15))
-					break;
+		P_DoPlayerExit(player);
+	}
+}
 
-				// Find line #3 tagged to this sector
-				lineindex = Tag_FindLineSpecial(3, sectag);
+static void P_ProcessRopeHang(player_t *player, mtag_t sectag)
+{
+	INT32 sequence;
+	fixed_t speed;
+	INT32 lineindex;
+	mobj_t *waypointmid = NULL;
+	mobj_t *waypointhigh = NULL;
+	mobj_t *waypointlow = NULL;
+	mobj_t *closest = NULL;
+	vector3_t p, line[2], resulthigh, resultlow;
+
+	if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && player->powers[pw_carry] == CR_ROPEHANG)
+		return;
 
-				if (lineindex == -1)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Sector special %d missing line special #3.\n", sector->special);
-					break;
-				}
+	if (player->powers[pw_ignorelatch] & (1<<15))
+		return;
 
-				// Grab speed and sequence values
-				speed = -abs(sides[lines[lineindex].sidenum[0]].textureoffset)/8; // Negative means reverse
-				sequence = abs(sides[lines[lineindex].sidenum[0]].rowoffset)>>FRACBITS;
+	if (player->mo->momz > 0)
+		return;
 
-				if (speed == 0)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
-					break;
-				}
+	if (player->cmd.buttons & BT_SPIN)
+		return;
 
-				waypoint = P_GetLastWaypoint(sequence);
+	if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate])
+		return;
 
-				if (!waypoint)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: LAST WAYPOINT IN SEQUENCE %d NOT FOUND.\n", sequence);
-					break;
-				}
-				else
-				{
-					CONS_Debug(DBG_GAMELOGIC, "Waypoint %d found in sequence %d - speed = %d\n", waypoint->health, sequence, speed);
-				}
+	if (player->exiting)
+		return;
 
-				an = R_PointToAngle2(player->mo->x, player->mo->y, waypoint->x, waypoint->y) - player->mo->angle;
+	//initialize resulthigh and resultlow with 0
+	memset(&resultlow, 0x00, sizeof(resultlow));
+	memset(&resulthigh, 0x00, sizeof(resulthigh));
 
-				if (an > ANGLE_90 && an < ANGLE_270 && !(lines[lineindex].flags & ML_EFFECT4))
-					break; // behind back
+	// Find line #11 tagged to this sector
+	lineindex = Tag_FindLineSpecial(11, sectag);
 
-				P_SetTarget(&player->mo->tracer, waypoint);
-				player->powers[pw_carry] = CR_ZOOMTUBE;
-				player->speed = speed;
-				player->pflags |= PF_SPINNING;
-				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
-				player->climbing = 0;
+	if (lineindex == -1)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "ERROR: Rope hang missing line special #11.\n");
+		return;
+	}
 
-				if (player->mo->state-states != S_PLAY_ROLL)
-				{
-					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-					S_StartSound(player->mo, sfx_spin);
-				}
-			}
-			break;
+	// Grab speed and sequence values
+	speed = abs(lines[lineindex].args[0]) << (FRACBITS - 3);
+	sequence = abs(lines[lineindex].args[1]);
 
-		case 10: // Finish Line
-			if (((gametyperules & (GTR_RACE|GTR_LIVES)) == GTR_RACE) && !player->exiting)
-			{
-				if (player->starpostnum == numstarposts) // Must have touched all the starposts
-				{
-					player->laps++;
+	// Find the closest waypoint
+	// Find the preceding waypoint
+	// Find the proceeding waypoint
+	// Determine the closest spot on the line between the three waypoints
+	// Put player at that location.
 
-					if (player->powers[pw_carry] == CR_NIGHTSMODE)
-						player->drillmeter += 48*20;
+	waypointmid = P_GetClosestWaypoint(sequence, player->mo);
 
-					if (player->laps >= (UINT8)cv_numlaps.value)
-						CONS_Printf(M_GetText("%s has finished the race.\n"), player_names[player-players]);
-					else if (player->laps == (UINT8)cv_numlaps.value-1)
-						CONS_Printf(M_GetText("%s started the \205final lap\200!\n"), player_names[player-players]);
-					else
-						CONS_Printf(M_GetText("%s started lap %u\n"), player_names[player-players], (UINT32)player->laps+1);
+	if (!waypointmid)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "ERROR: WAYPOINT(S) IN SEQUENCE %d NOT FOUND.\n", sequence);
+		return;
+	}
 
-					// Reset starposts (checkpoints) info
-					player->starpostscale = player->starpostangle = player->starposttime = player->starpostnum = 0;
-					player->starpostx = player->starposty = player->starpostz = 0;
-					P_ResetStarposts();
+	waypointlow = P_GetPreviousWaypoint(waypointmid, true);
+	waypointhigh = P_GetNextWaypoint(waypointmid, true);
 
-					// Play the starpost sound for 'consistency'
-					S_StartSound(player->mo, sfx_strpst);
-				}
-				else if (player->starpostnum)
-				{
-					// blatant reuse of a variable that's normally unused in circuit
-					if (!player->tossdelay)
-						S_StartSound(player->mo, sfx_lose);
-					player->tossdelay = 3;
-				}
+	CONS_Debug(DBG_GAMELOGIC, "WaypointMid: %d; WaypointLow: %d; WaypointHigh: %d\n",
+					waypointmid->health, waypointlow ? waypointlow->health : -1, waypointhigh ? waypointhigh->health : -1);
 
-				if (player->laps >= (unsigned)cv_numlaps.value)
-				{
-					if (P_IsLocalPlayer(player))
-					{
-						HU_SetCEchoFlags(0);
-						HU_SetCEchoDuration(5);
-						HU_DoCEcho("FINISHED!");
-					}
+	// Now we have three waypoints... the closest one we're near, and the one that comes before, and after.
+	// Next, we need to find the closest point on the line between each set, and determine which one we're
+	// closest to.
 
-					P_DoPlayerExit(player);
-				}
-			}
-			break;
+	p.x = player->mo->x;
+	p.y = player->mo->y;
+	p.z = player->mo->z;
 
-		case 11: // Rope hang
-			{
-				INT32 sequence;
-				fixed_t speed;
-				INT32 lineindex;
-				mobj_t *waypointmid = NULL;
-				mobj_t *waypointhigh = NULL;
-				mobj_t *waypointlow = NULL;
-				mobj_t *closest = NULL;
-				vector3_t p, line[2], resulthigh, resultlow;
-
-				if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT && player->powers[pw_carry] == CR_ROPEHANG)
-					break;
+	// Waypointmid and Waypointlow:
+	if (waypointlow)
+	{
+		line[0].x = waypointmid->x;
+		line[0].y = waypointmid->y;
+		line[0].z = waypointmid->z;
+		line[1].x = waypointlow->x;
+		line[1].y = waypointlow->y;
+		line[1].z = waypointlow->z;
 
-				if (player->powers[pw_ignorelatch] & (1<<15))
-					break;
+		P_ClosestPointOnLine3D(&p, line, &resultlow);
+	}
 
-				if (player->mo->momz > 0)
-					break;
+	// Waypointmid and Waypointhigh:
+	if (waypointhigh)
+	{
+		line[0].x = waypointmid->x;
+		line[0].y = waypointmid->y;
+		line[0].z = waypointmid->z;
+		line[1].x = waypointhigh->x;
+		line[1].y = waypointhigh->y;
+		line[1].z = waypointhigh->z;
 
-				if (player->cmd.buttons & BT_SPIN)
-					break;
+		P_ClosestPointOnLine3D(&p, line, &resulthigh);
+	}
 
-				if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate])
-					break;
+	// 3D support now available. Disregard the previous notice here. -Red
 
-				if (player->exiting)
-					break;
+	P_UnsetThingPosition(player->mo);
+	P_ResetPlayer(player);
+	player->mo->momx = player->mo->momy = player->mo->momz = 0;
+
+	if (lines[lineindex].args[2]) // Don't wrap
+	{
+		mobj_t *highest = P_GetLastWaypoint(sequence);
+		highest->flags |= MF_SLIDEME;
+	}
 
-				//initialize resulthigh and resultlow with 0
-				memset(&resultlow, 0x00, sizeof(resultlow));
-				memset(&resulthigh, 0x00, sizeof(resulthigh));
+	// Changing the conditions on these ifs to fix issues with snapping to the wrong spot -Red
+	if ((lines[lineindex].args[2]) && waypointmid->health == 0)
+	{
+		closest = waypointhigh;
+		player->mo->x = resulthigh.x;
+		player->mo->y = resulthigh.y;
+		player->mo->z = resulthigh.z - P_GetPlayerHeight(player);
+	}
+	else if ((lines[lineindex].args[2]) && waypointmid->health == numwaypoints[sequence] - 1)
+	{
+		closest = waypointmid;
+		player->mo->x = resultlow.x;
+		player->mo->y = resultlow.y;
+		player->mo->z = resultlow.z - P_GetPlayerHeight(player);
+	}
+	else
+	{
+		if (P_AproxDistance(P_AproxDistance(player->mo->x-resultlow.x, player->mo->y-resultlow.y),
+				player->mo->z-resultlow.z) < P_AproxDistance(P_AproxDistance(player->mo->x-resulthigh.x,
+					player->mo->y-resulthigh.y), player->mo->z-resulthigh.z))
+		{
+			// Line between Mid and Low is closer
+			closest = waypointmid;
+			player->mo->x = resultlow.x;
+			player->mo->y = resultlow.y;
+			player->mo->z = resultlow.z - P_GetPlayerHeight(player);
+		}
+		else
+		{
+			// Line between Mid and High is closer
+			closest = waypointhigh;
+			player->mo->x = resulthigh.x;
+			player->mo->y = resulthigh.y;
+			player->mo->z = resulthigh.z - P_GetPlayerHeight(player);
+		}
+	}
 
-				// Find line #11 tagged to this sector
-				lineindex = Tag_FindLineSpecial(11, sectag);
+	P_SetTarget(&player->mo->tracer, closest);
+	player->powers[pw_carry] = CR_ROPEHANG;
+	player->speed = speed;
 
-				if (lineindex == -1)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Sector special %d missing line special #11.\n", sector->special);
-					break;
-				}
+	S_StartSound(player->mo, sfx_s3k4a);
 
-				// Grab speed and sequence values
-				speed = abs(sides[lines[lineindex].sidenum[0]].textureoffset)/8;
-				sequence = abs(sides[lines[lineindex].sidenum[0]].rowoffset)>>FRACBITS;
+	player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
+	player->climbing = 0;
+	P_SetThingPosition(player->mo);
+	P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+}
 
-				if (speed == 0)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Waypoint sequence %d at zero speed.\n", sequence);
-					break;
-				}
+static boolean P_SectorHasSpecial(sector_t *sec)
+{
+	if (sec->specialflags)
+		return true;
 
-				// Find the closest waypoint
-				// Find the preceding waypoint
-				// Find the proceeding waypoint
-				// Determine the closest spot on the line between the three waypoints
-				// Put player at that location.
+	if (sec->damagetype != SD_NONE)
+		return true;
 
-				waypointmid = P_GetClosestWaypoint(sequence, player->mo);
+	if (sec->triggertag)
+		return true;
 
-				if (!waypointmid)
-				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: WAYPOINT(S) IN SEQUENCE %d NOT FOUND.\n", sequence);
-					break;
-				}
+	if (sec->special)
+		return true;
 
-				waypointlow = P_GetPreviousWaypoint(waypointmid, true);
-				waypointhigh = P_GetNextWaypoint(waypointmid, true);
+	return false;
+}
 
-				CONS_Debug(DBG_GAMELOGIC, "WaypointMid: %d; WaypointLow: %d; WaypointHigh: %d\n",
-								waypointmid->health, waypointlow ? waypointlow->health : -1, waypointhigh ? waypointhigh->health : -1);
+static void P_EvaluateSpecialFlags(player_t *player, sector_t *sector, sector_t *roversector, boolean isTouching)
+{
+	mtag_t sectag = Tag_FGet(&sector->tags);
 
-				// Now we have three waypoints... the closest one we're near, and the one that comes before, and after.
-				// Next, we need to find the closest point on the line between each set, and determine which one we're
-				// closest to.
+	if (sector->specialflags & SSF_OUTERSPACE)
+	{
+		if (!(player->powers[pw_shield] & SH_PROTECTWATER) && !player->powers[pw_spacetime])
+			player->powers[pw_spacetime] = spacetimetics + 1;
+	}
+	if (sector->specialflags & SSF_WINDCURRENT)
+		player->onconveyor = 2;
+	if (sector->specialflags & SSF_CONVEYOR)
+		player->onconveyor = 4;
+	if ((sector->specialflags & SSF_SPEEDPAD) && isTouching)
+		P_ProcessSpeedPad(player, sector, roversector, sectag);
+	if (sector->specialflags & SSF_STARPOSTACTIVATOR)
+	{
+		mobj_t *post = P_GetObjectTypeInSectorNum(MT_STARPOST, sector - sectors);
+		if (post)
+			P_TouchStarPost(post, player, false);
+	}
+	if (sector->specialflags & SSF_EXIT)
+		P_ProcessExitSector(player, sectag);
+	if ((sector->specialflags & SSF_SPECIALSTAGEPIT) && isTouching)
+		P_ProcessSpecialStagePit(player);
+	if ((sector->specialflags & SSF_REDTEAMBASE) && isTouching)
+		P_ProcessTeamBase(player, true);
+	if ((sector->specialflags & SSF_BLUETEAMBASE) && isTouching)
+		P_ProcessTeamBase(player, false);
+	if (sector->specialflags & SSF_FAN)
+	{
+		player->mo->momz += mobjinfo[MT_FAN].mass/4;
 
-				p.x = player->mo->x;
-				p.y = player->mo->y;
-				p.z = player->mo->z;
+		if (player->mo->momz > mobjinfo[MT_FAN].mass)
+			player->mo->momz = mobjinfo[MT_FAN].mass;
 
-				// Waypointmid and Waypointlow:
-				if (waypointlow)
-				{
-					line[0].x = waypointmid->x;
-					line[0].y = waypointmid->y;
-					line[0].z = waypointmid->z;
-					line[1].x = waypointlow->x;
-					line[1].y = waypointlow->y;
-					line[1].z = waypointlow->z;
-
-					P_ClosestPointOnLine3D(&p, line, &resultlow);
-				}
+		P_ResetPlayer(player);
+		if (player->panim != PA_FALL)
+			P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+	}
+	if (sector->specialflags & SSF_SUPERTRANSFORM)
+	{
+		if (player->mo->health > 0 && !player->bot && (player->charflags & SF_SUPER) && !player->powers[pw_super] && ALL7EMERALDS(emeralds))
+			P_DoSuperTransformation(player, true);
+	}
+	if ((sector->specialflags & SSF_FORCESPIN) && isTouching)
+	{
+		if (!(player->pflags & PF_SPINNING))
+		{
+			player->pflags |= PF_SPINNING;
+			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+			S_StartAttackSound(player->mo, sfx_spin);
 
-				// Waypointmid and Waypointhigh:
-				if (waypointhigh)
-				{
-					line[0].x = waypointmid->x;
-					line[0].y = waypointmid->y;
-					line[0].z = waypointmid->z;
-					line[1].x = waypointhigh->x;
-					line[1].y = waypointhigh->y;
-					line[1].z = waypointhigh->z;
-
-					P_ClosestPointOnLine3D(&p, line, &resulthigh);
-				}
+			if (abs(player->rmomx) < FixedMul(5*FRACUNIT, player->mo->scale)
+			&& abs(player->rmomy) < FixedMul(5*FRACUNIT, player->mo->scale))
+				P_InstaThrust(player->mo, player->mo->angle, FixedMul(10*FRACUNIT, player->mo->scale));
+		}
+	}
+	if (sector->specialflags & SSF_ZOOMTUBESTART)
+		P_ProcessZoomTube(player, sectag, false);
+	if (sector->specialflags & SSF_ZOOMTUBEEND)
+		P_ProcessZoomTube(player, sectag, true);
+	if (sector->specialflags & SSF_FINISHLINE)
+		P_ProcessFinishLine(player);
+	if ((sector->specialflags & SSF_ROPEHANG) && isTouching)
+		P_ProcessRopeHang(player, sectag);
+}
 
-				// 3D support now available. Disregard the previous notice here. -Red
+static void P_EvaluateDamageType(player_t *player, sector_t *sector, boolean isTouching)
+{
+	switch (sector->damagetype)
+	{
+		case SD_GENERIC:
+			if (isTouching)
+				P_DamageMobj(player->mo, NULL, NULL, 1, 0);
+			break;
+		case SD_WATER:
+			if (isTouching && (player->powers[pw_underwater] || player->powers[pw_carry] == CR_NIGHTSMODE))
+				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_WATER);
+			break;
+		case SD_FIRE:
+		case SD_LAVA:
+			if (isTouching)
+				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_FIRE);
+			break;
+		case SD_ELECTRIC:
+			if (isTouching)
+				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_ELECTRIC);
+			break;
+		case SD_SPIKE:
+			if (isTouching)
+				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPIKE);
+			break;
+		case SD_DEATHPITTILT:
+		case SD_DEATHPITNOTILT:
+			if (!isTouching)
+				break;
+			if (player->quittime)
+				G_MovePlayerToSpawnOrStarpost(player - players);
+			else
+				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_DEATHPIT);
+			break;
+		case SD_INSTAKILL:
+			if (player->quittime)
+				G_MovePlayerToSpawnOrStarpost(player - players);
+			else
+				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
+			break;
+		case SD_SPECIALSTAGE:
+			if (!isTouching)
+				break;
 
-				P_UnsetThingPosition(player->mo);
-				P_ResetPlayer(player);
-				player->mo->momx = player->mo->momy = player->mo->momz = 0;
+			if (player->exiting || player->bot) // Don't do anything for bots or players who have just finished
+				break;
 
-				if (lines[lineindex].flags & ML_EFFECT1) // Don't wrap
-				{
-					mobj_t *highest = P_GetLastWaypoint(sequence);
-					highest->flags |= MF_SLIDEME;
-				}
+			if (!(player->powers[pw_shield] || player->spheres > 0)) // Don't do anything if no shield or spheres anyway
+				break;
 
-				// Changing the conditions on these ifs to fix issues with snapping to the wrong spot -Red
-				if ((lines[lineindex].flags & ML_EFFECT1) && waypointmid->health == 0)
-				{
-					closest = waypointhigh;
-					player->mo->x = resulthigh.x;
-					player->mo->y = resulthigh.y;
-					player->mo->z = resulthigh.z - P_GetPlayerHeight(player);
-				}
-				else if ((lines[lineindex].flags & ML_EFFECT1) && waypointmid->health == numwaypoints[sequence] - 1)
-				{
-					closest = waypointmid;
-					player->mo->x = resultlow.x;
-					player->mo->y = resultlow.y;
-					player->mo->z = resultlow.z - P_GetPlayerHeight(player);
-				}
-				else
-				{
-					if (P_AproxDistance(P_AproxDistance(player->mo->x-resultlow.x, player->mo->y-resultlow.y),
-							player->mo->z-resultlow.z) < P_AproxDistance(P_AproxDistance(player->mo->x-resulthigh.x,
-								player->mo->y-resulthigh.y), player->mo->z-resulthigh.z))
-					{
-						// Line between Mid and Low is closer
-						closest = waypointmid;
-						player->mo->x = resultlow.x;
-						player->mo->y = resultlow.y;
-						player->mo->z = resultlow.z - P_GetPlayerHeight(player);
-					}
-					else
-					{
-						// Line between Mid and High is closer
-						closest = waypointhigh;
-						player->mo->x = resulthigh.x;
-						player->mo->y = resulthigh.y;
-						player->mo->z = resulthigh.z - P_GetPlayerHeight(player);
-					}
-				}
+			P_SpecialStageDamage(player, NULL, NULL);
+			break;
+		default:
+			break;
+	}
+}
 
-				P_SetTarget(&player->mo->tracer, closest);
-				player->powers[pw_carry] = CR_ROPEHANG;
+static void P_EvaluateLinedefExecutorTrigger(player_t *player, sector_t *sector, boolean isTouching)
+{
+	if (player->bot)
+		return;
 
-				// Option for static ropes.
-				if (lines[lineindex].flags & ML_NOCLIMB)
-					player->speed = 0;
-				else
-					player->speed = speed;
+	if (!sector->triggertag)
+		return;
+
+	if (sector->triggerer == TO_MOBJ)
+		return;
+	else if (sector->triggerer == TO_ALLPLAYERS && !P_DoAllPlayersTrigger(sector->triggertag))
+		return;
 
-				S_StartSound(player->mo, sfx_s3k4a);
+	if ((sector->flags & MSF_TRIGGERLINE_PLANE) && !isTouching)
+		return;
+
+	P_LinedefExecute(sector->triggertag, player->mo, sector);
+}
 
-				player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
-				player->climbing = 0;
-				P_SetThingPosition(player->mo);
-				P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+static void P_EvaluateOldSectorSpecial(player_t *player, sector_t *sector, sector_t *roversector, boolean isTouching)
+{
+	switch (GETSECSPECIAL(sector->special, 1))
+	{
+		case 9: // Ring Drainer (Floor Touch)
+			if (!isTouching)
+				break;
+			/* FALLTHRU */
+		case 10: // Ring Drainer (No Floor Touch)
+			if (leveltime % (TICRATE/2) == 0 && player->rings > 0)
+			{
+				player->rings--;
+				S_StartSound(player->mo, sfx_antiri);
 			}
 			break;
-		case 12: // Camera noclip
-		case 13: // Unused
-		case 14: // Unused
-		case 15: // Unused
+	}
+
+	switch (GETSECSPECIAL(sector->special, 2))
+	{
+		case 9: // Egg trap capsule
+			if (roversector)
+				P_ProcessEggCapsule(player, sector);
 			break;
 	}
 }
 
-/** Checks if an object is standing on or is inside a special 3D floor.
-  * If so, the sector is returned.
+/** Applies a sector special to a player.
   *
-  * \param mo Object to check.
-  * \return Pointer to the sector with a special type, or NULL if no special 3D
-  *         floors are being contacted.
-  * \sa P_PlayerOnSpecial3DFloor
+  * \param player       Player in the sector.
+  * \param sector       Sector with the special.
+  * \param roversector  If !NULL, sector is actually an FOF; otherwise, sector
+  *                     is being physically contacted by the player.
+  * \sa P_PlayerInSpecialSector, P_PlayerOnSpecial3DFloor
   */
-sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo)
+void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *roversector)
 {
-	sector_t *sector;
-	ffloor_t *rover;
-	fixed_t topheight, bottomheight;
-
-	sector = mo->subsector->sector;
-	if (!sector->ffloors)
-		return NULL;
+	boolean isTouching;
 
-	for (rover = sector->ffloors; rover; rover = rover->next)
-	{
-		if (!rover->master->frontsector->special)
-			continue;
+	if (!P_SectorHasSpecial(sector))
+		return;
 
-		if (!(rover->flags & FF_EXISTS))
-			continue;
+	// Ignore spectators
+	if (player->spectator)
+		return;
 
-		topheight = P_GetSpecialTopZ(mo, sectors + rover->secnum, sector);
-		bottomheight = P_GetSpecialBottomZ(mo, sectors + rover->secnum, sector);
+	// Ignore dead players.
+	// If this strange phenomenon could be potentially used in levels,
+	// TODO: modify this to accommodate for it.
+	if (player->playerstate != PST_LIVE)
+		return;
 
-		// Check the 3D floor's type...
-		if (((rover->flags & FF_BLOCKPLAYER) && mo->player)
-			|| ((rover->flags & FF_BLOCKOTHERS) && !mo->player))
-		{
-			boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == topheight));
-			boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == bottomheight));
-			// Thing must be on top of the floor to be affected...
-			if (!(floorallowed || ceilingallowed))
-				continue;
-		}
-		else
-		{
-			// Water and intangible FOFs
-			if (mo->z > topheight || (mo->z + mo->height) < bottomheight)
-				continue;
-		}
+	isTouching = roversector || P_IsMobjTouchingSectorPlane(player->mo, sector);
 
-		return rover->master->frontsector;
-	}
+	P_EvaluateSpecialFlags(player, sector, roversector, isTouching);
+	P_EvaluateDamageType(player, sector, isTouching);
+	P_EvaluateLinedefExecutorTrigger(player, sector, isTouching);
 
-	return NULL;
+	if (!udmf)
+		P_EvaluateOldSectorSpecial(player, sector, roversector, isTouching);
 }
 
-#define TELEPORTED (player->mo->subsector->sector != originalsector)
+#define TELEPORTED(mo) (mo->subsector->sector != originalsector)
 
 /** Checks if a player is standing on or is inside a 3D floor (e.g. water) and
   * applies any specials.
@@ -5073,200 +5140,58 @@ static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
 {
 	sector_t *originalsector = player->mo->subsector->sector;
 	ffloor_t *rover;
-	fixed_t topheight, bottomheight;
 
 	for (rover = sector->ffloors; rover; rover = rover->next)
 	{
-		if (!rover->master->frontsector->special)
+		if (!P_SectorHasSpecial(rover->master->frontsector))
 			continue;
 
 		if (!(rover->flags & FF_EXISTS))
 			continue;
 
-		topheight = P_GetSpecialTopZ(player->mo, sectors + rover->secnum, sector);
-		bottomheight = P_GetSpecialBottomZ(player->mo, sectors + rover->secnum, sector);
-
-		// Check the 3D floor's type...
-		if (rover->flags & FF_BLOCKPLAYER)
-		{
-			boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == topheight));
-			boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == bottomheight));
-			// Thing must be on top of the floor to be affected...
-			if (!(floorallowed || ceilingallowed))
-				continue;
-		}
-		else
-		{
-			// Water and DEATH FOG!!! heh
-			if (player->mo->z > topheight || (player->mo->z + player->mo->height) < bottomheight)
-				continue;
-		}
+		if (!P_IsMobjTouching3DFloor(player->mo, rover, sector))
+			continue;
 
 		// This FOF has the special we're looking for, but are we allowed to touch it?
 		if (sector == player->mo->subsector->sector
-			|| (rover->master->frontsector->flags & SF_TRIGGERSPECIAL_TOUCH))
+			|| (rover->master->frontsector->flags & MSF_TRIGGERSPECIAL_TOUCH))
 		{
 			P_ProcessSpecialSector(player, rover->master->frontsector, sector);
-			if TELEPORTED return;
-		}
-	}
-
-	// Allow sector specials to be applied to polyobjects!
-	if (player->mo->subsector->polyList)
-	{
-		polyobj_t *po = player->mo->subsector->polyList;
-		sector_t *polysec;
-		boolean touching = false;
-		boolean inside = false;
-
-		while (po)
-		{
-			if (po->flags & POF_NOSPECIALS)
-			{
-				po = (polyobj_t *)(po->link.next);
-				continue;
-			}
-
-			polysec = po->lines[0]->backsector;
-
-			if ((polysec->flags & SF_TRIGGERSPECIAL_TOUCH))
-				touching = P_MobjTouchingPolyobj(po, player->mo);
-			else
-				touching = false;
-
-			inside = P_MobjInsidePolyobj(po, player->mo);
-
-			if (!(inside || touching))
-			{
-				po = (polyobj_t *)(po->link.next);
-				continue;
-			}
-
-			// We're inside it! Yess...
-			if (!polysec->special)
-			{
-				po = (polyobj_t *)(po->link.next);
-				continue;
-			}
-
-			if (!(po->flags & POF_TESTHEIGHT)) // Don't do height checking
-				;
-			else if (po->flags & POF_SOLID)
-			{
-				boolean floorallowed = ((polysec->flags & SF_FLIPSPECIAL_FLOOR) && ((polysec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == polysec->ceilingheight));
-				boolean ceilingallowed = ((polysec->flags & SF_FLIPSPECIAL_CEILING) && ((polysec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == polysec->floorheight));
-				// Thing must be on top of the floor to be affected...
-				if (!(floorallowed || ceilingallowed))
-				{
-					po = (polyobj_t *)(po->link.next);
-					continue;
-				}
-			}
-			else
-			{
-				// Water and DEATH FOG!!! heh
-				if (player->mo->z > polysec->ceilingheight || (player->mo->z + player->mo->height) < polysec->floorheight)
-				{
-					po = (polyobj_t *)(po->link.next);
-					continue;
-				}
-			}
-
-			P_ProcessSpecialSector(player, polysec, sector);
-			if TELEPORTED return;
-
-			po = (polyobj_t *)(po->link.next);
+			if TELEPORTED(player->mo) return;
 		}
 	}
 }
 
-#define VDOORSPEED (FRACUNIT*2)
-
-//
-// P_RunSpecialSectorCheck
-//
-// Helper function to P_PlayerInSpecialSector
-//
-static void P_RunSpecialSectorCheck(player_t *player, sector_t *sector)
+static void P_PlayerOnSpecialPolyobj(player_t *player)
 {
-	boolean nofloorneeded = false;
-	fixed_t f_affectpoint, c_affectpoint;
-
-	if (!sector->special) // nothing special, exit
-		return;
-
-	if (GETSECSPECIAL(sector->special, 2) == 9) // Egg trap capsule -- should only be for 3dFloors!
-		return;
-
-	// The list of specials that activate without floor touch
-	// Check Section 1
-	switch(GETSECSPECIAL(sector->special, 1))
-	{
-		case 2: // Damage (water)
-		case 8: // Instant kill
-		case 10: // Ring drainer that doesn't require floor touch
-		case 12: // Space countdown
-			nofloorneeded = true;
-			break;
-	}
-
-	// Check Section 2
-	switch(GETSECSPECIAL(sector->special, 2))
-	{
-		case 2: // Linedef executor (All players needed)
-		case 4: // Linedef executor
-		case 6: // Linedef executor (7 Emeralds)
-		case 7: // Linedef executor (NiGHTS Mare)
-			nofloorneeded = true;
-			break;
-	}
+	sector_t *originalsector = player->mo->subsector->sector;
+	polyobj_t *po;
+	sector_t *polysec;
+	boolean touching = false;
+	boolean inside = false;
 
-	// Check Section 3
-/*	switch(GETSECSPECIAL(sector->special, 3))
+	for (po = player->mo->subsector->polyList; po; po = (polyobj_t *)(po->link.next))
 	{
+		if (po->flags & POF_NOSPECIALS)
+			continue;
 
-	}*/
-
-	// Check Section 4
-	switch(GETSECSPECIAL(sector->special, 4))
-	{
-		case 2: // Level Exit / GOAL Sector / Flag Return
-			if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))
-			{
-				// Special stage GOAL sector
-				// requires touching floor.
-				break;
-			}
-			/* FALLTHRU */
+		polysec = po->lines[0]->backsector;
 
-		case 1: // Starpost activator
-		case 5: // Fan sector
-		case 6: // Super Sonic Transform
-		case 8: // Zoom Tube Start
-		case 9: // Zoom Tube End
-		case 10: // Finish line
-			nofloorneeded = true;
-			break;
-	}
+		if (!P_SectorHasSpecial(polysec))
+			continue;
 
-	if (nofloorneeded)
-	{
-		P_ProcessSpecialSector(player, sector, NULL);
-		return;
-	}
+		touching = (polysec->flags & MSF_TRIGGERSPECIAL_TOUCH) && P_MobjTouchingPolyobj(po, player->mo);
+		inside = P_MobjInsidePolyobj(po, player->mo);
 
-	f_affectpoint = P_GetSpecialBottomZ(player->mo, sector, sector);
-	c_affectpoint = P_GetSpecialTopZ(player->mo, sector, sector);
+		if (!(inside || touching))
+			continue;
 
-	{
-		boolean floorallowed = ((sector->flags & SF_FLIPSPECIAL_FLOOR) && ((sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == f_affectpoint));
-		boolean ceilingallowed = ((sector->flags & SF_FLIPSPECIAL_CEILING) && ((sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == c_affectpoint));
-		// Thing must be on top of the floor to be affected...
-		if (!(floorallowed || ceilingallowed))
-			return;
-	}
+		if (!P_IsMobjTouchingPolyobj(player->mo, po, polysec))
+			continue;
 
-	P_ProcessSpecialSector(player, sector, NULL);
+		P_ProcessSpecialSector(player, polysec, originalsector);
+		if TELEPORTED(player->mo) return;
+	}
 }
 
 /** Checks if the player is in a special sector or FOF and apply any specials.
@@ -5286,10 +5211,14 @@ void P_PlayerInSpecialSector(player_t *player)
 	originalsector = player->mo->subsector->sector;
 
 	P_PlayerOnSpecial3DFloor(player, originalsector); // Handle FOFs first.
-	if TELEPORTED return;
+	if TELEPORTED(player->mo) return;
+
+	// Allow sector specials to be applied to polyobjects!
+	P_PlayerOnSpecialPolyobj(player);
+	if TELEPORTED(player->mo) return;
 
-	P_RunSpecialSectorCheck(player, originalsector);
-	if TELEPORTED return;
+	P_ProcessSpecialSector(player, originalsector, NULL);
+	if TELEPORTED(player->mo) return;
 
 	// Iterate through touching_sectorlist for SF_TRIGGERSPECIAL_TOUCH
 	for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
@@ -5301,16 +5230,110 @@ void P_PlayerInSpecialSector(player_t *player)
 
 		// Check 3D floors...
 		P_PlayerOnSpecial3DFloor(player, loopsector);
-		if TELEPORTED return;
+		if TELEPORTED(player->mo) return;
+
+		if (!(loopsector->flags & MSF_TRIGGERSPECIAL_TOUCH))
+			continue;
+
+		P_ProcessSpecialSector(player, loopsector, NULL);
+		if TELEPORTED(player->mo) return;
+	}
+}
+
+static void P_CheckMobj3DFloorTrigger(mobj_t *mo, sector_t *sec)
+{
+	sector_t *originalsector = mo->subsector->sector;
+	ffloor_t *rover;
+
+	for (rover = sec->ffloors; rover; rover = rover->next)
+	{
+		if (!rover->master->frontsector->triggertag)
+			continue;
+
+		if (rover->master->frontsector->triggerer != TO_MOBJ)
+			continue;
+
+		if (!(rover->flags & FF_EXISTS))
+			continue;
+
+		if (!P_IsMobjTouching3DFloor(mo, rover, sec))
+			continue;
+
+		P_LinedefExecute(rover->master->frontsector->triggertag, mo, rover->master->frontsector);
+		if TELEPORTED(mo) return;
+	}
+}
+
+static void P_CheckMobjPolyobjTrigger(mobj_t *mo)
+{
+	sector_t *originalsector = mo->subsector->sector;
+	polyobj_t *po;
+	sector_t *polysec;
+	boolean touching = false;
+	boolean inside = false;
+
+	for (po = mo->subsector->polyList; po; po = (polyobj_t *)(po->link.next))
+	{
+		if (po->flags & POF_NOSPECIALS)
+			continue;
+
+		polysec = po->lines[0]->backsector;
 
-		if (!(loopsector->flags & SF_TRIGGERSPECIAL_TOUCH))
+		if (!polysec->triggertag)
 			continue;
 
-		P_RunSpecialSectorCheck(player, loopsector);
-		if TELEPORTED return;
+		if (polysec->triggerer != TO_MOBJ)
+			continue;
+
+		touching = (polysec->flags & MSF_TRIGGERSPECIAL_TOUCH) && P_MobjTouchingPolyobj(po, mo);
+		inside = P_MobjInsidePolyobj(po, mo);
+
+		if (!(inside || touching))
+			continue;
+
+		if (!P_IsMobjTouchingPolyobj(mo, po, polysec))
+			continue;
+
+		P_LinedefExecute(polysec->triggertag, mo, polysec);
+		if TELEPORTED(mo) return;
 	}
 }
 
+static void P_CheckMobjSectorTrigger(mobj_t *mo, sector_t *sec)
+{
+	if (!sec->triggertag)
+		return;
+
+	if (sec->triggerer != TO_MOBJ)
+		return;
+
+	if ((sec->flags & MSF_TRIGGERLINE_PLANE) && !P_IsMobjTouchingSectorPlane(mo, sec))
+		return;
+
+	P_LinedefExecute(sec->triggertag, mo, sec);
+}
+
+void P_CheckMobjTrigger(mobj_t *mobj, boolean pushable)
+{
+	sector_t *originalsector;
+
+	if (!mobj->subsector)
+		return;
+
+	originalsector = mobj->subsector->sector;
+
+	if (!pushable && !(originalsector->flags & MSF_TRIGGERLINE_MOBJ))
+		return;
+
+	P_CheckMobj3DFloorTrigger(mobj, originalsector);
+	if TELEPORTED(mobj)	return;
+
+	P_CheckMobjPolyobjTrigger(mobj);
+	if TELEPORTED(mobj)	return;
+
+	P_CheckMobjSectorTrigger(mobj, originalsector);
+}
+
 #undef TELEPORTED
 
 /** Animate planes, scroll walls, etc. and keeps track of level timelimit and exits if time is up.
@@ -5460,12 +5483,14 @@ static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *fflr)
   * \param sec         Target sector.
   * \param sec2        Control sector.
   * \param master      Control linedef.
+  * \param alpha       Alpha value (0-255).
+  * \param blendmode   Blending mode.
   * \param flags       Options affecting this 3Dfloor.
   * \param secthinkers List of relevant thinkers sorted by sector. May be NULL.
   * \return Pointer to the new 3Dfloor.
   * \sa P_AddFFloor, P_AddFakeFloorsByLine, P_SpawnSpecials
   */
-static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, ffloortype_e flags, thinkerlist_t *secthinkers)
+static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, INT32 alpha, UINT8 blendmode, ffloortype_e flags, thinkerlist_t *secthinkers)
 {
 	ffloor_t *fflr;
 	thinker_t *th;
@@ -5483,7 +5508,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	{
 		fixed_t tempceiling = sec2->ceilingheight;
 		//flip the sector around and print an error instead of crashing 12.1.08 -Inuyasha
-		CONS_Alert(CONS_ERROR, M_GetText("FOF (line %s) has a top height below its bottom.\n"), sizeu1(master - lines));
+		CONS_Alert(CONS_ERROR, M_GetText("A FOF tagged %d has a top height below its bottom.\n"), master->args[0]);
 		sec2->ceilingheight = sec2->floorheight;
 		sec2->floorheight = tempceiling;
 	}
@@ -5539,12 +5564,6 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	if (sec2->hasslope)
 		sec->hasslope = true;
 
-	if ((flags & FF_SOLID) && (master->flags & ML_EFFECT1)) // Block player only
-		flags &= ~FF_BLOCKOTHERS;
-
-	if ((flags & FF_SOLID) && (master->flags & ML_EFFECT2)) // Block all BUT player
-		flags &= ~FF_BLOCKPLAYER;
-
 	fflr->spawnflags = fflr->flags = flags;
 	fflr->master = master;
 	fflr->norender = INFTICS;
@@ -5585,44 +5604,50 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 			p = (pusher_t *)th;
 
 			if (p->affectee == (INT32)sec2num)
-				Add_Pusher(p->type, p->x_mag<<FRACBITS, p->y_mag<<FRACBITS, p->source, (INT32)(sec-sectors), p->affectee, p->exclusive, p->slider);
+				Add_Pusher(p->type, p->x_mag, p->y_mag, p->z_mag, (INT32)(sec-sectors), p->affectee, p->exclusive, p->slider);
 		}
 
 		if(secthinkers) i++;
 		else th = th->next;
 	}
 
-
-	if (flags & FF_TRANSLUCENT)
+	fflr->alpha = max(0, min(0xff, alpha));
+	if (fflr->alpha < 0xff || flags & FF_SPLAT)
 	{
-		if (sides[master->sidenum[0]].toptexture > 0)
-		{
-			 // for future reference, "#0" is 1, and "#255" is 256. Be warned
-			fflr->alpha = sides[master->sidenum[0]].toptexture;
-
-			if (fflr->alpha >= 1001) // fourth digit
-			{
-				fflr->blend = (fflr->alpha/1000)+1; // becomes an AST
-				fflr->alpha %= 1000;
-			}
-		}
-		else
-			fflr->alpha = 0x80;
+		fflr->flags |= FF_TRANSLUCENT;
+		fflr->spawnflags = fflr->flags;
 	}
-	else
-		fflr->alpha = 0xff;
-
 	fflr->spawnalpha = fflr->alpha; // save for netgames
 
+	switch (blendmode)
+	{
+		case TMB_TRANSLUCENT:
+		default:
+			fflr->blend = AST_COPY;
+			break;
+		case TMB_ADD:
+			fflr->blend = AST_ADD;
+			break;
+		case TMB_SUBTRACT:
+			fflr->blend = AST_SUBTRACT;
+			break;
+		case TMB_REVERSESUBTRACT:
+			fflr->blend = AST_REVERSESUBTRACT;
+			break;
+		case TMB_MODULATE:
+			fflr->blend = AST_MODULATE;
+			break;
+	}
+
 	if (flags & FF_QUICKSAND)
 		CheckForQuicksand = true;
 
-	if ((flags & FF_BUSTUP) || (flags & FF_SHATTER) || (flags & FF_SPINBUST))
+	if (flags & FF_BUSTUP)
 		CheckForBustableBlocks = true;
 
 	if ((flags & FF_MARIO))
 	{
-		if (!(flags & FF_SHATTERBOTTOM)) // Don't change the textures of a brick block, just a question block
+		if (!(flags & FF_GOOWATER)) // Don't change the textures of a brick block, just a question block
 			P_AddBlockThinker(sec2, master);
 		CheckForMarioBlocks = true;
 	}
@@ -5632,7 +5657,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 
 	if ((flags & FF_FLOATBOB))
 	{
-		P_AddFloatThinker(sec2, Tag_FGet(&master->tags), master);
+		P_AddFloatThinker(sec2, master->args[0], master);
 		CheckForFloatBob = true;
 	}
 
@@ -5748,7 +5773,7 @@ static void P_AddRaiseThinker(sector_t *sec, INT16 tag, fixed_t speed, fixed_t c
 	raise->ceilingtop = ceilingtop;
 	raise->ceilingbottom = ceilingbottom;
 
-	raise->basespeed = speed;
+	raise->basespeed = speed >> 2;
 
 	if (lower)
 		raise->flags |= RF_REVERSE;
@@ -5812,7 +5837,7 @@ static inline void P_AddThwompThinker(sector_t *sec, line_t *sourceline, fixed_t
 	thwomp->floorstartheight = sec->floorheight;
 	thwomp->ceilingstartheight = sec->ceilingheight;
 	thwomp->delay = 1;
-	thwomp->tag = Tag_FGet(&sourceline->tags);
+	thwomp->tag = sourceline->args[0];
 	thwomp->sound = sound;
 
 	sec->floordata = thwomp;
@@ -5848,7 +5873,7 @@ static inline void P_AddNoEnemiesThinker(line_t *sourceline)
   * \sa P_SpawnSpecials, T_EachTimeThinker
   * \author SSNTails <http://www.ssntails.org>
   */
-static void P_AddEachTimeThinker(line_t *sourceline)
+static void P_AddEachTimeThinker(line_t *sourceline, boolean triggerOnExit)
 {
 	eachtime_t *eachtime;
 
@@ -5859,7 +5884,7 @@ static void P_AddEachTimeThinker(line_t *sourceline)
 	eachtime->thinker.function.acp1 = (actionf_p1)T_EachTimeThinker;
 
 	eachtime->sourceline = sourceline;
-	eachtime->triggerOnExit = !!(sourceline->flags & ML_BOUNCY);
+	eachtime->triggerOnExit = triggerOnExit;
 }
 
 /** Adds a camera scanner.
@@ -6024,16 +6049,16 @@ void P_InitSpecials(void)
 	globalweather = mapheaderinfo[gamemap-1]->weather;
 }
 
-static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs)
+void P_ApplyFlatAlignment(sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs, boolean floor, boolean ceiling)
 {
-	if (!(master->flags & ML_NETONLY)) // Modify floor flat alignment unless ML_NETONLY flag is set
+	if (floor)
 	{
 		sector->floorpic_angle = flatangle;
 		sector->floor_xoffs += xoffs;
 		sector->floor_yoffs += yoffs;
 	}
 
-	if (!(master->flags & ML_NONET)) // Modify ceiling flat alignment unless ML_NONET flag is set
+	if (ceiling)
 	{
 		sector->ceilingpic_angle = flatangle;
 		sector->ceiling_xoffs += xoffs;
@@ -6042,6 +6067,58 @@ static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flata
 
 }
 
+static void P_MakeFOFBouncy(line_t *paramline, line_t *masterline)
+{
+	INT32 s;
+
+	if (masterline->special < 100 || masterline->special >= 300)
+		return;
+
+	TAG_ITER_SECTORS(masterline->args[0], s)
+	{
+		ffloor_t *rover;
+
+		for (rover = sectors[s].ffloors; rover; rover = rover->next)
+		{
+			if (rover->master != masterline)
+				continue;
+
+			rover->flags |= FF_BOUNCY;
+			rover->spawnflags |= FF_BOUNCY;
+			rover->bouncestrength = (paramline->args[1]<< FRACBITS)/100;
+			CheckForBouncySector = true;
+			break;
+		}
+	}
+
+}
+
+static boolean P_CheckGametypeRules(INT32 checktype, UINT32 target)
+{
+	switch (checktype)
+	{
+		case TMF_HASALL:
+		default:
+			return (gametyperules & target) == target;
+		case TMF_HASANY:
+			return !!(gametyperules & target);
+		case TMF_HASEXACTLY:
+			return gametyperules == target;
+		case TMF_DOESNTHAVEALL:
+			return (gametyperules & target) != target;
+		case TMF_DOESNTHAVEANY:
+			return !(gametyperules & target);
+	}
+}
+
+fixed_t P_GetSectorGravityFactor(sector_t *sec)
+{
+	if (sec->gravityptr)
+		return FixedDiv(*sec->gravityptr >> FRACBITS, 1000);
+	else
+		return sec->gravity;
+}
+
 /** After the map has loaded, scans for specials that spawn 3Dfloors and
   * thinkers.
   *
@@ -6070,6 +6147,14 @@ void P_SpawnSpecials(boolean fromnetsave)
 	sector = sectors;
 	for (i = 0; i < numsectors; i++, sector++)
 	{
+		CheckForReverseGravity |= (sector->flags & MSF_GRAVITYFLIP);
+
+		if (sector->specialflags & SSF_FINISHLINE)
+		{
+			if ((gametyperules & (GTR_RACE|GTR_LIVES)) == GTR_RACE)
+				circuitmap = true;
+		}
+
 		if (!sector->special)
 			continue;
 
@@ -6078,11 +6163,14 @@ void P_SpawnSpecials(boolean fromnetsave)
 		{
 			case 5: // Spikes
 				//Terrible hack to replace an even worse hack:
-				//Spike damage automatically sets SF_TRIGGERSPECIAL_TOUCH.
+				//Spike damage automatically sets MSF_TRIGGERSPECIAL_TOUCH.
 				//Yes, this also affects other specials on the same sector. Sorry.
-				sector->flags |= SF_TRIGGERSPECIAL_TOUCH;
+				sector->flags |= MSF_TRIGGERSPECIAL_TOUCH;
 				break;
-			case 15: // Bouncy sector
+			case 15: // Bouncy FOF
+				if (udmf)
+					break;
+				CONS_Alert(CONS_WARNING, M_GetText("Deprecated bouncy FOF sector type detected. Please use linedef type 76 instead.\n"));
 				CheckForBouncySector = true;
 				break;
 		}
@@ -6091,29 +6179,20 @@ void P_SpawnSpecials(boolean fromnetsave)
 		switch(GETSECSPECIAL(sector->special, 2))
 		{
 			case 10: // Time for special stage
+				if (udmf)
+					break;
+				CONS_Alert(CONS_WARNING, M_GetText("Deprecated sector type for special stage requirements detected. Please use the SpecialStageTime and SpecialStageSpheres level header options instead.\n"));
 				sstimer = (sector->floorheight>>FRACBITS) * TICRATE + 6; // Time to finish
 				ssspheres = sector->ceilingheight>>FRACBITS; // Ring count for special stage
 				break;
 
 			case 11: // Custom global gravity!
+				if (udmf)
+					break;
+				CONS_Alert(CONS_WARNING, M_GetText("Deprecated sector type for global gravity detected. Please use the Gravity level header option instead.\n"));
 				gravity = sector->floorheight/1000;
 				break;
 		}
-
-		// Process Section 3
-/*		switch(GETSECSPECIAL(player->specialsector, 3))
-		{
-
-		}*/
-
-		// Process Section 4
-		switch(GETSECSPECIAL(sector->special, 4))
-		{
-			case 10: // Circuit finish line
-				if ((gametyperules & (GTR_RACE|GTR_LIVES)) == GTR_RACE)
-					circuitmap = true;
-				break;
-		}
 	}
 
 	P_SpawnScrollers(); // Add generalized scrollers
@@ -6160,527 +6239,406 @@ void P_SpawnSpecials(boolean fromnetsave)
 	// Init line EFFECTs
 	for (i = 0; i < numlines; i++)
 	{
-		mtag_t tag = Tag_FGet(&lines[i].tags);
-
-		if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment in netgames...
+		// set line specials to 0 here too, same reason as above
+		if (netgame || multiplayer)
 		{
-			// set line specials to 0 here too, same reason as above
-			if (netgame || multiplayer)
-			{
-				if (lines[i].flags & ML_NONET)
-				{
-					lines[i].special = 0;
-					continue;
-				}
-			}
-			else if (lines[i].flags & ML_NETONLY)
+			if (lines[i].flags & ML_NONET)
 			{
 				lines[i].special = 0;
 				continue;
 			}
 		}
+		else if (lines[i].flags & ML_NETONLY)
+		{
+			lines[i].special = 0;
+			continue;
+		}
 
 		switch (lines[i].special)
 		{
 			INT32 s;
+			INT32 l;
 			size_t sec;
 			ffloortype_e ffloorflags;
 
 			case 1: // Definable gravity per sector
+				if (udmf)
+					break;
+
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				TAG_ITER_SECTORS(tag, s)
+				TAG_ITER_SECTORS(Tag_FGet(&lines[i].tags), s)
 				{
-					sectors[s].gravity = &sectors[sec].floorheight; // This allows it to change in realtime!
+					sectors[s].gravityptr = &sectors[sec].floorheight; // This allows it to change in realtime!
 
 					if (lines[i].flags & ML_NOCLIMB)
-						sectors[s].verticalflip = true;
+						sectors[s].flags |= MSF_GRAVITYFLIP;
 					else
-						sectors[s].verticalflip = false;
+						sectors[s].flags &= ~MSF_GRAVITYFLIP;
 
-					CheckForReverseGravity = sectors[s].verticalflip;
+					CheckForReverseGravity |= (sectors[s].flags & MSF_GRAVITYFLIP);
 				}
 				break;
 
-			case 2: // Custom exit
-				break;
-
-			case 3: // Zoom Tube Parameters
-				break;
-
-			case 4: // Speed pad (combines with sector special Section3:5 or Section3:6)
-				break;
-
 			case 5: // Change camera info
+				if (udmf)
+					break;
+
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				TAG_ITER_SECTORS(tag, s)
+				TAG_ITER_SECTORS(Tag_FGet(&lines[i].tags), s)
 					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;
 
 			case 7: // Flat alignment - redone by toast
-				if ((lines[i].flags & (ML_NETONLY|ML_NONET)) != (ML_NETONLY|ML_NONET)) // If you can do something...
-				{
-					angle_t flatangle = InvAngle(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y));
-					fixed_t xoffs;
-					fixed_t yoffs;
-
-					if (lines[i].flags & ML_EFFECT6) // Set offset through x and y texture offsets if ML_EFFECT6 flag is set
-					{
-						xoffs = sides[lines[i].sidenum[0]].textureoffset;
-						yoffs = sides[lines[i].sidenum[0]].rowoffset;
-					}
-					else // Otherwise, set calculated offsets such that line's v1 is the apparent origin
-					{
-						xoffs = -lines[i].v1->x;
-						yoffs = lines[i].v1->y;
-					}
-
-					//If no tag is given, apply to front sector
-					if (tag == 0)
-						P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
-					else
-					{
-						TAG_ITER_SECTORS(tag, s)
-							P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
-					}
-				}
-				else // Otherwise, print a helpful warning. Can I do no less?
-					CONS_Alert(CONS_WARNING,
-					M_GetText("Flat alignment linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
-					tag);
-				break;
+			{
+				// Set calculated offsets such that line's v1 is the apparent origin
+				angle_t flatangle = InvAngle(R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y));
+				fixed_t xoffs = -lines[i].v1->x;
+				fixed_t yoffs = lines[i].v1->y;
 
-			case 8: // Sector Parameters
-				TAG_ITER_SECTORS(tag, s)
+				//If no tag is given, apply to front sector
+				if (lines[i].args[0] == 0)
+					P_ApplyFlatAlignment(lines[i].frontsector, flatangle, xoffs, yoffs, lines[i].args[1] != TMP_CEILING, lines[i].args[1] != TMP_FLOOR);
+				else
 				{
-					if (lines[i].flags & ML_NOCLIMB)
-					{
-						sectors[s].flags &= ~SF_FLIPSPECIAL_FLOOR;
-						sectors[s].flags |= SF_FLIPSPECIAL_CEILING;
-					}
-					else if (lines[i].flags & ML_EFFECT4)
-						sectors[s].flags |= SF_FLIPSPECIAL_BOTH;
-
-					if (lines[i].flags & ML_EFFECT3)
-						sectors[s].flags |= SF_TRIGGERSPECIAL_TOUCH;
-					if (lines[i].flags & ML_EFFECT2)
-						sectors[s].flags |= SF_TRIGGERSPECIAL_HEADBUMP;
-
-					if (lines[i].flags & ML_EFFECT1)
-						sectors[s].flags |= SF_INVERTPRECIP;
-
-					if (lines[i].frontsector && GETSECSPECIAL(lines[i].frontsector->special, 4) == 12)
-						sectors[s].camsec = sides[*lines[i].sidenum].sector-sectors;
+					TAG_ITER_SECTORS(lines[i].args[0], s)
+						P_ApplyFlatAlignment(sectors + s, flatangle, xoffs, yoffs, lines[i].args[1] != TMP_CEILING, lines[i].args[1] != TMP_FLOOR);
 				}
 				break;
+			}
 
-			case 9: // Chain Parameters
+			case 8: // Set camera collision planes
+				if (lines[i].frontsector)
+					TAG_ITER_SECTORS(lines[i].args[0], s)
+						sectors[s].camsec = lines[i].frontsector-sectors;
 				break;
 
 			case 10: // Vertical culling plane for sprites and FOFs
-				TAG_ITER_SECTORS(tag, s)
+				TAG_ITER_SECTORS(lines[i].args[0], s)
 					sectors[s].cullheight = &lines[i]; // This allows it to change in realtime!
 				break;
 
 			case 50: // Insta-Lower Sector
-				EV_DoFloor(&lines[i], instantLower);
+				if (!udmf)
+					EV_DoFloor(lines[i].args[0], &lines[i], instantLower);
 				break;
 
 			case 51: // Instant raise for ceilings
-				EV_DoCeiling(&lines[i], instantRaise);
+				if (!udmf)
+					EV_DoCeiling(lines[i].args[0], &lines[i], instantRaise);
 				break;
 
 			case 52: // Continuously Falling sector
-				EV_DoContinuousFall(lines[i].frontsector, lines[i].backsector, P_AproxDistance(lines[i].dx, lines[i].dy), (lines[i].flags & ML_NOCLIMB));
+				EV_DoContinuousFall(lines[i].frontsector, lines[i].backsector, lines[i].args[0] << FRACBITS, lines[i].args[1]);
 				break;
 
-			case 53: // New super cool and awesome moving floor and ceiling type
-			case 54: // New super cool and awesome moving floor type
-				if (lines[i].backsector)
-					EV_DoFloor(&lines[i], bounceFloor);
-				if (lines[i].special == 54)
-					break;
-				/* FALLTHRU */
-
-			case 55: // New super cool and awesome moving ceiling type
+			case 53: // Continuous plane movement (slowdown)
 				if (lines[i].backsector)
-					EV_DoCeiling(&lines[i], bounceCeiling);
+				{
+					if (lines[i].args[1] != TMP_CEILING)
+						EV_DoFloor(lines[i].args[0], &lines[i], bounceFloor);
+					if (lines[i].args[1] != TMP_FLOOR)
+						EV_DoCeiling(lines[i].args[0], &lines[i], bounceCeiling);
+				}
 				break;
 
-			case 56: // New super cool and awesome moving floor and ceiling crush type
-			case 57: // New super cool and awesome moving floor crush type
-				if (lines[i].backsector)
-					EV_DoFloor(&lines[i], bounceFloorCrush);
-
-				if (lines[i].special == 57)
-					break; //only move the floor
-				/* FALLTHRU */
-
-			case 58: // New super cool and awesome moving ceiling crush type
+			case 56: // Continuous plane movement (constant)
 				if (lines[i].backsector)
-					EV_DoCeiling(&lines[i], bounceCeilingCrush);
-				break;
-
-			case 59: // Activate floating platform
-				EV_DoElevator(&lines[i], elevateContinuous, false);
+				{
+					if (lines[i].args[1] != TMP_CEILING)
+						EV_DoFloor(lines[i].args[0], &lines[i], bounceFloorCrush);
+					if (lines[i].args[1] != TMP_FLOOR)
+						EV_DoCeiling(lines[i].args[0], &lines[i], bounceCeilingCrush);
+				}
 				break;
 
-			case 60: // Floating platform with adjustable speed
-				EV_DoElevator(&lines[i], elevateContinuous, true);
+			case 60: // Moving platform
+				EV_DoElevator(lines[i].args[0], &lines[i], elevateContinuous);
 				break;
 
 			case 61: // Crusher!
-				EV_DoCrush(&lines[i], crushAndRaise);
-				break;
-
-			case 62: // Crusher (up and then down)!
-				EV_DoCrush(&lines[i], fastCrushAndRaise);
+				EV_DoCrush(lines[i].args[0], &lines[i], lines[i].args[1] ? raiseAndCrush : crushAndRaise);
 				break;
 
 			case 63: // support for drawn heights coming from different sector
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				TAG_ITER_SECTORS(tag, s)
+				TAG_ITER_SECTORS(lines[i].args[0], s)
 					sectors[s].heightsec = (INT32)sec;
 				break;
 
 			case 64: // Appearing/Disappearing FOF option
-				if (lines[i].flags & ML_BLOCKMONSTERS) { // Find FOFs by control sector tag
-					TAG_ITER_SECTORS(tag, s)
-						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
+				if (lines[i].args[0] == 0) // Find FOFs by control sector tag
 				{
-					TAG_ITER_LINES(tag, s)
+					TAG_ITER_SECTORS(lines[i].args[1], s)
 					{
-						if ((size_t)s == i)
-							continue;
-						if (Tag_Find(&sides[lines[s].sidenum[0]].sector->tags, Tag_FGet(&sides[lines[i].sidenum[0]].sector->tags)))
-							Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), s, (INT32)i);
-					}
-				}
-				break;
-
-			case 66: // Displace floor by front sector
-				TAG_ITER_SECTORS(tag, s)
-					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
-				TAG_ITER_SECTORS(tag, s)
-					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
-				TAG_ITER_SECTORS(tag, s)
-					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;
-
-			case 100: // FOF (solid, opaque, shadows)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				break;
-
-			case 101: // FOF (solid, opaque, no shadows)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_CUTLEVEL, secthinkers);
-				break;
-
-			case 102: // TL block: FOF (solid, translucent)
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
-
-				// Draw the 'insides' of the block too
-				if (lines[i].flags & ML_NOCLIMB)
-				{
-					ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
-					ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
-				}
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 103: // Solid FOF with no floor/ceiling (quite possibly useless)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERSIDES|FF_NOSHADE|FF_CUTLEVEL, secthinkers);
-				break;
-
-			case 104: // 3D Floor type that doesn't draw sides
-				// If line has no-climb set, give it shadows, otherwise don't
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_CUTLEVEL;
-				if (!(lines[i].flags & ML_NOCLIMB))
-					ffloorflags |= FF_NOSHADE;
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 105: // FOF (solid, invisible)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_NOSHADE, secthinkers);
-				break;
-
-			case 120: // Opaque water
-				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_SWIMMABLE|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_DOUBLESHADOW;
-				if (lines[i].flags & ML_EFFECT4)
-					ffloorflags |= FF_COLORMAPONLY;
-				if (lines[i].flags & ML_EFFECT5)
-					ffloorflags |= FF_RIPPLE;
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 121: // TL water
-				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_SWIMMABLE|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_DOUBLESHADOW;
-				if (lines[i].flags & ML_EFFECT4)
-					ffloorflags |= FF_COLORMAPONLY;
-				if (lines[i].flags & ML_EFFECT5)
-					ffloorflags |= FF_RIPPLE;
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 122: // Opaque water, no sides
-				ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_DOUBLESHADOW;
-				if (lines[i].flags & ML_EFFECT4)
-					ffloorflags |= FF_COLORMAPONLY;
-				if (lines[i].flags & ML_EFFECT5)
-					ffloorflags |= FF_RIPPLE;
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 123: // TL water, no sides
-				ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_TRANSLUCENT|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_DOUBLESHADOW;
-				if (lines[i].flags & ML_EFFECT4)
-					ffloorflags |= FF_COLORMAPONLY;
-				if (lines[i].flags & ML_EFFECT5)
-					ffloorflags |= FF_RIPPLE;
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 124: // goo water
-				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_SWIMMABLE|FF_GOOWATER|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_DOUBLESHADOW;
-				if (lines[i].flags & ML_EFFECT4)
-					ffloorflags |= FF_COLORMAPONLY;
-				if (lines[i].flags & ML_EFFECT5)
-					ffloorflags |= FF_RIPPLE;
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 125: // goo water, no sides
-				ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_TRANSLUCENT|FF_SWIMMABLE|FF_GOOWATER|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_DOUBLESHADOW;
-				if (lines[i].flags & ML_EFFECT4)
-					ffloorflags |= FF_COLORMAPONLY;
-				if (lines[i].flags & ML_EFFECT5)
-					ffloorflags |= FF_RIPPLE;
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 140: // '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);
-				break;
-
-			case 141: // 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_EXTRA|FF_CUTEXTRA;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_NOSHADE;
+						for (j = 0; (unsigned)j < sectors[s].linecount; j++)
+						{
+							if (sectors[s].lines[j]->special < 100 || sectors[s].lines[j]->special >= 300)
+								continue;
 
-				// Draw the 'insides' of the block too
-				if (lines[i].flags & ML_EFFECT2)
-				{
-					ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
-					ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
+							Add_MasterDisappearer(abs(lines[i].args[2]), abs(lines[i].args[3]), abs(lines[i].args[4]), (INT32)(sectors[s].lines[j] - lines), (INT32)i);
+						}
+					}
 				}
+				else // Find FOFs by effect sector tag
+				{
+					TAG_ITER_LINES(lines[i].args[0], s)
+					{
+						if (lines[s].special < 100 || lines[s].special >= 300)
+							continue;
 
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 142: // Translucent "platform" with no sides
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_TRANSLUCENT|FF_PLATFORM|FF_EXTRA|FF_CUTEXTRA;
-				if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
-					ffloorflags |= FF_NOSHADE;
+						if (lines[i].args[1] != 0 && !Tag_Find(&lines[s].frontsector->tags, lines[i].args[1]))
+							continue;
 
-				// Draw the 'insides' of the block too
-				if (lines[i].flags & ML_EFFECT2)
-				{
-					ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
-					ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
+						Add_MasterDisappearer(abs(lines[i].args[2]), abs(lines[i].args[3]), abs(lines[i].args[4]), s, (INT32)i);
+					}
 				}
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
 				break;
 
-			case 143: // 'Reverse platform' - You fall through it
-				// If line has no-climb set, don't give it shadows, otherwise do
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_REVERSEPLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_NOSHADE;
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
+			case 66: // Displace planes by front sector
+				TAG_ITER_SECTORS(lines[i].args[0], s)
+					P_AddPlaneDisplaceThinker(lines[i].args[1], abs(lines[i].args[2])<<8, sides[lines[i].sidenum[0]].sector-sectors, s, lines[i].args[2] < 0);
 				break;
 
-			case 144: // Translucent "reverse platform"
-				// If line has no-climb set, don't give it shadows, otherwise do
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_REVERSEPLATFORM|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_NOSHADE;
-
-				// Draw the 'insides' of the block too
-				if (lines[i].flags & ML_EFFECT2)
+			case 70: // Add raise thinker to FOF
+				if (udmf)
 				{
-					ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
-					ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
-				}
+					fixed_t destheight = lines[i].args[2] << FRACBITS;
+					fixed_t startheight, topheight, bottomheight;
 
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
+					TAG_ITER_LINES(lines[i].args[0], l)
+					{
+						if (lines[l].special < 100 || lines[l].special >= 300)
+							continue;
 
-			case 145: // Translucent "reverse platform" with no sides
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_TRANSLUCENT|FF_REVERSEPLATFORM|FF_EXTRA|FF_CUTEXTRA;
-				if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
-					ffloorflags |= FF_NOSHADE;
+						startheight = lines[l].frontsector->ceilingheight;
+						topheight = max(startheight, destheight);
+						bottomheight = min(startheight, destheight);
 
-				// Draw the 'insides' of the block too
-				if (lines[i].flags & ML_EFFECT2)
-				{
-					ffloorflags |= FF_CUTLEVEL|FF_BOTHPLANES|FF_ALLSIDES;
-					ffloorflags &= ~(FF_EXTRA|FF_CUTEXTRA);
+						P_AddRaiseThinker(lines[l].frontsector, lines[l].args[0], lines[i].args[1] << FRACBITS, topheight, bottomheight, (destheight < startheight), !!(lines[i].args[3]));
+					}
 				}
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
 				break;
 
-			case 146: // Intangible floor/ceiling with solid sides (fences/hoops maybe?)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERSIDES|FF_ALLSIDES|FF_INTANGIBLEFLATS, secthinkers);
-				break;
+			case 71: // Add air bob thinker to FOF
+				if (udmf)
+				{
+					TAG_ITER_LINES(lines[i].args[0], l)
+					{
+						if (lines[l].special < 100 || lines[l].special >= 300)
+							continue;
 
-			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);
-				P_AddAirbob(lines[i].frontsector, 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, 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);
-				P_AddAirbob(lines[i].frontsector, tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
+						P_AddAirbob(lines[l].frontsector, lines[l].args[0], lines[i].args[1] << FRACBITS, !!(lines[i].args[2] & TMFB_REVERSE), !!(lines[i].args[2] & TMFB_SPINDASH), !!(lines[i].args[2] & TMFB_DYNAMIC));
+					}
+				}
 				break;
 
-			case 160: // Float/bob platform
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB, secthinkers);
-				break;
+			case 72: // Add thwomp thinker to FOF
+				if (udmf)
+				{
+					UINT16 sound = (lines[i].stringargs[0]) ? get_number(lines[i].stringargs[0]) : sfx_thwomp;
 
-			case 170: // Crumbling platform
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
-				break;
+					TAG_ITER_LINES(lines[i].args[0], l)
+					{
+						if (lines[l].special < 100 || lines[l].special >= 300)
+							continue;
 
-			case 171: // Crumbling platform that will not return
-				P_AddFakeFloorsByLine(i,
-					FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE|FF_NORETURN, secthinkers);
+						P_AddThwompThinker(lines[l].frontsector, &lines[l], lines[i].args[1] << (FRACBITS - 3), lines[i].args[2] << (FRACBITS - 3), sound);
+					}
+				}
 				break;
 
-			case 172: // "Platform" that crumbles and returns
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_CRUMBLE|FF_BOTHPLANES|FF_ALLSIDES;
-				if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
-					ffloorflags |= FF_NOSHADE;
+			case 73: // Add laser thinker to FOF
+				if (udmf)
+				{
+					TAG_ITER_LINES(lines[i].args[0], l)
+					{
+						if (lines[l].special < 100 || lines[l].special >= 300)
+							continue;
 
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
+						P_AddLaserThinker(lines[l].args[0], lines + l, !!(lines[i].args[1]));
+					}
+				}
 				break;
 
-			case 173: // "Platform" that crumbles and doesn't return
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_CRUMBLE|FF_NORETURN|FF_BOTHPLANES|FF_ALLSIDES;
-				if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
-					ffloorflags |= FF_NOSHADE;
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
+			case 100: // FOF (solid)
+				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL;
 
-			case 174: // Translucent "platform" that crumbles and returns
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES;
-				if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
+				//Appearance settings
+				if (lines[i].args[3] & TMFA_NOPLANES)
+					ffloorflags &= ~FF_RENDERPLANES;
+				if (lines[i].args[3] & TMFA_NOSIDES)
+					ffloorflags &= ~FF_RENDERSIDES;
+				if (lines[i].args[3] & TMFA_INSIDES)
+				{
+					if (ffloorflags & FF_RENDERPLANES)
+						ffloorflags |= FF_BOTHPLANES;
+					if (ffloorflags & FF_RENDERSIDES)
+						ffloorflags |= FF_ALLSIDES;
+				}
+				if (lines[i].args[3] & TMFA_ONLYINSIDES)
+				{
+					if (ffloorflags & FF_RENDERPLANES)
+						ffloorflags |= FF_INVERTPLANES;
+					if (ffloorflags & FF_RENDERSIDES)
+						ffloorflags |= FF_INVERTSIDES;
+				}
+				if (lines[i].args[3] & TMFA_NOSHADE)
 					ffloorflags |= FF_NOSHADE;
+				if (lines[i].args[3] & TMFA_SPLAT)
+					ffloorflags |= FF_SPLAT;
+
+				//Tangibility settings
+				if (lines[i].args[4] & TMFT_INTANGIBLETOP)
+					ffloorflags |= FF_REVERSEPLATFORM;
+				if (lines[i].args[4] & TMFT_INTANGIBLEBOTTOM)
+					ffloorflags |= FF_PLATFORM;
+				if (lines[i].args[4] & TMFT_DONTBLOCKPLAYER)
+					ffloorflags &= ~FF_BLOCKPLAYER;
+				if (lines[i].args[4] & TMFT_DONTBLOCKOTHERS)
+					ffloorflags &= ~FF_BLOCKOTHERS;
+
+				//Cutting options
+				if (ffloorflags & FF_RENDERALL)
+				{
+					//If translucent or player can enter it, cut inner walls
+					if ((lines[i].args[1] < 255) || (lines[i].args[4] & TMFT_VISIBLEFROMINSIDE))
+						ffloorflags |= FF_CUTEXTRA|FF_EXTRA;
+					else
+						ffloorflags |= FF_CUTLEVEL;
+				}
 
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 				break;
 
-			case 175: // Translucent "platform" that crumbles and doesn't return
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_NORETURN|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES;
-				if (lines[i].flags & ML_NOCLIMB) // shade it unless no-climb
-					ffloorflags |= FF_NOSHADE;
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
+			case 120: // FOF (water)
+				ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
+				if (!(lines[i].args[3] & TMFW_NOSIDES))
+					ffloorflags |= FF_RENDERSIDES|FF_ALLSIDES;
+				if (lines[i].args[3] & TMFW_DOUBLESHADOW)
+					ffloorflags |= FF_DOUBLESHADOW;
+				if (lines[i].args[3] & TMFW_COLORMAPONLY)
+					ffloorflags |= FF_COLORMAPONLY;
+				if (!(lines[i].args[3] & TMFW_NORIPPLE))
+					ffloorflags |= FF_RIPPLE;
+				if (lines[i].args[3] & TMFW_GOOWATER)
+					ffloorflags |= FF_GOOWATER;
+				if (lines[i].args[3] & TMFW_SPLAT)
+					ffloorflags |= FF_SPLAT;
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 				break;
 
-			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);
-				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+			case 150: // FOF (Air bobbing)
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, FF_EXISTS|FF_SOLID|FF_RENDERALL, secthinkers);
+				P_AddAirbob(lines[i].frontsector, lines[i].args[0], lines[i].args[1] << FRACBITS, !!(lines[i].args[2] & TMFB_REVERSE), !!(lines[i].args[2] & TMFB_SPINDASH), !!(lines[i].args[2] & TMFB_DYNAMIC));
 				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);
-				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+			case 160: // FOF (Water bobbing)
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB, secthinkers);
 				break;
 
-			case 178: // Crumbling platform that will float when it hits water
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CRUMBLE|FF_FLOATBOB, secthinkers);
-				break;
+			case 170: // FOF (Crumbling)
+				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CRUMBLE;
 
-			case 179: // Crumbling platform that will float when it hits water, but not return
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE|FF_FLOATBOB|FF_NORETURN, secthinkers);
-				break;
+				//Tangibility settings
+				if (lines[i].args[3] & TMFT_INTANGIBLETOP)
+					ffloorflags |= FF_REVERSEPLATFORM;
+				if (lines[i].args[3] & TMFT_INTANGIBLEBOTTOM)
+					ffloorflags |= FF_PLATFORM;
+				if (lines[i].args[3] & TMFT_DONTBLOCKPLAYER)
+					ffloorflags &= ~FF_BLOCKPLAYER;
+				if (lines[i].args[3] & TMFT_DONTBLOCKOTHERS)
+					ffloorflags &= ~FF_BLOCKOTHERS;
+
+				//Flags
+				if (lines[i].args[4] & TMFC_NOSHADE)
+					ffloorflags |= FF_NOSHADE;
+				if (lines[i].args[4] & TMFC_NORETURN)
+					ffloorflags |= FF_NORETURN;
+				if (lines[i].args[4] & TMFC_FLOATBOB)
+					ffloorflags |= FF_FLOATBOB;
+				if (lines[i].args[4] & TMFC_SPLAT)
+					ffloorflags |= FF_SPLAT;
+
+				//If translucent or player can enter it, cut inner walls
+				if (lines[i].args[1] < 0xff || (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE))
+					ffloorflags |= FF_CUTEXTRA|FF_EXTRA;
+				else
+					ffloorflags |= FF_CUTLEVEL;
+
+				//If player can enter it, render insides
+				if (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE)
+				{
+					if (ffloorflags & FF_RENDERPLANES)
+						ffloorflags |= FF_BOTHPLANES;
+					if (ffloorflags & FF_RENDERSIDES)
+						ffloorflags |= FF_ALLSIDES;
+				}
 
-			case 180: // Air bobbing platform that will crumble
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
-				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
+				if (lines[i].args[4] & TMFC_AIRBOB)
+					P_AddAirbob(lines[i].frontsector, lines[i].args[0], 16*FRACUNIT, false, false, false);
 				break;
 
-			case 190: // Rising Platform FOF (solid, opaque, shadows)
-			case 191: // Rising Platform FOF (solid, opaque, no shadows)
-			case 192: // Rising Platform TL block: FOF (solid, translucent)
-			case 193: // Rising Platform FOF (solid, invisible)
-			case 194: // Rising Platform 'Platform' - You can jump up through it
-			case 195: // Rising Platform Translucent "platform"
+			case 190: // FOF (Rising)
 			{
-				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_EXISTS|FF_SOLID|FF_RENDERALL;
+
+				//Appearance settings
+				if (lines[i].args[3] & TMFA_NOPLANES)
+					ffloorflags &= ~FF_RENDERPLANES;
+				if (lines[i].args[3] & TMFA_NOSIDES)
+					ffloorflags &= ~FF_RENDERSIDES;
+				if (lines[i].args[3] & TMFA_INSIDES)
+				{
+					if (ffloorflags & FF_RENDERPLANES)
+						ffloorflags |= FF_BOTHPLANES;
+					if (ffloorflags & FF_RENDERSIDES)
+						ffloorflags |= FF_ALLSIDES;
+				}
+				if (lines[i].args[3] & TMFA_ONLYINSIDES)
+				{
+					if (ffloorflags & FF_RENDERPLANES)
+						ffloorflags |= FF_INVERTPLANES;
+					if (ffloorflags & FF_RENDERSIDES)
+						ffloorflags |= FF_INVERTSIDES;
+				}
+				if (lines[i].args[3] & TMFA_NOSHADE)
 					ffloorflags |= FF_NOSHADE;
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
+				if (lines[i].args[3] & TMFA_SPLAT)
+					ffloorflags |= FF_SPLAT;
+
+				//Tangibility settings
+				if (lines[i].args[4] & TMFT_INTANGIBLETOP)
+					ffloorflags |= FF_REVERSEPLATFORM;
+				if (lines[i].args[4] & TMFT_INTANGIBLEBOTTOM)
+					ffloorflags |= FF_PLATFORM;
+				if (lines[i].args[4] & TMFT_DONTBLOCKPLAYER)
+					ffloorflags &= ~FF_BLOCKPLAYER;
+				if (lines[i].args[4] & TMFT_DONTBLOCKOTHERS)
+					ffloorflags &= ~FF_BLOCKOTHERS;
+
+				//Cutting options
+				if (ffloorflags & FF_RENDERALL)
+				{
+					//If translucent or player can enter it, cut inner walls
+					if ((lines[i].args[1] < 255) || (lines[i].args[4] & TMFT_VISIBLEFROMINSIDE))
+						ffloorflags |= FF_CUTEXTRA|FF_EXTRA;
+					else
+						ffloorflags |= FF_CUTLEVEL;
+				}
 
-				P_AddRaiseThinker(lines[i].frontsector, tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
+				P_AddRaiseThinker(lines[i].frontsector, lines[i].args[0], lines[i].args[5] << FRACBITS, ceilingtop, ceilingbottom, !!(lines[i].args[6] & TMFR_REVERSE), !!(lines[i].args[6] & TMFR_SPINDASH));
 				break;
 			}
-
-			case 200: // Double light effect
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_CUTSPRITES|FF_DOUBLESHADOW, secthinkers);
-				break;
-
-			case 201: // Light effect
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_CUTSPRITES, secthinkers);
+			case 200: // Light block
+				ffloorflags = FF_EXISTS|FF_CUTSPRITES;
+				if (!lines[i].args[1])
+					ffloorflags |= FF_DOUBLESHADOW;
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, ffloorflags, secthinkers);
 				break;
 
 			case 202: // Fog
@@ -6689,136 +6647,274 @@ void P_SpawnSpecials(boolean fromnetsave)
 				// SoM: Because it's fog, check for an extra colormap and set the fog flag...
 				if (sectors[sec].extra_colormap)
 					sectors[sec].extra_colormap->flags = CMF_FOG;
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, ffloorflags, secthinkers);
 				break;
 
-			case 220: // Like opaque water, but not swimmable. (Good for snow effect on FOFs)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES, secthinkers);
-				break;
+			case 220: //Intangible
+				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
 
-			case 221: // FOF (intangible, translucent)
-				// If line has no-climb set, give it shadows, otherwise don't
-				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA|FF_CUTSPRITES;
-				if (!(lines[i].flags & ML_NOCLIMB))
+				//Appearance settings
+				if (lines[i].args[3] & TMFA_NOPLANES)
+					ffloorflags &= ~FF_RENDERPLANES;
+				if (lines[i].args[3] & TMFA_NOSIDES)
+					ffloorflags &= ~FF_RENDERSIDES;
+				if (!(lines[i].args[3] & TMFA_INSIDES))
+				{
+					if (ffloorflags & FF_RENDERPLANES)
+						ffloorflags |= FF_BOTHPLANES;
+					if (ffloorflags & FF_RENDERSIDES)
+						ffloorflags |= FF_ALLSIDES;
+				}
+				if (lines[i].args[3] & TMFA_ONLYINSIDES)
+				{
+					if (ffloorflags & FF_RENDERPLANES)
+						ffloorflags |= FF_INVERTPLANES;
+					if (ffloorflags & FF_RENDERSIDES)
+						ffloorflags |= FF_INVERTSIDES;
+				}
+				if (lines[i].args[3] & TMFA_NOSHADE)
 					ffloorflags |= FF_NOSHADE;
+				if (lines[i].args[3] & TMFA_SPLAT)
+					ffloorflags |= FF_SPLAT;
 
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
-
-			case 222: // FOF with no floor/ceiling (good for GFZGRASS effect on FOFs)
-				// If line has no-climb set, give it shadows, otherwise don't
-				ffloorflags = FF_EXISTS|FF_RENDERSIDES|FF_ALLSIDES;
-				if (!(lines[i].flags & ML_NOCLIMB))
-					ffloorflags |= FF_NOSHADE|FF_CUTSPRITES;
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 				break;
 
 			case 223: // FOF (intangible, invisible) - for combining specials in a sector
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_NOSHADE, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, FF_EXISTS|FF_NOSHADE, secthinkers);
 				break;
 
 			case 250: // Mario Block
 				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_MARIO;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_SHATTERBOTTOM;
-				if (lines[i].flags & ML_EFFECT1)
+				if (lines[i].args[1] & TMFM_BRICK)
+					ffloorflags |= FF_GOOWATER;
+				if (lines[i].args[1] & TMFM_INVISIBLE)
 					ffloorflags &= ~(FF_SOLID|FF_RENDERALL|FF_CUTLEVEL);
 
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, ffloorflags, secthinkers);
 				break;
 
 			case 251: // A THWOMP!
 			{
-				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], crushspeed, retractspeed, sound);
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
+				UINT16 sound = (lines[i].stringargs[0]) ? get_number(lines[i].stringargs[0]) : sfx_thwomp;
+				P_AddThwompThinker(lines[i].frontsector, &lines[i], lines[i].args[1] << (FRACBITS - 3), lines[i].args[2] << (FRACBITS - 3), sound);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, 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;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_BLOCKPLAYER|FF_SHATTERBOTTOM;
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
+			case 254: // Bustable block
+			{
+				UINT8 busttype = BT_REGULAR;
+				ffloorbustflags_e bustflags = 0;
 
-			case 253: // Translucent shatter block (see 76)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_BLOCKOTHERS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER|FF_TRANSLUCENT, secthinkers);
-				break;
+				ffloorflags = FF_EXISTS|FF_BLOCKOTHERS|FF_RENDERALL|FF_BUSTUP;
 
-			case 254: // Bustable block
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_STRONGBUST;
+				//Bustable type
+				switch (lines[i].args[3])
+				{
+					case TMFB_TOUCH:
+						busttype = BT_TOUCH;
+						break;
+					case TMFB_SPIN:
+						busttype = BT_SPINBUST;
+						break;
+					case TMFB_REGULAR:
+						busttype = BT_REGULAR;
+						break;
+					case TMFB_STRONG:
+						busttype = BT_STRONG;
+						break;
+				}
 
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				break;
+				//Flags
+				if (lines[i].args[4] & TMFB_PUSHABLES)
+					bustflags |= FB_PUSHABLES;
+				if (lines[i].args[4] & TMFB_EXECUTOR)
+					bustflags |= FB_EXECUTOR;
+				if (lines[i].args[4] & TMFB_ONLYBOTTOM)
+					bustflags |= FB_ONLYBOTTOM;
+				if (lines[i].args[4] & TMFB_SPLAT)
+					ffloorflags |= FF_SPLAT;
 
-			case 255: // Spin bust block (breaks when jumped or spun downwards onto)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SPINBUST, secthinkers);
-				break;
+				if (busttype != BT_TOUCH || bustflags & FB_ONLYBOTTOM)
+					ffloorflags |= FF_BLOCKPLAYER;
 
-			case 256: // Translucent spin bust block (see 78)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SPINBUST|FF_TRANSLUCENT, secthinkers);
+				TAG_ITER_SECTORS(lines[i].args[0], s)
+				{
+					ffloor_t *fflr = P_AddFakeFloor(&sectors[s], lines[i].frontsector, lines + i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
+					if (!fflr)
+						continue;
+					fflr->bustflags = bustflags;
+					fflr->busttype = busttype;
+					fflr->busttag = lines[i].args[5];
+				}
 				break;
-
+			}
 			case 257: // Quicksand
 				ffloorflags = FF_EXISTS|FF_QUICKSAND|FF_RENDERALL|FF_ALLSIDES|FF_CUTSPRITES;
-				if (lines[i].flags & ML_EFFECT5)
+				if (!(lines[i].args[1]))
 					ffloorflags |= FF_RIPPLE;
 
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
+				TAG_ITER_SECTORS(lines[i].args[0], s)
+				{
+					ffloor_t *fflr = P_AddFakeFloor(&sectors[s], lines[i].frontsector, lines + i, 0xff, TMB_TRANSLUCENT, ffloorflags, secthinkers);
+					if (!fflr)
+						continue;
+					fflr->sinkspeed = abs(lines[i].args[2]) << (FRACBITS - 1);
+					fflr->friction = abs(lines[i].args[3]) << (FRACBITS - 6);
+				}
 				break;
 
 			case 258: // Laser block
-				P_AddLaserThinker(tag, lines + i, !!(lines[i].flags & ML_EFFECT1));
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA|FF_TRANSLUCENT, secthinkers);
+				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA|FF_TRANSLUCENT;
+				P_AddLaserThinker(lines[i].args[0], lines + i, !!(lines[i].args[3] & TMFL_NOBOSSES));
+				if (lines[i].args[3] & TMFL_SPLAT)
+					ffloorflags |= FF_SPLAT;
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 				break;
 
 			case 259: // Custom FOF
-				if (lines[i].sidenum[1] != 0xffff)
+				TAG_ITER_SECTORS(lines[i].args[0], s)
 				{
-					ffloortype_e fofflags = sides[lines[i].sidenum[1]].toptexture;
-					P_AddFakeFloorsByLine(i, fofflags, secthinkers);
+					ffloor_t *fflr = P_AddFakeFloor(&sectors[s], lines[i].frontsector, lines + i, lines[i].args[1], lines[i].args[2], lines[i].args[3], secthinkers);
+					if (!fflr)
+						continue;
+					if (!udmf) // Ugly backwards compatibility stuff
+					{
+						if (lines[i].args[3] & FF_QUICKSAND)
+						{
+							fflr->sinkspeed = abs(lines[i].dx) >> 1;
+							fflr->friction = abs(lines[i].dy) >> 6;
+						}
+						if (lines[i].args[3] & FF_BUSTUP)
+						{
+							switch (lines[i].args[4] % TMFB_ONLYBOTTOM)
+							{
+								case TMFB_TOUCH:
+									fflr->busttype = BT_TOUCH;
+									break;
+								case TMFB_SPIN:
+									fflr->busttype = BT_SPINBUST;
+									break;
+								case TMFB_REGULAR:
+									fflr->busttype = BT_REGULAR;
+									break;
+								case TMFB_STRONG:
+									fflr->busttype = BT_STRONG;
+									break;
+							}
+
+							if (lines[i].args[4] & TMFB_ONLYBOTTOM)
+								fflr->bustflags |= FB_ONLYBOTTOM;
+							if (lines[i].flags & ML_MIDSOLID)
+								fflr->bustflags |= FB_PUSHABLES;
+							if (lines[i].flags & ML_WRAPMIDTEX)
+							{
+								fflr->bustflags |= FB_EXECUTOR;
+								fflr->busttag = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+							}
+						}
+					}
 				}
-				else
-					I_Error("Custom FOF (tag %d) found without a linedef back side!", tag);
 				break;
 
-			case 300: // Linedef executor (combines with sector special 974/975) and commands
-			case 302:
-			case 303:
-			case 304:
+			case 260: // GZDoom-like 3D Floor.
+				{
+					UINT8 dtype = lines[i].args[1] & 3;
+					UINT8 dflags1 = lines[i].args[1] - dtype;
+					UINT8 dflags2 = lines[i].args[2];
+					UINT8 dopacity = lines[i].args[3];
+					boolean isfog = false;
+
+					if (dtype == 0)
+						dtype = 1;
+
+					ffloorflags = FF_EXISTS;
+
+					if (dflags2 & 1) ffloorflags |= FF_NOSHADE; // Disable light effects (Means no shadowcast)
+					if (dflags2 & 2) ffloorflags |= FF_DOUBLESHADOW; // Restrict light inside (Means doubleshadow)
+					if (dflags2 & 4) isfog = true; // Fog effect (Explicitly render like a fog block)
+
+					if (dflags1 & 4) ffloorflags |= FF_BOTHPLANES|FF_ALLSIDES; // Render-inside
+					if (dflags1 & 16) ffloorflags |= FF_INVERTSIDES|FF_INVERTPLANES; // Invert visibility rules
+
+					// Fog block
+					if (isfog)
+						ffloorflags |= FF_RENDERALL|FF_CUTEXTRA|FF_CUTSPRITES|FF_BOTHPLANES|FF_EXTRA|FF_FOG|FF_INVERTPLANES|FF_ALLSIDES|FF_INVERTSIDES;
+					else
+					{
+						ffloorflags |= FF_RENDERALL;
+
+						// Solid
+						if (dtype == 1)
+							ffloorflags |= FF_SOLID|FF_CUTLEVEL;
+						// Water
+						else if (dtype == 2)
+							ffloorflags |= FF_SWIMMABLE|FF_CUTEXTRA|FF_CUTSPRITES|FF_EXTRA|FF_RIPPLE;
+						// Intangible
+						else if (dtype == 3)
+							ffloorflags |= FF_CUTEXTRA|FF_CUTSPRITES|FF_EXTRA;
+					}
+
+					// Non-opaque
+					if (dopacity < 255)
+					{
+						// Invisible
+						if (dopacity == 0)
+						{
+							// True invisible
+							if (ffloorflags & FF_NOSHADE)
+								ffloorflags &= ~(FF_RENDERALL|FF_CUTEXTRA|FF_CUTSPRITES|FF_EXTRA|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTLEVEL);
+							// Shadow block
+							else
+							{
+								ffloorflags |= FF_CUTSPRITES;
+								ffloorflags &= ~(FF_RENDERALL|FF_CUTEXTRA|FF_EXTRA|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTLEVEL);
+							}
+						}
+						else
+						{
+							ffloorflags |= FF_TRANSLUCENT|FF_CUTEXTRA|FF_EXTRA;
+							ffloorflags &= ~FF_CUTLEVEL;
+						}
+					}
+
+					P_AddFakeFloorsByLine(i, dopacity, TMB_TRANSLUCENT, ffloorflags, secthinkers);
+				}
+				break;
 
-			// Charability linedef executors
-			case 305:
-			case 307:
+			case 300: // Trigger linedef executor
+			case 303: // Count rings
+			case 305: // Character ability
+			case 314: // Pushable linedef executors (count # of pushables)
+			case 317: // Condition set trigger
+			case 319: // Unlockable trigger
+			case 331: // Player skin
+			case 334: // Object dye
+			case 337: // Emerald check
+				if (lines[i].args[0] > TMT_EACHTIMEMASK)
+					P_AddEachTimeThinker(&lines[i], lines[i].args[0] == TMT_EACHTIMEENTERANDEXIT);
 				break;
 
 			case 308: // Race-only linedef executor. Triggers once.
-				if (!(gametyperules & GTR_RACE))
+				if (!P_CheckGametypeRules(lines[i].args[2], (UINT32)lines[i].args[1]))
+				{
 					lines[i].special = 0;
+					break;
+				}
+				if (lines[i].args[0] > TMT_EACHTIMEMASK)
+					P_AddEachTimeThinker(&lines[i], lines[i].args[0] == TMT_EACHTIMEENTERANDEXIT);
 				break;
 
 			// Linedef executor triggers for CTF teams.
 			case 309:
-			case 311:
 				if (!(gametyperules & GTR_TEAMFLAGS))
+				{
 					lines[i].special = 0;
-				break;
-
-			// Each time executors
-			case 306:
-			case 301:
-			case 310:
-			case 312:
-			case 332:
-			case 335:
-				P_AddEachTimeThinker(&lines[i]);
+					break;
+				}
+				if (lines[i].args[0] > TMT_EACHTIMEMASK)
+					P_AddEachTimeThinker(&lines[i], lines[i].args[0] == TMT_EACHTIMEENTERANDEXIT);
 				break;
 
 			// No More Enemies Linedef Exec
@@ -6826,100 +6922,24 @@ void P_SpawnSpecials(boolean fromnetsave)
 				P_AddNoEnemiesThinker(&lines[i]);
 				break;
 
-			// Pushable linedef executors (count # of pushables)
-			case 314:
-			case 315:
-				break;
-
-			// Unlock trigger executors
-			case 317:
-			case 318:
-				break;
-			case 319:
-			case 320:
-				break;
-
 			// Trigger on X calls
 			case 321:
-			case 322:
-				if (lines[i].flags & ML_NOCLIMB && sides[lines[i].sidenum[0]].rowoffset > 0) // optional "starting" count
-					lines[i].callcount = sides[lines[i].sidenum[0]].rowoffset>>FRACBITS;
-				else
-					lines[i].callcount = sides[lines[i].sidenum[0]].textureoffset>>FRACBITS;
-				if (lines[i].special == 322) // Each time
-					P_AddEachTimeThinker(&lines[i]);
-				break;
-
-			// NiGHTS trigger executors
-			case 323:
-			case 324:
-			case 325:
-			case 326:
-			case 327:
-			case 328:
-			case 329:
-			case 330:
-				break;
-
-			// Skin trigger executors
-			case 331:
-			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;
-
-			case 400:
-			case 401:
-			case 402:
-			case 403:
-			case 404:
-			case 405:
-			case 406:
-			case 407:
-			case 408:
-			case 409:
-			case 410:
-			case 411:
-			case 412:
-			case 413:
-			case 414:
-			case 415:
-			case 416:
-			case 417:
-			case 418:
-			case 419:
-			case 420:
-			case 421:
-			case 422:
-			case 423:
-			case 424:
-			case 425:
-			case 426:
-			case 427:
-			case 428:
-			case 429:
-			case 430:
-			case 431:
+				lines[i].callcount = (lines[i].args[2] && lines[i].args[3] > 0) ? lines[i].args[3] : lines[i].args[1]; // optional "starting" count
+				if (lines[i].args[0] > TMXT_EACHTIMEMASK) // Each time
+					P_AddEachTimeThinker(&lines[i], lines[i].args[0] == TMXT_EACHTIMEENTERANDEXIT);
 				break;
 
 			case 449: // Enable bosses with parameter
 			{
-				INT32 bossid = sides[*lines[i].sidenum].textureoffset>>FRACBITS;
+				INT32 bossid = lines[i].args[0];
 				if (bossid & ~15) // if any bits other than first 16 are set
 				{
 					CONS_Alert(CONS_WARNING,
-						M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
-						tag);
+						M_GetText("Boss enable linedef has an invalid boss ID (%d).\nConsider changing it or removing it entirely.\n"),
+						bossid);
 					break;
 				}
-				if (!(lines[i].flags & ML_NOCLIMB))
+				if (!(lines[i].args[1]))
 				{
 					bossdisabled |= (1<<bossid); // gotta disable in the first place to enable
 					CONS_Debug(DBG_GAMELOGIC, "Line type 449 spawn effect: bossid disabled = %d", bossid);
@@ -6927,116 +6947,191 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 			}
 
-			// 500 is used for a scroller
-			// 501 is used for a scroller
-			// 502 is used for a scroller
-			// 503 is used for a scroller
-			// 504 is used for a scroller
-			// 505 is used for a scroller
-			// 506 is used for a scroller
-			// 507 is used for a scroller
-			// 508 is used for a scroller
-			// 510 is used for a scroller
-			// 511 is used for a scroller
-			// 512 is used for a scroller
-			// 513 is used for a scroller
-			// 514 is used for a scroller
-			// 515 is used for a scroller
-			// 520 is used for a scroller
-			// 521 is used for a scroller
-			// 522 is used for a scroller
-			// 523 is used for a scroller
-			// 524 is used for a scroller
-			// 525 is used for a scroller
-			// 530 is used for a scroller
-			// 531 is used for a scroller
-			// 532 is used for a scroller
-			// 533 is used for a scroller
-			// 534 is used for a scroller
-			// 535 is used for a scroller
-			// 540 is used for friction
-			// 541 is used for wind
-			// 542 is used for upwards wind
-			// 543 is used for downwards wind
-			// 544 is used for current
-			// 545 is used for upwards current
-			// 546 is used for downwards current
-			// 547 is used for push/pull
-
-			case 600: // floor lighting independently (e.g. lava)
-				sec = sides[*lines[i].sidenum].sector-sectors;
-				TAG_ITER_SECTORS(tag, s)
-					sectors[s].floorlightsec = (INT32)sec;
-				break;
-
-			case 601: // ceiling lighting independently
+			case 600: // Copy light level to tagged sector's planes
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				TAG_ITER_SECTORS(tag, s)
-					sectors[s].ceilinglightsec = (INT32)sec;
+				TAG_ITER_SECTORS(lines[i].args[0], s)
+				{
+					if (lines[i].args[1] != TMP_CEILING)
+						sectors[s].floorlightsec = (INT32)sec;
+					if (lines[i].args[1] != TMP_FLOOR)
+						sectors[s].ceilinglightsec = (INT32)sec;
+				}
 				break;
 
 			case 602: // Adjustable pulsating light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				TAG_ITER_SECTORS(tag, s)
-					P_SpawnAdjustableGlowingLight(&sectors[sec], &sectors[s],
-						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
+				TAG_ITER_SECTORS(lines[i].args[0], s)
+					P_SpawnAdjustableGlowingLight(&sectors[s], lines[i].args[2],
+						lines[i].args[3] ? sectors[s].lightlevel : lines[i].args[4], lines[i].args[1]);
 				break;
 
 			case 603: // Adjustable flickering light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				TAG_ITER_SECTORS(tag, s)
-					P_SpawnAdjustableFireFlicker(&sectors[sec], &sectors[s],
-						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
+				TAG_ITER_SECTORS(lines[i].args[0], s)
+					P_SpawnAdjustableFireFlicker(&sectors[s], lines[i].args[2],
+						lines[i].args[3] ? sectors[s].lightlevel : lines[i].args[4], lines[i].args[1]);
 				break;
 
-			case 604: // Adjustable Blinking Light (unsynchronized)
+			case 604: // Adjustable Blinking Light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				TAG_ITER_SECTORS(tag, s)
-					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
-						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, false);
+				TAG_ITER_SECTORS(lines[i].args[0], s)
+					P_SpawnAdjustableStrobeFlash(&sectors[s], lines[i].args[3],
+						(lines[i].args[4] & TMB_USETARGET) ? sectors[s].lightlevel : lines[i].args[5],
+						lines[i].args[1], lines[i].args[2], lines[i].args[4] & TMB_SYNC);
 				break;
 
-			case 605: // Adjustable Blinking Light (synchronized)
-				sec = sides[*lines[i].sidenum].sector - sectors;
-				TAG_ITER_SECTORS(tag, s)
-					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
-						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, true);
+			case 606: // HACK! Copy colormaps. Just plain colormaps.
+				TAG_ITER_SECTORS(lines[i].args[0], s)
+				{
+					extracolormap_t *exc;
+
+					if (sectors[s].colormap_protected)
+						continue;
+
+					if (!udmf)
+						exc = sides[lines[i].sidenum[0]].colormap_data;
+					else
+					{
+						if (!lines[i].args[1])
+							exc = lines[i].frontsector->extra_colormap;
+						else
+						{
+							INT32 sourcesec = Tag_Iterate_Sectors(lines[i].args[1], 0);
+							if (sourcesec == -1)
+							{
+								CONS_Debug(DBG_GAMELOGIC, "Line type 606: Can't find sector with source colormap (tag %d)!\n", lines[i].args[1]);
+								return;
+							}
+							exc = sectors[sourcesec].extra_colormap;
+						}
+					}
+					sectors[s].extra_colormap = sectors[s].spawn_extra_colormap = exc;
+				}
+				break;
+
+			default:
+				break;
+		}
+	}
+
+	// And another round, this time with all FOFs already created
+	for (i = 0; i < numlines; i++)
+	{
+		switch (lines[i].special)
+		{
+			INT32 s;
+			INT32 l;
+
+			case 74: // Make FOF bustable
+			{
+				UINT8 busttype = BT_REGULAR;
+				ffloorbustflags_e bustflags = 0;
+
+				if (!udmf)
+					break;
+
+				switch (lines[i].args[1])
+				{
+					case TMFB_TOUCH:
+						busttype = BT_TOUCH;
+						break;
+					case TMFB_SPIN:
+						busttype = BT_SPINBUST;
+						break;
+					case TMFB_REGULAR:
+						busttype = BT_REGULAR;
+						break;
+					case TMFB_STRONG:
+						busttype = BT_STRONG;
+						break;
+				}
+
+				if (lines[i].args[2] & TMFB_PUSHABLES)
+					bustflags |= FB_PUSHABLES;
+				if (lines[i].args[2] & TMFB_EXECUTOR)
+					bustflags |= FB_EXECUTOR;
+				if (lines[i].args[2] & TMFB_ONLYBOTTOM)
+					bustflags |= FB_ONLYBOTTOM;
+
+				TAG_ITER_LINES(lines[i].args[0], l)
+				{
+					if (lines[l].special < 100 || lines[l].special >= 300)
+						continue;
+
+					TAG_ITER_SECTORS(lines[l].args[0], s)
+					{
+						ffloor_t *rover;
+
+						for (rover = sectors[s].ffloors; rover; rover = rover->next)
+						{
+							if (rover->master != lines + l)
+								continue;
+
+							rover->flags |= FF_BUSTUP;
+							rover->spawnflags |= FF_BUSTUP;
+							rover->bustflags = bustflags;
+							rover->busttype = busttype;
+							rover->busttag = lines[i].args[3];
+							CheckForBustableBlocks = true;
+							break;
+						}
+					}
+				}
 				break;
+			}
 
-			case 606: // HACK! Copy colormaps. Just plain colormaps.
-				TAG_ITER_SECTORS(lines[i].args[0], s)
+			case 75: // Make FOF quicksand
+			{
+				if (!udmf)
+					break;
+				TAG_ITER_LINES(lines[i].args[0], l)
 				{
-					extracolormap_t *exc;
-
-					if (sectors[s].colormap_protected)
+					if (lines[l].special < 100 || lines[l].special >= 300)
 						continue;
 
-					if (!udmf)
-						exc = sides[lines[i].sidenum[0]].colormap_data;
-					else
+					TAG_ITER_SECTORS(lines[l].args[0], s)
 					{
-						if (!lines[i].args[1])
-							exc = lines[i].frontsector->extra_colormap;
-						else
+						ffloor_t *rover;
+
+						for (rover = sectors[s].ffloors; rover; rover = rover->next)
 						{
-							INT32 sourcesec = Tag_Iterate_Sectors(lines[i].args[1], 0);
-							if (sourcesec == -1)
-							{
-								CONS_Debug(DBG_GAMELOGIC, "Line type 606: Can't find sector with source colormap (tag %d)!\n", lines[i].args[1]);
-								return;
-							}
-							exc = sectors[sourcesec].extra_colormap;
+							if (rover->master != lines + l)
+								continue;
+
+							rover->flags |= FF_QUICKSAND;
+							rover->spawnflags |= FF_QUICKSAND;
+							rover->sinkspeed = abs(lines[i].args[1]) << (FRACBITS - 1);
+							rover->friction = abs(lines[i].args[2]) << (FRACBITS - 6);
+							CheckForQuicksand = true;
+							break;
 						}
 					}
-					sectors[s].extra_colormap = sectors[s].spawn_extra_colormap = exc;
 				}
 				break;
+			}
 
-			default:
+			case 76: // Make FOF bouncy
+			{
+				if (udmf)
+				{
+					TAG_ITER_LINES(lines[i].args[0], l)
+						P_MakeFOFBouncy(lines + i, lines + l);
+				}
+				else
+				{
+					TAG_ITER_SECTORS(lines[i].args[0], s)
+						for (j = 0; (unsigned)j < sectors[s].linecount; j++)
+							P_MakeFOFBouncy(lines + i, sectors[s].lines[j]);
+				}
 				break;
+			}
 		}
 	}
 
+
+
+
+
 	// Allocate each list
 	for (i = 0; i < numsectors; i++)
 		if(secthinkers[i].thinkers)
@@ -7072,20 +7167,22 @@ void P_SpawnSpecials(boolean fromnetsave)
 /** Adds 3Dfloors as appropriate based on a common control linedef.
   *
   * \param line        Control linedef to use.
+  * \param alpha       Alpha value (0-255).
+  * \param blendmode   Blending mode.
   * \param ffloorflags 3Dfloor flags to use.
   * \param secthkiners Lists of thinkers sorted by sector. May be NULL.
   * \sa P_SpawnSpecials, P_AddFakeFloor
   * \author Graue <graue@oceanbase.org>
   */
-static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers)
+static void P_AddFakeFloorsByLine(size_t line, INT32 alpha, UINT8 blendmode, ffloortype_e ffloorflags, thinkerlist_t *secthinkers)
 {
 	INT32 s;
-	mtag_t tag = Tag_FGet(&lines[line].tags);
+	mtag_t tag = lines[line].args[0];
 	size_t sec = sides[*lines[line].sidenum].sector-sectors;
 
 	line_t* li = lines + line;
 	TAG_ITER_SECTORS(tag, s)
-		P_AddFakeFloor(&sectors[s], &sectors[sec], li, ffloorflags, secthinkers);
+		P_AddFakeFloor(&sectors[s], &sectors[sec], li, alpha, blendmode, ffloorflags, secthinkers);
 }
 
 /*
@@ -7230,7 +7327,7 @@ void T_Scroll(scroll_t *s)
 				if (!is3dblock)
 					continue;
 
-				TAG_ITER_SECTORS(Tag_FGet(&line->tags), sect)
+				TAG_ITER_SECTORS(line->args[0], sect)
 				{
 					sector_t *psec;
 					psec = sectors + sect;
@@ -7305,7 +7402,7 @@ void T_Scroll(scroll_t *s)
 
 				if (!is3dblock)
 					continue;
-				TAG_ITER_SECTORS(Tag_FGet(&line->tags), sect)
+				TAG_ITER_SECTORS(line->args[0], sect)
 				{
 					sector_t *psec;
 					psec = sectors + sect;
@@ -7388,12 +7485,33 @@ static void Add_Scroller(INT32 type, fixed_t dx, fixed_t dy, INT32 control, INT3
 	s->accel = accel;
 	s->exclusive = exclusive;
 	s->vdx = s->vdy = 0;
-	if ((s->control = control) != -1)
+	s->control = control;
+	if (s->control != -1)
 		s->last_height = sectors[control].floorheight + sectors[control].ceilingheight;
 	s->affectee = affectee;
+	if (type == sc_carry || type == sc_carry_ceiling)
+		sectors[affectee].specialflags |= SSF_CONVEYOR;
 	P_AddThinker(THINK_MAIN, &s->thinker);
 }
 
+static void P_SpawnPlaneScroller(line_t *l, fixed_t dx, fixed_t dy, INT32 control, INT32 affectee, INT32 accel, INT32 exclusive)
+{
+	if (l->args[1] != TMP_CEILING)
+	{
+		if (l->args[2] != TMS_SCROLLONLY)
+			Add_Scroller(sc_carry, FixedMul(dx, CARRYFACTOR), FixedMul(dy, CARRYFACTOR), control, affectee, accel, exclusive);
+		if (l->args[2] != TMS_CARRYONLY)
+			Add_Scroller(sc_floor, -dx, dy, control, affectee, accel, exclusive);
+	}
+	if (l->args[1] != TMP_FLOOR)
+	{
+		if (l->args[2] != TMS_SCROLLONLY)
+			Add_Scroller(sc_carry_ceiling, FixedMul(dx, CARRYFACTOR), FixedMul(dy, CARRYFACTOR), control, affectee, accel, exclusive);
+		if (l->args[2] != TMS_CARRYONLY)
+			Add_Scroller(sc_ceiling, -dx, dy, control, affectee, accel, exclusive);
+	}
+}
+
 /** Initializes the scrollers.
   *
   * \todo Get rid of all the magic numbers.
@@ -7403,140 +7521,65 @@ static void P_SpawnScrollers(void)
 {
 	size_t i;
 	line_t *l = lines;
-	mtag_t tag;
 
 	for (i = 0; i < numlines; i++, l++)
 	{
-		fixed_t dx = l->dx >> SCROLL_SHIFT; // direction and speed of scrolling
-		fixed_t dy = l->dy >> SCROLL_SHIFT;
 		INT32 control = -1, accel = 0; // no control sector or acceleration
-		INT32 special = l->special;
 
-		tag = Tag_FGet(&l->tags);
-
-		// These types are same as the ones they get set to except that the
-		// first side's sector's heights cause scrolling when they change, and
-		// this linedef controls the direction and speed of the scrolling. The
-		// most complicated linedef since donuts, but powerful :)
-
-		if (special == 515 || special == 512 || special == 522 || special == 532 || special == 504) // displacement scrollers
-		{
-			special -= 2;
-			control = (INT32)(sides[*l->sidenum].sector - sectors);
-		}
-		else if (special == 514 || special == 511 || special == 521 || special == 531 || special == 503) // accelerative scrollers
+		if (l->special == 502 || l->special == 510)
 		{
-			special--;
-			accel = 1;
-			control = (INT32)(sides[*l->sidenum].sector - sectors);
-		}
-		else if (special == 535 || special == 525) // displacement scrollers
-		{
-			special -= 2;
-			control = (INT32)(sides[*l->sidenum].sector - sectors);
-		}
-		else if (special == 534 || special == 524) // accelerative scrollers
-		{
-			accel = 1;
-			special--;
-			control = (INT32)(sides[*l->sidenum].sector - sectors);
+			if ((l->args[4] & TMST_TYPEMASK) != TMST_REGULAR)
+				control = (INT32)(sides[*l->sidenum].sector - sectors);
+			if ((l->args[4] & TMST_TYPEMASK) == TMST_ACCELERATIVE)
+				accel = 1;
 		}
 
-		switch (special)
+		switch (l->special)
 		{
 			register INT32 s;
 
-			case 513: // scroll effect ceiling
-			case 533: // scroll and carry objects on ceiling
-				TAG_ITER_SECTORS(tag, s)
-					Add_Scroller(sc_ceiling, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
-				if (special != 533)
-					break;
-				/* FALLTHRU */
-
-			case 523:	// carry objects on ceiling
-				dx = FixedMul(dx, CARRYFACTOR);
-				dy = FixedMul(dy, CARRYFACTOR);
-				TAG_ITER_SECTORS(tag, s)
-					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
-				TAG_ITER_SECTORS(tag, s)
-					Add_Scroller(sc_floor, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
-				if (special != 530)
-					break;
-				/* FALLTHRU */
+			case 510: // plane scroller
+			{
+				fixed_t length = R_PointToDist2(l->v2->x, l->v2->y, l->v1->x, l->v1->y);
+				fixed_t speed = l->args[3] << FRACBITS;
+				fixed_t dx = FixedMul(FixedDiv(l->dx, length), speed) >> SCROLL_SHIFT;
+				fixed_t dy = FixedMul(FixedDiv(l->dy, length), speed) >> SCROLL_SHIFT;
 
-			case 520:	// carry objects on floor
-				dx = FixedMul(dx, CARRYFACTOR);
-				dy = FixedMul(dy, CARRYFACTOR);
-				TAG_ITER_SECTORS(tag, s)
-					Add_Scroller(sc_carry, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
+				if (l->args[0] == 0)
+					P_SpawnPlaneScroller(l, dx, dy, control, (INT32)(l->frontsector - sectors), accel, l->args[4] & TMST_NONEXCLUSIVE);
+				else
+				{
+					TAG_ITER_SECTORS(l->args[0], s)
+						P_SpawnPlaneScroller(l, dx, dy, control, s, accel, l->args[4] & TMST_NONEXCLUSIVE);
+				}
 				break;
+			}
 
 			// scroll wall according to linedef
 			// (same direction and speed as scrolling floors)
 			case 502:
 			{
-				TAG_ITER_LINES(tag, s)
+				TAG_ITER_LINES(l->args[0], s)
 					if (s != (INT32)i)
 					{
-						if (l->flags & ML_EFFECT2) // use texture offsets instead
-						{
-							dx = sides[l->sidenum[0]].textureoffset;
-							dy = sides[l->sidenum[0]].rowoffset;
-						}
-						if (l->flags & ML_EFFECT3)
-						{
-							if (lines[s].sidenum[1] != 0xffff)
-								Add_Scroller(sc_side, dx, dy, control, lines[s].sidenum[1], accel, 0);
-						}
-						else
-							Add_Scroller(sc_side, dx, dy, control, lines[s].sidenum[0], accel, 0);
+						if (l->args[1] != TMSD_BACK)
+							Add_Scroller(sc_side, l->args[2] << FRACBITS, l->args[3] << FRACBITS, control, lines[s].sidenum[0], accel, 0);
+						if (l->args[1] != TMSD_FRONT && lines[s].sidenum[1] != 0xffff)
+							Add_Scroller(sc_side, l->args[2] << FRACBITS, l->args[3] << FRACBITS, control, lines[s].sidenum[1], accel, 0);
 					}
 				break;
 			}
 
-			case 505:
-				s = lines[i].sidenum[0];
-				Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, s, accel, 0);
-				break;
-
-			case 506:
-				s = lines[i].sidenum[1];
-
-				if (s != 0xffff)
-					Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, lines[i].sidenum[0], accel, 0);
-				else
-					CONS_Debug(DBG_GAMELOGIC, "Line special 506 (line #%s) missing back side!\n", sizeu1(i));
-				break;
-
-			case 507:
-				s = lines[i].sidenum[0];
-
-				if (lines[i].sidenum[1] != 0xffff)
-					Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, lines[i].sidenum[1], accel, 0);
-				else
-					CONS_Debug(DBG_GAMELOGIC, "Line special 507 (line #%s) missing back side!\n", sizeu1(i));
-				break;
-
-			case 508:
-				s = lines[i].sidenum[1];
-
-				if (s != 0xffff)
-					Add_Scroller(sc_side, -sides[s].textureoffset, sides[s].rowoffset, -1, s, accel, 0);
-				else
-					CONS_Debug(DBG_GAMELOGIC, "Line special 508 (line #%s) missing back side!\n", sizeu1(i));
-				break;
-
-			case 500: // scroll first side
-				Add_Scroller(sc_side, FRACUNIT, 0, -1, lines[i].sidenum[0], accel, 0);
-				break;
-
-			case 501: // jff 1/30/98 2-way scroll
-				Add_Scroller(sc_side, -FRACUNIT, 0, -1, lines[i].sidenum[0], accel, 0);
+			case 500:
+				if (l->args[0] != TMSD_BACK)
+					Add_Scroller(sc_side, -l->args[1] << FRACBITS, l->args[2] << FRACBITS, -1, l->sidenum[0], accel, 0);
+				if (l->args[0] != TMSD_FRONT)
+				{
+					if (l->sidenum[1] != 0xffff)
+						Add_Scroller(sc_side, -l->args[1] << FRACBITS, l->args[2] << FRACBITS, -1, l->sidenum[1], accel, 0);
+					else
+						CONS_Debug(DBG_GAMELOGIC, "Line special 500 (line #%s) missing back side!\n", sizeu1(i));
+				}
 				break;
 		}
 	}
@@ -7581,7 +7624,7 @@ void T_Disappear(disappear_t *d)
 	{
 		ffloor_t *rover;
 		register INT32 s;
-		mtag_t afftag = Tag_FGet(&lines[d->affectee].tags);
+		mtag_t afftag = lines[d->affectee].args[0];
 
 		TAG_ITER_SECTORS(afftag, s)
 		{
@@ -7596,7 +7639,7 @@ void T_Disappear(disappear_t *d)
 				{
 					rover->flags |= FF_EXISTS;
 
-					if (!(lines[d->sourceline].flags & ML_NOCLIMB))
+					if (!(lines[d->sourceline].args[5]))
 					{
 						sectors[s].soundorg.z = P_GetFFloorTopZAt(rover, sectors[s].soundorg.x, sectors[s].soundorg.y);
 						S_StartSound(&sectors[s].soundorg, sfx_appear);
@@ -8079,7 +8122,7 @@ static void P_ResetColormapFader(sector_t *sector)
 		// The thinker is the first member in all the action structs,
 		// so just let the thinker get freed, and that will free the whole
 		// structure.
-		P_RemoveThinker(&((elevator_t *)sector->fadecolormapdata)->thinker);
+		P_RemoveThinker(&((thinkerdata_t *)sector->fadecolormapdata)->thinker);
 		sector->fadecolormapdata = NULL;
 	}
 }
@@ -8308,40 +8351,32 @@ void T_Friction(friction_t *f)
 static void P_SpawnFriction(void)
 {
 	size_t i;
-	line_t *l = lines;
-	mtag_t tag;
-	register INT32 s;
-	fixed_t strength; // frontside texture offset controls magnitude
+	sector_t *s = sectors;
+
 	fixed_t friction; // friction value to be applied during movement
 	INT32 movefactor; // applied to each player move to simulate inertia
 
-	for (i = 0; i < numlines; i++, l++)
-		if (l->special == 540)
-		{
-			tag = Tag_FGet(&l->tags);
-			strength = sides[l->sidenum[0]].textureoffset>>FRACBITS;
-			if (strength > 0) // sludge
-				strength = strength*2; // otherwise, the maximum sludginess value is +967...
-
-			// The following might seem odd. At the time of movement,
-			// the move distance is multiplied by 'friction/0x10000', so a
-			// higher friction value actually means 'less friction'.
-			friction = ORIG_FRICTION - (0x1EB8*strength)/0x80; // ORIG_FRICTION is 0xE800
-
-			if (friction > FRACUNIT)
-				friction = FRACUNIT;
-			if (friction < 0)
-				friction = 0;
-
-			movefactor = FixedDiv(ORIG_FRICTION, friction);
-			if (movefactor < FRACUNIT)
-				movefactor = 8*movefactor - 7*FRACUNIT;
-			else
-				movefactor = FRACUNIT;
+	for (i = 0; i < numsectors; i++, s++)
+	{
+		if (s->friction == ORIG_FRICTION)
+			continue;
 
-			TAG_ITER_SECTORS(tag, s)
-				Add_Friction(friction, movefactor, s, -1);
-		}
+		friction = s->friction;
+
+		if (friction > FRACUNIT)
+			friction = FRACUNIT;
+		if (friction < 0)
+			friction = 0;
+
+		movefactor = FixedDiv(ORIG_FRICTION, friction);
+		if (movefactor < FRACUNIT)
+			movefactor = 8*movefactor - 7*FRACUNIT;
+		else
+			movefactor = FRACUNIT;
+
+		Add_Friction(friction, movefactor, (INT32)(s-sectors), -1);
+
+	}
 }
 
 /*
@@ -8360,20 +8395,20 @@ static void P_SpawnFriction(void)
   * \param type     Type of push/pull effect.
   * \param x_mag    X magnitude.
   * \param y_mag    Y magnitude.
-  * \param source   For a point pusher/puller, the source object.
+  * \param z_mag    Z magnitude.
   * \param affectee Target sector.
   * \param referrer What sector set it
   * \sa T_Pusher, P_GetPushThing, P_SpawnPushers
   */
-static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *source, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider)
+static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, fixed_t z_mag, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider)
 {
 	pusher_t *p = Z_Calloc(sizeof *p, PU_LEVSPEC, NULL);
 
 	p->thinker.function.acp1 = (actionf_p1)T_Pusher;
-	p->source = source;
 	p->type = type;
-	p->x_mag = x_mag>>FRACBITS;
-	p->y_mag = y_mag>>FRACBITS;
+	p->x_mag = x_mag;
+	p->y_mag = y_mag;
+	p->z_mag = z_mag;
 	p->exclusive = exclusive;
 	p->slider = slider;
 
@@ -8381,185 +8416,18 @@ static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, mobj_t *
 	{
 		p->roverpusher = true;
 		p->referrer = referrer;
+		sectors[referrer].specialflags |= SSF_WINDCURRENT;
 	}
 	else
-		p->roverpusher = false;
-
-	// "The right triangle of the square of the length of the hypotenuse is equal to the sum of the squares of the lengths of the other two sides."
-	// "Bah! Stupid brains! Don't you know anything besides the Pythagorean Theorem?" - Earthworm Jim
-	if (type == p_downcurrent || type == p_upcurrent || type == p_upwind || type == p_downwind)
-		p->magnitude = P_AproxDistance(p->x_mag,p->y_mag)<<(FRACBITS-PUSH_FACTOR);
-	else
-		p->magnitude = P_AproxDistance(p->x_mag,p->y_mag);
-	if (source) // point source exist?
 	{
-		// where force goes to zero
-		if (type == p_push)
-			p->radius = AngleFixed(source->angle);
-		else
-			p->radius = (p->magnitude)<<(FRACBITS+1);
-
-		p->x = p->source->x;
-		p->y = p->source->y;
-		p->z = p->source->z;
+		p->roverpusher = false;
+		sectors[affectee].specialflags |= SSF_WINDCURRENT;
 	}
+
 	p->affectee = affectee;
 	P_AddThinker(THINK_MAIN, &p->thinker);
 }
 
-
-// PIT_PushThing determines the angle and magnitude of the effect.
-// The object's x and y momentum values are changed.
-static pusher_t *tmpusher; // pusher structure for blockmap searches
-
-/** Applies a point pusher/puller to a thing.
-  *
-  * \param thing Thing to be pushed.
-  * \return True if the thing was pushed.
-  * \todo Make a more robust P_BlockThingsIterator() so the hidden parameter
-  *       ::tmpusher won't need to be used.
-  * \sa T_Pusher
-  */
-static inline boolean PIT_PushThing(mobj_t *thing)
-{
-	if (thing->eflags & MFE_PUSHED)
-		return false;
-
-	if (thing->player && thing->player->powers[pw_carry] == CR_ROPEHANG)
-		return false;
-
-	if (!tmpusher->source)
-		return false;
-
-	// Allow this to affect pushable objects at some point?
-	if (thing->player && (!(thing->flags & (MF_NOGRAVITY | MF_NOCLIP)) || thing->player->powers[pw_carry] == CR_NIGHTSMODE))
-	{
-		INT32 dist;
-		INT32 speed;
-		INT32 sx, sy, sz;
-
-		sx = tmpusher->x;
-		sy = tmpusher->y;
-		sz = tmpusher->z;
-
-		// don't fade wrt Z if health & 2 (mapthing has multi flag)
-		if (tmpusher->source->health & 2)
-			dist = P_AproxDistance(thing->x - sx,thing->y - sy);
-		else
-		{
-			// Make sure the Z is in range
-			if (thing->z < sz - tmpusher->radius || thing->z > sz + tmpusher->radius)
-				return false;
-
-			dist = P_AproxDistance(P_AproxDistance(thing->x - sx, thing->y - sy),
-				thing->z - sz);
-		}
-
-		speed = (tmpusher->magnitude - ((dist>>FRACBITS)>>1))<<(FRACBITS - PUSH_FACTOR - 1);
-
-		// If speed <= 0, you're outside the effective radius. You also have
-		// to be able to see the push/pull source point.
-
-		// Written with bits and pieces of P_HomingAttack
-		if ((speed > 0) && (P_CheckSight(thing, tmpusher->source)))
-		{
-			if (thing->player->powers[pw_carry] != CR_NIGHTSMODE)
-			{
-				// only push wrt Z if health & 1 (mapthing has ambush flag)
-				if (tmpusher->source->health & 1)
-				{
-					fixed_t tmpmomx, tmpmomy, tmpmomz;
-
-					tmpmomx = FixedMul(FixedDiv(sx - thing->x, dist), speed);
-					tmpmomy = FixedMul(FixedDiv(sy - thing->y, dist), speed);
-					tmpmomz = FixedMul(FixedDiv(sz - thing->z, dist), speed);
-					if (tmpusher->source->type == MT_PUSH) // away!
-					{
-						tmpmomx *= -1;
-						tmpmomy *= -1;
-						tmpmomz *= -1;
-					}
-
-					thing->momx += tmpmomx;
-					thing->momy += tmpmomy;
-					thing->momz += tmpmomz;
-
-					if (thing->player)
-					{
-						thing->player->cmomx += tmpmomx;
-						thing->player->cmomy += tmpmomy;
-						thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
-						thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
-					}
-				}
-				else
-				{
-					angle_t pushangle;
-
-					pushangle = R_PointToAngle2(thing->x, thing->y, sx, sy);
-					if (tmpusher->source->type == MT_PUSH)
-						pushangle += ANGLE_180; // away
-					pushangle >>= ANGLETOFINESHIFT;
-					thing->momx += FixedMul(speed, FINECOSINE(pushangle));
-					thing->momy += FixedMul(speed, FINESINE(pushangle));
-
-					if (thing->player)
-					{
-						thing->player->cmomx += FixedMul(speed, FINECOSINE(pushangle));
-						thing->player->cmomy += FixedMul(speed, FINESINE(pushangle));
-						thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
-						thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
-					}
-				}
-			}
-			else
-			{
-				//NiGHTS-specific handling.
-				//By default, pushes and pulls only affect the Z-axis.
-				//By having the ambush flag, it affects the X-axis.
-				//By having the object special flag, it affects the Y-axis.
-				fixed_t tmpmomx, tmpmomy, tmpmomz;
-
-				if (tmpusher->source->health & 1)
-					tmpmomx = FixedMul(FixedDiv(sx - thing->x, dist), speed);
-				else
-					tmpmomx = 0;
-
-				if (tmpusher->source->health & 2)
-					tmpmomy = FixedMul(FixedDiv(sy - thing->y, dist), speed);
-				else
-					tmpmomy = 0;
-
-				tmpmomz = FixedMul(FixedDiv(sz - thing->z, dist), speed);
-
-				if (tmpusher->source->type == MT_PUSH) // away!
-				{
-					tmpmomx *= -1;
-					tmpmomy *= -1;
-					tmpmomz *= -1;
-				}
-
-				thing->momx += tmpmomx;
-				thing->momy += tmpmomy;
-				thing->momz += tmpmomz;
-
-				if (thing->player)
-				{
-					thing->player->cmomx += tmpmomx;
-					thing->player->cmomy += tmpmomy;
-					thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
-					thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
-				}
-			}
-		}
-	}
-
-	if (tmpusher->exclusive)
-		thing->eflags |= MFE_PUSHED;
-
-	return true;
-}
-
 /** Applies a pusher to all affected objects.
   *
   * \param p Thinker for the pusher effect.
@@ -8571,30 +8439,19 @@ void T_Pusher(pusher_t *p)
 	sector_t *sec, *referrer = NULL;
 	mobj_t *thing;
 	msecnode_t *node;
-	INT32 xspeed = 0,yspeed = 0;
-	INT32 xl, xh, yl, yh, bx, by;
-	INT32 radius;
-	//INT32 ht = 0;
+	fixed_t x_mag, y_mag, z_mag;
+	fixed_t xspeed = 0, yspeed = 0, zspeed = 0;
 	boolean inFOF;
 	boolean touching;
 	boolean moved;
 
-	xspeed = yspeed = 0;
+	x_mag = p->x_mag >> PUSH_FACTOR;
+	y_mag = p->y_mag >> PUSH_FACTOR;
+	z_mag = p->z_mag >> PUSH_FACTOR;
 
 	sec = sectors + p->affectee;
-
-	// Be sure the special sector type is still turned on. If so, proceed.
-	// Else, bail out; the sector type has been changed on us.
-
 	if (p->roverpusher)
-	{
-		referrer = &sectors[p->referrer];
-
-		if (GETSECSPECIAL(referrer->special, 3) != 2)
-			return;
-	}
-	else if (GETSECSPECIAL(sec->special, 3) != 2)
-		return;
+		referrer = sectors + p->referrer;
 
 	// For constant pushers (wind/current) there are 3 situations:
 	//
@@ -8614,29 +8471,6 @@ void T_Pusher(pusher_t *p)
 	//
 	// In Phase II, you can apply these effects to Things other than players.
 
-	if (p->type == p_push)
-	{
-
-		// Seek out all pushable things within the force radius of this
-		// point pusher. Crosses sectors, so use blockmap.
-
-		tmpusher = p; // MT_PUSH/MT_PULL point source
-		radius = p->radius; // where force goes to zero
-		tmbbox[BOXTOP]    = p->y + radius;
-		tmbbox[BOXBOTTOM] = p->y - radius;
-		tmbbox[BOXRIGHT]  = p->x + radius;
-		tmbbox[BOXLEFT]   = p->x - radius;
-
-		xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
-		xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
-		yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
-		yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
-		for (bx = xl; bx <= xh; bx++)
-			for (by = yl; by <= yh; by++)
-				P_BlockThingsIterator(bx,by, PIT_PushThing);
-		return;
-	}
-
 	// constant pushers p_wind and p_current
 	node = sec->touching_thinglist; // things touching this sector
 	for (; node; node = node->m_thinglist_next)
@@ -8711,84 +8545,36 @@ void T_Pusher(pusher_t *p)
 		if (!touching && !inFOF) // Object is out of range of effect
 			continue;
 
-		if (p->type == p_wind)
-		{
-			if (touching) // on ground
-			{
-				xspeed = (p->x_mag)>>1; // half force
-				yspeed = (p->y_mag)>>1;
-				moved = true;
-			}
-			else if (inFOF)
-			{
-				xspeed = (p->x_mag); // full force
-				yspeed = (p->y_mag);
-				moved = true;
-			}
-		}
-		else if (p->type == p_upwind)
-		{
-			if (touching) // on ground
-			{
-				thing->momz += (p->magnitude)>>1;
-				moved = true;
-			}
-			else if (inFOF)
-			{
-				thing->momz += p->magnitude;
-				moved = true;
-			}
-		}
-		else if (p->type == p_downwind)
+		if (inFOF || (p->type == p_current && touching))
 		{
-			if (touching) // on ground
-			{
-				thing->momz -= (p->magnitude)>>1;
-				moved = true;
-			}
-			else if (inFOF)
-			{
-				thing->momz -= p->magnitude;
-				moved = true;
-			}
+			xspeed = x_mag; // full force
+			yspeed = y_mag;
+			zspeed = z_mag;
+			moved = true;
 		}
-		else // p_current
+		else if (p->type == p_wind && touching)
 		{
-			if (!touching && !inFOF) // Not in water at all
-				xspeed = yspeed = 0; // no force
-			else // underwater / touching water
-			{
-				if (p->type == p_upcurrent)
-					thing->momz += p->magnitude;
-				else if (p->type == p_downcurrent)
-					thing->momz -= p->magnitude;
-				else
-				{
-					xspeed = p->x_mag; // full force
-					yspeed = p->y_mag;
-				}
-				moved = true;
-			}
+			xspeed = x_mag>>1; // half force
+			yspeed = y_mag>>1;
+			zspeed = z_mag>>1;
+			moved = true;
 		}
 
-		if (p->type != p_downcurrent && p->type != p_upcurrent
-			&& p->type != p_upwind && p->type != p_downwind)
+		thing->momx += xspeed;
+		thing->momy += yspeed;
+		thing->momz += zspeed;
+		if (thing->player)
 		{
-			thing->momx += xspeed<<(FRACBITS-PUSH_FACTOR);
-			thing->momy += yspeed<<(FRACBITS-PUSH_FACTOR);
-			if (thing->player)
-			{
-				thing->player->cmomx += xspeed<<(FRACBITS-PUSH_FACTOR);
-				thing->player->cmomy += yspeed<<(FRACBITS-PUSH_FACTOR);
-				thing->player->cmomx = FixedMul(thing->player->cmomx, ORIG_FRICTION);
-				thing->player->cmomy = FixedMul(thing->player->cmomy, ORIG_FRICTION);
-			}
-
-			// Tumbleweeds bounce a bit...
-			if (thing->type == MT_LITTLETUMBLEWEED || thing->type == MT_BIGTUMBLEWEED)
-				thing->momz += P_AproxDistance(xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR)) >> 2;
+			thing->player->cmomx += xspeed;
+			thing->player->cmomy += yspeed;
+			thing->player->cmomx = FixedMul(thing->player->cmomx, ORIG_FRICTION);
+			thing->player->cmomy = FixedMul(thing->player->cmomy, ORIG_FRICTION);
 		}
 
+		// Tumbleweeds bounce a bit...
+		if (thing->type == MT_LITTLETUMBLEWEED || thing->type == MT_BIGTUMBLEWEED)
+			thing->momz += P_AproxDistance(xspeed, yspeed) >> 2;
+
 		if (moved)
 		{
 			if (p->slider && thing->player)
@@ -8800,7 +8586,7 @@ void T_Pusher(pusher_t *p)
 					thing->player->pflags |= jumped;
 
 				thing->player->pflags |= PF_SLIDING;
-				thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR));
+				thing->angle = R_PointToAngle2(0, 0, xspeed, yspeed);
 
 				if (!demoplayback || P_ControlStyle(thing->player) == CS_LMAOGALOG)
 				{
@@ -8819,86 +8605,33 @@ void T_Pusher(pusher_t *p)
 	}
 }
 
-
-/** Gets a push/pull object.
-  *
-  * \param s Sector number to look in.
-  * \return Pointer to the first ::MT_PUSH or ::MT_PULL object found in the
-  *         sector.
-  * \sa P_GetTeleportDestThing, P_GetStarpostThing, P_GetAltViewThing
-  */
-mobj_t *P_GetPushThing(UINT32 s)
-{
-	mobj_t *thing;
-	sector_t *sec;
-
-	sec = sectors + s;
-	thing = sec->thinglist;
-	while (thing)
-	{
-		switch (thing->type)
-		{
-			case MT_PUSH:
-			case MT_PULL:
-				return thing;
-			default:
-				break;
-		}
-		thing = thing->snext;
-	}
-	return NULL;
-}
-
 /** Spawns pushers.
   *
-  * \todo Remove magic numbers.
   * \sa P_SpawnSpecials, Add_Pusher
   */
 static void P_SpawnPushers(void)
 {
 	size_t i;
 	line_t *l = lines;
-	mtag_t tag;
 	register INT32 s;
-	mobj_t *thing;
+	fixed_t length, hspeed, dx, dy;
 
 	for (i = 0; i < numlines; i++, l++)
 	{
-		tag = Tag_FGet(&l->tags);
-		switch (l->special)
+		if (l->special != 541)
+			continue;
+
+		length = R_PointToDist2(l->v2->x, l->v2->y, l->v1->x, l->v1->y);
+		hspeed = l->args[1] << FRACBITS;
+		dx = FixedMul(FixedDiv(l->dx, length), hspeed);
+		dy = FixedMul(FixedDiv(l->dy, length), hspeed);
+
+		if (l->args[0] == 0)
+			Add_Pusher(l->args[3], dx, dy, l->args[2] << FRACBITS, (INT32)(l->frontsector - sectors), -1, !(l->args[4] & TMPF_NONEXCLUSIVE), !!(l->args[4] & TMPF_SLIDE));
+		else
 		{
-			case 541: // wind
-				TAG_ITER_SECTORS(tag, s)
-					Add_Pusher(p_wind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
-				break;
-			case 544: // current
-				TAG_ITER_SECTORS(tag, s)
-					Add_Pusher(p_current, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
-				break;
-			case 547: // push/pull
-				TAG_ITER_SECTORS(tag, s)
-				{
-					thing = P_GetPushThing(s);
-					if (thing) // No MT_P* means no effect
-						Add_Pusher(p_push, l->dx, l->dy, thing, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
-				}
-				break;
-			case 545: // current up
-				TAG_ITER_SECTORS(tag, s)
-					Add_Pusher(p_upcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
-				break;
-			case 546: // current down
-				TAG_ITER_SECTORS(tag, s)
-					Add_Pusher(p_downcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
-				break;
-			case 542: // wind up
-				TAG_ITER_SECTORS(tag, s)
-					Add_Pusher(p_upwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
-				break;
-			case 543: // wind down
-				TAG_ITER_SECTORS(tag, s)
-					Add_Pusher(p_downwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
-				break;
+			TAG_ITER_SECTORS(l->args[0], s)
+				Add_Pusher(l->args[3], dx, dy, l->args[2] << FRACBITS, s, -1, !(l->args[4] & TMPF_NONEXCLUSIVE), !!(l->args[4] & TMPF_SLIDE));
 		}
 	}
 }
diff --git a/src/p_spec.h b/src/p_spec.h
index 75954abe2f7b8964c2695c67655474b35daedb41..bdc912c346943afeeb0be761e9e88d5cbaa779af 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -21,6 +21,449 @@ extern mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpo
 extern mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs
 extern mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
 
+// Amount (dx, dy) vector linedef is shifted right to get scroll amount
+#define SCROLL_SHIFT 5
+
+typedef enum
+{
+	TMM_DOUBLESIZE      = 1,
+	TMM_SILENT          = 1<<1,
+	TMM_ALLOWYAWCONTROL = 1<<2,
+	TMM_SWING           = 1<<3,
+	TMM_MACELINKS       = 1<<4,
+	TMM_CENTERLINK      = 1<<5,
+	TMM_CLIP            = 1<<6,
+	TMM_ALWAYSTHINK     = 1<<7,
+} textmapmaceflags_t;
+
+typedef enum
+{
+	TMDA_BOTTOMOFFSET = 1,
+	TMDA_BOTTOM       = 1<<1,
+	TMDA_MIDDLE       = 1<<2,
+	TMDA_TOP          = 1<<3,
+} textmapdronealignment_t;
+
+typedef enum
+{
+	TMSF_RETRACTED  = 1,
+	TMSF_INTANGIBLE = 1<<1,
+} textmapspikeflags_t;
+
+typedef enum
+{
+	TMFF_AIMLESS    = 1,
+	TMFF_STATIONARY = 1<<1,
+	TMFF_HOP        = 1<<2,
+} textmapflickyflags_t;
+
+typedef enum
+{
+	TMFH_NOFLAME = 1,
+	TMFH_CORONA  = 1<<1,
+} textmapflameholderflags_t;
+
+typedef enum
+{
+	TMDS_NOGRAVITY   = 1,
+	TMDS_ROTATEEXTRA = 1<<1,
+} textmapdiagonalspringflags_t;
+
+typedef enum
+{
+	TMF_INVISIBLE       = 1,
+	TMF_NODISTANCECHECK = 1<<1,
+} textmapfanflags_t;
+
+typedef enum
+{
+	TMGD_BACK  = 0,
+	TMGD_RIGHT = 1,
+	TMGD_LEFT  = 2,
+} textmapguarddirection_t;
+
+typedef enum
+{
+	TMNI_BONUSONLY = 1,
+	TMNI_REVEAL    = 1<<1,
+} textmapnightsitem_t;
+
+typedef enum
+{
+	TMP_NORMAL    = 0,
+	TMP_SLIDE     = 1,
+	TMP_IMMOVABLE = 2,
+	TMP_CLASSIC   = 3,
+} textmappushabletype_t;
+
+typedef enum
+{
+	TMED_NONE  = 0,
+	TMED_RIGHT = 1,
+	TMED_LEFT  = 2,
+} textmapeggrobodirection_t;
+
+typedef enum
+{
+	TMMR_SAME   = 0,
+	TMMR_WEAK   = 1,
+	TMMR_STRONG = 2,
+} textmapmonitorrespawn_t;
+
+typedef enum
+{
+	TMF_GRAYSCALE = 1,
+	TMF_SKIPINTRO = 1<<1,
+} textmapfangflags_t;
+
+typedef enum
+{
+	TMB_NODEATHFLING = 1,
+	TMB_BARRIER      = 1<<1,
+} textmapbrakflags_t;
+
+typedef enum
+{
+	TMEF_SKIPTALLY    = 1,
+	TMEF_EMERALDCHECK = 1<<1,
+} textmapexitflags_t;
+
+typedef enum
+{
+	TMSP_NOTELEPORT = 1,
+	TMSP_FORCESPIN  = 1<<1,
+} textmapspeedpadflags_t;
+
+//FOF flags
+typedef enum
+{
+	TMFA_NOPLANES    = 1,
+	TMFA_NOSIDES     = 1<<1,
+	TMFA_INSIDES     = 1<<2,
+	TMFA_ONLYINSIDES = 1<<3,
+	TMFA_NOSHADE     = 1<<4,
+	TMFA_SPLAT       = 1<<5,
+} textmapfofappearance_t;
+
+typedef enum
+{
+	TMFT_INTANGIBLETOP    = 1,
+	TMFT_INTANGIBLEBOTTOM = 1<<1,
+	TMFT_DONTBLOCKPLAYER  = 1<<2,
+	TMFT_VISIBLEFROMINSIDE = (TMFT_INTANGIBLETOP|TMFT_INTANGIBLEBOTTOM|TMFT_DONTBLOCKPLAYER),
+	TMFT_DONTBLOCKOTHERS  = 1<<3,
+	TMFT_INTANGIBLE       = (TMFT_DONTBLOCKPLAYER|TMFT_DONTBLOCKOTHERS),
+} textmapfoftangibility_t;
+
+typedef enum
+{
+	TMFW_NOSIDES      = 1,
+	TMFW_DOUBLESHADOW = 1<<1,
+	TMFW_COLORMAPONLY = 1<<2,
+	TMFW_NORIPPLE     = 1<<3,
+	TMFW_GOOWATER     = 1<<4,
+	TMFW_SPLAT        = 1<<5,
+} textmapfofwater_t;
+
+typedef enum
+{
+	TMFB_REVERSE  = 1,
+	TMFB_SPINDASH = 1<<1,
+	TMFB_DYNAMIC  = 1<<2,
+} textmapfofbobbing_t;
+
+typedef enum
+{
+	TMFC_NOSHADE     = 1,
+	TMFC_NORETURN    = 1<<1,
+	TMFC_AIRBOB      = 1<<2,
+	TMFC_FLOATBOB    = 1<<3,
+	TMFC_SPLAT       = 1<<4,
+} textmapfofcrumbling_t;
+
+typedef enum
+{
+	TMFR_REVERSE  = 1,
+	TMFR_SPINDASH = 1<<1,
+} textmapfofrising_t;
+
+typedef enum
+{
+	TMFM_BRICK     = 1,
+	TMFM_INVISIBLE = 1<<1,
+} textmapfofmario_t;
+
+typedef enum
+{
+	TMFB_TOUCH,
+	TMFB_SPIN,
+	TMFB_REGULAR,
+	TMFB_STRONG,
+} textmapfofbusttype_t;
+
+typedef enum
+{
+	TMFB_PUSHABLES   = 1,
+	TMFB_EXECUTOR    = 1<<1,
+	TMFB_ONLYBOTTOM  = 1<<2,
+	TMFB_SPLAT       = 1<<3,
+} textmapfofbustflags_t;
+
+typedef enum
+{
+	TMFL_NOBOSSES = 1,
+	TMFL_SPLAT    = 1<<1,
+} textmapfoflaserflags_t;
+
+typedef enum
+{
+	TMT_CONTINUOUS           = 0,
+	TMT_ONCE                 = 1,
+	TMT_EACHTIMEMASK         = TMT_ONCE,
+	TMT_EACHTIMEENTER        = 2,
+	TMT_EACHTIMEENTERANDEXIT = 3,
+} textmaptriggertype_t;
+
+typedef enum
+{
+	TMXT_CONTINUOUS           = 0,
+	TMXT_EACHTIMEMASK         = TMXT_CONTINUOUS,
+	TMXT_EACHTIMEENTER        = 1,
+	TMXT_EACHTIMEENTERANDEXIT = 2,
+} textmapxtriggertype_t;
+
+typedef enum
+{
+	TMF_HASALL        = 0,
+	TMF_HASANY        = 1,
+	TMF_HASEXACTLY    = 2,
+	TMF_DOESNTHAVEALL = 3,
+	TMF_DOESNTHAVEANY = 4,
+} textmapflagcheck_t;
+
+typedef enum
+{
+	TMT_RED  = 0,
+	TMT_BLUE = 1,
+} textmapteam_t;
+
+typedef enum
+{
+	TMC_EQUAL = 0,
+	TMC_LTE   = 1,
+	TMC_GTE   = 2,
+} textmapcomparison_t;
+
+typedef enum
+{
+	TMNP_FASTEST   = 0,
+	TMNP_SLOWEST   = 1,
+	TMNP_TRIGGERER = 2,
+} textmapnightsplayer_t;
+
+typedef enum
+{
+	TMN_ALWAYS       = 0,
+	TMN_FROMNONIGHTS = 1,
+	TMN_FROMNIGHTS   = 2,
+} textmapnighterizeoptions_t;
+
+typedef enum
+{
+	TMN_BONUSLAPS       = 1,
+	TMN_LEVELCOMPLETION = 1<<2,
+} textmapnightserizeflags_t;
+
+typedef enum
+{
+	TMD_ALWAYS         = 0,
+	TMD_NOBODYNIGHTS   = 1,
+	TMD_SOMEBODYNIGHTS = 2,
+} textmapdenighterizeoptions_t;
+
+typedef enum
+{
+	TMS_IFENOUGH    = 0,
+	TMS_IFNOTENOUGH = 1,
+	TMS_ALWAYS      = 2,
+} textmapspherescheck_t;
+
+typedef enum
+{
+	TMI_BONUSLAPS = 1,
+	TMI_ENTER     = 1<<2,
+} textmapideyacaptureflags_t;
+
+typedef enum
+{
+	TMP_FLOOR = 0,
+	TMP_CEILING = 1,
+	TMP_BOTH = 2,
+} textmapplanes_t;
+
+typedef enum
+{
+	TMT_ADD          = 0,
+	TMT_REMOVE       = 1,
+	TMT_REPLACEFIRST = 2,
+} textmaptagoptions_t;
+
+typedef enum
+{
+	TMT_SILENT       = 1,
+	TMT_KEEPANGLE    = 1<<1,
+	TMT_KEEPMOMENTUM = 1<<2,
+	TMT_RELATIVE     = 1<<3,
+} textmapteleportflags_t;
+
+typedef enum
+{
+	TMM_ALLPLAYERS = 1,
+	TMM_OFFSET = 1<<1,
+	TMM_FADE = 1<<2,
+	TMM_NORELOAD = 1<<3,
+	TMM_FORCERESET = 1<<4,
+	TMM_NOLOOP = 1<<5,
+} textmapmusicflags_t;
+
+typedef enum
+{
+	TMSS_TRIGGERMOBJ   = 0,
+	TMSS_TRIGGERSECTOR = 1,
+	TMSS_NOWHERE       = 2,
+	TMSS_TAGGEDSECTOR  = 3,
+} textmapsoundsource_t;
+
+typedef enum
+{
+	TMSL_EVERYONE     = 0,
+	TMSL_TRIGGERER    = 1,
+	TMSL_TAGGEDSECTOR = 2,
+} textmapsoundlistener_t;
+
+typedef enum
+{
+	TML_SECTOR  = 0,
+	TML_FLOOR   = 1,
+	TML_CEILING = 2,
+} textmaplightareas_t;
+
+typedef enum
+{
+	TMLC_NOSECTOR  = 1,
+	TMLC_NOFLOOR   = 1<<1,
+	TMLC_NOCEILING = 1<<2,
+} textmaplightcopyflags_t;
+
+typedef enum
+{
+	TMF_RELATIVE = 1,
+	TMF_OVERRIDE = 1<<1,
+	TMF_TICBASED = 1<<2,
+} textmapfadeflags_t;
+
+typedef enum
+{
+	TMB_USETARGET = 1,
+	TMB_SYNC      = 1<<1,
+} textmapblinkinglightflags_t;
+
+typedef enum
+{
+	TMFR_NORETURN  = 1,
+	TMFR_CHECKFLAG = 1<<1,
+} textmapfofrespawnflags_t;
+
+typedef enum
+{
+	TMST_RELATIVE          = 1,
+	TMST_DONTDOTRANSLUCENT = 1<<1,
+} textmapsettranslucencyflags_t;
+
+typedef enum
+{
+	TMFT_RELATIVE          = 1,
+	TMFT_OVERRIDE          = 1<<1,
+	TMFT_TICBASED          = 1<<2,
+	TMFT_IGNORECOLLISION   = 1<<3,
+	TMFT_GHOSTFADE         = 1<<4,
+	TMFT_DONTDOTRANSLUCENT = 1<<5,
+	TMFT_DONTDOEXISTS      = 1<<6,
+	TMFT_DONTDOLIGHTING    = 1<<7,
+	TMFT_DONTDOCOLORMAP    = 1<<8,
+	TMFT_USEEXACTALPHA     = 1<<9,
+} textmapfadetranslucencyflags_t;
+
+typedef enum
+{
+	TMS_VIEWPOINT   = 0,
+	TMS_CENTERPOINT = 1,
+	TMS_BOTH        = 2,
+} textmapskybox_t;
+
+typedef enum
+{
+	TMP_CLOSE          = 1,
+	TMP_RUNPOSTEXEC    = 1<<1,
+	TMP_CALLBYNAME     = 1<<2,
+	TMP_KEEPCONTROLS   = 1<<3,
+	TMP_KEEPREALTIME   = 1<<4,
+	//TMP_ALLPLAYERS     = 1<<5,
+	//TMP_FREEZETHINKERS = 1<<6,
+} textmappromptflags_t;
+
+typedef enum
+{
+	TMF_NOCHANGE = 0,
+	TMF_ADD      = 1,
+	TMF_REMOVE   = 2,
+} textmapsetflagflags_t;
+
+typedef enum
+{
+	TMSD_FRONT = 0,
+	TMSD_BACK = 1,
+	TMSD_FRONTBACK = 2,
+} textmapsides_t;
+
+typedef enum
+{
+	TMS_SCROLLCARRY = 0,
+	TMS_SCROLLONLY = 1,
+	TMS_CARRYONLY = 2,
+} textmapscroll_t;
+
+typedef enum
+{
+	TMST_REGULAR = 0,
+	TMST_ACCELERATIVE = 1,
+	TMST_DISPLACEMENT = 2,
+	TMST_TYPEMASK = 3,
+	TMST_NONEXCLUSIVE = 4,
+} textmapscrolltype_t;
+
+typedef enum
+{
+	TMPF_SLIDE = 1,
+	TMPF_NONEXCLUSIVE = 1<<1,
+} textmappusherflags_t;
+
+typedef enum
+{
+	TMPP_NOZFADE      = 1,
+	TMPP_PUSHZ        = 1<<1,
+	TMPP_NONEXCLUSIVE = 1<<2,
+} textmappointpushflags_t;
+
+typedef enum
+{
+	TMB_TRANSLUCENT     = 0,
+	TMB_ADD             = 1,
+	TMB_SUBTRACT        = 2,
+	TMB_REVERSESUBTRACT = 3,
+	TMB_MODULATE        = 4,
+} textmapblendmodes_t;
+
 // GETSECSPECIAL (specialval, section)
 //
 // Pulls out the special # from a particular section.
@@ -38,12 +481,21 @@ void P_SetupLevelFlatAnims(void);
 
 // at map load
 void P_InitSpecials(void);
+void P_ApplyFlatAlignment(sector_t* sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs, boolean floor, boolean ceiling);
+fixed_t P_GetSectorGravityFactor(sector_t *sec);
 void P_SpawnSpecials(boolean fromnetsave);
 
 // every tic
 void P_UpdateSpecials(void);
+sector_t *P_MobjTouchingSectorSpecial(mobj_t *mo, INT32 section, INT32 number);
+sector_t *P_MobjTouchingSectorSpecialFlag(mobj_t *mo, sectorspecialflags_t flag);
 sector_t *P_PlayerTouchingSectorSpecial(player_t *player, INT32 section, INT32 number);
+sector_t *P_PlayerTouchingSectorSpecialFlag(player_t *player, sectorspecialflags_t flag);
 void P_PlayerInSpecialSector(player_t *player);
+void P_CheckMobjTrigger(mobj_t *mobj, boolean pushable);
+sector_t *P_FindPlayerTrigger(player_t *player, line_t *sourceline);
+boolean P_IsPlayerValid(size_t playernum);
+boolean P_CanPlayerTrigger(size_t playernum);
 void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *roversector);
 
 fixed_t P_FindLowestFloorSurrounding(sector_t *sec);
@@ -60,6 +512,10 @@ INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max);
 void P_SetupSignExit(player_t *player);
 boolean P_IsFlagAtBase(mobjtype_t flag);
 
+boolean P_IsMobjTouchingSectorPlane(mobj_t *mo, sector_t *sec);
+boolean P_IsMobjTouching3DFloor(mobj_t *mo, ffloor_t *ffloor, sector_t *sec);
+boolean P_IsMobjTouchingPolyobj(mobj_t *mo, polyobj_t *po, sector_t *polysec);
+
 void P_SwitchWeather(INT32 weathernum);
 
 boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller);
@@ -72,6 +528,12 @@ void P_RunNightsCapsuleTouchExecutors(mobj_t *actor, boolean entering, boolean e
 UINT16 P_GetFFloorID(ffloor_t *fflr);
 ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id);
 
+// Use this when you don't know the type of your thinker data struct but need to access its thinker.
+typedef struct
+{
+	thinker_t thinker;
+} thinkerdata_t;
+
 //
 // P_LIGHTS
 //
@@ -83,8 +545,8 @@ typedef struct
 	sector_t *sector;  ///< The sector where action is taking place.
 	INT32 count;
 	INT32 resetcount;
-	INT32 maxlight;    ///< The brightest light level to use.
-	INT32 minlight;    ///< The darkest light level to use.
+	INT16 maxlight;    ///< The brightest light level to use.
+	INT16 minlight;    ///< The darkest light level to use.
 } fireflicker_t;
 
 typedef struct
@@ -112,8 +574,8 @@ typedef struct
 	thinker_t thinker; ///< The thinker in use for the effect.
 	sector_t *sector;  ///< The sector where the action is taking place.
 	INT32 count;
-	INT32 minlight;    ///< The minimum light level to use.
-	INT32 maxlight;    ///< The maximum light level to use.
+	INT16 minlight;    ///< The minimum light level to use.
+	INT16 maxlight;    ///< The maximum light level to use.
 	INT32 darktime;    ///< How INT32 to use minlight.
 	INT32 brighttime;  ///< How INT32 to use maxlight.
 } strobe_t;
@@ -122,10 +584,10 @@ typedef struct
 {
 	thinker_t thinker;
 	sector_t *sector;
-	INT32 minlight;
-	INT32 maxlight;
-	INT32 direction;
-	INT32 speed;
+	INT16 minlight;
+	INT16 maxlight;
+	INT16 direction;
+	INT16 speed;
 } glow_t;
 
 /** Thinker struct for fading lights.
@@ -151,18 +613,18 @@ typedef struct
 void P_RemoveLighting(sector_t *sector);
 
 void T_FireFlicker(fireflicker_t *flick);
-fireflicker_t *P_SpawnAdjustableFireFlicker(sector_t *minsector, sector_t *maxsector, INT32 length);
+fireflicker_t *P_SpawnAdjustableFireFlicker(sector_t *sector, INT16 lighta, INT16 lightb, INT32 length);
 void T_LightningFlash(lightflash_t *flash);
 void T_StrobeFlash(strobe_t *flash);
 
 void P_SpawnLightningFlash(sector_t *sector);
-strobe_t * P_SpawnAdjustableStrobeFlash(sector_t *minsector, sector_t *maxsector, INT32 darktime, INT32 brighttime, boolean inSync);
+strobe_t * P_SpawnAdjustableStrobeFlash(sector_t *sector, INT16 lighta, INT16 lightb, INT32 darktime, INT32 brighttime, boolean inSync);
 
 void T_Glow(glow_t *g);
-glow_t *P_SpawnAdjustableGlowingLight(sector_t *minsector, sector_t *maxsector, INT32 length);
+glow_t *P_SpawnAdjustableGlowingLight(sector_t *sector, INT16 lighta, INT16 lightb, INT32 length);
 
 void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean ticbased);
-void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, boolean force);
+void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, boolean force, boolean relative);
 void T_LightFade(lightlevel_t *ll);
 
 typedef enum
@@ -178,22 +640,19 @@ typedef enum
 typedef enum
 {
 	raiseToHighest,
-	lowerToLowest,
-	raiseToLowest,
 	lowerToLowestFast,
 
 	instantRaise, // instant-move for ceilings
 
-	lowerAndCrush,
 	crushAndRaise,
-	fastCrushAndRaise,
+	raiseAndCrush,
 	crushCeilOnce,
 	crushBothOnce,
 
 	moveCeilingByFrontSector,
 	instantMoveCeilingByFrontSector,
 
-	moveCeilingByFrontTexture,
+	moveCeilingByDistance,
 
 	bounceCeiling,
 	bounceCeilingCrush,
@@ -209,7 +668,6 @@ typedef struct
 	fixed_t bottomheight; ///< The lowest height to move to.
 	fixed_t topheight;    ///< The highest height to move to.
 	fixed_t speed;        ///< Ceiling speed.
-	fixed_t oldspeed;
 	fixed_t delay;
 	fixed_t delaytimer;
 	UINT8 crush;           ///< Whether to crush things or not.
@@ -218,17 +676,16 @@ typedef struct
 	INT32 direction;      ///< 1 = up, 0 = waiting, -1 = down.
 
 	// ID
-	INT32 tag;
-	INT32 olddirection;
+	INT16 tag;            ///< Tag of linedef executor to run when movement is done.
 	fixed_t origspeed;    ///< The original, "real" speed.
 	INT32 sourceline;     ///< Index of the source linedef
 } ceiling_t;
 
 #define CEILSPEED (FRACUNIT)
 
-INT32 EV_DoCeiling(line_t *line, ceiling_e type);
+INT32 EV_DoCeiling(mtag_t tag, line_t *line, ceiling_e type);
 
-INT32 EV_DoCrush(line_t *line, ceiling_e type);
+INT32 EV_DoCrush(mtag_t tag, line_t *line, ceiling_e type);
 void T_CrushCeiling(ceiling_t *ceiling);
 
 void T_MoveCeiling(ceiling_t *ceiling);
@@ -238,9 +695,6 @@ void T_MoveCeiling(ceiling_t *ceiling);
 //
 typedef enum
 {
-	// lower floor to lowest surrounding floor
-	lowerFloorToLowest,
-
 	// raise floor to next highest surrounding floor
 	raiseFloorToNearestFast,
 
@@ -250,7 +704,7 @@ typedef enum
 	moveFloorByFrontSector,
 	instantMoveFloorByFrontSector,
 
-	moveFloorByFrontTexture,
+	moveFloorByDistance,
 
 	bounceFloor,
 	bounceFloorCrush,
@@ -262,7 +716,6 @@ typedef enum
 {
 	elevateUp,
 	elevateDown,
-	elevateCurrent,
 	elevateContinuous,
 	elevateBounce,
 	elevateHighest,
@@ -282,6 +735,8 @@ typedef struct
 	fixed_t origspeed;
 	fixed_t delay;
 	fixed_t delaytimer;
+	INT16 tag;
+	INT32 sourceline;
 } floormove_t;
 
 typedef struct
@@ -403,7 +858,6 @@ typedef struct
 	thinker_t thinker;
 	line_t *sourceline; // Source line of the thinker
 	boolean playersInArea[MAXPLAYERS];
-	boolean playersOnArea[MAXPLAYERS];
 	boolean triggerOnExit;
 } eachtime_t;
 
@@ -439,8 +893,8 @@ typedef enum
 
 result_e T_MovePlane(sector_t *sector, fixed_t speed, fixed_t dest, boolean crush,
 	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_DoFloor(mtag_t tag, line_t *line, floor_e floortype);
+void EV_DoElevator(mtag_t tag, line_t *line, elevator_e elevtype);
 void EV_CrumbleChain(sector_t *sec, ffloor_t *rover);
 void EV_BounceSector(sector_t *sector, fixed_t momz, line_t *sourceline);
 
@@ -525,30 +979,20 @@ void T_Friction(friction_t *f);
 
 typedef enum
 {
-	p_push,        ///< Point pusher or puller.
 	p_wind,        ///< Wind.
 	p_current,     ///< Current.
-	p_upcurrent,   ///< Upwards current.
-	p_downcurrent, ///< Downwards current.
-	p_upwind,      ///< Upwards wind.
-	p_downwind     ///< Downwards wind.
 } pushertype_e;
 
 // Model for pushers for push/pull effects
 typedef struct
 {
-	thinker_t thinker; ///< Thinker structure for push/pull effect.
-	/** Types of push/pull effects.
-	*/
-	pushertype_e type;  ///< Type of push/pull effect.
-	mobj_t *source;     ///< Point source if point pusher/puller.
-	INT32 x_mag;        ///< X strength.
-	INT32 y_mag;        ///< Y strength.
-	INT32 magnitude;    ///< Vector strength for point pusher/puller.
-	INT32 radius;       ///< Effective radius for point pusher/puller.
-	INT32 x, y, z;      ///< Point source if point pusher/puller.
+	thinker_t thinker;  ///< Thinker structure for pusher effect.
+	pushertype_e type;  ///< Type of pusher effect.
+	fixed_t x_mag;      ///< X strength.
+	fixed_t y_mag;      ///< Y strength.
+	fixed_t z_mag;      ///< Z strength.
 	INT32 affectee;     ///< Number of affected sector.
-	UINT8 roverpusher;   ///< flag for whether pusher originated from a FOF or not
+	UINT8 roverpusher;  ///< flag for whether pusher originated from a FOF or not
 	INT32 referrer;     ///< If roverpusher == true, then this will contain the sector # of the control sector where the effect was applied.
 	INT32 exclusive;    /// < Once this affect has been applied to a mobj, no other pushers may affect it.
 	INT32 slider;       /// < Should the player go into an uncontrollable slide?
@@ -610,9 +1054,8 @@ typedef struct
 
 void T_FadeColormap(fadecolormap_t *d);
 
-// Prototype functions for pushers
+// Prototype function for pushers
 void T_Pusher(pusher_t *p);
-mobj_t *P_GetPushThing(UINT32 s);
 
 // Plane displacement
 typedef struct
@@ -637,6 +1080,4 @@ void T_PlaneDisplace(planedisplace_t *pd);
 
 void P_CalcHeight(player_t *player);
 
-sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo);
-
 #endif
diff --git a/src/p_user.c b/src/p_user.c
index 8e8194da0f064a329ca62f29279208efdc787a82..10cc0dadec4023219b034e6b4a663096daa8c17e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -706,8 +706,7 @@ static void P_DeNightserizePlayer(player_t *player)
 
 		// If you screwed up, kiss your score and ring bonus goodbye.
 		// But only do this in special stage (and instakill!) In regular stages, wait til we hit the ground.
-		player->marescore = player->spheres =\
-		 player->rings = 0;
+		player->marescore = player->spheres = player->rings = 0;
 	}
 
 	// Check to see if the player should be killed.
@@ -717,13 +716,12 @@ static void P_DeNightserizePlayer(player_t *player)
 			continue;
 
 		mo2 = (mobj_t *)th;
-		if (!(mo2->type == MT_NIGHTSDRONE))
+		if (mo2->type != MT_NIGHTSDRONE)
 			continue;
 
 		if (mo2->flags2 & MF2_AMBUSH)
 		{
-			player->marescore = player->spheres =\
-			 player->rings = 0;
+			player->marescore = player->spheres = player->rings = 0;
 			P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
 
 			// Reset music to beginning if MIXNIGHTSCOUNTDOWN
@@ -777,7 +775,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	UINT8 oldmare, oldmarelap, oldmarebonuslap;
 
 	// Bots can't be NiGHTSerized, silly!1 :P
-	if (player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN)
+	if (player->bot)
 		return;
 
 	if (player->powers[pw_carry] != CR_NIGHTSMODE)
@@ -969,9 +967,6 @@ pflags_t P_GetJumpFlags(player_t *player)
 //
 boolean P_PlayerInPain(player_t *player)
 {
-	// If the player doesn't have a mobj, it can't be in pain.
-	if (!player->mo)
-		return false;
 	// no silly, sliding isn't pain
 	if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate] && player->powers[pw_flashing])
 		return true;
@@ -1238,8 +1233,8 @@ void P_GivePlayerSpheres(player_t *player, INT32 num_spheres)
 	if (!player)
 		return;
 
-	if ((player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN) && player->botleader)
-		player = player->botleader;
+	if (player->bot)
+		player = &players[consoleplayer];
 
 	if (!player->mo)
 		return;
@@ -1265,8 +1260,8 @@ void P_GivePlayerLives(player_t *player, INT32 numlives)
 	if (!player)
 		return;
 
-	if ((player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN) && player->botleader)
-		player = player->botleader;
+	if (player->bot)
+		player = &players[consoleplayer];
 
 	if (gamestate == GS_LEVEL)
 	{
@@ -1732,89 +1727,6 @@ boolean P_IsObjectOnGround(mobj_t *mo)
 	return false;
 }
 
-//
-// P_IsObjectOnGroundIn
-//
-// Returns true if the player is
-// on the ground in a specific sector. Takes reverse
-// gravity and FOFs into account.
-//
-boolean P_IsObjectOnGroundIn(mobj_t *mo, sector_t *sec)
-{
-	ffloor_t *rover;
-
-	// Is the object in reverse gravity?
-	if (mo->eflags & MFE_VERTICALFLIP)
-	{
-		// Detect if the player is on the ceiling.
-		if (mo->z+mo->height >= P_GetSpecialTopZ(mo, sec, sec))
-			return true;
-		// Otherwise, detect if the player is on the bottom of a FOF.
-		else
-		{
-			for (rover = sec->ffloors; rover; rover = rover->next)
-			{
-				// If the FOF doesn't exist, continue.
-				if (!(rover->flags & FF_EXISTS))
-					continue;
-
-				// If the FOF is configured to let the object through, continue.
-				if (!((rover->flags & FF_BLOCKPLAYER && mo->player)
-					|| (rover->flags & FF_BLOCKOTHERS && !mo->player)))
-					continue;
-
-				// If the the platform is intangible from below, continue.
-				if (rover->flags & FF_PLATFORM)
-					continue;
-
-				// If the FOF is a water block, continue. (Unnecessary check?)
-				if (rover->flags & FF_SWIMMABLE)
-					continue;
-
-				// Actually check if the player is on the suitable FOF.
-				if (mo->z+mo->height == P_GetSpecialBottomZ(mo, sectors + rover->secnum, sec))
-					return true;
-			}
-		}
-	}
-	// Nope!
-	else
-	{
-		// Detect if the player is on the floor.
-		if (mo->z <= P_GetSpecialBottomZ(mo, sec, sec))
-			return true;
-		// Otherwise, detect if the player is on the top of a FOF.
-		else
-		{
-			for (rover = sec->ffloors; rover; rover = rover->next)
-			{
-				// If the FOF doesn't exist, continue.
-				if (!(rover->flags & FF_EXISTS))
-					continue;
-
-				// If the FOF is configured to let the object through, continue.
-				if (!((rover->flags & FF_BLOCKPLAYER && mo->player)
-					|| (rover->flags & FF_BLOCKOTHERS && !mo->player)))
-					continue;
-
-				// If the the platform is intangible from above, continue.
-				if (rover->flags & FF_REVERSEPLATFORM)
-					continue;
-
-				// If the FOF is a water block, continue. (Unnecessary check?)
-				if (rover->flags & FF_SWIMMABLE)
-					continue;
-
-				// Actually check if the player is on the suitable FOF.
-				if (mo->z == P_GetSpecialTopZ(mo, sectors + rover->secnum, sec))
-					return true;
-			}
-		}
-	}
-
-	return false;
-}
-
 //
 // P_SetObjectMomZ
 //
@@ -2283,13 +2195,12 @@ void P_DoPlayerExit(player_t *player)
 	P_RestoreMusic(player);
 }
 
-#define SPACESPECIAL 12
 boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 {
 	sector_t *sector = mo->subsector->sector;
 	fixed_t topheight, bottomheight;
 
-	if (GETSECSPECIAL(sector->special, 1) == SPACESPECIAL)
+	if (sector->specialflags & SSF_OUTERSPACE)
 		return true;
 
 	if (sector->ffloors)
@@ -2301,7 +2212,7 @@ boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 			if (!(rover->flags & FF_EXISTS))
 				continue;
 
-			if (GETSECSPECIAL(rover->master->frontsector->special, 1) != SPACESPECIAL)
+			if (!(rover->master->frontsector->specialflags & SSF_OUTERSPACE))
 				continue;
 			topheight    = P_GetFFloorTopZAt   (rover, mo->x, mo->y);
 			bottomheight = P_GetFFloorBottomZAt(rover, mo->x, mo->y);
@@ -2584,57 +2495,59 @@ static boolean P_PlayerCanBust(player_t *player, ffloor_t *rover)
 	/*if (rover->master->frontsector->crumblestate != CRUMBLE_NONE)
 		return false;*/
 
-	// If it's an FF_SHATTER, you can break it just by touching it.
-	if (rover->flags & FF_SHATTER)
-		return true;
-
-	// If it's an FF_SPINBUST, you can break it if you are in your spinning frames
-	// (either from jumping or spindashing).
-	if (rover->flags & FF_SPINBUST)
+	switch (rover->busttype)
 	{
+	case BT_TOUCH: // Shatters on contact
+		return true;
+	case BT_SPINBUST: // Can be busted by spinning (either from jumping or spindashing)
 		if ((player->pflags & PF_SPINNING) && !(player->pflags & PF_STARTDASH))
 			return true;
 
 		if ((player->pflags & PF_JUMPED) && !(player->pflags & PF_NOJUMPDAMAGE))
 			return true;
-	}
 
-	// Strong abilities can break even FF_STRONGBUST.
-	if (player->charflags & SF_CANBUSTWALLS)
-		return true;
+		/* FALLTHRU */
+	case BT_REGULAR:
+		// Spinning (and not jumping)
+		if ((player->pflags & PF_SPINNING) && !(player->pflags & PF_JUMPED))
+			return true;
 
-	if (player->pflags & PF_BOUNCING)
-		return true;
+		// Strong abilities can break even FF_STRONGBUST.
+		if (player->charflags & SF_CANBUSTWALLS)
+			return true;
 
-	if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
-		return true;
+		// Super
+		if (player->powers[pw_super])
+			return true;
 
-	if (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
-		return true;
+		// Dashmode
+		if ((player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE) && player->dashmode >= DASHMODE_THRESHOLD)
+			return true;
 
-	// Everyone else is out of luck.
-	if (rover->flags & FF_STRONGBUST)
-		return false;
+		// NiGHTS drill
+		if (player->pflags & PF_DRILLING)
+			return true;
 
-	// Spinning (and not jumping)
-	if ((player->pflags & PF_SPINNING) && !(player->pflags & PF_JUMPED))
-		return true;
+		// Recording for Metal Sonic
+		if (metalrecording)
+			return true;
 
-	// Super
-	if (player->powers[pw_super])
-		return true;
+		/* FALLTHRU */
+	case BT_STRONG: // Requires a "strong ability"
+		if (player->charability == CA_GLIDEANDCLIMB)
+			return true;
 
-	// Dashmode
-	if ((player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE) && player->dashmode >= DASHMODE_THRESHOLD)
-		return true;
+		if (player->pflags & PF_BOUNCING)
+			return true;
 
-	// NiGHTS drill
-	if (player->pflags & PF_DRILLING)
-		return true;
+		if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
+			return true;
 
-	// Recording for Metal Sonic
-	if (metalrecording)
-		return true;
+		if (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
+			return true;
+
+		break;
+	}
 
 	return false;
 }
@@ -2686,7 +2599,7 @@ static void P_CheckBustableBlocks(player_t *player)
 			}
 
 			// Height checks
-			if (rover->flags & FF_SHATTERBOTTOM)
+			if (rover->bustflags & FB_ONLYBOTTOM)
 			{
 				if (player->mo->z + player->mo->momz + player->mo->height < bottomheight)
 					continue;
@@ -2694,35 +2607,41 @@ static void P_CheckBustableBlocks(player_t *player)
 				if (player->mo->z + player->mo->height > bottomheight)
 					continue;
 			}
-			else if (rover->flags & FF_SPINBUST)
+			else
 			{
-				if (player->mo->z + player->mo->momz > topheight)
-					continue;
+				switch (rover->busttype)
+				{
+				case BT_TOUCH:
+					if (player->mo->z + player->mo->momz > topheight)
+						continue;
 
-				if (player->mo->z + player->mo->height < bottomheight)
-					continue;
-			}
-			else if (rover->flags & FF_SHATTER)
-			{
-				if (player->mo->z + player->mo->momz > topheight)
-					continue;
+					if (player->mo->z + player->mo->momz + player->mo->height < bottomheight)
+						continue;
 
-				if (player->mo->z + player->mo->momz + player->mo->height < bottomheight)
-					continue;
-			}
-			else
-			{
-				if (player->mo->z >= topheight)
-					continue;
+					break;
+				case BT_SPINBUST:
+					if (player->mo->z + player->mo->momz > topheight)
+						continue;
 
-				if (player->mo->z + player->mo->height < bottomheight)
-					continue;
+					if (player->mo->z + player->mo->height < bottomheight)
+						continue;
+
+					break;
+				default:
+					if (player->mo->z >= topheight)
+						continue;
+
+					if (player->mo->z + player->mo->height < bottomheight)
+						continue;
+
+					break;
+				}
 			}
 
 			// Impede the player's fall a bit
-			if (((rover->flags & FF_SPINBUST) || (rover->flags & FF_SHATTER)) && player->mo->z >= topheight)
+			if (((rover->busttype == BT_TOUCH) || (rover->busttype == BT_SPINBUST)) && player->mo->z >= topheight)
 				player->mo->momz >>= 1;
-			else if (rover->flags & FF_SHATTER)
+			else if (rover->busttype == BT_TOUCH)
 			{
 				player->mo->momx >>= 1;
 				player->mo->momy >>= 1;
@@ -2734,8 +2653,8 @@ static void P_CheckBustableBlocks(player_t *player)
 			EV_CrumbleChain(NULL, rover); // node->m_sector
 
 			// Run a linedef executor??
-			if (rover->master->flags & ML_EFFECT5)
-				P_LinedefExecute((INT16)(P_AproxDistance(rover->master->dx, rover->master->dy)>>FRACBITS), player->mo, node->m_sector);
+			if (rover->bustflags & FB_EXECUTOR)
+				P_LinedefExecute(rover->busttag, player->mo, node->m_sector);
 
 			goto bustupdone;
 		}
@@ -2780,14 +2699,20 @@ static void P_CheckBouncySectors(player_t *player)
 
 		for (rover = node->m_sector->ffloors; rover; rover = rover->next)
 		{
-			fixed_t bouncestrength;
 			fixed_t topheight, bottomheight;
 
 			if (!(rover->flags & FF_EXISTS))
 				continue; // FOFs should not be bouncy if they don't even "exist"
 
-			if (GETSECSPECIAL(rover->master->frontsector->special, 1) != 15)
-				continue; // this sector type is required for FOFs to be bouncy
+			// Handle deprecated bouncy FOF sector type
+			if (!udmf && GETSECSPECIAL(rover->master->frontsector->special, 1) == 15)
+			{
+				rover->flags |= FF_BOUNCY;
+				rover->bouncestrength = P_AproxDistance(rover->master->dx, rover->master->dy)/100;
+			}
+
+			if (!(rover->flags & FF_BOUNCY))
+				continue;
 
 			topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 			bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
@@ -2798,14 +2723,11 @@ static void P_CheckBouncySectors(player_t *player)
 			if (player->mo->z + player->mo->height < bottomheight)
 				continue;
 
-			bouncestrength = P_AproxDistance(rover->master->dx, rover->master->dy)/100;
-
 			if (oldz < P_GetFOFTopZ(player->mo, node->m_sector, rover, oldx, oldy, NULL)
 					&& oldz + player->mo->height > P_GetFOFBottomZ(player->mo, node->m_sector, rover, oldx, oldy, NULL))
 			{
-				player->mo->momx = -FixedMul(player->mo->momx,bouncestrength);
-				player->mo->momy = -FixedMul(player->mo->momy,bouncestrength);
-
+				player->mo->momx = -FixedMul(player->mo->momx,rover->bouncestrength);
+				player->mo->momy = -FixedMul(player->mo->momy,rover->bouncestrength);
 			}
 			else
 			{
@@ -2818,9 +2740,9 @@ static void P_CheckBouncySectors(player_t *player)
 				if (slope)
 					P_ReverseQuantizeMomentumToSlope(&momentum, slope);
 
-				momentum.z = -FixedMul(momentum.z,bouncestrength)/2;
+				momentum.z = -FixedMul(momentum.z,rover->bouncestrength)/2;
 
-				if (abs(momentum.z) < (bouncestrength*2))
+				if (abs(momentum.z) < (rover->bouncestrength*2))
 					goto bouncydone;
 
 				if (momentum.z > FixedMul(24*FRACUNIT, player->mo->scale)) //half of the default player height
@@ -2854,7 +2776,7 @@ bouncydone:
 static void P_CheckQuicksand(player_t *player)
 {
 	ffloor_t *rover;
-	fixed_t sinkspeed, friction;
+	fixed_t sinkspeed;
 	fixed_t topheight, bottomheight;
 
 	if (!(player->mo->subsector->sector->ffloors && player->mo->momz <= 0))
@@ -2872,9 +2794,7 @@ static void P_CheckQuicksand(player_t *player)
 
 		if (topheight >= player->mo->z && bottomheight < player->mo->z + player->mo->height)
 		{
-			sinkspeed = abs(rover->master->v1->x - rover->master->v2->x)>>1;
-
-			sinkspeed = FixedDiv(sinkspeed,TICRATE*FRACUNIT);
+			sinkspeed = FixedDiv(rover->sinkspeed,TICRATE*FRACUNIT);
 
 			if (player->mo->eflags & MFE_VERTICALFLIP)
 			{
@@ -2901,10 +2821,8 @@ static void P_CheckQuicksand(player_t *player)
 					P_PlayerHitFloor(player, false);
 			}
 
-			friction = abs(rover->master->v1->y - rover->master->v2->y)>>6;
-
-			player->mo->momx = FixedMul(player->mo->momx, friction);
-			player->mo->momy = FixedMul(player->mo->momy, friction);
+			player->mo->momx = FixedMul(player->mo->momx, rover->friction);
+			player->mo->momy = FixedMul(player->mo->momy, rover->friction);
 		}
 	}
 }
@@ -4788,7 +4706,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 	if (onground && player->pflags & PF_SPINNING && !(player->pflags & PF_STARTDASH)
 		&& player->speed < 5*player->mo->scale && canstand)
 	{
-		if (GETSECSPECIAL(player->mo->subsector->sector->special, 4) == 7 || (player->mo->ceilingz - player->mo->floorz < P_GetPlayerHeight(player)))
+		if ((player->mo->subsector->sector->specialflags & SSF_FORCESPIN) || (player->mo->ceilingz - player->mo->floorz < P_GetPlayerHeight(player)))
 			P_InstaThrust(player->mo, player->mo->angle, 10*player->mo->scale);
 		else
 		{
@@ -5682,6 +5600,22 @@ INT32 P_GetPlayerControlDirection(player_t *player)
 		return 1; // Controls pointing in player's general direction
 }
 
+static boolean P_ShouldResetConveyorMomentum(player_t *player)
+{
+	switch (player->onconveyor)
+	{
+		case 1:
+			return false;
+		case 2: // Wind/Current
+			return !(player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER));
+		case 3:
+		default:
+			return true;
+		case 4: // Actual conveyor belt
+			return !P_IsObjectOnGround(player->mo);
+	}
+}
+
 // Control scheme for 2d levels.
 static void P_2dMovement(player_t *player)
 {
@@ -5716,16 +5650,7 @@ static void P_2dMovement(player_t *player)
 		}
 	}
 
-	// cmomx/cmomy stands for the conveyor belt speed.
-	if (player->onconveyor == 2) // Wind/Current
-	{
-		//if (player->mo->z > player->mo->watertop || player->mo->z + player->mo->height < player->mo->waterbottom)
-		if (!(player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
-			player->cmomx = player->cmomy = 0;
-	}
-	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 && player->onconveyor != 1)
+	if (P_ShouldResetConveyorMomentum(player))
 		player->cmomx = player->cmomy = 0;
 
 	player->rmomx = player->mo->momx - player->cmomx;
@@ -5908,16 +5833,7 @@ static void P_3dMovement(player_t *player)
 	}
 	movepushsideangle = movepushangle-ANGLE_90;
 
-	// cmomx/cmomy stands for the conveyor belt speed.
-	if (player->onconveyor == 2) // Wind/Current
-	{
-		//if (player->mo->z > player->mo->watertop || player->mo->z + player->mo->height < player->mo->waterbottom)
-		if (!(player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
-			player->cmomx = player->cmomy = 0;
-	}
-	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 && player->onconveyor != 1)
+	if (P_ShouldResetConveyorMomentum(player))
 		player->cmomx = player->cmomy = 0;
 
 	player->rmomx = player->mo->momx - player->cmomx;
@@ -7630,8 +7546,8 @@ static void P_NiGHTSMovement(player_t *player)
 		}
 	}
 
-	if (objectplacing)
-		OP_NightsObjectplace(player);
+	//if (objectplacing)
+	//	OP_NightsObjectplace(player);
 }
 
 // May be used in future for CTF
@@ -8692,7 +8608,7 @@ void P_MovePlayer(player_t *player)
 #endif
 
 	// Look for blocks to bust up
-	// Because of FF_SHATTER, we should look for blocks constantly,
+	// Because of BT_TOUCH, we should look for blocks constantly,
 	// not just when spinning or playing as Knuckles
 	if (CheckForBustableBlocks)
 		P_CheckBustableBlocks(player);
@@ -10156,7 +10072,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 			for (rover = newsubsec->sector->ffloors; rover; rover = rover->next)
 			{
 				fixed_t topheight, bottomheight;
-				if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERALL) || GETSECSPECIAL(rover->master->frontsector->special, 4) == 12)
+				if (!(rover->flags & FF_BLOCKOTHERS) || !(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERALL) || (rover->master->frontsector->flags & MSF_NOCLIPCAMERA))
 					continue;
 
 				topheight = P_CameraGetFOFTopZ(thiscam, newsubsec->sector, rover, midx, midy, NULL);
@@ -10220,7 +10136,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 						// We're inside it! Yess...
 						polysec = po->lines[0]->backsector;
 
-						if (GETSECSPECIAL(polysec->special, 4) == 12)
+						if (polysec->flags & MSF_NOCLIPCAMERA)
 						{ // Camera noclip polyobj.
 							plink = (polymaplink_t *)(plink->link.next);
 							continue;
@@ -10282,7 +10198,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 			for (rover = newsubsec->sector->ffloors; rover; rover = rover->next)
 			{
 				fixed_t topheight, bottomheight;
-				if ((rover->flags & FF_BLOCKOTHERS) && (rover->flags & FF_RENDERALL) && (rover->flags & FF_EXISTS) && GETSECSPECIAL(rover->master->frontsector->special, 4) != 12)
+				if ((rover->flags & FF_BLOCKOTHERS) && (rover->flags & FF_RENDERALL) && (rover->flags & FF_EXISTS) && !(rover->master->frontsector->flags & MSF_NOCLIPCAMERA))
 				{
 					topheight = P_CameraGetFOFTopZ(thiscam, newsubsec->sector, rover, midx, midy, NULL);
 					bottomheight = P_CameraGetFOFBottomZ(thiscam, newsubsec->sector, rover, midx, midy, NULL);
@@ -10358,7 +10274,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		thiscam->momx = FixedMul(x - thiscam->x, camspeed);
 		thiscam->momy = FixedMul(y - thiscam->y, camspeed);
 
-		if (GETSECSPECIAL(thiscam->subsector->sector->special, 1) == 6
+		if (thiscam->subsector->sector->damagetype == SD_DEATHPITTILT
 			&& thiscam->z < thiscam->subsector->sector->floorheight + 256*FRACUNIT
 			&& FixedMul(z - thiscam->z, camspeed) < 0)
 		{
@@ -10565,7 +10481,6 @@ static void P_CalcPostImg(player_t *player)
 	postimg_t *type;
 	INT32 *param;
 	fixed_t pviewheight;
-	size_t i;
 
 	if (player->mo->eflags & MFE_VERTICALFLIP)
 		pviewheight = player->mo->z + player->mo->height - player->viewheight;
@@ -10590,45 +10505,30 @@ static void P_CalcPostImg(player_t *player)
 	}
 
 	// see if we are in heat (no, not THAT kind of heat...)
-	for (i = 0; i < sector->tags.count; i++)
+	if (sector->flags & MSF_HEATWAVE)
+		*type = postimg_heat;
+	else if (sector->ffloors)
 	{
-		if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
-		{
-			*type = postimg_heat;
-			break;
-		}
-		else if (sector->ffloors)
-		{
-			ffloor_t *rover;
-			fixed_t topheight;
-			fixed_t bottomheight;
-			boolean gotres = false;
-
-			for (rover = sector->ffloors; rover; rover = rover->next)
-			{
-				size_t j;
+		ffloor_t *rover;
+		fixed_t topheight;
+		fixed_t bottomheight;
 
-				if (!(rover->flags & FF_EXISTS))
-					continue;
+		for (rover = sector->ffloors; rover; rover = rover->next)
+		{
+			if (!(rover->flags & FF_EXISTS))
+				continue;
 
-				topheight    = P_GetFFloorTopZAt   (rover, player->mo->x, player->mo->y);
-				bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
+			topheight    = P_GetFFloorTopZAt   (rover, player->mo->x, player->mo->y);
+			bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
 
-				if (pviewheight >= topheight || pviewheight <= bottomheight)
-					continue;
+			if (pviewheight >= topheight || pviewheight <= bottomheight)
+				continue;
 
-				for (j = 0; j < rover->master->frontsector->tags.count; j++)
-				{
-					if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
-					{
-						*type = postimg_heat;
-						gotres = true;
-						break;
-					}
-				}
-			}
-			if (gotres)
+			if (rover->master->frontsector->flags & MSF_HEATWAVE)
+			{
+				*type = postimg_heat;
 				break;
+			}
 		}
 	}
 
@@ -10867,7 +10767,7 @@ static mobj_t *P_LookForRails(mobj_t* mobj, fixed_t c, fixed_t s, angle_t target
 			fixed_t nx, ny;
 			angle_t nang, dummy, angdiff;
 			mobj_t *mark;
-			mobj_t *snax = P_GetAxis(sides[lines[lline].sidenum[0]].textureoffset >> FRACBITS);
+			mobj_t *snax = P_GetAxis(lines[lline].args[0]);
 			if (!snax)
 				return NULL;
 			P_GetAxisPosition(x, y, snax, &nx, &ny, &nang, &dummy);
@@ -10969,7 +10869,7 @@ static void P_MinecartThink(player_t *player)
 		// Update axis if the cart is standing on a rail.
 		if (sec && lnum != -1)
 		{
-			mobj_t *axis = P_GetAxis(sides[lines[lnum].sidenum[0]].textureoffset >> FRACBITS);
+			mobj_t *axis = P_GetAxis(lines[lnum].args[0]);
 			fixed_t newx, newy;
 			angle_t targetangle, grind;
 			angle_t prevangle, angdiff;
@@ -12348,7 +12248,10 @@ static boolean P_MobjAboveLava(mobj_t *mobj)
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
-			if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE) || GETSECSPECIAL(rover->master->frontsector->special, 1) != 3)
+			if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE))
+				continue;
+
+			if (rover->master->frontsector->damagetype != SD_FIRE && rover->master->frontsector->damagetype != SD_LAVA)
 				continue;
 
 			if (mobj->eflags & MFE_VERTICALFLIP)
diff --git a/src/r_bsp.c b/src/r_bsp.c
index c9f2698166aed732ac9c9d69834b1d305809466f..f0a761d7b25244cedb6dcc0cd9ac7cce465a5d90 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -238,11 +238,11 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 {
 	if (floorlightlevel)
 		*floorlightlevel = sec->floorlightsec == -1 ?
-			sec->lightlevel : sectors[sec->floorlightsec].lightlevel;
+			(sec->floorlightabsolute ? sec->floorlightlevel : max(0, min(255, sec->lightlevel + sec->floorlightlevel))) : sectors[sec->floorlightsec].lightlevel;
 
 	if (ceilinglightlevel)
 		*ceilinglightlevel = sec->ceilinglightsec == -1 ?
-			sec->lightlevel : sectors[sec->ceilinglightsec].lightlevel;
+			(sec->ceilinglightabsolute ? sec->ceilinglightlevel : max(0, min(255, sec->lightlevel + sec->ceilinglightlevel))) : sectors[sec->ceilinglightsec].lightlevel;
 
 	// if (sec->midmap != -1)
 	//	mapnum = sec->midmap;
@@ -301,11 +301,11 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 			tempsec->lightlevel = s->lightlevel;
 
 			if (floorlightlevel)
-				*floorlightlevel = s->floorlightsec == -1 ? s->lightlevel
+				*floorlightlevel = s->floorlightsec == -1 ? (s->floorlightabsolute ? s->floorlightlevel : max(0, min(255, s->lightlevel + s->floorlightlevel)))
 					: sectors[s->floorlightsec].lightlevel;
 
 			if (ceilinglightlevel)
-				*ceilinglightlevel = s->ceilinglightsec == -1 ? s->lightlevel
+				*ceilinglightlevel = s->ceilinglightsec == -1 ? (s->ceilinglightabsolute ? s->ceilinglightlevel : max(0, min(255, s->lightlevel + s->ceilinglightlevel)))
 					: sectors[s->ceilinglightsec].lightlevel;
 		}
 		else if (heightsec != -1 && viewz >= sectors[heightsec].ceilingheight
@@ -339,12 +339,12 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 			tempsec->lightlevel = s->lightlevel;
 
 			if (floorlightlevel)
-				*floorlightlevel = s->floorlightsec == -1 ? s->lightlevel :
-			sectors[s->floorlightsec].lightlevel;
+				*floorlightlevel = s->floorlightsec == -1 ? (s->floorlightabsolute ? s->floorlightlevel : max(0, min(255, s->lightlevel + s->floorlightlevel)))
+					: sectors[s->floorlightsec].lightlevel;
 
 			if (ceilinglightlevel)
-				*ceilinglightlevel = s->ceilinglightsec == -1 ? s->lightlevel :
-			sectors[s->ceilinglightsec].lightlevel;
+				*ceilinglightlevel = s->ceilinglightsec == -1 ? (s->ceilinglightabsolute ? s->ceilinglightlevel : max(0, min(255, s->lightlevel + s->ceilinglightlevel)))
+					: sectors[s->ceilinglightsec].lightlevel;
 		}
 		sec = tempsec;
 	}
@@ -370,6 +370,10 @@ boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back)
 		&& back->ceiling_yoffs == front->ceiling_yoffs
 		&& back->ceilingpic_angle == front->ceilingpic_angle
 		// Consider altered lighting.
+		&& back->floorlightlevel == front->floorlightlevel
+		&& back->floorlightabsolute == front->floorlightabsolute
+		&& back->ceilinglightlevel == front->ceilinglightlevel
+		&& back->ceilinglightabsolute == front->ceilinglightabsolute
 		&& back->floorlightsec == front->floorlightsec
 		&& back->ceilinglightsec == front->ceilinglightsec
 		// Consider colormaps
@@ -872,12 +876,12 @@ static void R_Subsector(size_t num)
 		}
 
 		light = R_GetPlaneLight(frontsector, floorcenterz, false);
-		if (frontsector->floorlightsec == -1)
-			floorlightlevel = *frontsector->lightlist[light].lightlevel;
+		if (frontsector->floorlightsec == -1 && !frontsector->floorlightabsolute)
+			floorlightlevel = max(0, min(255, *frontsector->lightlist[light].lightlevel + frontsector->floorlightlevel));
 		floorcolormap = *frontsector->lightlist[light].extra_colormap;
 		light = R_GetPlaneLight(frontsector, ceilingcenterz, false);
-		if (frontsector->ceilinglightsec == -1)
-			ceilinglightlevel = *frontsector->lightlist[light].lightlevel;
+		if (frontsector->ceilinglightsec == -1 && !frontsector->ceilinglightabsolute)
+			ceilinglightlevel = max(0, min(255, *frontsector->lightlist[light].lightlevel + frontsector->ceilinglightlevel));
 		ceilingcolormap = *frontsector->lightlist[light].extra_colormap;
 	}
 
diff --git a/src/r_defs.h b/src/r_defs.h
index b72d3ddd82e8fcff7ba34b10d0b3c7e3f6c1e61c..9788e6b58c6d0ebf726a94589dfdaf5540cf008f 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -139,21 +139,34 @@ typedef enum
 	FF_FLOATBOB          = 0x40000,    ///< Floats on water and bobs if you step on it.
 	FF_NORETURN          = 0x80000,    ///< Used with ::FF_CRUMBLE. Will not return to its original position after falling.
 	FF_CRUMBLE           = 0x100000,   ///< Falls 2 seconds after being stepped on, and randomly brings all touching crumbling 3dfloors down with it, providing their master sectors share the same tag (allows crumble platforms above or below, to also exist).
-	FF_SHATTERBOTTOM     = 0x200000,   ///< Used with ::FF_BUSTUP. Like FF_SHATTER, but only breaks from the bottom. Good for springing up through rubble.
+	FF_GOOWATER          = 0x200000,   ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
 	FF_MARIO             = 0x400000,   ///< Acts like a question block when hit from underneath. Goodie spawned at top is determined by master sector.
 	FF_BUSTUP            = 0x800000,   ///< You can spin through/punch this block and it will crumble!
 	FF_QUICKSAND         = 0x1000000,  ///< Quicksand!
 	FF_PLATFORM          = 0x2000000,  ///< You can jump up through this to the top.
 	FF_REVERSEPLATFORM   = 0x4000000,  ///< A fall-through floor in normal gravity, a platform in reverse gravity.
 	FF_INTANGIBLEFLATS   = 0x6000000,  ///< Both flats are intangible, but the sides are still solid.
-	FF_SHATTER           = 0x8000000,  ///< Used with ::FF_BUSTUP. Bustable on mere touch.
-	FF_SPINBUST          = 0x10000000, ///< Used with ::FF_BUSTUP. Also bustable if you're in your spinning frames.
-	FF_STRONGBUST        = 0x20000000, ///< Used with ::FF_BUSTUP. Only bustable by "strong" characters (Knuckles) and abilities (bouncing, twinspin, melee).
-	FF_RIPPLE            = 0x40000000, ///< Ripple the flats
-	FF_COLORMAPONLY      = 0x80000000, ///< Only copy the colormap, not the lightlevel
-	FF_GOOWATER          = FF_SHATTERBOTTOM, ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
+	FF_RIPPLE            = 0x8000000,  ///< Ripple the flats
+	FF_COLORMAPONLY      = 0x10000000, ///< Only copy the colormap, not the lightlevel
+	FF_BOUNCY            = 0x20000000, ///< Bounces players
+	FF_SPLAT             = 0x40000000, ///< Use splat flat renderer (treat cyan pixels as invisible)
 } ffloortype_e;
 
+typedef enum
+{
+	FB_PUSHABLES   = 0x1, // Bustable by pushables
+	FB_EXECUTOR    = 0x2, // Trigger linedef executor
+	FB_ONLYBOTTOM  = 0x4, // Only bustable from below
+} ffloorbustflags_e;
+
+typedef enum
+{
+	BT_TOUCH,
+	BT_SPINBUST,
+	BT_REGULAR,
+	BT_STRONG,
+} busttype_e;
+
 typedef struct ffloor_s
 {
 	fixed_t *topheight;
@@ -187,6 +200,18 @@ typedef struct ffloor_s
 	UINT8 blend; // blendmode
 	tic_t norender; // for culling
 
+	// Only relevant for FF_BUSTUP
+	ffloorbustflags_e bustflags;
+	UINT8 busttype;
+	INT16 busttag;
+
+	// Only relevant for FF_QUICKSAND
+	fixed_t sinkspeed;
+	fixed_t friction;
+
+	// Only relevant for FF_BOUNCY
+	fixed_t bouncestrength;
+
 	// these are saved for netgames, so do not let Lua touch these!
 	ffloortype_e spawnflags; // flags the 3D floor spawned with
 	INT32 spawnalpha; // alpha the 3D floor spawned with
@@ -252,16 +277,68 @@ typedef struct pslope_s
 typedef enum
 {
 	// flipspecial - planes with effect
-	SF_FLIPSPECIAL_FLOOR       =  1,
-	SF_FLIPSPECIAL_CEILING     =  1<<1,
-	SF_FLIPSPECIAL_BOTH        =  (SF_FLIPSPECIAL_FLOOR|SF_FLIPSPECIAL_CEILING),
+	MSF_FLIPSPECIAL_FLOOR       =  1,
+	MSF_FLIPSPECIAL_CEILING     =  1<<1,
+	MSF_FLIPSPECIAL_BOTH        =  (MSF_FLIPSPECIAL_FLOOR|MSF_FLIPSPECIAL_CEILING),
 	// triggerspecial - conditions under which plane touch causes effect
-	SF_TRIGGERSPECIAL_TOUCH    =  1<<2,
-	SF_TRIGGERSPECIAL_HEADBUMP =  1<<3,
+	MSF_TRIGGERSPECIAL_TOUCH    =  1<<2,
+	MSF_TRIGGERSPECIAL_HEADBUMP =  1<<3,
+	// triggerline - conditions for linedef executor triggering
+	MSF_TRIGGERLINE_PLANE       =  1<<4, // require plane touch
+	MSF_TRIGGERLINE_MOBJ        =  1<<5, // allow non-pushable mobjs to trigger
 	// invertprecip - inverts presence of precipitation
-	SF_INVERTPRECIP            =  1<<4,
+	MSF_INVERTPRECIP            =  1<<6,
+	MSF_GRAVITYFLIP             =  1<<7,
+	MSF_HEATWAVE                =  1<<8,
+	MSF_NOCLIPCAMERA            =  1<<9,
 } sectorflags_t;
 
+typedef enum
+{
+	SSF_OUTERSPACE = 1,
+	SSF_DOUBLESTEPUP = 1<<1,
+	SSF_NOSTEPDOWN = 1<<2,
+	SSF_WINDCURRENT = 1<<3,
+	SSF_CONVEYOR = 1<<4,
+	SSF_SPEEDPAD = 1<<5,
+	SSF_STARPOSTACTIVATOR = 1<<6,
+	SSF_EXIT = 1<<7,
+	SSF_SPECIALSTAGEPIT = 1<<8,
+	SSF_RETURNFLAG = 1<<9,
+	SSF_REDTEAMBASE = 1<<10,
+	SSF_BLUETEAMBASE = 1<<11,
+	SSF_FAN = 1<<12,
+	SSF_SUPERTRANSFORM = 1<<13,
+	SSF_FORCESPIN = 1<<14,
+	SSF_ZOOMTUBESTART = 1<<15,
+	SSF_ZOOMTUBEEND = 1<<16,
+	SSF_FINISHLINE = 1<<17,
+	SSF_ROPEHANG = 1<<18,
+} sectorspecialflags_t;
+
+typedef enum
+{
+	SD_NONE = 0,
+	SD_GENERIC = 1,
+	SD_WATER = 2,
+	SD_FIRE = 3,
+	SD_LAVA = 4,
+	SD_ELECTRIC = 5,
+	SD_SPIKE = 6,
+	SD_DEATHPITTILT = 7,
+	SD_DEATHPITNOTILT = 8,
+	SD_INSTAKILL = 9,
+	SD_SPECIALSTAGE = 10,
+} sectordamage_t;
+
+typedef enum
+{
+	TO_PLAYER = 0,
+	TO_ALLPLAYERS = 1,
+	TO_MOBJ = 2,
+	TO_PLAYEREMERALDS = 3, // only for binary backwards compatibility: check player emeralds
+	TO_PLAYERNIGHTS = 4, // only for binary backwards compatibility: check NiGHTS mare
+} triggerobject_t;
 
 typedef enum
 {
@@ -313,7 +390,11 @@ typedef struct sector_s
 	INT32 heightsec; // other sector, or -1 if no other sector
 	INT32 camsec; // used for camera clipping
 
-	INT32 floorlightsec, ceilinglightsec;
+	// floor and ceiling lighting
+	INT16 floorlightlevel, ceilinglightlevel;
+	boolean floorlightabsolute, ceilinglightabsolute; // absolute or relative to sector's light level?
+	INT32 floorlightsec, ceilinglightsec; // take floor/ceiling light level from another sector
+
 	INT32 crumblestate; // used for crumbling and bobbing
 
 	// list of mobjs that are at least partially in the sector
@@ -337,10 +418,18 @@ typedef struct sector_s
 	extracolormap_t *extra_colormap;
 	boolean colormap_protected;
 
-	// This points to the master's floorheight, so it can be changed in realtime!
-	fixed_t *gravity; // per-sector gravity
-	boolean verticalflip; // If gravity < 0, then allow flipped physics
+	fixed_t gravity; // per-sector gravity factor
+	fixed_t *gravityptr; // For binary format: Read gravity from floor height of master sector
+
 	sectorflags_t flags;
+	sectorspecialflags_t specialflags;
+	UINT8 damagetype;
+
+	// Linedef executor triggering
+	mtag_t triggertag; // tag to call upon triggering
+	UINT8 triggerer; // who can trigger?
+
+	fixed_t friction;
 
 	// Sprite culling feature
 	struct line_s *cullheight;
@@ -377,7 +466,7 @@ typedef enum
 
 #define HORIZONSPECIAL 41
 
-#define NUMLINEARGS 6
+#define NUMLINEARGS 10
 #define NUMLINESTRINGARGS 2
 
 typedef struct line_s
diff --git a/src/r_main.c b/src/r_main.c
index 13d2413fae4611ab4a1079e08e260466790b1529..f19962d412667646804fc72c6ad9f5535fb20315 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -445,7 +445,7 @@ fixed_t R_ScaleFromGlobalAngle(angle_t visangle)
 // R_DoCulling
 // Checks viewz and top/bottom heights of an item against culling planes
 // Returns true if the item is to be culled, i.e it shouldn't be drawn!
-// if ML_NOCLIMB is set, the camera view is required to be in the same area for culling to occur
+// if args[1] is set, the camera view is required to be in the same area for culling to occur
 boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixed_t bottomh, fixed_t toph)
 {
 	fixed_t cullplane;
@@ -454,7 +454,7 @@ boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixe
 		return false;
 
 	cullplane = cullheight->frontsector->floorheight;
-	if (cullheight->flags & ML_NOCLIMB) // Group culling
+	if (cullheight->args[1]) // Group culling
 	{
 		if (!viewcullheight)
 			return false;
diff --git a/src/r_plane.c b/src/r_plane.c
index e31d52be9a904b2410061939bc1c5f867452382f..7ea10f61676530486a798ed36920b10ca54f7f18 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -852,7 +852,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 
 			if (pl->ffloor->flags & FF_TRANSLUCENT)
 			{
-				spanfunctype = (pl->ffloor->master->flags & ML_EFFECT6) ? SPANDRAWFUNC_TRANSSPLAT : SPANDRAWFUNC_TRANS;
+				spanfunctype = (pl->ffloor->flags & FF_SPLAT) ? SPANDRAWFUNC_TRANSSPLAT : SPANDRAWFUNC_TRANS;
 
 				// Hacked up support for alpha value in software mode Tails 09-24-2002
 				// ...unhacked by toaster 04-01-2021, re-hacked a little by sphere 19-11-2021
diff --git a/src/r_segs.c b/src/r_segs.c
index 41ffa4103aab482ceda2070afd595c5e2d6156d1..c9f5f0d7bbb68c4ff451c8cc4b1a479692cdccf1 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -301,7 +301,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 
 	if (ds->curline->sidedef->repeatcnt)
 		repeats = 1 + ds->curline->sidedef->repeatcnt;
-	else if (ldef->flags & ML_EFFECT5)
+	else if (ldef->flags & ML_WRAPMIDTEX)
 	{
 		fixed_t high, low;
 
@@ -345,7 +345,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 		{
 			dc_texturemid = ds->maskedtextureheight[dc_x];
 
-			if (!!(curline->linedef->flags & ML_DONTPEGBOTTOM) ^ !!(curline->linedef->flags & ML_EFFECT3))
+			if (curline->linedef->flags & ML_MIDPEG)
 				dc_texturemid += (textureheight[texnum])*times + textureheight[texnum];
 			else
 				dc_texturemid -= (textureheight[texnum])*times;
@@ -765,10 +765,10 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	skewslope = *pfloor->t_slope; // skew using top slope by default
 	if (newline)
 	{
-		if (newline->flags & ML_DONTPEGTOP)
+		if (newline->flags & ML_SKEWTD)
 			slopeskew = true;
 	}
-	else if (pfloor->master->flags & ML_DONTPEGTOP)
+	else if (pfloor->master->flags & ML_SKEWTD)
 		slopeskew = true;
 
 	if (slopeskew)
@@ -1455,9 +1455,9 @@ static void R_RenderSegLoop (void)
 			maskedtexturecol[rw_x] = (INT16)texturecolumn;
 
 			if (maskedtextureheight != NULL) {
-				maskedtextureheight[rw_x] = (!!(curline->linedef->flags & ML_DONTPEGBOTTOM) ^ !!(curline->linedef->flags & ML_EFFECT3) ?
+				maskedtextureheight[rw_x] = (curline->linedef->flags & ML_MIDPEG) ?
 											max(rw_midtexturemid, rw_midtextureback) :
-											min(rw_midtexturemid, rw_midtextureback));
+											min(rw_midtexturemid, rw_midtextureback);
 			}
 		}
 
@@ -1766,7 +1766,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		texheight = textureheight[midtexture];
 		// a single sided line is terminal, so it must mark ends
 		markfloor = markceiling = true;
-		if (linedef->flags & ML_EFFECT2) {
+		if (linedef->flags & ML_NOSKEW) {
 			if (linedef->flags & ML_DONTPEGBOTTOM)
 				rw_midtexturemid = frontsector->floorheight + texheight - viewz;
 			else
@@ -1903,18 +1903,20 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		else if (worldlow != worldbottom
 			|| worldlowslope != worldbottomslope
 			|| backsector->f_slope != frontsector->f_slope
-		    || backsector->floorpic != frontsector->floorpic
-		    || backsector->lightlevel != frontsector->lightlevel
-		    //SoM: 3/22/2000: Check floor x and y offsets.
-		    || backsector->floor_xoffs != frontsector->floor_xoffs
-		    || backsector->floor_yoffs != frontsector->floor_yoffs
-		    || backsector->floorpic_angle != frontsector->floorpic_angle
-		    //SoM: 3/22/2000: Prevents bleeding.
-		    || (frontsector->heightsec != -1 && frontsector->floorpic != skyflatnum)
-		    || backsector->floorlightsec != frontsector->floorlightsec
-		    //SoM: 4/3/2000: Check for colormaps
-		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
+			|| backsector->floorpic != frontsector->floorpic
+			|| backsector->lightlevel != frontsector->lightlevel
+			//SoM: 3/22/2000: Check floor x and y offsets.
+			|| backsector->floor_xoffs != frontsector->floor_xoffs
+			|| backsector->floor_yoffs != frontsector->floor_yoffs
+			|| backsector->floorpic_angle != frontsector->floorpic_angle
+			//SoM: 3/22/2000: Prevents bleeding.
+			|| (frontsector->heightsec != -1 && frontsector->floorpic != skyflatnum)
+			|| backsector->floorlightlevel != frontsector->floorlightlevel
+			|| backsector->floorlightabsolute != frontsector->floorlightabsolute
+			|| backsector->floorlightsec != frontsector->floorlightsec
+			//SoM: 4/3/2000: Check for colormaps
+			|| frontsector->extra_colormap != backsector->extra_colormap
+			|| (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 			markfloor = true;
 		}
@@ -1934,18 +1936,20 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		else if (worldhigh != worldtop
 			|| worldhighslope != worldtopslope
 			|| backsector->c_slope != frontsector->c_slope
-		    || backsector->ceilingpic != frontsector->ceilingpic
-		    || backsector->lightlevel != frontsector->lightlevel
-		    //SoM: 3/22/2000: Check floor x and y offsets.
-		    || backsector->ceiling_xoffs != frontsector->ceiling_xoffs
-		    || backsector->ceiling_yoffs != frontsector->ceiling_yoffs
-		    || backsector->ceilingpic_angle != frontsector->ceilingpic_angle
-		    //SoM: 3/22/2000: Prevents bleeding.
-		    || (frontsector->heightsec != -1 && frontsector->ceilingpic != skyflatnum)
-		    || backsector->ceilinglightsec != frontsector->ceilinglightsec
-		    //SoM: 4/3/2000: Check for colormaps
-		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
+			|| backsector->ceilingpic != frontsector->ceilingpic
+			|| backsector->lightlevel != frontsector->lightlevel
+			//SoM: 3/22/2000: Check floor x and y offsets.
+			|| backsector->ceiling_xoffs != frontsector->ceiling_xoffs
+			|| backsector->ceiling_yoffs != frontsector->ceiling_yoffs
+			|| backsector->ceilingpic_angle != frontsector->ceilingpic_angle
+			//SoM: 3/22/2000: Prevents bleeding.
+			|| (frontsector->heightsec != -1 && frontsector->ceilingpic != skyflatnum)
+			|| backsector->ceilinglightlevel != frontsector->ceilinglightlevel
+			|| backsector->ceilinglightabsolute != frontsector->ceilinglightabsolute
+			|| backsector->ceilinglightsec != frontsector->ceilinglightsec
+			//SoM: 4/3/2000: Check for colormaps
+			|| frontsector->extra_colormap != backsector->extra_colormap
+			|| (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 				markceiling = true;
 		}
@@ -1971,23 +1975,10 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		{
 			fixed_t texheight;
 			// top texture
-			if ((linedef->flags & (ML_DONTPEGTOP) && (linedef->flags & ML_DONTPEGBOTTOM))
-				&& linedef->sidenum[1] != 0xffff)
-			{
-				// Special case... use offsets from 2nd side but only if it has a texture.
-				side_t *def = &sides[linedef->sidenum[1]];
-				toptexture = R_GetTextureNum(def->toptexture);
+			toptexture = R_GetTextureNum(sidedef->toptexture);
+			texheight = textureheight[toptexture];
 
-				if (!toptexture) //Second side has no texture, use the first side's instead.
-					toptexture = R_GetTextureNum(sidedef->toptexture);
-				texheight = textureheight[toptexture];
-			}
-			else
-			{
-				toptexture = R_GetTextureNum(sidedef->toptexture);
-				texheight = textureheight[toptexture];
-			}
-			if (!(linedef->flags & ML_EFFECT1)) { // Ignore slopes for lower/upper textures unless flag is checked
+			if (!(linedef->flags & ML_SKEWTD)) { // Ignore slopes for lower/upper textures unless flag is checked
 				if (linedef->flags & ML_DONTPEGTOP)
 					rw_toptexturemid = frontsector->ceilingheight - viewz;
 				else
@@ -2012,7 +2003,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			// bottom texture
 			bottomtexture = R_GetTextureNum(sidedef->bottomtexture);
 
-			if (!(linedef->flags & ML_EFFECT1)) { // Ignore slopes for lower/upper textures unless flag is checked
+			if (!(linedef->flags & ML_SKEWTD)) { // Ignore slopes for lower/upper textures unless flag is checked
 				if (linedef->flags & ML_DONTPEGBOTTOM)
 					rw_bottomtexturemid = frontsector->floorheight - viewz;
 				else
@@ -2243,7 +2234,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			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))
+				if (linedef->flags & ML_MIDPEG)
 					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;
@@ -2251,16 +2242,16 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			else
 			{
 				// Set midtexture starting height
-				if (linedef->flags & ML_EFFECT2)
+				if (linedef->flags & ML_NOSKEW)
 				{ // Ignore slopes when texturing
 					rw_midtextureslide = rw_midtexturebackslide = 0;
-					if (!!(linedef->flags & ML_DONTPEGBOTTOM) ^ !!(linedef->flags & ML_EFFECT3))
+					if (linedef->flags & ML_MIDPEG)
 						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))
+				else if (linedef->flags & ML_MIDPEG)
 				{
 					rw_midtexturemid = worldbottom;
 					rw_midtextureslide = floorfrontslide;
diff --git a/src/taglist.c b/src/taglist.c
index 6b50a51ab081b3604b0aef8f93b725a3e2ab3c35..9bc6021b091ecab70de33ff1c31b0225571ea254 100644
--- a/src/taglist.c
+++ b/src/taglist.c
@@ -37,6 +37,25 @@ void Tag_Add (taglist_t* list, const mtag_t tag)
 	list->tags[list->count++] = tag;
 }
 
+/// Removes a tag from a given element's taglist.
+/// \warning This does not rebuild the global taggroups, which are used for iteration.
+void Tag_Remove(taglist_t* list, const mtag_t tag)
+{
+	UINT16 i;
+
+	for (i = 0; i < list->count; i++)
+	{
+		if (list->tags[i] != tag)
+			continue;
+
+		for (; i+1 < list->count; i++)
+			list->tags[i] = list->tags[i+1];
+
+		list->tags = Z_Realloc(list->tags, (list->count - 1) * sizeof(mtag_t), PU_LEVEL, NULL);
+		return;
+	}
+}
+
 /// Sets the first tag entry in a taglist.
 /// Replicates the old way of accessing element->tag.
 void Tag_FSet (taglist_t* list, const mtag_t tag)
@@ -408,6 +427,22 @@ INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
 
 // Ingame list manipulation.
 
+/// Adds the tag to the given sector, and updates the global taggroups.
+void Tag_SectorAdd (const size_t id, const mtag_t tag)
+{
+	sector_t* sec = &sectors[id];
+	Tag_Add(&sec->tags, tag);
+	Taggroup_Add(tags_sectors, tag, id);
+}
+
+/// Removes the tag from the given sector, and updates the global taggroups.
+void Tag_SectorRemove (const size_t id, const mtag_t tag)
+{
+	sector_t* sec = &sectors[id];
+	Tag_Remove(&sec->tags, tag);
+	Taggroup_Remove(tags_sectors, tag, id);
+}
+
 /// Changes the first tag for a given sector, and updates the global taggroups.
 void Tag_SectorFSet (const size_t id, const mtag_t tag)
 {
@@ -420,3 +455,16 @@ void Tag_SectorFSet (const size_t id, const mtag_t tag)
 	Taggroup_Add(tags_sectors, tag, id);
 	Tag_FSet(&sec->tags, tag);
 }
+
+mtag_t Tag_NextUnused(mtag_t start)
+{
+	while ((UINT16)start < MAXTAGS)
+	{
+		if (!in_bit_array(tags_available, (UINT16)start))
+			return start;
+
+		start++;
+	}
+
+	return MAXTAGS;
+}
diff --git a/src/taglist.h b/src/taglist.h
index d326ef357855efeb3ea70729fcc7d1812a47391c..de6e9abd5fd7b5f599601da8382af7540a0efd36 100644
--- a/src/taglist.h
+++ b/src/taglist.h
@@ -28,12 +28,15 @@ typedef struct
 } taglist_t;
 
 void Tag_Add (taglist_t* list, const mtag_t tag);
+void Tag_Remove (taglist_t* list, const mtag_t tag);
 void Tag_FSet (taglist_t* list, const mtag_t tag);
 mtag_t Tag_FGet (const taglist_t* list);
 boolean Tag_Find (const taglist_t* list, const mtag_t tag);
 boolean Tag_Share (const taglist_t* list1, const taglist_t* list2);
 boolean Tag_Compare (const taglist_t* list1, const taglist_t* list2);
 
+void Tag_SectorAdd (const size_t id, const mtag_t tag);
+void Tag_SectorRemove (const size_t id, const mtag_t tag);
 void Tag_SectorFSet (const size_t id, const mtag_t tag);
 
 /// Taggroup list. It is essentially just an element id list.
@@ -46,6 +49,8 @@ typedef struct
 
 extern bitarray_t tags_available[];
 
+extern mtag_t Tag_NextUnused(mtag_t start);
+
 extern size_t num_tags;
 
 extern taggroup_t* tags_sectors[];