diff --git a/dep/.gitignore b/dep/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..fb941664fc5718a31185a0cf67ccd2cfd68a2be8
--- /dev/null
+++ b/dep/.gitignore
@@ -0,0 +1,2 @@
+#All folders
+*.d
diff --git a/dep/FreeBSD/SDL/Debug/.gitignore b/dep/FreeBSD/SDL/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/FreeBSD/SDL/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/FreeBSD/SDL/Release/.gitignore b/dep/FreeBSD/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/FreeBSD/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Linux/SDL/Debug/.gitignore b/dep/Linux/SDL/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Linux/SDL/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Linux/SDL/Release/.gitignore b/dep/Linux/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Linux/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Linux64/SDL/Debug/.gitignore b/dep/Linux64/SDL/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Linux64/SDL/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Linux64/SDL/Release/.gitignore b/dep/Linux64/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Linux64/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/MasterClient/.gitignore b/dep/MasterClient/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/MasterClient/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/MasterServer/.gitignore b/dep/MasterServer/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/MasterServer/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw/Debug/.gitignore b/dep/Mingw/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw/Release/.gitignore b/dep/Mingw/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw/SDL/Debug/.gitignore b/dep/Mingw/SDL/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw/SDL/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw/SDL/Release/.gitignore b/dep/Mingw/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw64/Debug/.gitignore b/dep/Mingw64/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw64/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw64/Release/.gitignore b/dep/Mingw64/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw64/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw64/SDL/Debug/.gitignore b/dep/Mingw64/SDL/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw64/SDL/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw64/SDL/Release/.gitignore b/dep/Mingw64/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw64/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/SDL/Release/.gitignore b/dep/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/VC/.gitignore b/dep/VC/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/VC/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/VC9/.gitignore b/dep/VC9/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/VC9/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/cygwin/Debug/.gitignore b/dep/cygwin/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/cygwin/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/cygwin/Release/.gitignore b/dep/cygwin/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/cygwin/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/dummy/.gitignore b/dep/dummy/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/dummy/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/extras/conf/SRB2-22.cfg b/extras/conf/SRB2-22.cfg
index a0d40cdf0003dcc9f8ed81b0812825a625f6048a..3fd4b6ccdc0f6b24f1533d912daf234042dd870e 100644
--- a/extras/conf/SRB2-22.cfg
+++ b/extras/conf/SRB2-22.cfg
@@ -746,13 +746,13 @@ linedeftypes
 
 		20
 		{
-			title = "First Line";
+			title = "PolyObject First Line";
 			prefix = "(20)";
 		}
 
 		22
 		{
-			title = "Parameters";
+			title = "PolyObject Parameters";
 			prefix = "(22)";
 			flags8text = "[3] Set translucency by X offset";
 			flags32text = "[5] Render outer sides only";
@@ -765,19 +765,19 @@ linedeftypes
 
 		30
 		{
-			title = "Waving Flag";
+			title = "PolyObject Waving Flag";
 			prefix = "(30)";
 		}
 
 		31
 		{
-			title = "Displacement by Front Sector";
+			title = "Move PolyObject by Front Sector Displacement";
 			prefix = "(31)";
 		}
 
 		32
 		{
-			title = "Angular Displacement by Front Sector";
+			title = "Rotate PolyObject by Front Sector Displacement";
 			prefix = "(32)";
 			flags64text = "[6] Don't turn players";
 			flags512text = "[9] Turn all objects";
@@ -2498,35 +2498,35 @@ linedeftypes
 
 		480
 		{
-			title = "Door Slide";
+			title = "PolyObject Door Slide";
 			prefix = "(480)";
 			flags8text = "[3] Set delay by backside sector";
 		}
 
 		481
 		{
-			title = "Door Swing";
+			title = "PolyObject Door Swing";
 			prefix = "(481)";
 			flags8text = "[3] Set delay by backside sector";
 		}
 
 		482
 		{
-			title = "Move";
+			title = "Move PolyObject";
 			prefix = "(482)";
 			flags8text = "[3] Set delay by backside sector";
 		}
 
 		483
 		{
-			title = "Move, Override";
+			title = "Move PolyObject, Override";
 			prefix = "(483)";
 			flags8text = "[3] Set delay by backside sector";
 		}
 
 		484
 		{
-			title = "Rotate Right";
+			title = "Rotate PolyObject Right";
 			prefix = "(484)";
 			flags8text = "[3] Set delay by backside sector";
 			flags64text = "[6] Don't turn players";
@@ -2535,7 +2535,7 @@ linedeftypes
 
 		485
 		{
-			title = "Rotate Right, Override";
+			title = "Rotate PolyObject Right, Override";
 			prefix = "(485)";
 			flags8text = "[3] Set delay by backside sector";
 			flags64text = "[6] Don't turn players";
@@ -2544,7 +2544,7 @@ linedeftypes
 
 		486
 		{
-			title = "Rotate Left";
+			title = "Rotate PolyObject Left";
 			prefix = "(486)";
 			flags8text = "[3] Set delay by backside sector";
 			flags64text = "[6] Don't turn players";
@@ -2553,7 +2553,7 @@ linedeftypes
 
 		487
 		{
-			title = "Rotate Left, Override";
+			title = "Rotate PolyObject Left, Override";
 			prefix = "(487)";
 			flags8text = "[3] Set delay by backside sector";
 			flags64text = "[6] Don't turn players";
@@ -2562,7 +2562,7 @@ linedeftypes
 
 		488
 		{
-			title = "Move by Waypoints";
+			title = "Move PolyObject by Waypoints";
 			prefix = "(488)";
 			flags8text = "[3] Set delay by backside sector";
 			flags32text = "[5] Reverse order";
@@ -2573,7 +2573,7 @@ linedeftypes
 
 		489
 		{
-			title = "Turn Invisible, Intangible";
+			title = "Turn PolyObject Invisible, Intangible";
 			prefix = "(489)";
 			flags8text = "[3] Set delay by backside sector";
 			flags64text = "[6] Only invisible";
@@ -2581,7 +2581,7 @@ linedeftypes
 
 		490
 		{
-			title = "Turn Visible, Tangible";
+			title = "Turn PolyObject Visible, Tangible";
 			prefix = "(490)";
 			flags8text = "[3] Set delay by backside sector";
 			flags64text = "[6] Only visible";
@@ -2589,7 +2589,7 @@ linedeftypes
 
 		491
 		{
-			title = "Set Translucency";
+			title = "Set PolyObject Translucency";
 			prefix = "(491)";
 			flags8text = "[3] Set delay by backside sector";
 			flags16text = "[4] Set raw alpha by Front X";
@@ -2598,7 +2598,7 @@ linedeftypes
 
 		492
 		{
-			title = "Fade Translucency";
+			title = "Fade PolyObject Translucency";
 			prefix = "(492)";
 			flags8text = "[3] Set delay by backside sector";
 			flags16text = "[4] Set raw alpha by Front X";
@@ -3393,6 +3393,7 @@ thingtypes
 			width = 8;
 			height = 28;
 			angletext = "Jump strength";
+			fixedrotation = 1;
 		}
 		103
 		{
@@ -3431,6 +3432,7 @@ thingtypes
 			width = 12;
 			height = 64;
 			angletext = "Firing delay";
+			fixedrotation = 1;
 		}
 		122
 		{
@@ -3547,9 +3549,10 @@ thingtypes
 		{
 			title = "Pterabyte Spawner";
 			sprite = "PTERA2A8";
-			width = 16;
-			height = 16;
-			parametertext = "No. Pterabytes";
+			width = 24;
+			height = 48;
+			parametertext = "Spawns +1";
+			arrow = 0;
 		}
 		136
 		{
@@ -3771,6 +3774,7 @@ thingtypes
 			height = 16;
 			sprite = "internal:capsule";
 			angletext = "Tag";
+			fixedrotation = 1;
 		}
 		292
 		{
@@ -3781,11 +3785,13 @@ thingtypes
 			flags8text = "[8] Sea Egg shooting point";
 			sprite = "internal:eggmanway";
 			angletext = "No. (Sea Egg)";
+			fixedrotation = 1;
 			flagsvaluetext = "No. (Brak)";
 			parametertext = "Next";
 		}
 		293
 		{
+			arrow = 0;
 			title = "Metal Sonic Gather Point";
 			sprite = "internal:metal";
 			width = 8;
@@ -3793,6 +3799,7 @@ thingtypes
 		}
 		294
 		{
+			arrow = 0;
 			title = "Fang Waypoint";
 			flags8text = "[8] Center waypoint";
 			sprite = "internal:eggmanway";
@@ -3820,79 +3827,79 @@ thingtypes
 		301
 		{
 			title = "Bounce Ring";
-			sprite = "internal:RNGBA0";
+			sprite = "RNGBA0";
 		}
 		302
 		{
 			title = "Rail Ring";
-			sprite = "internal:RNGRA0";
+			sprite = "RNGRA0";
 		}
 		303
 		{
 			title = "Infinity Ring";
-			sprite = "internal:RNGIA0";
+			sprite = "RNGIA0";
 		}
 		304
 		{
 			title = "Automatic Ring";
-			sprite = "internal:RNGAA0";
+			sprite = "RNGAA0";
 		}
 		305
 		{
 			title = "Explosion Ring";
-			sprite = "internal:RNGEA0";
+			sprite = "RNGEA0";
 		}
 		306
 		{
 			title = "Scatter Ring";
-			sprite = "internal:RNGSA0";
+			sprite = "RNGSA0";
 		}
 		307
 		{
 			title = "Grenade Ring";
-			sprite = "internal:RNGGA0";
+			sprite = "RNGGA0";
 		}
 		308
 		{
 			title = "CTF Team Ring (Red)";
-			sprite = "internal:RRNGA0";
+			sprite = "internal:TRNGA0r";
 			width = 16;
 		}
 		309
 		{
 			title = "CTF Team Ring (Blue)";
-			sprite = "internal:BRNGA0";
+			sprite = "internal:TRNGA0b";
 			width = 16;
 		}
 		330
 		{
 			title = "Bounce Ring Panel";
-			sprite = "internal:PIKBA0";
+			sprite = "PIKBA0";
 		}
 		331
 		{
 			title = "Rail Ring Panel";
-			sprite = "internal:PIKRA0";
+			sprite = "PIKRA0";
 		}
 		332
 		{
 			title = "Automatic Ring Panel";
-			sprite = "internal:PIKAA0";
+			sprite = "PIKAA0";
 		}
 		333
 		{
 			title = "Explosion Ring Panel";
-			sprite = "internal:PIKEA0";
+			sprite = "PIKEA0";
 		}
 		334
 		{
 			title = "Scatter Ring Panel";
-			sprite = "internal:PIKSA0";
+			sprite = "PIKSA0";
 		}
 		335
 		{
 			title = "Grenade Ring Panel";
-			sprite = "internal:PIKGA0";
+			sprite = "PIKGA0";
 		}
 	}
 
@@ -3986,6 +3993,7 @@ thingtypes
 			flags8height = 24;
 			flags8text = "[8] Float";
 			angletext = "Tag";
+			fixedrotation = 1;
 		}
 	}
 
@@ -4000,6 +4008,7 @@ thingtypes
 		flags4text = "[4] Random (Strong)";
 		flags8text = "[8] Random (Weak)";
 		angletext = "Tag";
+		fixedrotation = 1;
 
 		400
 		{
@@ -4131,6 +4140,7 @@ thingtypes
 		height = 44;
 		flags1text = "[1] Run linedef executor on pop";
 		angletext = "Tag";
+		fixedrotation = 1;
 
 		431
 		{
@@ -4228,6 +4238,7 @@ thingtypes
 			height = 128;
 			flags4text = "[4] Respawn at center";
 			angletext = "Angle/Order";
+			fixedrotation = 1;
 			parametertext = "Order";
 		}
 		520
@@ -4259,7 +4270,7 @@ thingtypes
 			flags1text = "[1] Start retracted";
 			flags4text = "[4] Retractable";
 			flags8text = "[8] Intangible";
-			parametertext = "Initial delay";
+			parametertext = "Start delay";
 		}
 		523
 		{
@@ -4271,7 +4282,8 @@ thingtypes
 			flags4text = "[4] Retractable";
 			flags8text = "[8] Intangible";
 			angletext = "Retraction interval";
-			parametertext = "Initial delay";
+			fixedrotation = 1;
+			parametertext = "Start delay";
 		}
 		1130
 		{
@@ -4320,6 +4332,7 @@ thingtypes
 			flags4text = "[4] Invisible";
 			flags8text = "[8] No distance check";
 			angletext = "Lift height";
+			fixedrotation = 1;
 		}
 		541
 		{
@@ -4335,6 +4348,7 @@ thingtypes
 			width = 32;
 			height = 64;
 			angletext = "Strength";
+			fixedrotation = 1;
 		}
 		543
 		{
@@ -4344,6 +4358,7 @@ thingtypes
 			height = 64;
 			flags8text = "[8] Respawn";
 			angletext = "Color";
+			fixedrotation = 1;
 		}
 		550
 		{
@@ -4617,6 +4632,9 @@ thingtypes
 			title = "Slope Vertex";
 			sprite = "internal:vertexslope";
 			angletext = "Tag";
+			fixedrotation = 1;
+			parametertext = "Absolute?";
+			flagsvaluetext = "Absolute Z";
 		}
 
 		751
@@ -4638,6 +4656,7 @@ thingtypes
 			title = "Zoom Tube Waypoint";
 			sprite = "internal:zoom";
 			angletext = "Order";
+			fixedrotation = 1;
 		}
 
 		754
@@ -4647,6 +4666,7 @@ thingtypes
 			flags8text = "[8] Push using XYZ";
 			sprite = "GWLGA0";
 			angletext = "Radius";
+			fixedrotation = 1;
 		}
 		755
 		{
@@ -4655,6 +4675,7 @@ thingtypes
 			flags8text = "[8] Pull using XYZ";
 			sprite = "GWLRA0";
 			angletext = "Radius";
+			fixedrotation = 1;
 		}
 		756
 		{
@@ -4663,6 +4684,7 @@ thingtypes
 			width = 32;
 			height = 16;
 			angletext = "Tag";
+			fixedrotation = 1;
 		}
 		757
 		{
@@ -4671,6 +4693,7 @@ thingtypes
 			width = 8;
 			height = 16;
 			angletext = "Tag";
+			fixedrotation = 1;
 		}
 		758
 		{
@@ -4681,21 +4704,24 @@ thingtypes
 		{
 			title = "PolyObject Anchor";
 			sprite = "internal:polyanchor";
-			angletext = "ID";
+			angletext = "Tag";
+			fixedrotation = 1;
 		}
 
 		761
 		{
 			title = "PolyObject Spawn Point";
 			sprite = "internal:polycenter";
-			angletext = "ID";
+			angletext = "Tag";
+			fixedrotation = 1;
 		}
 
 		762
 		{
 			title = "PolyObject Spawn Point (Crush)";
 			sprite = "internal:polycentercrush";
-			angletext = "ID";
+			angletext = "Tag";
+			fixedrotation = 1;
 		}
 		780
 		{
@@ -4703,6 +4729,7 @@ thingtypes
 			sprite = "internal:skyb";
 			flags4text = "[4] In-map centerpoint";
 			parametertext = "ID";
+			fixedrotation = 1;
 		}
 	}
 
@@ -4897,6 +4924,7 @@ thingtypes
 			height = 16;
 			hangs = 1;
 			angletext = "Dripping interval";
+			fixedrotation = 1;
 		}
 		1003
 		{
@@ -4953,7 +4981,7 @@ thingtypes
 		1011
 		{
 			title = "Stalagmite (DSZ2)";
-			sprite = "DSTGA0";
+			sprite = "DSTGB0";
 			width = 8;
 			height = 116;
 			flags4text = "[4] Double size";
@@ -5038,6 +5066,8 @@ thingtypes
 			flags4text = "[4] No sounds";
 			flags8text = "[8] Double size";
 			angletext = "Tag";
+			parametertext = "Spokes";
+			fixedrotation = 1;
 		}
 		1105
 		{
@@ -5048,6 +5078,8 @@ thingtypes
 			flags4text = "[4] No sounds";
 			flags8text = "[8] Double size";
 			angletext = "Tag";
+			parametertext = "Spokes";
+			fixedrotation = 1;
 		}
 		1106
 		{
@@ -5058,6 +5090,8 @@ thingtypes
 			flags4text = "[4] No sounds";
 			flags8text = "[8] Red spring";
 			angletext = "Tag";
+			parametertext = "Spokes";
+			fixedrotation = 1;
 		}
 		1107
 		{
@@ -5067,6 +5101,8 @@ thingtypes
 			height = 34;
 			flags8text = "[8] Double size";
 			angletext = "Tag";
+			parametertext = "Spokes";
+			fixedrotation = 1;
 		}
 		1108
 		{
@@ -5086,6 +5122,8 @@ thingtypes
 			flags4text = "[4] No sounds";
 			flags8text = "[8] Double size";
 			angletext = "Tag";
+			parametertext = "Spokes";
+			fixedrotation = 1;
 		}
 		1110
 		{
@@ -5095,6 +5133,8 @@ thingtypes
 			height = 34;
 			flags4text = "[4] No sounds";
 			angletext = "Tag";
+			parametertext = "Spokes";
+			fixedrotation = 1;
 		}
 		1111
 		{
@@ -5224,6 +5264,7 @@ thingtypes
 			sprite = "EGR1A1";
 			width = 20;
 			height = 72;
+			arrow = 1;
 		}
 		1128
 		{
@@ -5272,6 +5313,7 @@ thingtypes
 			width = 8;
 			height = 16;
 			angletext = "Tag";
+			fixedrotation = 1;
 		}
 		1203
 		{
@@ -5342,6 +5384,7 @@ thingtypes
 			sprite = "WWSGAR";
 			width = 22;
 			height = 64;
+			arrow = 1;
 		}
 		1213
 		{
@@ -5349,6 +5392,7 @@ thingtypes
 			sprite = "WWS2AR";
 			width = 22;
 			height = 64;
+			arrow = 1;
 		}
 		1214
 		{
@@ -5356,6 +5400,7 @@ thingtypes
 			sprite = "WWS3ALAR";
 			width = 16;
 			height = 192;
+			arrow = 1;
 		}
 		1215
 		{
@@ -5371,6 +5416,7 @@ thingtypes
 			sprite = "BARRA1";
 			width = 24;
 			height = 63;
+			arrow = 1;
 		}
 		1217
 		{
@@ -5392,6 +5438,7 @@ thingtypes
 			sprite = "MCRTCLFR";
 			width = 22;
 			height = 32;
+			arrow = 1;
 		}
 		1220
 		{
@@ -5399,6 +5446,7 @@ thingtypes
 			sprite = "MCRTIR";
 			width = 32;
 			height = 32;
+			arrow = 1;
 		}
 		1221
 		{
@@ -5406,6 +5454,7 @@ thingtypes
 			sprite = "SALDARAL";
 			width = 96;
 			height = 160;
+			arrow = 1;
 			flags8text = "[8] Allow non-minecart players";
 		}
 		1222
@@ -5467,6 +5516,7 @@ thingtypes
 			height = 40;
 			flags8text = "[8] Waves vertically";
 			angletext = "On/Off time";
+			fixedrotation = 1;
 			parametertext = "Strength";
 		}
 		1301
@@ -5477,6 +5527,7 @@ thingtypes
 			height = 40;
 			flags8text = "[8] Shoot downwards";
 			angletext = "On/Off time";
+			fixedrotation = 1;
 			parametertext = "Strength";
 		}
 		1302
@@ -5500,6 +5551,7 @@ thingtypes
 			width = 30;
 			height = 32;
 			angletext = "Initial delay";
+			fixedrotation = 1;
 			flags8text = "[8] Double size";
 		}
 		1305
@@ -5537,6 +5589,7 @@ thingtypes
 			sprite = "WVINALAR";
 			width = 1;
 			height = 288;
+			arrow = 1;
 		}
 		1310
 		{
@@ -5544,6 +5597,7 @@ thingtypes
 			sprite = "WVINBLBR";
 			width = 1;
 			height = 288;
+			arrow = 1;
 		}
 	}
 
@@ -5901,6 +5955,7 @@ thingtypes
 		width = 8;
 		height = 4096;
 		sprite = "UNKNA0";
+		fixedrotation = 1;
 
 		1700
 		{
@@ -5959,6 +6014,7 @@ thingtypes
 			flags4text = "[4] Align player to top";
 			flags8text = "[8] Die upon time up";
 			angletext = "Time limit";
+			fixedrotation = 1;
 			parametertext = "Height";
 		}
 		1704
@@ -5971,6 +6027,7 @@ thingtypes
 			unflippable = true;
 			flagsvaluetext = "Pitch";
 			angletext = "Yaw";
+			fixedrotation = 1;
 		}
 		1705
 		{
@@ -5983,6 +6040,7 @@ thingtypes
 			centerHitbox = true;
 			flagsvaluetext = "Height";
 			angletext = "Pitch/Yaw";
+			fixedrotation = 1;
 		}
 		1706
 		{
@@ -6104,6 +6162,7 @@ thingtypes
 			width = 8;
 			height = 16;
 			angletext = "Jump strength";
+			fixedrotation = 1;
 		}
 		1806
 		{
@@ -6336,6 +6395,7 @@ thingtypes
 			width = 18;
 			height = 28;
 			angletext = "Initial delay";
+			fixedrotation = 1;
 		}
 		2001
 		{
@@ -6459,6 +6519,7 @@ thingtypes
 			sprite = "XMS6A0";
 			width = 52;
 			height = 106;
+			hangs = 1;
 		}
 	}
 
@@ -6472,6 +6533,7 @@ thingtypes
 		flags4text = "[4] No movement";
 		flags8text = "[8] Hop";
 		angletext = "Radius";
+		fixedrotation = 1;
 
 		2200
 		{
diff --git a/extras/conf/udb/Includes/SRB222_things.cfg b/extras/conf/udb/Includes/SRB222_things.cfg
index 0ea452155181cfd080a65a0713afd8e966531196..113c1a4c26eb08a7109da2afeba747dfb1cdda14 100644
--- a/extras/conf/udb/Includes/SRB222_things.cfg
+++ b/extras/conf/udb/Includes/SRB222_things.cfg
@@ -1247,6 +1247,7 @@ patterns
 		sprite = "SPHRA0";
 		width = 96;
 		height = 192;
+	}
 	609
 	{
 		title = "Circle of Rings and Spheres (Big)";
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d35e774e934d5b624b4d2bcc5c19b5bc24b6abf6..87a0499b6d088b5bb51cf36658de9238397b3402 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -276,6 +276,7 @@ set(SRB2_LUA_SOURCES
 	lua_hudlib.c
 	lua_infolib.c
 	lua_maplib.c
+	lua_taglib.c
 	lua_mathlib.c
 	lua_mobjlib.c
 	lua_playerlib.c
diff --git a/src/Makefile b/src/Makefile
index 0c1626fc955c5465f9ae23b62875bf9395c73e25..a4c3c4fdbaea6992f150d85f495c2becd328008b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -2,7 +2,7 @@
 #     GNU Make makefile for SRB2
 #############################################################################
 # Copyright (C) 1998-2000 by DooM Legacy Team.
-# Copyright (C) 2003-2020 by Sonic Team Junior.
+# Copyright (C) 2003-2021 by Sonic Team Junior.
 #
 # This program is free software distributed under the
 # terms of the GNU General Public License, version 2.
@@ -13,8 +13,7 @@
 #     -DHAVE_SDL  -> use for the SDL interface
 #
 # Sets:
-#     Compile the DirectX/Mingw version with 'make MINGW=1'
-#     Compile the SDL/Mingw version with 'make MINGW=1 SDL=1'
+#     Compile the SDL/Mingw version with 'make MINGW=1'
 #     Compile the SDL/Linux version with 'make LINUX=1'
 #     Compile the SDL/Solaris version with 'make SOLARIS=1'
 #     Compile the SDL/FreeBSD version with 'gmake FREEBSD=1'
@@ -25,7 +24,9 @@
 #     clean
 #       Remove all object files
 #     cleandep
-#       Remove depend.dep
+#       Remove dependency files
+#     distclean
+#       Remove autogenerated files
 #     dll
 #       compile primary HW render DLL/SO
 #     all_dll
@@ -80,6 +81,17 @@
 #
 #############################################################################
 
+,=,
+
+ifeq (,$(filter-out cleandep clean distclean,$(or $(MAKECMDGOALS),all)))
+CLEANONLY=1
+else ifndef SILENT
+echo=@echo "$(1)"
+ifndef MAKE_RESTARTS
+print=$(info $(1))
+endif
+endif
+
 ALL_SYSTEMS=\
 	PANDORA\
 	LINUX64\
@@ -99,11 +111,10 @@ ALL_SYSTEMS=\
 ifeq (,$(filter $(ALL_SYSTEMS),$(.VARIABLES)))
 ifeq ($(OS),Windows_NT) # all windows are Windows_NT...
 
- $(info Detected a Windows system, compiling for 32-bit MinGW SDL2...)
+ $(call print,Detected a Windows system$(,) compiling for 32-bit MinGW SDL2...)
 
  # go for a 32-bit sdl mingw exe by default
  MINGW=1
- SDL=1
  WINDOWSHELL=1
 
 else # if you on the *nix
@@ -125,7 +136,7 @@ else # if you on the *nix
  new_system:=$(new_system)64
  endif
 
- $(info Detected $(system) ($(new_system))...)
+ $(call print,Detected $(system) ($(new_system))...)
  $(new_system)=1
 
 endif
@@ -239,6 +250,12 @@ endif
 
 MSGFMT?=msgfmt
 
+ifdef WINDOWSHELL
+	COMPTIME=-..\comptime.bat
+else
+	COMPTIME=-../comptime.sh
+endif
+
 ifndef ECHO
 	NASM:=@$(NASM)
 	REMOVE:=@$(REMOVE)
@@ -253,6 +270,7 @@ ifndef ECHO
 	MSGFMT:=@$(MSGFMT)
 	UPX:=@$(UPX)
 	UPX_OPTS+=-q
+	COMPTIME:=@$(COMPTIME)
 endif
 
 ifdef NONET
@@ -417,7 +435,7 @@ ifdef GCC48
 else
 	CFLAGS+=-O0
 endif
-	CFLAGS+= -Wall -DPARANOIA -DRANGECHECK -DPACKETDROP -DMOBJCONSISTANCY
+	CFLAGS+= -Wall -DPARANOIA -DRANGECHECK -DPACKETDROP
 else
 
 
@@ -457,7 +475,6 @@ DBGNAME?=$(EXENAME).debug
 
 # not too sophisticated dependency
 OBJS:=$(i_main_o) \
-		$(OBJDIR)/comptime.o \
 		$(OBJDIR)/string.o   \
 		$(OBJDIR)/d_main.o   \
 		$(OBJDIR)/d_clisrv.o \
@@ -544,7 +561,10 @@ OBJS:=$(i_main_o) \
 		$(i_sound_o)    \
 		$(OBJS)
 
+DEPS:=$(patsubst $(OBJDIR)/%.o,$(DEPDIR)/%.d,$(filter %.o,$(OBJS)))
+OBJS+=$(OBJDIR)/comptime.o
 
+ifndef SILENT
 ifndef ECHO
 ifndef NOECHOFILENAMES
 define echoName =
@@ -552,6 +572,7 @@ define echoName =
 endef
 endif
 endif
+endif
 
 # List of languages to compile.
 # For reference, this is the command I use to build a srb2.pot file from the source code.
@@ -564,18 +585,12 @@ OPTS+=-DGETTEXT
 endif
 
 ifdef PANDORA
-all:	pre-build $(BIN)/$(PNDNAME)
+all: $(BIN)/$(PNDNAME)
 endif
 
 
-ifdef MINGW
-ifndef SDL
-all:	 pre-build $(BIN)/$(EXENAME) dll
-endif
-endif
-
 ifdef SDL
-all:	 pre-build $(BIN)/$(EXENAME)
+all: $(BIN)/$(EXENAME)
 endif
 
 ifdef DUMMY
@@ -583,20 +598,15 @@ all:	$(BIN)/$(EXENAME)
 endif
 
 cleandep:
-	$(REMOVE) $(OBJDIR)/depend.dep
+	$(REMOVE) $(DEPS)
 	$(REMOVE) comptime.h
 
-pre-build:
-ifdef WINDOWSHELL
-	-..\comptime.bat .
-else
-	-@../comptime.sh .
-endif
-
 clean:
 	$(REMOVE) *~ *.flc
 	$(REMOVE) $(OBJDIR)/*.o
 
+distclean: clean cleandep
+
 ifdef MINGW
 	$(REMOVE) $(OBJDIR)/*.res
 endif
@@ -616,11 +626,11 @@ asm:
 
 $(BIN)/$(EXENAME): $(POS) $(OBJS)
 	-$(MKDIR) $(BIN)
-	@echo Linking $(EXENAME)...
+	$(call echo,Linking $(EXENAME)...)
 	$(LD) $(LDFLAGS) $(OBJS) -o $(BIN)/$(EXENAME) $(LIBS)
 ifndef VALGRIND
 ifndef NOOBJDUMP
-	@echo Dumping debugging info
+	$(call echo,Dumping debugging info)
 	$(OBJDUMP) $(OBJDUMP_OPTS) $(BIN)/$(EXENAME) > $(BIN)/$(DBGNAME).txt
 ifdef WINDOWSHELL
 	-$(GZIP) $(GZIP_OPTS) $(BIN)/$(DBGNAME).txt
@@ -639,10 +649,10 @@ ifndef NOUPX
 	-$(UPX) $(UPX_OPTS) $(BIN)/$(EXENAME)
 endif
 endif
-	@echo Build is done, please look for $(EXENAME) in $(BIN), \(checking for post steps\)
+	$(call echo,Build is done$(,) please look for $(EXENAME) in $(BIN)$(,) (checking for post steps))
 
 reobjdump:
-	@echo Redumping debugging info
+	$(call echo,Redumping debugging info)
 	$(OBJDUMP) $(OBJDUMP_OPTS) $(BIN)/$(DBGNAME) > $(BIN)/$(DBGNAME).txt
 ifdef WINDOWSHELL
 	-$(GZIP) $(GZIP_OPTS) $(BIN)/$(DBGNAME).txt
@@ -653,57 +663,6 @@ endif
 $(OBJDIR):
 	-$(MKDIR) $(OBJDIR)
 
-ifndef SDL
-ifdef NOHW
-dll :
-else
-dll : opengl_dll
-endif
-ifdef MINGW
-all_dll: opengl_dll ds3d_dll fmod_dll openal_dll
-
-opengl_dll: $(BIN)/r_opengl.dll
-$(BIN)/r_opengl.dll: $(OBJDIR)/ogl_win.o $(OBJDIR)/r_opengl.o
-	-$(MKDIR) $(BIN)
-	@echo Linking R_OpenGL.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -lgdi32 -static-libgcc
-ifndef NOUPX
-	-$(UPX) $(UPX_OPTS) $@
-endif
-
-ds3d_dll: $(BIN)/s_ds3d.dll
-$(BIN)/s_ds3d.dll: $(OBJDIR)/s_ds3d.o
-	@echo Linking S_DS3d.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -ldsound -luuid
-
-fmod_dll: $(BIN)/s_fmod.dll
-$(BIN)/s_fmod.dll: $(OBJDIR)/s_fmod.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_FMOD.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -lfmod
-
-openal_dll: $(BIN)/s_openal.dll
-$(BIN)/s_openal.dll: $(OBJDIR)/s_openal.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_OpenAL.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -lopenal32
-else
-all_dll: fmod_so openal_so
-
-fmod_so: $(BIN)/s_fmod.so
-$(BIN)/s_fmod.so: $(OBJDIR)/s_fmod.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_FMOD.so...
-	$(CC) --shared $^ -o $@ -g --nostartfiles -lm -lfmod
-
-openal_so: $(BIN)/s_openal.so
-$(BIN)/s_openal.so: $(OBJDIR)/s_openal.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_OpenAL.so...
-	$(CC) --shared $^ -o $@ -g --nostartfiles -lm -lopenal
-endif
-
-else
 ifdef SDL
 ifdef MINGW
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
@@ -728,27 +687,41 @@ $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h
 endif
 endif
 
-endif
-
 #dependecy made by gcc itself !
-$(OBJS):
 ifndef DUMMY
--include $(OBJDIR)/depend.dep
+ifndef CLEANONLY
+$(call print,Checking dependency files...)
+-include $(DEPS)
+endif
 endif
 
-$(OBJDIR)/depend.dep:
-	@echo "Creating dependency file, depend.dep"
-	@echo > comptime.h
-	-$(MKDIR) $(OBJDIR)
-	$(CC) $(CFLAGS) -MM *.c > $(OBJDIR)/depend.ped
-	$(CC) $(CFLAGS) -MM $(INTERFACE)/*.c >> $(OBJDIR)/depend.ped
-ifndef NOHW
-	$(CC) $(CFLAGS) -MM hardware/*.c >> $(OBJDIR)/depend.ped
+undefine deps_rule
+
+# windows makes it too hard !
+ifndef WINDOWSHELL
+ifdef echoName
+define deps_rule =
+	@printf "%-20.20s\r" $<
+
+endef
+endif
 endif
-	$(CC) $(CFLAGS) -MM blua/*.c >> $(OBJDIR)/depend.ped
-	@sed -e 's,\(.*\)\.o: ,$(subst /,\/,$(OBJDIR))\/&,g' < $(OBJDIR)/depend.ped > $(OBJDIR)/depend.dep
-	$(REMOVE) $(OBJDIR)/depend.ped
-	@echo "Created dependency file, depend.dep"
+
+define deps_rule +=
+	$(CC) $(CFLAGS) -M -MF $@ -MT $(OBJDIR)/$(<:.c=.o) $<
+endef
+
+$(DEPDIR)/%.d: %.c
+	$(deps_rule)
+
+$(DEPDIR)/%.d: $(INTERFACE)/%.c
+	$(deps_rule)
+
+$(DEPDIR)/%.d: hardware/%.c
+	$(deps_rule)
+
+$(DEPDIR)/%.d: blua/%.c
+	$(deps_rule)
 
 ifdef VALGRIND
 $(OBJDIR)/z_zone.o: z_zone.c
@@ -756,9 +729,12 @@ $(OBJDIR)/z_zone.o: z_zone.c
 	$(CC) $(CFLAGS) $(WFLAGS) -DHAVE_VALGRIND $(VALGRIND_CFLAGS) -c $< -o $@
 endif
 
-$(OBJDIR)/comptime.o: comptime.c pre-build
-	$(echoName)
-	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
+$(OBJDIR)/comptime.o::
+ifdef echoName
+	@echo -- comptime.c ...
+endif
+	$(COMPTIME) .
+	$(CC) $(CFLAGS) $(WFLAGS) -c comptime.c -o $@
 
 $(BIN)/%.mo: locale/%.po
 	-$(MKDIR) $(BIN)
@@ -804,33 +780,6 @@ $(OBJDIR)/SRB2.res: win32/Srb2win.rc win32/afxres.h win32/resource.h
 	$(WINDRES) -i $< -O rc $(WINDRESFLAGS) --include-dir=win32 -o $@ -O coff
 
 
-ifdef MINGW
-ifndef SDL
-ifndef NOHW
-$(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
- doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
- command.h hardware/hw_data.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
- hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
- am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
- p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
-	$(echoName)
-	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
-
-$(OBJDIR)/ogl_win.o: hardware/r_opengl/ogl_win.c hardware/r_opengl/r_opengl.h \
- doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
- command.h hardware/hw_data.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
- hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
- am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
- p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
-	$(echoName)
-	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
-endif
-
-endif
-endif
-
 ifdef SDL
 
 ifdef MINGW
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index db7230bb4b42ffe4a21f27d94fddf73174153e1c..075cd2d3a8defa3fd7d11feb431d2ad9cc67a704 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -47,7 +47,8 @@ ifdef MACOSX
 endif
 
 # Automatically set version flag, but not if one was manually set
-ifeq   (,$(filter GCC%,$(.VARIABLES)))
+# And don't bother if this is a clean only run
+ifeq   (,$(filter GCC% CLEANONLY,$(.VARIABLES)))
  version:=$(shell $(CC) --version)
  # check if this is in fact GCC
  ifneq (,$(or $(findstring gcc,$(version)),$(findstring GCC,$(version))))
@@ -60,12 +61,14 @@ ifeq   (,$(filter GCC%,$(.VARIABLES)))
 
   # If this version is not in the list, default to the latest supported
   ifeq (,$(filter $(v),$(SUPPORTED_GCC_VERSIONS)))
-   $(info\
-		Your compiler version, GCC $(version), is not supported by the Makefile.\
-		The Makefile will assume GCC $(LATEST_GCC_VERSION).)
+	define line =
+	Your compiler version, GCC $(version), is not supported by the Makefile.
+	The Makefile will assume GCC $(LATEST_GCC_VERSION).))
+	endef
+   $(call print,$(line))
    GCC$(subst .,,$(LATEST_GCC_VERSION))=1
   else
-   $(info Detected GCC $(version) (GCC$(v)))
+   $(call print,Detected GCC $(version) (GCC$(v)))
    GCC$(v)=1
   endif
  endif
@@ -361,6 +364,7 @@ i_main_o=$(OBJDIR)/i_main.o
 #set OBJDIR and BIN's starting place
 OBJDIR=../objs
 BIN=../bin
+DEPDIR=../dep
 #Nasm ASM and rm
 ifdef YASM
 NASM?=yasm
@@ -383,6 +387,7 @@ ifdef DUMMY
 	INTERFACE=dummy
 	OBJDIR:=$(OBJDIR)/dummy
 	BIN:=$(BIN)/dummy
+	DEPDIR:=$(DEPDIR)/dummy
 else
 ifdef LINUX
 	NASMFORMAT=elf -DLINUX
@@ -390,9 +395,11 @@ ifdef LINUX
 ifdef LINUX64
 	OBJDIR:=$(OBJDIR)/Linux64
 	BIN:=$(BIN)/Linux64
+	DEPDIR:=$(DEPDIR)/Linux64
 else
 	OBJDIR:=$(OBJDIR)/Linux
 	BIN:=$(BIN)/Linux
+	DEPDIR:=$(DEPDIR)/Linux
 endif
 else
 ifdef FREEBSD
@@ -402,6 +409,7 @@ ifdef FREEBSD
 
 	OBJDIR:=$(OBJDIR)/FreeBSD
 	BIN:=$(BIN)/FreeBSD
+	DEPDIR:=$(DEPDIR)/Linux
 else
 ifdef SOLARIS
 	INTERFACE=sdl
@@ -410,6 +418,7 @@ ifdef SOLARIS
 
 	OBJDIR:=$(OBJDIR)/Solaris
 	BIN:=$(BIN)/Solaris
+	DEPDIR:=$(DEPDIR)/Solaris
 else
 ifdef CYGWIN32
 	INTERFACE=sdl
@@ -418,18 +427,21 @@ ifdef CYGWIN32
 
 	OBJDIR:=$(OBJDIR)/cygwin
 	BIN:=$(BIN)/Cygwin
+	DEPDIR:=$(DEPDIR)/Cygwin
 else
 ifdef MINGW64
-	INTERFACE=win32
 	#NASMFORMAT=win64
+	SDL=1
 	OBJDIR:=$(OBJDIR)/Mingw64
 	BIN:=$(BIN)/Mingw64
+	DEPDIR:=$(DEPDIR)/Mingw64
 else
 ifdef MINGW
-	INTERFACE=win32
 	NASMFORMAT=win32
+	SDL=1
 	OBJDIR:=$(OBJDIR)/Mingw
 	BIN:=$(BIN)/Mingw
+	DEPDIR:=$(DEPDIR)/Mingw
 endif
 endif
 endif
@@ -441,6 +453,7 @@ endif
 ifdef ARCHNAME
 	OBJDIR:=$(OBJDIR)/$(ARCHNAME)
 	BIN:=$(BIN)/$(ARCHNAME)
+	DEPDIR:=$(DEPDIR)/$(ARCHNAME)
 endif
 
 OBJDUMP_OPTS?=--wide --source --line-numbers
@@ -449,14 +462,17 @@ LD=$(CC)
 ifdef SDL
 	INTERFACE=sdl
 	OBJDIR:=$(OBJDIR)/SDL
+	DEPDIR:=$(DEPDIR)/SDL
 endif
 
 ifndef DUMMY
 ifdef DEBUGMODE
 	OBJDIR:=$(OBJDIR)/Debug
 	BIN:=$(BIN)/Debug
+	DEPDIR:=$(DEPDIR)/Debug
 else
 	OBJDIR:=$(OBJDIR)/Release
 	BIN:=$(BIN)/Release
+	DEPDIR:=$(DEPDIR)/Release
 endif
 endif
diff --git a/src/blua/Makefile.cfg b/src/blua/Makefile.cfg
index eae95ba3ae2cf52656cd40eaff40b185a694cf51..3a2962e659e24cbb34f9690afe52348393a31ed3 100644
--- a/src/blua/Makefile.cfg
+++ b/src/blua/Makefile.cfg
@@ -47,6 +47,7 @@ OBJS:=$(OBJS) \
 	$(OBJDIR)/lua_skinlib.o \
 	$(OBJDIR)/lua_thinkerlib.o \
 	$(OBJDIR)/lua_maplib.o \
+	$(OBJDIR)/lua_taglib.o \
 	$(OBJDIR)/lua_polyobjlib.o \
 	$(OBJDIR)/lua_blockmaplib.o \
 	$(OBJDIR)/lua_hudlib.o
diff --git a/src/blua/lcode.c b/src/blua/lcode.c
index 5c7fed4541a4442d9d40691663965250de434619..fd4aaff24c33ae99e936497bac797d9bce95b061 100644
--- a/src/blua/lcode.c
+++ b/src/blua/lcode.c
@@ -686,6 +686,15 @@ static void codearith (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) {
 }
 
 
+static void codeunaryarith (FuncState *fs, OpCode op, expdesc *e) {
+  expdesc e2;
+  e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0;
+  if (op == OP_LEN || !isnumeral(e))
+    luaK_exp2anyreg(fs, e);  /* cannot operate on non-numeric constants */
+  codearith(fs, op, e, &e2);
+}
+
+
 static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1,
                                                           expdesc *e2) {
   int o1 = luaK_exp2RK(fs, e1);
@@ -703,27 +712,11 @@ static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1,
 
 
 void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e) {
-  expdesc e2;
-  e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0;
   switch (op) {
-    case OPR_MINUS: {
-      if (!isnumeral(e))
-        luaK_exp2anyreg(fs, e);  /* cannot operate on non-numeric constants */
-      codearith(fs, OP_UNM, e, &e2);
-      break;
-    }
-    case OPR_BNOT: {
-      if (e->k == VK)
-        luaK_exp2anyreg(fs, e);  /* cannot operate on non-numeric constants */
-      codearith(fs, OP_BNOT, e, &e2);
-      break;
-    }
+    case OPR_MINUS: codeunaryarith(fs, OP_UNM, e); break;
+    case OPR_BNOT: codeunaryarith(fs, OP_BNOT, e); break;
     case OPR_NOT: codenot(fs, e); break;
-    case OPR_LEN: {
-      luaK_exp2anyreg(fs, e);  /* cannot operate on constants */
-      codearith(fs, OP_LEN, e, &e2);
-      break;
-    }
+    case OPR_LEN: codeunaryarith(fs, OP_LEN, e); break;
     default: lua_assert(0);
   }
 }
diff --git a/src/command.c b/src/command.c
index 58434ef8983a1a0ed1816a9522783214896351b1..951e3dd0918c7d8ac542248f011508ab8f96fcdc 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1433,6 +1433,7 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 						if (var->revert.allocated)
 						{
 							Z_Free(var->revert.v.string);
+							var->revert.allocated = false; // the below value is not allocated in zone memory, don't try to free it!
 						}
 
 						var->revert.v.const_munge = var->PossibleValue[i].strvalue;
@@ -1440,6 +1441,10 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 						return;
 					}
 
+					// free the old value string
+					Z_Free(var->zstring);
+					var->zstring = NULL;
+
 					var->value = var->PossibleValue[i].value;
 					var->string = var->PossibleValue[i].strvalue;
 					goto finish;
@@ -1502,13 +1507,7 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 found:
 			if (client && execversion_enabled)
 			{
-				if (var->revert.allocated)
-				{
-					Z_Free(var->revert.v.string);
-				}
-
 				var->revert.v.const_munge = var->PossibleValue[i].strvalue;
-
 				return;
 			}
 
@@ -1523,6 +1522,7 @@ found:
 		if (var->revert.allocated)
 		{
 			Z_Free(var->revert.v.string);
+			// Z_StrDup creates a new zone memory block, so we can keep the allocated flag on
 		}
 
 		var->revert.v.string = Z_StrDup(valstr);
@@ -1787,6 +1787,7 @@ void CV_RevertNetVars(void)
 			if (cvar->revert.allocated)
 			{
 				Z_Free(cvar->revert.v.string);
+				cvar->revert.allocated = false; // no value being held now
 			}
 
 			cvar->revert.v.string = NULL;
diff --git a/src/command.h b/src/command.h
index ea5593395cc369e4f771bb7f55abf737045a503b..d4033e6efe963db1523b709de045012f3c381900 100644
--- a/src/command.h
+++ b/src/command.h
@@ -158,7 +158,7 @@ typedef struct consvar_s //NULL, NULL, 0, NULL, NULL |, 0, NULL, NULL, 0, 0, NUL
 
 /* name, defaultvalue, flags, PossibleValue, func */
 #define CVAR_INIT( ... ) \
-{ __VA_ARGS__, 0, NULL, NULL, {0}, 0U, (char)0, NULL }
+{ __VA_ARGS__, 0, NULL, NULL, {0, {NULL}}, 0U, (char)0, NULL }
 
 #ifdef OLD22DEMOCOMPAT
 typedef struct old_demo_var old_demo_var_t;
diff --git a/src/console.c b/src/console.c
index b19b8818d709bca4e55f61c033cdd5e97d5f8413..1560220f6ecc257cd6cacd20cc25fb83534e152c 100644
--- a/src/console.c
+++ b/src/console.c
@@ -360,30 +360,48 @@ static void CON_SetupColormaps(void)
 	for (i = 0; i < (256*15); i++, ++memorysrc)
 		*memorysrc = (UINT8)(i & 0xFF); // remap each color to itself...
 
-#define colset(map, a, b, c) \
-	map[1] = (UINT8)a;\
-	map[3] = (UINT8)b;\
-	map[9] = (UINT8)c
-
-	colset(magentamap, 177, 178, 184);
-	colset(yellowmap,   82,  73,  66);
-	colset(lgreenmap,   97,  98, 106);
-	colset(bluemap,    146, 147, 155);
-	colset(redmap,     210,  32,  39);
-	colset(graymap,      6,  8,   14);
-	colset(orangemap,   51,  52,  57);
-	colset(skymap,     129, 130, 133);
-	colset(purplemap,  160, 161, 163);
-	colset(aquamap,    120, 121, 123);
-	colset(peridotmap,  88, 188, 190);
-	colset(azuremap,   144, 145, 170);
-	colset(brownmap,   219, 221, 224);
-	colset(rosymap,    200, 201, 203);
-	colset(invertmap,   27,  26,  22);
-	invertmap[26] = (UINT8)3;
+#define colset(map, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
+	map[0x0] = (UINT8)a;\
+	map[0x1] = (UINT8)b;\
+	map[0x2] = (UINT8)c;\
+	map[0x3] = (UINT8)d;\
+	map[0x4] = (UINT8)e;\
+	map[0x5] = (UINT8)f;\
+	map[0x6] = (UINT8)g;\
+	map[0x7] = (UINT8)h;\
+	map[0x8] = (UINT8)i;\
+	map[0x9] = (UINT8)j;\
+	map[0xA] = (UINT8)k;\
+	map[0xB] = (UINT8)l;\
+	map[0xC] = (UINT8)m;\
+	map[0xD] = (UINT8)n;\
+	map[0xE] = (UINT8)o;\
+	map[0xF] = (UINT8)p;
+
+	// Tried to keep the colors vanilla while adding some shades in between them ~SonicX8000
+
+	//                      0x1       0x3                           0x9                           0xF
+	colset(magentamap, 177, 177, 178, 178, 178, 180, 180, 180, 182, 182, 182, 182, 184, 184, 184, 185);
+	colset(yellowmap,   82,  82,  73,  73,  73,  64,  64,  64,  66,  66,  66,  66,  67,  67,  67,  68);
+	colset(lgreenmap,   96,  96,  98,  98,  98, 101, 101, 101, 104, 104, 104, 104, 106, 106, 106, 107);
+	colset(bluemap,    146, 146, 147, 147, 147, 149, 149, 149, 152, 152, 152, 152, 155, 155, 155, 157);
+	colset(redmap,      32,  32,  33,  33,  33,  35,  35,  35,  39,  39,  39,  39,  42,  42,  42,  44);
+	colset(graymap,      8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23);
+	colset(orangemap,   50,  50,  52,  52,  52,  54,  54,  54,  56,  56,  56,  56,  59,  59,  59,  60);
+	colset(skymap,     129, 129, 130, 130, 130, 131, 131, 131, 133, 133, 133, 133, 135, 135, 135, 136);
+	colset(purplemap,  160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 163, 164, 164, 164, 165);
+	colset(aquamap,    120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 123, 124, 124, 124, 125);
+	colset(peridotmap,  72,  72, 188, 188, 189, 189, 189, 189, 190, 190, 190, 190, 191, 191, 191,  94);
+	colset(azuremap,   144, 144, 145, 145, 145, 146, 146, 146, 170, 170, 170, 170, 171, 171, 171, 172);
+	colset(brownmap,   219, 219, 221, 221, 221, 222, 222, 222, 224, 224, 224, 224, 227, 227, 227, 229);
+	colset(rosymap,    200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 203, 204, 204, 204, 205);
 
 #undef colset
 
+	// Yeah just straight up invert it like a normal person
+	for (i = 0x00; i <= 0x1F; i++)
+		invertmap[0x1F - i] = i;
+
 	// Init back colormap
 	CON_SetupBackColormap();
 }
@@ -808,6 +826,12 @@ static void CON_InputDelSelection(void)
 
 	Lock_state();
 
+	if (!input_cur)
+	{
+		Unlock_state();
+		return;
+	}
+
 	if (input_cur > input_sel)
 	{
 		start = input_sel;
@@ -1279,10 +1303,6 @@ boolean CON_Responder(event_t *ev)
 	if (key < 32 || key > 127)
 		return true;
 
-	// add key to cmd line here
-	if (key >= 'A' && key <= 'Z' && !(shiftdown ^ capslock)) //this is only really necessary for dedicated servers
-		key = key + 'a' - 'A';
-
 	if (input_sel != input_cur)
 		CON_InputDelSelection();
 	CON_InputAddChar(key);
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index b198011a0eeff38f0e3936cc7a28b1ce3281554e..c4026d52d22fc58afe5081ca28aa9e0bdc56f310 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1568,11 +1568,6 @@ static void CL_LoadReceivedSavegame(boolean reloading)
 	else
 	{
 		CONS_Alert(CONS_ERROR, M_GetText("Can't load the level!\n"));
-		Z_Free(savebuffer);
-		save_p = NULL;
-		if (unlink(tmpsave) == -1)
-			CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave);
-		return;
 	}
 
 	// done
@@ -1595,9 +1590,7 @@ static void CL_ReloadReceivedSavegame(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-#ifdef HAVE_BLUA
 		LUA_InvalidatePlayer(&players[i]);
-#endif
 		sprintf(player_names[i], "Player %d", i + 1);
 	}
 
@@ -2260,11 +2253,15 @@ void D_SaveBan(void)
 	size_t i;
 	banreason_t *reasonlist = reasonhead;
 	const char *address, *mask;
+	const char *path = va("%s"PATHSEP"%s", srb2home, "ban.txt");
 
 	if (!reasonhead)
+	{
+		remove(path);
 		return;
+	}
 
-	f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "w");
+	f = fopen(path, "w");
 
 	if (!f)
 	{
@@ -2308,16 +2305,14 @@ static void Ban_Add(const char *reason)
 	reasontail = reasonlist;
 }
 
-static void Command_ClearBans(void)
+static void Ban_Clear(void)
 {
 	banreason_t *temp;
 
-	if (!I_ClearBans)
-		return;
-
 	I_ClearBans();
-	D_SaveBan();
+
 	reasontail = NULL;
+
 	while (reasonhead)
 	{
 		temp = reasonhead->next;
@@ -2327,6 +2322,15 @@ static void Command_ClearBans(void)
 	}
 }
 
+static void Command_ClearBans(void)
+{
+	if (!I_ClearBans)
+		return;
+
+	Ban_Clear();
+	D_SaveBan();
+}
+
 static void Ban_Load_File(boolean warning)
 {
 	FILE *f;
@@ -2334,6 +2338,9 @@ static void Ban_Load_File(boolean warning)
 	const char *address, *mask;
 	char buffer[MAX_WADPATH];
 
+	if (!I_ClearBans)
+		return;
+
 	f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "r");
 
 	if (!f)
@@ -2343,13 +2350,7 @@ static void Ban_Load_File(boolean warning)
 		return;
 	}
 
-	if (I_ClearBans)
-		Command_ClearBans();
-	else
-	{
-		fclose(f);
-		return;
-	}
+	Ban_Clear();
 
 	for (i=0; fgets(buffer, (int)sizeof(buffer), f); i++)
 	{
@@ -3026,8 +3027,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 
 	if (pnum == consoleplayer)
 	{
-		if (Playing())
-			LUAh_GameQuit();
+		LUAh_GameQuit(false);
 #ifdef DUMPCONSISTENCY
 		if (msg == KICK_MSG_CON_FAIL) SV_SavedGame();
 #endif
@@ -3107,7 +3107,7 @@ consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_SAVE|CV_NETVAR, maxpl
 static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}};
 consvar_t cv_joindelay = CVAR_INIT ("joindelay", "10", CV_SAVE|CV_NETVAR, joindelay_cons_t, NULL);
 static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}};
-consvar_t cv_rejointimeout = CVAR_INIT ("rejointimeout", "Off", CV_SAVE|CV_NETVAR|CV_FLOAT, rejointimeout_cons_t, NULL);
+consvar_t cv_rejointimeout = CVAR_INIT ("rejointimeout", "2", CV_SAVE|CV_NETVAR|CV_FLOAT, rejointimeout_cons_t, NULL);
 
 static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}};
 consvar_t cv_resynchattempts = CVAR_INIT ("resynchattempts", "10", CV_SAVE|CV_NETVAR, resynchattempts_cons_t, NULL);
@@ -3727,8 +3727,7 @@ static void HandleConnect(SINT8 node)
 static void HandleShutdown(SINT8 node)
 {
 	(void)node;
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(false);
 	D_QuitNetGame();
 	CL_Reset();
 	D_StartTitle();
@@ -3743,8 +3742,7 @@ static void HandleShutdown(SINT8 node)
 static void HandleTimeout(SINT8 node)
 {
 	(void)node;
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(false);
 	D_QuitNetGame();
 	CL_Reset();
 	D_StartTitle();
@@ -4477,70 +4475,73 @@ static INT16 Consistancy(void)
 		ret += P_GetRandSeed();
 
 #ifdef MOBJCONSISTANCY
-	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	if (gamestate == GS_LEVEL)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-			continue;
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		{
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				continue;
 
-		mo = (mobj_t *)th;
+			mo = (mobj_t *)th;
 
-		if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY))
-		{
-			ret -= mo->type;
-			ret += mo->x;
-			ret -= mo->y;
-			ret += mo->z;
-			ret -= mo->momx;
-			ret += mo->momy;
-			ret -= mo->momz;
-			ret += mo->angle;
-			ret -= mo->flags;
-			ret += mo->flags2;
-			ret -= mo->eflags;
-			if (mo->target)
+			if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY))
 			{
-				ret += mo->target->type;
-				ret -= mo->target->x;
-				ret += mo->target->y;
-				ret -= mo->target->z;
-				ret += mo->target->momx;
-				ret -= mo->target->momy;
-				ret += mo->target->momz;
-				ret -= mo->target->angle;
-				ret += mo->target->flags;
-				ret -= mo->target->flags2;
-				ret += mo->target->eflags;
-				ret -= mo->target->state - states;
-				ret += mo->target->tics;
-				ret -= mo->target->sprite;
-				ret += mo->target->frame;
-			}
-			else
-				ret ^= 0x3333;
-			if (mo->tracer && mo->tracer->type != MT_OVERLAY)
-			{
-				ret += mo->tracer->type;
-				ret -= mo->tracer->x;
-				ret += mo->tracer->y;
-				ret -= mo->tracer->z;
-				ret += mo->tracer->momx;
-				ret -= mo->tracer->momy;
-				ret += mo->tracer->momz;
-				ret -= mo->tracer->angle;
-				ret += mo->tracer->flags;
-				ret -= mo->tracer->flags2;
-				ret += mo->tracer->eflags;
-				ret -= mo->tracer->state - states;
-				ret += mo->tracer->tics;
-				ret -= mo->tracer->sprite;
-				ret += mo->tracer->frame;
+				ret -= mo->type;
+				ret += mo->x;
+				ret -= mo->y;
+				ret += mo->z;
+				ret -= mo->momx;
+				ret += mo->momy;
+				ret -= mo->momz;
+				ret += mo->angle;
+				ret -= mo->flags;
+				ret += mo->flags2;
+				ret -= mo->eflags;
+				if (mo->target)
+				{
+					ret += mo->target->type;
+					ret -= mo->target->x;
+					ret += mo->target->y;
+					ret -= mo->target->z;
+					ret += mo->target->momx;
+					ret -= mo->target->momy;
+					ret += mo->target->momz;
+					ret -= mo->target->angle;
+					ret += mo->target->flags;
+					ret -= mo->target->flags2;
+					ret += mo->target->eflags;
+					ret -= mo->target->state - states;
+					ret += mo->target->tics;
+					ret -= mo->target->sprite;
+					ret += mo->target->frame;
+				}
+				else
+					ret ^= 0x3333;
+				if (mo->tracer && mo->tracer->type != MT_OVERLAY)
+				{
+					ret += mo->tracer->type;
+					ret -= mo->tracer->x;
+					ret += mo->tracer->y;
+					ret -= mo->tracer->z;
+					ret += mo->tracer->momx;
+					ret -= mo->tracer->momy;
+					ret += mo->tracer->momz;
+					ret -= mo->tracer->angle;
+					ret += mo->tracer->flags;
+					ret -= mo->tracer->flags2;
+					ret += mo->tracer->eflags;
+					ret -= mo->tracer->state - states;
+					ret += mo->tracer->tics;
+					ret -= mo->tracer->sprite;
+					ret += mo->tracer->frame;
+				}
+				else
+					ret ^= 0xAAAA;
+				ret -= mo->state - states;
+				ret += mo->tics;
+				ret -= mo->sprite;
+				ret += mo->frame;
 			}
-			else
-				ret ^= 0xAAAA;
-			ret -= mo->state - states;
-			ret += mo->tics;
-			ret -= mo->sprite;
-			ret += mo->frame;
 		}
 	}
 #endif
@@ -4847,14 +4848,14 @@ void TryRunTics(tic_t realtics)
 			{
 				DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
 
-				ps_tictime = I_GetTimeMicros();
+				ps_tictime = I_GetPreciseTime();
 
 				G_Ticker((gametic % NEWTICRATERATIO) == 0);
 				ExtraDataTicker();
 				gametic++;
 				consistancy[gametic%BACKUPTICS] = Consistancy();
 
-				ps_tictime = I_GetTimeMicros() - ps_tictime;
+				ps_tictime = I_GetPreciseTime() - ps_tictime;
 
 				// Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
 				if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value)
diff --git a/src/d_main.c b/src/d_main.c
index 1045d4d99b86d2295f26d5342bf195d8da9a71f1..61510d5903e2ec5ed89085839ffa50daa11628bb 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -61,7 +61,7 @@
 #include "p_local.h" // chasecam
 #include "mserv.h" // ms_RoomId
 #include "m_misc.h" // screenshot functionality
-#include "dehacked.h" // Dehacked list test
+#include "deh_tables.h" // Dehacked list test
 #include "m_cond.h" // condition initialization
 #include "fastcmp.h"
 #include "keys.h"
@@ -413,7 +413,7 @@ static void D_Display(void)
 
 			if (!automapactive && !dedicated && cv_renderview.value)
 			{
-				ps_rendercalltime = I_GetTimeMicros();
+				ps_rendercalltime = I_GetPreciseTime();
 				if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
 				{
 					topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
@@ -460,7 +460,7 @@ static void D_Display(void)
 					if (postimgtype2)
 						V_DoPostProcessor(1, postimgtype2, postimgparam2);
 				}
-				ps_rendercalltime = I_GetTimeMicros() - ps_rendercalltime;
+				ps_rendercalltime = I_GetPreciseTime() - ps_rendercalltime;
 			}
 
 			if (lastdraw)
@@ -474,7 +474,7 @@ static void D_Display(void)
 				lastdraw = false;
 			}
 
-			ps_uitime = I_GetTimeMicros();
+			ps_uitime = I_GetPreciseTime();
 
 			if (gamestate == GS_LEVEL)
 			{
@@ -487,7 +487,7 @@ static void D_Display(void)
 		}
 		else
 		{
-			ps_uitime = I_GetTimeMicros();
+			ps_uitime = I_GetPreciseTime();
 		}
 	}
 
@@ -529,7 +529,7 @@ static void D_Display(void)
 
 	CON_Drawer();
 
-	ps_uitime = I_GetTimeMicros() - ps_uitime;
+	ps_uitime = I_GetPreciseTime() - ps_uitime;
 
 	//
 	// wipe update
@@ -615,9 +615,9 @@ static void D_Display(void)
 			M_DrawPerfStats();
 		}
 
-		ps_swaptime = I_GetTimeMicros();
+		ps_swaptime = I_GetPreciseTime();
 		I_FinishUpdate(); // page flip or blit buffer
-		ps_swaptime = I_GetTimeMicros() - ps_swaptime;
+		ps_swaptime = I_GetPreciseTime() - ps_swaptime;
 	}
 }
 
@@ -998,7 +998,7 @@ static void IdentifyVersion(void)
 #define MUSICTEST(str) \
 		{\
 			const char *musicpath = va(pandf,srb2waddir,str);\
-			int ms = W_VerifyNMUSlumps(musicpath); \
+			int ms = W_VerifyNMUSlumps(musicpath, false); \
 			if (ms == 1) \
 				D_AddFile(startupwadfiles, musicpath); \
 			else if (ms == 0) \
@@ -1045,7 +1045,7 @@ void D_SRB2Main(void)
 	// Print GPL notice for our console users (Linux)
 	CONS_Printf(
 	"\n\nSonic Robo Blast 2\n"
-	"Copyright (C) 1998-2020 by Sonic Team Junior\n\n"
+	"Copyright (C) 1998-2021 by Sonic Team Junior\n\n"
 	"This program comes with ABSOLUTELY NO WARRANTY.\n\n"
 	"This is free software, and you are welcome to redistribute it\n"
 	"and/or modify it under the terms of the GNU General Public License\n"
@@ -1072,7 +1072,7 @@ void D_SRB2Main(void)
 	G_LoadGameSettings();
 
 	// Test Dehacked lists
-	DEH_Check();
+	DEH_TableCheck();
 
 	// Netgame URL special case: change working dir to EXE folder.
 	ChangeDirForUrlHandler();
@@ -1187,11 +1187,7 @@ void D_SRB2Main(void)
 				const char *s = M_GetNextParm();
 
 				if (s) // Check for NULL?
-				{
-					if (!W_VerifyNMUSlumps(s))
-						G_SetGameModified(true);
 					D_AddFile(startuppwads, s);
-				}
 			}
 		}
 	}
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 31c10f58a8e0dbe7f709c0ffd98e63772fe175d6..09f9d46514b12bc7e987fecbd3cf2f81e95fc88b 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -214,11 +214,9 @@ consvar_t cv_respawntime = CVAR_INIT ("respawndelay", "3", CV_SAVE|CV_NETVAR|CV_
 
 consvar_t cv_competitionboxes = CVAR_INIT ("competitionboxes", "Mystery", CV_SAVE|CV_NETVAR|CV_CHEAT, competitionboxes_cons_t, NULL);
 
-#ifdef SEENAMES
 static CV_PossibleValue_t seenames_cons_t[] = {{0, "Off"}, {1, "Colorless"}, {2, "Team"}, {3, "Ally/Foe"}, {0, NULL}};
 consvar_t cv_seenames = CVAR_INIT ("seenames", "Ally/Foe", CV_SAVE, seenames_cons_t, 0);
 consvar_t cv_allowseenames = CVAR_INIT ("allowseenames", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL);
-#endif
 
 // names
 consvar_t cv_playername = CVAR_INIT ("name", "Sonic", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name_OnChange);
@@ -597,9 +595,7 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_pingtimeout);
 	CV_RegisterVar(&cv_showping);
 
-#ifdef SEENAMES
-	 CV_RegisterVar(&cv_allowseenames);
-#endif
+	CV_RegisterVar(&cv_allowseenames);
 
 	CV_RegisterVar(&cv_dummyconsvar);
 }
@@ -670,6 +666,7 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_zlib_strategya);
 	CV_RegisterVar(&cv_zlib_window_bitsa);
 	CV_RegisterVar(&cv_apng_delay);
+	CV_RegisterVar(&cv_apng_downscale);
 	// GIF variables
 	CV_RegisterVar(&cv_gif_optimize);
 	CV_RegisterVar(&cv_gif_downscale);
@@ -690,9 +687,7 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_defaultplayercolor2);
 	CV_RegisterVar(&cv_defaultskin2);
 
-#ifdef SEENAMES
 	CV_RegisterVar(&cv_seenames);
-#endif
 	CV_RegisterVar(&cv_rollingdemos);
 	CV_RegisterVar(&cv_netstat);
 	CV_RegisterVar(&cv_netticbuffer);
@@ -878,7 +873,7 @@ void D_RegisterClientCommands(void)
 //	CV_RegisterVar(&cv_snapto);
 
 	CV_RegisterVar(&cv_freedemocamera);
-	
+
 	// add cheat commands
 	COM_AddCommand("noclip", Command_CheatNoClip_f);
 	COM_AddCommand("god", Command_CheatGod_f);
@@ -2135,7 +2130,7 @@ static void Command_Pause(void)
 
 	if (cv_pause.value || server || (IsPlayerAdmin(consoleplayer)))
 	{
-		if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
+		if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) || (marathonmode && gamestate == GS_INTERMISSION))
 		{
 			CONS_Printf(M_GetText("You can't pause here.\n"));
 			return;
@@ -3294,7 +3289,13 @@ static void Command_Addfile(void)
 			if (!isprint(fn[i]) || fn[i] == ';')
 				return;
 
-		musiconly = W_VerifyNMUSlumps(fn);
+		musiconly = W_VerifyNMUSlumps(fn, false);
+
+		if (musiconly == -1)
+		{
+			addedfiles[numfilesadded++] = fn;
+			continue;
+		}
 
 		if (!musiconly)
 		{
@@ -3606,8 +3607,7 @@ static void Command_Playintro_f(void)
   */
 FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void)
 {
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(true);
 	I_Quit();
 }
 
@@ -4269,8 +4269,7 @@ void Command_ExitGame_f(void)
 {
 	INT32 i;
 
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(false);
 
 	D_QuitNetGame();
 	CL_Reset();
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 98d8f142576e46d18dd84a2820a1812b3289d7d6..ac39626a4e381a454ce0c81e51aa604f70840fed 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -31,9 +31,7 @@ extern consvar_t cv_defaultskin;
 extern consvar_t cv_defaultplayercolor2;
 extern consvar_t cv_defaultskin2;
 
-#ifdef SEENAMES
 extern consvar_t cv_seenames, cv_allowseenames;
-#endif
 extern consvar_t cv_usemouse;
 extern consvar_t cv_usejoystick;
 extern consvar_t cv_usejoystick2;
diff --git a/src/d_netfil.c b/src/d_netfil.c
index 8f661bb5fb26f8f341ba77daa379b77c25fa6dbc..50dc2ba6084d5aa9d90e3d36dc931f5ee1cb2fe7 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -562,7 +562,7 @@ static void SV_PrepareSendLuaFileToNextNode(void)
 
     // Find a client to send the file to
 	for (i = 1; i < MAXNETNODES; i++)
-		if (nodeingame[i] && luafiletransfers->nodestatus[i] == LFTNS_WAITING) // Node waiting
+		if (luafiletransfers->nodestatus[i] == LFTNS_WAITING) // Node waiting
 		{
 			// Tell the client we're about to send them the file
 			netbuffer->packettype = PT_SENDINGLUAFILE;
@@ -570,6 +570,7 @@ static void SV_PrepareSendLuaFileToNextNode(void)
 				I_Error("Failed to send a PT_SENDINGLUAFILE packet\n"); // !!! Todo: Handle failure a bit better lol
 
 			luafiletransfers->nodestatus[i] = LFTNS_ASKED;
+			luafiletransfers->nodetimeouts[i] = I_GetTime() + 30 * TICRATE;
 
 			return;
 		}
@@ -588,7 +589,7 @@ void SV_PrepareSendLuaFile(void)
 
 	// Set status to "waiting" for everyone
 	for (i = 0; i < MAXNETNODES; i++)
-		luafiletransfers->nodestatus[i] = LFTNS_WAITING;
+		luafiletransfers->nodestatus[i] = (nodeingame[i] ? LFTNS_WAITING : LFTNS_NONE);
 
 	if (FIL_ReadFileOK(luafiletransfers->realfilename))
 	{
@@ -649,12 +650,14 @@ void RemoveAllLuaFileTransfers(void)
 
 void SV_AbortLuaFileTransfer(INT32 node)
 {
-	if (luafiletransfers
-	&& (luafiletransfers->nodestatus[node] == LFTNS_ASKED
-	||  luafiletransfers->nodestatus[node] == LFTNS_SENDING))
+	if (luafiletransfers)
 	{
-		luafiletransfers->nodestatus[node] = LFTNS_WAITING;
-		SV_PrepareSendLuaFileToNextNode();
+		if (luafiletransfers->nodestatus[node] == LFTNS_ASKED
+			|| luafiletransfers->nodestatus[node] == LFTNS_SENDING)
+		{
+			SV_PrepareSendLuaFileToNextNode();
+		}
+		luafiletransfers->nodestatus[node] = LFTNS_NONE;
 	}
 }
 
@@ -928,6 +931,22 @@ void FileSendTicker(void)
 	filetx_t *f;
 	INT32 packetsent, ram, i, j;
 
+	// If someone is taking too long to download, kick them with a timeout
+	// to prevent blocking the rest of the server...
+	if (luafiletransfers)
+	{
+		for (i = 1; i < MAXNETNODES; i++)
+		{
+			luafiletransfernodestatus_t status = luafiletransfers->nodestatus[i];
+
+			if (status != LFTNS_NONE && status != LFTNS_WAITING && status != LFTNS_SENT
+				&& I_GetTime() > luafiletransfers->nodetimeouts[i])
+			{
+				Net_ConnectionTimeout(i);
+			}
+		}
+	}
+
 	if (!filestosend) // No file to send
 		return;
 
diff --git a/src/d_netfil.h b/src/d_netfil.h
index 1b399be75f31ae472de43c65cce64f24a8202376..158149477716cf2daf97c8d16b2298c164676de7 100644
--- a/src/d_netfil.h
+++ b/src/d_netfil.h
@@ -85,10 +85,11 @@ boolean PT_RequestFile(INT32 node);
 
 typedef enum
 {
+	LFTNS_NONE,    // This node is not connected
 	LFTNS_WAITING, // This node is waiting for the server to send the file
-	LFTNS_ASKED, // The server has told the node they're ready to send the file
+	LFTNS_ASKED,   // The server has told the node they're ready to send the file
 	LFTNS_SENDING, // The server is sending the file to this node
-	LFTNS_SENT // The node already has the file
+	LFTNS_SENT     // The node already has the file
 } luafiletransfernodestatus_t;
 
 typedef struct luafiletransfer_s
@@ -99,6 +100,7 @@ typedef struct luafiletransfer_s
 	INT32 id; // Callback ID
 	boolean ongoing;
 	luafiletransfernodestatus_t nodestatus[MAXNETNODES];
+	tic_t nodetimeouts[MAXNETNODES];
 	struct luafiletransfer_s *next;
 } luafiletransfer_t;
 
diff --git a/src/d_player.h b/src/d_player.h
index eb03728320fdd60d60163235082e51cd51ca474e..2e7afed882ccf4b1ca1966b7439785aadf4a993a 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -51,7 +51,9 @@ typedef enum
 	SF_NONIGHTSSUPER    = 1<<15, // Disable super colors for NiGHTS (if you have SF_SUPER)
 	SF_NOSUPERSPRITES   = 1<<16, // Don't use super sprites while super
 	SF_NOSUPERJUMPBOOST = 1<<17, // Disable the jump boost given while super (i.e. Knuckles)
-	SF_CANBUSTWALLS		= 1<<18, // Can naturally bust walls on contact? (i.e. Knuckles)
+	SF_CANBUSTWALLS     = 1<<18, // Can naturally bust walls on contact? (i.e. Knuckles)
+	SF_NOSHIELDABILITY  = 1<<19, // Disable shield abilities
+
 	// free up to and including 1<<31
 } skinflags_t;
 
diff --git a/src/deh_tables.c b/src/deh_tables.c
index 5733d9b0ede7466fab11fc0db2094f12d5f3d45b..dd6d7d69ff722bedd843456a4b5a7963889a1269 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -1522,6 +1522,13 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
 	"S_SPINFIRE5",
 	"S_SPINFIRE6",
 
+	"S_TEAM_SPINFIRE1",
+	"S_TEAM_SPINFIRE2",
+	"S_TEAM_SPINFIRE3",
+	"S_TEAM_SPINFIRE4",
+	"S_TEAM_SPINFIRE5",
+	"S_TEAM_SPINFIRE6",
+
 	// Spikes
 	"S_SPIKE1",
 	"S_SPIKE2",
@@ -3478,9 +3485,7 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
 	"S_BLUEBRICKDEBRIS",
 	"S_YELLOWBRICKDEBRIS",
 
-#ifdef SEENAMES
 	"S_NAMECHECK",
-#endif
 };
 
 // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
@@ -4260,9 +4265,7 @@ const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for sanity t
 	"MT_BLUEBRICKDEBRIS",
 	"MT_YELLOWBRICKDEBRIS",
 
-#ifdef SEENAMES
 	"MT_NAMECHECK",
-#endif
 };
 
 const char *const MOBJFLAG_LIST[] = {
@@ -4331,6 +4334,7 @@ const char *const MOBJFLAG2_LIST[] = {
 	"AMBUSH",         // Alternate behaviour typically set by MTF_AMBUSH
 	"LINKDRAW",       // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	"SHIELD",         // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
+	"SPLAT",          // Object is a splat
 	NULL
 };
 
@@ -4794,6 +4798,7 @@ struct int_const_s const INT_CONST[] = {
 
 	// fixed_t constants, from m_fixed.h
 	{"FRACUNIT",FRACUNIT},
+	{"FU"      ,FRACUNIT},
 	{"FRACBITS",FRACBITS},
 
 	// doomdef.h constants
@@ -4871,6 +4876,36 @@ struct int_const_s const INT_CONST[] = {
 	{"tr_trans90",tr_trans90},
 	{"NUMTRANSMAPS",NUMTRANSMAPS},
 
+	// Alpha styles (blend modes)
+	{"AST_COPY",AST_COPY},
+	{"AST_TRANSLUCENT",AST_TRANSLUCENT},
+	{"AST_ADD",AST_ADD},
+	{"AST_SUBTRACT",AST_SUBTRACT},
+	{"AST_REVERSESUBTRACT",AST_REVERSESUBTRACT},
+	{"AST_MODULATE",AST_MODULATE},
+	{"AST_OVERLAY",AST_OVERLAY},
+
+	// Render flags
+	{"RF_HORIZONTALFLIP",RF_HORIZONTALFLIP},
+	{"RF_VERTICALFLIP",RF_VERTICALFLIP},
+	{"RF_ABSOLUTEOFFSETS",RF_ABSOLUTEOFFSETS},
+	{"RF_FLIPOFFSETS",RF_FLIPOFFSETS},
+	{"RF_SPLATMASK",RF_SPLATMASK},
+	{"RF_SLOPESPLAT",RF_SLOPESPLAT},
+	{"RF_OBJECTSLOPESPLAT",RF_OBJECTSLOPESPLAT},
+	{"RF_NOSPLATBILLBOARD",RF_NOSPLATBILLBOARD},
+	{"RF_NOSPLATROLLANGLE",RF_NOSPLATROLLANGLE},
+	{"RF_BLENDMASK",RF_BLENDMASK},
+	{"RF_FULLBRIGHT",RF_FULLBRIGHT},
+	{"RF_FULLDARK",RF_FULLDARK},
+	{"RF_NOCOLORMAPS",RF_NOCOLORMAPS},
+	{"RF_SPRITETYPEMASK",RF_SPRITETYPEMASK},
+	{"RF_PAPERSPRITE",RF_PAPERSPRITE},
+	{"RF_FLOORSPRITE",RF_FLOORSPRITE},
+	{"RF_SHADOWDRAW",RF_SHADOWDRAW},
+	{"RF_SHADOWEFFECTS",RF_SHADOWEFFECTS},
+	{"RF_DROPSHADOW",RF_DROPSHADOW},
+
 	// Level flags
 	{"LF_SCRIPTISFILE",LF_SCRIPTISFILE},
 	{"LF_SPEEDMUSIC",LF_SPEEDMUSIC},
@@ -4987,6 +5022,7 @@ struct int_const_s const INT_CONST[] = {
 	{"SF_NOSUPERSPRITES",SF_NOSUPERSPRITES},
 	{"SF_NOSUPERJUMPBOOST",SF_NOSUPERJUMPBOOST},
 	{"SF_CANBUSTWALLS",SF_CANBUSTWALLS},
+	{"SF_NOSHIELDABILITY",SF_NOSHIELDABILITY},
 
 	// Dashmode constants
 	{"DASHMODE_THRESHOLD",DASHMODE_THRESHOLD},
@@ -5421,3 +5457,27 @@ struct int_const_s const INT_CONST[] = {
 
 	{NULL,0}
 };
+
+// For this to work compile-time without being in this file,
+// this function would need to check sizes at runtime, without sizeof
+void DEH_TableCheck(void)
+{
+#if defined(_DEBUG) || defined(PARANOIA)
+	const size_t dehstates = sizeof(STATE_LIST)/sizeof(const char*);
+	const size_t dehmobjs  = sizeof(MOBJTYPE_LIST)/sizeof(const char*);
+	const size_t dehpowers = sizeof(POWERS_LIST)/sizeof(const char*);
+	const size_t dehcolors = sizeof(COLOR_ENUMS)/sizeof(const char*);
+
+	if (dehstates != S_FIRSTFREESLOT)
+		I_Error("You forgot to update the Dehacked states list, you dolt!\n(%d states defined, versus %s in the Dehacked list)\n", S_FIRSTFREESLOT, sizeu1(dehstates));
+
+	if (dehmobjs != MT_FIRSTFREESLOT)
+		I_Error("You forgot to update the Dehacked mobjtype list, you dolt!\n(%d mobj types defined, versus %s in the Dehacked list)\n", MT_FIRSTFREESLOT, sizeu1(dehmobjs));
+
+	if (dehpowers != NUMPOWERS)
+		I_Error("You forgot to update the Dehacked powers list, you dolt!\n(%d powers defined, versus %s in the Dehacked list)\n", NUMPOWERS, sizeu1(dehpowers));
+
+	if (dehcolors != SKINCOLOR_FIRSTFREESLOT)
+		I_Error("You forgot to update the Dehacked colors list, you dolt!\n(%d colors defined, versus %s in the Dehacked list)\n", SKINCOLOR_FIRSTFREESLOT, sizeu1(dehcolors));
+#endif
+}
diff --git a/src/deh_tables.h b/src/deh_tables.h
index 2c6b3e20407ec454a47a9b301fcf5003cb4220a8..d094bcbad4e74b736aa6aa423cfe94a408dc18f2 100644
--- a/src/deh_tables.h
+++ b/src/deh_tables.h
@@ -72,4 +72,7 @@ extern const char *const MENUTYPES_LIST[];
 
 extern struct int_const_s const INT_CONST[];
 
+// Moved to this file because it can't work compile-time otherwise
+void DEH_TableCheck(void);
+
 #endif
diff --git a/src/dehacked.c b/src/dehacked.c
index e98ff71cf2f8a30baa8bd0e7fadd6114359fbaae..c2ea28d27cef95d623c480b0259940e390f7cd2f 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -59,6 +59,12 @@ ATTRINLINE static FUNCINLINE char myfget_color(MYFILE *f)
 
 	if (c >= '0' && c <= '9')
 		return 0x80+(c-'0');
+
+	c = tolower(c);
+
+	if (c >= 'a' && c <= 'f')
+		return 0x80+10+(c-'a');
+
 	return 0x80; // Unhandled -- default to no color
 }
 
@@ -639,25 +645,3 @@ void DEH_LoadDehackedLump(lumpnum_t lumpnum)
 {
 	DEH_LoadDehackedLumpPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum), false);
 }
-
-void DEH_Check(void)
-{
-#if defined(_DEBUG) || defined(PARANOIA)
-	const size_t dehstates = sizeof(STATE_LIST)/sizeof(const char*);
-	const size_t dehmobjs  = sizeof(MOBJTYPE_LIST)/sizeof(const char*);
-	const size_t dehpowers = sizeof(POWERS_LIST)/sizeof(const char*);
-	const size_t dehcolors = sizeof(COLOR_ENUMS)/sizeof(const char*);
-
-	if (dehstates != S_FIRSTFREESLOT)
-		I_Error("You forgot to update the Dehacked states list, you dolt!\n(%d states defined, versus %s in the Dehacked list)\n", S_FIRSTFREESLOT, sizeu1(dehstates));
-
-	if (dehmobjs != MT_FIRSTFREESLOT)
-		I_Error("You forgot to update the Dehacked mobjtype list, you dolt!\n(%d mobj types defined, versus %s in the Dehacked list)\n", MT_FIRSTFREESLOT, sizeu1(dehmobjs));
-
-	if (dehpowers != NUMPOWERS)
-		I_Error("You forgot to update the Dehacked powers list, you dolt!\n(%d powers defined, versus %s in the Dehacked list)\n", NUMPOWERS, sizeu1(dehpowers));
-
-	if (dehcolors != SKINCOLOR_FIRSTFREESLOT)
-		I_Error("You forgot to update the Dehacked colors list, you dolt!\n(%d colors defined, versus %s in the Dehacked list)\n", SKINCOLOR_FIRSTFREESLOT, sizeu1(dehcolors));
-#endif
-}
diff --git a/src/dehacked.h b/src/dehacked.h
index d5256be23f0b05b9b51e80faf12823e3a43e0366..1620314caaba5bbabafded19233b0c162f148f84 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -30,8 +30,6 @@ typedef enum
 void DEH_LoadDehackedLump(lumpnum_t lumpnum);
 void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump, boolean mainfile);
 
-void DEH_Check(void);
-
 fixed_t get_number(const char *word);
 FUNCPRINTF void deh_warning(const char *first, ...);
 void deh_strlcpy(char *dst, const char *src, size_t size, const char *warntext);
diff --git a/src/doomdef.h b/src/doomdef.h
index d0b7ea0c2391334c703051d02e0dae693dfdfe19..52abc95972cf4a2ed4b57c1dc939654d84f32906 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -582,9 +582,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 ///	Dumps the contents of a network save game upon consistency failure for debugging.
 //#define DUMPCONSISTENCY
 
-///	See name of player in your crosshair
-#define SEENAMES
-
 ///	Who put weights on my recycler?  ... Inuyasha did.
 ///	\note	XMOD port.
 //#define WEIGHTEDRECYCLER
diff --git a/src/doomtype.h b/src/doomtype.h
index 4e13ba96d3795bb9992369a48b7aacd27500ea1a..950f50856b7679e035c268e036660fe505d8dbab 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -379,4 +379,30 @@ Needed for some lua shenanigans.
 #define FIELDFROM( type, field, have, want ) \
 	(void *)((intptr_t)(field) - offsetof (type, have) + offsetof (type, want))
 
+typedef UINT8 bitarray_t;
+
+#define BIT_ARRAY_SIZE(n) (((n) + 7) >> 3)
+
+static inline int
+in_bit_array (const bitarray_t * const array, const int value)
+{
+	return (array[value >> 3] & (1<<(value & 7)));
+}
+
+static inline void
+set_bit_array (bitarray_t * const array, const int value)
+{
+	array[value >> 3] |= (1<<(value & 7));
+}
+
+static inline void
+unset_bit_array (bitarray_t * const array, const int value)
+{
+	array[value >> 3] &= ~(1<<(value & 7));
+}
+
+#ifdef HAVE_SDL
+typedef UINT64 precise_t;
+#endif
+
 #endif //__DOOMTYPE__
diff --git a/src/f_finale.c b/src/f_finale.c
index 688cd4fc7f24cf472d2e8302b0bd2f496cc9f427..fdcfad2795f94f5c656345caa0921191084c135f 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1074,6 +1074,7 @@ static const char *credits[] = {
 	"\1Programming",
 	"Alam \"GBC\" Arias",
 	"Logan \"GBA\" Arias",
+	"Zolton \"Zippy_Zolton\" Auburn",
 	"Colette \"fickleheart\" Bordelon",
 	"Andrew \"orospakr\" Clunis",
 	"Sally \"TehRealSalt\" Cochenour",
@@ -1104,6 +1105,7 @@ static const char *credits[] = {
 	"Sean \"Sryder13\" Ryder",
 	"Ehab \"Wolfy\" Saeed",
 	"Tasos \"tatokis\" Sahanidis", // Corrected C FixedMul, making 64-bit builds netplay compatible
+	"Riku \"Ors\" Salminen", // Demo consistency improvements
 	"Jonas \"MascaraSnake\" Sauer",
 	"Wessel \"sphere\" Smit",
 	"\"SSNTails\"",
@@ -1136,6 +1138,7 @@ static const char *credits[] = {
 	"Iestyn \"Monster Iestyn\" Jealous",
 	"William \"GuyWithThePie\" Kloppenberg",
 	"Alice \"Alacroix\" de Lemos",
+	"Logan \"Hyperchaotix\" McCloud",
 	"Alexander \"DrTapeworm\" Moench-Ford",
 	"Andrew \"Senku Niola\" Moran",
 	"\"MotorRoach\"",
@@ -2543,28 +2546,28 @@ static void F_UnloadAlacroixGraphics(SINT8 oldttscale)
 	oldttscale--; // zero-based index
 	for (i = 0; i < TTMAX_ALACROIX; i++)
 	{
-		if(ttembl[oldttscale][i]) { Z_Free(ttembl[oldttscale][i]); ttembl[oldttscale][i] = 0; }
-		if(ttribb[oldttscale][i]) { Z_Free(ttribb[oldttscale][i]); ttribb[oldttscale][i] = 0; }
-		if(ttsont[oldttscale][i]) { Z_Free(ttsont[oldttscale][i]); ttsont[oldttscale][i] = 0; }
-		if(ttrobo[oldttscale][i]) { Z_Free(ttrobo[oldttscale][i]); ttrobo[oldttscale][i] = 0; }
-		if(tttwot[oldttscale][i]) { Z_Free(tttwot[oldttscale][i]); tttwot[oldttscale][i] = 0; }
-		if(ttrbtx[oldttscale][i]) { Z_Free(ttrbtx[oldttscale][i]); ttrbtx[oldttscale][i] = 0; }
-		if(ttsoib[oldttscale][i]) { Z_Free(ttsoib[oldttscale][i]); ttsoib[oldttscale][i] = 0; }
-		if(ttsoif[oldttscale][i]) { Z_Free(ttsoif[oldttscale][i]); ttsoif[oldttscale][i] = 0; }
-		if(ttsoba[oldttscale][i]) { Z_Free(ttsoba[oldttscale][i]); ttsoba[oldttscale][i] = 0; }
-		if(ttsobk[oldttscale][i]) { Z_Free(ttsobk[oldttscale][i]); ttsobk[oldttscale][i] = 0; }
-		if(ttsodh[oldttscale][i]) { Z_Free(ttsodh[oldttscale][i]); ttsodh[oldttscale][i] = 0; }
-		if(tttaib[oldttscale][i]) { Z_Free(tttaib[oldttscale][i]); tttaib[oldttscale][i] = 0; }
-		if(tttaif[oldttscale][i]) { Z_Free(tttaif[oldttscale][i]); tttaif[oldttscale][i] = 0; }
-		if(tttaba[oldttscale][i]) { Z_Free(tttaba[oldttscale][i]); tttaba[oldttscale][i] = 0; }
-		if(tttabk[oldttscale][i]) { Z_Free(tttabk[oldttscale][i]); tttabk[oldttscale][i] = 0; }
-		if(tttabt[oldttscale][i]) { Z_Free(tttabt[oldttscale][i]); tttabt[oldttscale][i] = 0; }
-		if(tttaft[oldttscale][i]) { Z_Free(tttaft[oldttscale][i]); tttaft[oldttscale][i] = 0; }
-		if(ttknib[oldttscale][i]) { Z_Free(ttknib[oldttscale][i]); ttknib[oldttscale][i] = 0; }
-		if(ttknif[oldttscale][i]) { Z_Free(ttknif[oldttscale][i]); ttknif[oldttscale][i] = 0; }
-		if(ttknba[oldttscale][i]) { Z_Free(ttknba[oldttscale][i]); ttknba[oldttscale][i] = 0; }
-		if(ttknbk[oldttscale][i]) { Z_Free(ttknbk[oldttscale][i]); ttknbk[oldttscale][i] = 0; }
-		if(ttkndh[oldttscale][i]) { Z_Free(ttkndh[oldttscale][i]); ttkndh[oldttscale][i] = 0; }
+		if(ttembl[oldttscale][i]) { Patch_Free(ttembl[oldttscale][i]); ttembl[oldttscale][i] = 0; }
+		if(ttribb[oldttscale][i]) { Patch_Free(ttribb[oldttscale][i]); ttribb[oldttscale][i] = 0; }
+		if(ttsont[oldttscale][i]) { Patch_Free(ttsont[oldttscale][i]); ttsont[oldttscale][i] = 0; }
+		if(ttrobo[oldttscale][i]) { Patch_Free(ttrobo[oldttscale][i]); ttrobo[oldttscale][i] = 0; }
+		if(tttwot[oldttscale][i]) { Patch_Free(tttwot[oldttscale][i]); tttwot[oldttscale][i] = 0; }
+		if(ttrbtx[oldttscale][i]) { Patch_Free(ttrbtx[oldttscale][i]); ttrbtx[oldttscale][i] = 0; }
+		if(ttsoib[oldttscale][i]) { Patch_Free(ttsoib[oldttscale][i]); ttsoib[oldttscale][i] = 0; }
+		if(ttsoif[oldttscale][i]) { Patch_Free(ttsoif[oldttscale][i]); ttsoif[oldttscale][i] = 0; }
+		if(ttsoba[oldttscale][i]) { Patch_Free(ttsoba[oldttscale][i]); ttsoba[oldttscale][i] = 0; }
+		if(ttsobk[oldttscale][i]) { Patch_Free(ttsobk[oldttscale][i]); ttsobk[oldttscale][i] = 0; }
+		if(ttsodh[oldttscale][i]) { Patch_Free(ttsodh[oldttscale][i]); ttsodh[oldttscale][i] = 0; }
+		if(tttaib[oldttscale][i]) { Patch_Free(tttaib[oldttscale][i]); tttaib[oldttscale][i] = 0; }
+		if(tttaif[oldttscale][i]) { Patch_Free(tttaif[oldttscale][i]); tttaif[oldttscale][i] = 0; }
+		if(tttaba[oldttscale][i]) { Patch_Free(tttaba[oldttscale][i]); tttaba[oldttscale][i] = 0; }
+		if(tttabk[oldttscale][i]) { Patch_Free(tttabk[oldttscale][i]); tttabk[oldttscale][i] = 0; }
+		if(tttabt[oldttscale][i]) { Patch_Free(tttabt[oldttscale][i]); tttabt[oldttscale][i] = 0; }
+		if(tttaft[oldttscale][i]) { Patch_Free(tttaft[oldttscale][i]); tttaft[oldttscale][i] = 0; }
+		if(ttknib[oldttscale][i]) { Patch_Free(ttknib[oldttscale][i]); ttknib[oldttscale][i] = 0; }
+		if(ttknif[oldttscale][i]) { Patch_Free(ttknif[oldttscale][i]); ttknif[oldttscale][i] = 0; }
+		if(ttknba[oldttscale][i]) { Patch_Free(ttknba[oldttscale][i]); ttknba[oldttscale][i] = 0; }
+		if(ttknbk[oldttscale][i]) { Patch_Free(ttknbk[oldttscale][i]); ttknbk[oldttscale][i] = 0; }
+		if(ttkndh[oldttscale][i]) { Patch_Free(ttkndh[oldttscale][i]); ttkndh[oldttscale][i] = 0; }
 	}
 	ttloaded[oldttscale] = false;
 }
diff --git a/src/g_demo.c b/src/g_demo.c
index 9d3b8601584385d06bbd14e5ff8a2ec4ea63ca3c..593fd77234a329ba89140e9ad82b98a5e993c22d 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -1956,9 +1956,7 @@ void G_DoPlayDemo(char *defdemoname)
 	// Set skin
 	SetPlayerSkin(0, skin);
 
-#ifdef HAVE_BLUA
 	LUAh_MapChange(gamemap);
-#endif
 	displayplayer = consoleplayer = 0;
 	memset(playeringame,0,sizeof(playeringame));
 	playeringame[0] = true;
diff --git a/src/g_game.c b/src/g_game.c
index 283113bbeaec2a78b7756b6361d90e33764ec315..399c4f2bdc07cad33809be359fe694922466d97a 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -444,9 +444,7 @@ consvar_t cv_firenaxis2 = CVAR_INIT ("joyaxis2_firenormal", "Z-Axis", CV_SAVE, j
 consvar_t cv_deadzone2 = CVAR_INIT ("joy_deadzone2", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL);
 consvar_t cv_digitaldeadzone2 = CVAR_INIT ("joy_digdeadzone2", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL);
 
-#ifdef SEENAMES
 player_t *seenplayer; // player we're aiming at right now
-#endif
 
 // now automatically allocated in D_RegisterClientCommands
 // so that it doesn't have to be updated depending on the value of MAXPLAYERS
@@ -1680,7 +1678,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	// At this point, cmd doesn't contain the final angle yet,
 	// So we need to temporarily transform it so Lua scripters
 	// don't need to handle it differently than in other hooks.
-	if (gamestate == GS_LEVEL)
+	if (addedtogame && gamestate == GS_LEVEL)
 	{
 		INT16 extra = ticcmd_oldangleturn[forplayer] - player->oldrelangleturn;
 		INT16 origangle = cmd->angleturn;
@@ -2232,8 +2230,35 @@ void G_Ticker(boolean run)
 				// Costs a life to retry ... unless the player in question is dead already, or you haven't even touched the first starpost in marathon run.
 				if (marathonmode && gamemap == spmarathon_start && !players[consoleplayer].starposttime)
 				{
+					player_t *p = &players[consoleplayer];
 					marathonmode |= MA_INIT;
 					marathontime = 0;
+
+					numgameovers = tokenlist = token = 0;
+					countdown = countdown2 = exitfadestarted = 0;
+
+					p->playerstate = PST_REBORN;
+					p->starpostx = p->starposty = p->starpostz = 0;
+
+					p->lives = startinglivesbalance[0];
+					p->continues = 1;
+
+					p->score = 0;
+
+					// The latter two should clear by themselves, but just in case
+					p->pflags &= ~(PF_TAGIT|PF_GAMETYPEOVER|PF_FULLSTASIS);
+
+					// Clear cheatcodes too, just in case.
+					p->pflags &= ~(PF_GODMODE|PF_NOCLIP|PF_INVIS);
+
+					p->xtralife = 0;
+
+					// Reset unlockable triggers
+					unlocktriggers = 0;
+
+					emeralds = 0;
+
+					memset(&luabanks, 0, sizeof(luabanks));
 				}
 				else if (G_GametypeUsesLives() && players[consoleplayer].playerstate == PST_LIVE && players[consoleplayer].lives != INFLIVES)
 					players[consoleplayer].lives -= 1;
@@ -2266,14 +2291,21 @@ void G_Ticker(boolean run)
 	{
 		if (playeringame[i])
 		{
+			INT16 received;
+
 			G_CopyTiccmd(&players[i].cmd, &netcmds[buf][i], 1);
 
+			received = (players[i].cmd.angleturn & TICCMD_RECEIVED);
+
 			players[i].angleturn += players[i].cmd.angleturn - players[i].oldrelangleturn;
 			players[i].oldrelangleturn = players[i].cmd.angleturn;
 			if (P_ControlStyle(&players[i]) == CS_LMAOGALOG)
 				P_ForceLocalAngle(&players[i], players[i].angleturn << 16);
 			else
 				players[i].cmd.angleturn = players[i].angleturn;
+
+			players[i].cmd.angleturn &= ~TICCMD_RECEIVED;
+			players[i].cmd.angleturn |= received;
 		}
 	}
 
@@ -3475,6 +3507,7 @@ tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = {
 	{"MARIO",TOL_MARIO},
 	{"NIGHTS",TOL_NIGHTS},
 	{"OLDBRAK",TOL_ERZ3},
+	{"ERZ3",TOL_ERZ3},
 
 	{"XMAS",TOL_XMAS},
 	{"CHRISTMAS",TOL_XMAS},
@@ -3941,6 +3974,7 @@ static void G_DoCompleted(void)
 	{
 		G_SetGamestate(GS_INTERMISSION);
 		Y_StartIntermission();
+		Y_LoadIntermissionData();
 		G_UpdateVisited();
 		G_HandleSaveLevel();
 	}
diff --git a/src/g_game.h b/src/g_game.h
index 2bcf444c234c244c2f8f76aab62cebee226d18ee..744d6755abd35dc0765ec2174e1cfed94c2b15b8 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -25,9 +25,7 @@ extern char timeattackfolder[64];
 extern char customversionstring[32];
 #define GAMEDATASIZE (4*8192)
 
-#ifdef SEENAMES
 extern player_t *seenplayer;
-#endif
 extern char  player_names[MAXPLAYERS][MAXPLAYERNAME+1];
 extern INT32 player_name_changes[MAXPLAYERS];
 
diff --git a/src/hardware/hw_batching.c b/src/hardware/hw_batching.c
index 5ea9f55d4c9ba88cc36ffa6ba2e44f72ce79ff60..b13ad03eace3b21342b8f1048b780aea2f36c08a 100644
--- a/src/hardware/hw_batching.c
+++ b/src/hardware/hw_batching.c
@@ -137,6 +137,8 @@ static int comparePolygons(const void *p1, const void *p2)
 	PolygonArrayEntry* poly2 = &polygonArray[index2];
 	int diff;
 	INT64 diff64;
+	UINT32 downloaded1 = 0;
+	UINT32 downloaded2 = 0;
 
 	int shader1 = poly1->shader;
 	int shader2 = poly2->shader;
@@ -152,7 +154,11 @@ static int comparePolygons(const void *p1, const void *p2)
 	if (shader1 == -1 && shader2 == -1)
 		return index1 - index2;
 
-	diff64 = poly1->texture - poly2->texture;
+	if (poly1->texture)
+		downloaded1 = poly1->texture->downloaded; // there should be a opengl texture name here, usable for comparisons
+	if (poly2->texture)
+		downloaded2 = poly2->texture->downloaded;
+	diff64 = downloaded1 - downloaded2;
 	if (diff64 != 0) return diff64;
 
 	diff = poly1->polyFlags - poly2->polyFlags;
@@ -184,16 +190,21 @@ static int comparePolygonsNoShaders(const void *p1, const void *p2)
 
 	GLMipmap_t *texture1 = poly1->texture;
 	GLMipmap_t *texture2 = poly2->texture;
+	UINT32 downloaded1 = 0;
+	UINT32 downloaded2 = 0;
 	if (poly1->polyFlags & PF_NoTexture || poly1->horizonSpecial)
 		texture1 = NULL;
 	if (poly2->polyFlags & PF_NoTexture || poly2->horizonSpecial)
 		texture2 = NULL;
-	diff64 = texture1 - texture2;
-	if (diff64 != 0) return diff64;
-
+	if (texture1)
+		downloaded1 = texture1->downloaded; // there should be a opengl texture name here, usable for comparisons
+	if (texture2)
+		downloaded2 = texture2->downloaded;
 	// skywalls and horizon lines must retain their order for horizon lines to work
-	if (texture1 == NULL && texture2 == NULL)
+	if (!texture1 && !texture2)
 		return index1 - index2;
+	diff64 = downloaded1 - downloaded2;
+	if (diff64 != 0) return diff64;
 
 	diff = poly1->polyFlags - poly2->polyFlags;
 	if (diff != 0) return diff;
@@ -248,12 +259,12 @@ void HWR_RenderBatches(void)
 	}
 
 	// sort polygons
-	ps_hw_batchsorttime = I_GetTimeMicros();
+	ps_hw_batchsorttime = I_GetPreciseTime();
 	if (cv_glshaders.value && gl_shadersavailable)
 		qsort(polygonIndexArray, polygonArraySize, sizeof(unsigned int), comparePolygons);
 	else
 		qsort(polygonIndexArray, polygonArraySize, sizeof(unsigned int), comparePolygonsNoShaders);
-	ps_hw_batchsorttime = I_GetTimeMicros() - ps_hw_batchsorttime;
+	ps_hw_batchsorttime = I_GetPreciseTime() - ps_hw_batchsorttime;
 	// sort order
 	// 1. shader
 	// 2. texture
@@ -261,7 +272,7 @@ void HWR_RenderBatches(void)
 	// 4. colors + light level
 	// not sure about what order of the last 2 should be, or if it even matters
 
-	ps_hw_batchdrawtime = I_GetTimeMicros();
+	ps_hw_batchdrawtime = I_GetPreciseTime();
 
 	currentShader = polygonArray[polygonIndexArray[0]].shader;
 	currentTexture = polygonArray[polygonIndexArray[0]].texture;
@@ -446,7 +457,7 @@ void HWR_RenderBatches(void)
 	polygonArraySize = 0;
 	unsortedVertexArraySize = 0;
 
-	ps_hw_batchdrawtime = I_GetTimeMicros() - ps_hw_batchdrawtime;
+	ps_hw_batchdrawtime = I_GetPreciseTime() - ps_hw_batchdrawtime;
 }
 
 
diff --git a/src/hardware/hw_batching.h b/src/hardware/hw_batching.h
index 3d22324ac8aaef8baf7bae73ccd89e92dcd78385..42291a0dfd261731ce6d40c24e06de63260dc132 100644
--- a/src/hardware/hw_batching.h
+++ b/src/hardware/hw_batching.h
@@ -16,7 +16,7 @@
 #include "hw_data.h"
 #include "hw_drv.h"
 
-typedef struct 
+typedef struct
 {
 	FSurfaceInfo surf;// surf also has its own polyflags for some reason, but it seems unused
 	unsigned int vertsIndex;// location of verts in unsortedVertexArray
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index b4fa7ec6c1d8be984b7f0f86234b42980c6f3b7b..83a4e2e03d9b56cd503092762b7ac48fe52a5611 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -108,7 +108,7 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 
 			//Hurdler: 25/04/2000: now support colormap in hardware mode
 			if (mipmap->colormap)
-				texel = mipmap->colormap[texel];
+				texel = mipmap->colormap->data[texel];
 
 			// hope compiler will get this switch out of the loops (dreams...)
 			// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
@@ -218,7 +218,7 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 
 			//Hurdler: 25/04/2000: now support colormap in hardware mode
 			if (mipmap->colormap)
-				texel = mipmap->colormap[texel];
+				texel = mipmap->colormap->data[texel];
 
 			// hope compiler will get this switch out of the loops (dreams...)
 			// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
@@ -659,7 +659,10 @@ void HWR_FreeTextureColormaps(patch_t *patch)
 		// Free image data from memory.
 		if (next->data)
 			Z_Free(next->data);
+		if (next->colormap)
+			Z_Free(next->colormap);
 		next->data = NULL;
+		next->colormap = NULL;
 		HWD.pfnDeleteTexture(next);
 
 		// Free the old colormap mipmap from memory.
@@ -667,16 +670,29 @@ void HWR_FreeTextureColormaps(patch_t *patch)
 	}
 }
 
+static boolean FreeTextureCallback(void *mem)
+{
+	patch_t *patch = (patch_t *)mem;
+	HWR_FreeTexture(patch);
+	return false;
+}
+
+static boolean FreeColormapsCallback(void *mem)
+{
+	patch_t *patch = (patch_t *)mem;
+	HWR_FreeTextureColormaps(patch);
+	return false;
+}
+
 static void HWR_FreePatchCache(boolean freeall)
 {
-	INT32 i;
+	boolean (*callback)(void *mem) = FreeTextureCallback;
 
-	for (i = 0; i < numwadfiles; i++)
-	{
-		INT32 j = 0;
-		for (; j < wadfiles[i]->numlumps; j++)
-			(freeall ? HWR_FreeTexture : HWR_FreeTextureColormaps)(wadfiles[i]->patchcache[j]);
-	}
+	if (!freeall)
+		callback = FreeColormapsCallback;
+
+	Z_IterateTags(PU_PATCH, PU_PATCH_ROTATED, callback);
+	Z_IterateTags(PU_SPRITE, PU_HUDGFX, callback);
 }
 
 // free all textures after each level
@@ -850,7 +866,7 @@ static void HWR_CacheTextureAsFlat(GLMipmap_t *grMipmap, INT32 texturenum)
 }
 
 // Download a Doom 'flat' to the hardware cache and make it ready for use
-void HWR_LiterallyGetFlat(lumpnum_t flatlumpnum)
+void HWR_GetRawFlat(lumpnum_t flatlumpnum)
 {
 	GLMipmap_t *grmip;
 	patch_t *patch;
@@ -879,7 +895,7 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 		return;
 
 	if (levelflat->type == LEVELFLAT_FLAT)
-		HWR_LiterallyGetFlat(levelflat->u.flat.lumpnum);
+		HWR_GetRawFlat(levelflat->u.flat.lumpnum);
 	else if (levelflat->type == LEVELFLAT_TEXTURE)
 	{
 		GLMapTexture_t *grtex;
@@ -918,15 +934,17 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 #ifndef NO_PNG_LUMPS
 	else if (levelflat->type == LEVELFLAT_PNG)
 	{
-		INT32 pngwidth = 0, pngheight = 0;
 		GLMipmap_t *mipmap = levelflat->mipmap;
-		UINT8 *flat;
-		size_t size;
 
 		// Cache the picture.
-		if (!levelflat->picture)
+		if (!levelflat->mippic)
 		{
-			levelflat->picture = Picture_PNGConvert(W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE), PICFMT_FLAT, &pngwidth, &pngheight, NULL, NULL, W_LumpLength(levelflat->u.flat.lumpnum), NULL, 0);
+			INT32 pngwidth = 0, pngheight = 0;
+			void *pic = Picture_PNGConvert(W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE), PICFMT_FLAT, &pngwidth, &pngheight, NULL, NULL, W_LumpLength(levelflat->u.flat.lumpnum), NULL, 0);
+
+			Z_ChangeTag(pic, PU_LEVEL);
+			Z_SetUser(pic, &levelflat->mippic);
+
 			levelflat->width = (UINT16)pngwidth;
 			levelflat->height = (UINT16)pngheight;
 		}
@@ -934,7 +952,7 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 		// Make the mipmap.
 		if (mipmap == NULL)
 		{
-			mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_LEVEL, NULL);
+			mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_STATIC, NULL);
 			mipmap->format = GL_TEXFMT_P_8;
 			mipmap->flags = TF_WRAPXY|TF_CHROMAKEYED;
 			levelflat->mipmap = mipmap;
@@ -942,17 +960,22 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 
 		if (!mipmap->data && !mipmap->downloaded)
 		{
+			UINT8 *flat;
+			size_t size;
+
+			if (levelflat->mippic == NULL)
+				I_Error("HWR_GetLevelFlat: levelflat->mippic == NULL");
+
 			mipmap->width = levelflat->width;
 			mipmap->height = levelflat->height;
+
 			size = (mipmap->width * mipmap->height);
 			flat = Z_Malloc(size, PU_LEVEL, &mipmap->data);
-			if (levelflat->picture == NULL)
-				I_Error("HWR_GetLevelFlat: levelflat->picture == NULL");
-			M_Memcpy(flat, levelflat->picture, size);
+			M_Memcpy(flat, levelflat->mippic, size);
 		}
 
 		// Tell the hardware driver to bind the current texture to the flat's mipmap
-		HWD.pfnSetTexture(mipmap);
+		HWR_SetCurrentTexture(mipmap);
 	}
 #endif
 	else // set no texture
@@ -977,8 +1000,28 @@ static void HWR_LoadPatchMipmap(patch_t *patch, GLMipmap_t *grMipmap)
 	Z_ChangeTag(grMipmap->data, PU_HWRCACHE_UNLOCKED);
 }
 
+// ----------------------+
+// HWR_UpdatePatchMipmap : Updates a mipmap.
+// ----------------------+
+static void HWR_UpdatePatchMipmap(patch_t *patch, GLMipmap_t *grMipmap)
+{
+	GLPatch_t *grPatch = patch->hardware;
+	HWR_MakePatch(patch, grPatch, grMipmap, true);
+
+	// If hardware does not have the texture, then call pfnSetTexture to upload it
+	// If it does have the texture, then call pfnUpdateTexture to update it
+	if (!grMipmap->downloaded)
+		HWD.pfnSetTexture(grMipmap);
+	else
+		HWD.pfnUpdateTexture(grMipmap);
+	HWR_SetCurrentTexture(grMipmap);
+
+	// The system-memory data can be purged now.
+	Z_ChangeTag(grMipmap->data, PU_HWRCACHE_UNLOCKED);
+}
+
 // -----------------+
-// HWR_GetPatch     : Download a patch to the hardware cache and make it ready for use
+// HWR_GetPatch     : Downloads a patch to the hardware cache and make it ready for use
 // -----------------+
 void HWR_GetPatch(patch_t *patch)
 {
@@ -1006,14 +1049,20 @@ void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap)
 		return;
 	}
 
-	// search for the mimmap
+	// search for the mipmap
 	// skip the first (no colormap translated)
 	for (grMipmap = grPatch->mipmap; grMipmap->nextcolormap; )
 	{
 		grMipmap = grMipmap->nextcolormap;
-		if (grMipmap->colormap == colormap)
+		if (grMipmap->colormap && grMipmap->colormap->source == colormap)
 		{
-			HWR_LoadPatchMipmap(patch, grMipmap);
+			if (memcmp(grMipmap->colormap->data, colormap, 256 * sizeof(UINT8)))
+			{
+				M_Memcpy(grMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
+				HWR_UpdatePatchMipmap(patch, grMipmap);
+			}
+			else
+				HWR_LoadPatchMipmap(patch, grMipmap);
 			return;
 		}
 	}
@@ -1029,7 +1078,10 @@ void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap)
 		I_Error("%s: Out of memory", "HWR_GetMappedPatch");
 	grMipmap->nextcolormap = newMipmap;
 
-	newMipmap->colormap = colormap;
+	newMipmap->colormap = Z_Calloc(sizeof(*newMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL);
+	newMipmap->colormap->source = colormap;
+	M_Memcpy(newMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
+
 	HWR_LoadPatchMipmap(patch, newMipmap);
 }
 
@@ -1039,7 +1091,6 @@ void HWR_UnlockCachedPatch(GLPatch_t *gpatch)
 		return;
 
 	Z_ChangeTag(gpatch->mipmap->data, PU_HWRCACHE_UNLOCKED);
-	Z_ChangeTag(gpatch, PU_HWRPATCHINFO_UNLOCKED);
 }
 
 static const INT32 picmode2GR[] =
diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h
index 3ae4ef8bc2145430846a728523e5d85dc577dd8e..7e56a14d0f71b9d575046ccb9877487169c3877d 100644
--- a/src/hardware/hw_data.h
+++ b/src/hardware/hw_data.h
@@ -39,45 +39,53 @@ typedef enum GLTextureFormat_e
 	GL_TEXFMT_ALPHA_INTENSITY_88  = 0x22,
 } GLTextureFormat_t;
 
-// data holds the address of the graphics data cached in heap memory
-//                NULL if the texture is not in Doom heap cache.
+// Colormap structure for mipmaps.
+struct GLColormap_s
+{
+	const UINT8 *source;
+	UINT8 data[256];
+};
+typedef struct GLColormap_s GLColormap_t;
+
+
+// Texture information (misleadingly named "mipmap" all over the code.)
+// The *data pointer holds the address of the graphics data cached in heap memory.
+// NULL if the texture is not in SRB2's heap cache.
 struct GLMipmap_s
 {
-	// for TexDownloadMipMap
+	// for UpdateTexture
 	GLTextureFormat_t     format;
 	void                 *data;
 
 	UINT32                flags;
 	UINT16                height;
 	UINT16                width;
-	UINT32                downloaded;     // The GPU has this texture.
+	UINT32                downloaded; // The GPU has this texture.
 
 	struct GLMipmap_s    *nextcolormap;
-	const UINT8          *colormap;
-
-	struct GLMipmap_s    *nextmipmap; // Linked list of all textures
+	struct GLColormap_s  *colormap;
 };
 typedef struct GLMipmap_s GLMipmap_t;
 
 
 //
-// Doom texture info, as cached for hardware rendering
+// Level textures, as cached for hardware rendering.
 //
 struct GLMapTexture_s
 {
 	GLMipmap_t  mipmap;
-	float       scaleX;             //used for scaling textures on walls
+	float       scaleX; // Used for scaling textures on walls
 	float       scaleY;
 };
 typedef struct GLMapTexture_s GLMapTexture_t;
 
 
-// a cached patch as converted to hardware format
+// Patch information for the hardware renderer.
 struct GLPatch_s
 {
-	float               max_s,max_t;
-	GLMipmap_t          *mipmap;
-} ATTRPACK;
+	GLMipmap_t *mipmap; // Texture data. Allocated whenever the patch is.
+	float       max_s, max_t;
+};
 typedef struct GLPatch_s GLPatch_t;
 
 #endif //_HWR_DATA_
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index a782762a38c46dbb4161468b43b3041d215e8d2e..8df9b8916b2e563d7c1eebb16511b222a636a312 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -216,28 +216,28 @@ enum EPolyFlags
 	PF_Masked           = 0x00000001,   // Poly is alpha scaled and 0 alpha pixels are discarded (holes in texture)
 	PF_Translucent      = 0x00000002,   // Poly is transparent, alpha = level of transparency
 	PF_Environment      = 0x00000004,   // Poly should be drawn environment mapped. (Hurdler: used for text drawing)
-	PF_Additive         = 0x00000008,   // Additive color blending
-	PF_AdditiveSource   = 0x00000010,   // Source blending factor is additive. This is the opposite of regular additive blending.
-	PF_Subtractive      = 0x00000020,   // Subtractive color blending
-	PF_ReverseSubtract  = 0x00000040,   // Reverse subtract, used in wall splats (decals)
-	PF_Multiplicative   = 0x00000080,   // Multiplicative color blending
+	PF_Additive         = 0x00000008,   // Source blending factor is additive.
+	PF_Subtractive      = 0x00000010,   // Subtractive color blending
+	PF_ReverseSubtract  = 0x00000020,   // Reverse subtract, used in wall splats (decals)
+	PF_Multiplicative   = 0x00000040,   // Multiplicative color blending
 	PF_Fog              = 0x20000000,   // Fog blocks
 	PF_NoAlphaTest      = 0x40000000,   // Disables alpha testing
-	PF_Blending         = (PF_Masked|PF_Translucent|PF_Environment|PF_Additive|PF_AdditiveSource|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative|PF_Fog) & ~PF_NoAlphaTest,
+	PF_Blending         = (PF_Masked|PF_Translucent|PF_Environment|PF_Additive|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative|PF_Fog) & ~PF_NoAlphaTest,
 
 	// other flag bits
 	PF_Occlude          = 0x00000100,   // Updates the depth buffer
 	PF_NoDepthTest      = 0x00000200,   // Disables the depth test mode
 	PF_Invisible        = 0x00000400,   // Disables write to color buffer
 	PF_Decal            = 0x00000800,   // Enables polygon offset
-	PF_Modulated        = 0x00001000,   // Modulation (multiply output with constant ARGB)
+	PF_Modulated        = 0x00001000,   // Modulation (multiply output with constant RGBA)
 	                                    // When set, pass the color constant into the FSurfaceInfo -> PolyColor
 	PF_NoTexture        = 0x00002000,   // Disables texturing
 	PF_Corona           = 0x00004000,   // Tells the renderer we are drawing a corona
-	PF_Ripple           = 0x00008000,   // Water effect shader
+	PF_ColorMapped      = 0x00008000,   // Surface has "tint" and "fade" colors, which are sent as uniforms to a shader.
 	PF_RemoveYWrap      = 0x00010000,   // Forces clamp texture on Y
 	PF_ForceWrapX       = 0x00020000,   // Forces repeat texture on X
-	PF_ForceWrapY       = 0x00040000    // Forces repeat texture on Y
+	PF_ForceWrapY       = 0x00040000,   // Forces repeat texture on Y
+	PF_Ripple           = 0x00100000    // Water ripple effect. The current backend doesn't use it for anything.
 };
 
 
@@ -255,9 +255,17 @@ enum ETextureFlags
 	TF_TRANSPARENT = 0x00000040,        // texture with some alpha == 0
 };
 
-typedef struct GLMipmap_s FTextureInfo;
+struct FTextureInfo
+{
+	UINT32 width, height;
+	UINT32 downloaded;
+	UINT32 format;
+
+	struct GLMipmap_s *texture;
+	struct FTextureInfo *prev, *next;
+};
+typedef struct FTextureInfo FTextureInfo;
 
-// jimita 14032019
 struct FLightInfo
 {
 	FUINT			light_level;
@@ -273,7 +281,7 @@ struct FSurfaceInfo
 	RGBA_t			PolyColor;
 	RGBA_t			TintColor;
 	RGBA_t			FadeColor;
-	FLightInfo		LightInfo;	// jimita 14032019
+	FLightInfo		LightInfo;
 };
 typedef struct FSurfaceInfo FSurfaceInfo;
 
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index b9cb288e910b1263dfa46e48b792c811de9d9beb..ba4923d10e76149cf0dd088aa7633db43e4a8ff0 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -437,18 +437,9 @@ void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 
 		if (!(option & V_SCALEPATCHMASK))
 		{
-			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
-			// cx and cy are possibly *slightly* off from float maths
-			// This is done before here compared to software because we directly alter cx and cy to centre
-			if (cx >= -0.1f && cx <= 0.1f && gpatch->width == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && gpatch->height == BASEVIDHEIGHT)
-			{
-				const column_t *column = (const column_t *)((const UINT8 *)(gpatch->columns) + (gpatch->columnofs[0]));
-				if (!column->topdelta)
-				{
-					const UINT8 *source = (const UINT8 *)(column) + 3;
-					HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
-				}
-			}
+			// if it's meant to cover the whole screen, black out the rest
+			// no the patch is cropped do not do this ever
+
 			// centre screen
 			if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
 			{
@@ -470,11 +461,11 @@ void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 	fwidth = w;
 	fheight = h;
 
-	if (fwidth > gpatch->width)
-		fwidth = gpatch->width;
+	if (sx + w > gpatch->width)
+		fwidth = gpatch->width - sx;
 
-	if (fheight > gpatch->height)
-		fheight = gpatch->height;
+	if (sy + h > gpatch->height)
+		fheight = gpatch->height - sy;
 
 	if (pscale != FRACUNIT)
 	{
@@ -639,7 +630,7 @@ void HWR_DrawFlatFill (INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum
 	v[0].t = v[1].t = (float)((y & flatflag)/dflatsize);
 	v[2].t = v[3].t = (float)(v[0].t + h/dflatsize);
 
-	HWR_LiterallyGetFlat(flatlumpnum);
+	HWR_GetRawFlat(flatlumpnum);
 
 	//Hurdler: Boris, the same comment as above... but maybe for pics
 	// it not a problem since they don't have any transparent pixel
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 5a2e0e44eaeb13ffc0465403fdd606e2f556fe2e..8cae144ff81fdb2ca5ee53deea1e9c1fc8cc8531 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -40,13 +40,12 @@ EXPORT void HWRAPI(DrawIndexedTriangles) (FSurfaceInfo *pSurf, FOutVector *pOutV
 EXPORT void HWRAPI(RenderSkyDome) (gl_sky_t *sky);
 EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags);
 EXPORT void HWRAPI(ClearBuffer) (FBOOLEAN ColorMask, FBOOLEAN DepthMask, FRGBAFloat *ClearColor);
-EXPORT void HWRAPI(SetTexture) (FTextureInfo *TexInfo);
-EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *TexInfo);
-EXPORT void HWRAPI(DeleteTexture) (FTextureInfo *TexInfo);
+EXPORT void HWRAPI(SetTexture) (GLMipmap_t *TexInfo);
+EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *TexInfo);
+EXPORT void HWRAPI(DeleteTexture) (GLMipmap_t *TexInfo);
 EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height, INT32 dst_stride, UINT16 *dst_data);
 EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip);
 EXPORT void HWRAPI(ClearMipMapCache) (void);
-EXPORT void HWRAPI(ClearCacheList) (void);
 
 //Hurdler: added for backward compatibility
 EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value);
@@ -69,7 +68,6 @@ EXPORT void HWRAPI(DrawScreenFinalTexture) (int width, int height);
 #define SCREENVERTS 10
 EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2]);
 
-// jimita
 EXPORT boolean HWRAPI(CompileShaders) (void);
 EXPORT void HWRAPI(CleanShaders) (void);
 EXPORT void HWRAPI(SetShader) (int type);
@@ -101,7 +99,6 @@ struct hwdriver_s
 	ReadRect            pfnReadRect;
 	GClipRect           pfnGClipRect;
 	ClearMipMapCache    pfnClearMipMapCache;
-	ClearCacheList      pfnClearCacheList;
 	SetSpecialState     pfnSetSpecialState;//Hurdler: added for backward compatibility
 	DrawModel           pfnDrawModel;
 	CreateModelVBOs     pfnCreateModelVBOs;
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 87405d3d457080e1fccd86206ac2d0dfb9b97db4..2aba622481d618a4e8f5d2d499e420e74123b9ce 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -118,7 +118,7 @@ patch_t *HWR_GetPic(lumpnum_t lumpnum);
 
 GLMapTexture_t *HWR_GetTexture(INT32 tex);
 void HWR_GetLevelFlat(levelflat_t *levelflat);
-void HWR_LiterallyGetFlat(lumpnum_t flatlumpnum);
+void HWR_GetRawFlat(lumpnum_t flatlumpnum);
 
 void HWR_FreeTexture(patch_t *patch);
 void HWR_FreeTextureData(patch_t *patch);
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index 987d70c69e22b293bb8d07cce5ce10520b5d387e..e83d9a6ec025143150754ab6910afed140b4fdcd 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -35,7 +35,7 @@
 
 #define DL_HIGH_QUALITY
 //#define STATICLIGHT  //Hurdler: TODO!
-#define LIGHTMAPFLAGS (PF_Modulated|PF_AdditiveSource)
+#define LIGHTMAPFLAGS (PF_Modulated|PF_Additive)
 
 #ifdef ALAM_LIGHTING
 static dynlights_t view_dynlights[2]; // 2 players in splitscreen mode
@@ -253,6 +253,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_SIGN
 	&lspr[NOLIGHT],     // SPR_SPIK
 	&lspr[NOLIGHT],     // SPR_SFLM
+	&lspr[NOLIGHT],     // SPR_TFLM
 	&lspr[NOLIGHT],     // SPR_USPK
 	&lspr[NOLIGHT],     // SPR_WSPK
 	&lspr[NOLIGHT],     // SPR_WSPB
@@ -1055,7 +1056,7 @@ void HWR_DoCoronasLighting(FOutVector *outVerts, gl_vissprite_t *spr)
 
 		HWR_GetPic(coronalumpnum);  /// \todo use different coronas
 
-		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_AdditiveSource | PF_Corona | PF_NoDepthTest);
+		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_Additive | PF_Corona | PF_NoDepthTest);
 	}
 }
 #endif
@@ -1143,7 +1144,7 @@ void HWR_DrawCoronas(void)
 		light[3].y = cy+size*1.33f;
 		light[3].s = 0.0f;   light[3].t = 1.0f;
 
-		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_AdditiveSource | PF_NoDepthTest | PF_Corona);
+		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_Additive | PF_NoDepthTest | PF_Corona);
 	}
 }
 #endif
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 5dd2727bcc71207ad006943ab7b22e96f5606585..e94c637e46ca7fb1f6e99463042e00d760944745 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -147,11 +147,11 @@ static angle_t gl_aimingangle;
 static void HWR_SetTransformAiming(FTransform *trans, player_t *player, boolean skybox);
 
 // Render stats
-int ps_hw_skyboxtime = 0;
-int ps_hw_nodesorttime = 0;
-int ps_hw_nodedrawtime = 0;
-int ps_hw_spritesorttime = 0;
-int ps_hw_spritedrawtime = 0;
+precise_t ps_hw_skyboxtime = 0;
+precise_t ps_hw_nodesorttime = 0;
+precise_t ps_hw_nodedrawtime = 0;
+precise_t ps_hw_spritesorttime = 0;
+precise_t ps_hw_spritedrawtime = 0;
 
 // Render stats for batching
 int ps_hw_numpolys = 0;
@@ -161,8 +161,8 @@ int ps_hw_numshaders = 0;
 int ps_hw_numtextures = 0;
 int ps_hw_numpolyflags = 0;
 int ps_hw_numcolors = 0;
-int ps_hw_batchsorttime = 0;
-int ps_hw_batchdrawtime = 0;
+precise_t ps_hw_batchsorttime = 0;
+precise_t ps_hw_batchdrawtime = 0;
 
 boolean gl_init = false;
 boolean gl_maploaded = false;
@@ -173,6 +173,11 @@ boolean gl_shadersavailable = true;
 // Lighting
 // ==========================================================================
 
+static boolean HWR_UseShader(void)
+{
+	return (cv_glshaders.value && gl_shadersavailable);
+}
+
 void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *colormap)
 {
 	RGBA_t poly_color, tint_color, fade_color;
@@ -182,7 +187,7 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col
 	fade_color.rgba = (colormap != NULL) ? (UINT32)colormap->fadergba : GL_DEFAULTFOG;
 
 	// Crappy backup coloring if you can't do shaders
-	if (!cv_glshaders.value || !gl_shadersavailable)
+	if (!HWR_UseShader())
 	{
 		// be careful, this may get negative for high lightlevel values.
 		float tint_alpha, fade_alpha;
@@ -371,7 +376,7 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	static FOutVector *planeVerts = NULL;
 	static UINT16 numAllocedPlaneVerts = 0;
 
-	int shader;
+	INT32 shader = SHADER_DEFAULT;
 
 	// no convex poly were generated for this subsector
 	if (!xsub->planepoly)
@@ -568,12 +573,17 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	else
 		PolyFlags |= PF_Masked|PF_Modulated;
 
-	if (PolyFlags & PF_Fog)
-		shader = SHADER_FOG;	// fog shader
-	else if (PolyFlags & PF_Ripple)
-		shader = SHADER_WATER;	// water shader
-	else
-		shader = SHADER_FLOOR;	// floor shader
+	if (HWR_UseShader())
+	{
+		if (PolyFlags & PF_Fog)
+			shader = SHADER_FOG;
+		else if (PolyFlags & PF_Ripple)
+			shader = SHADER_WATER;
+		else
+			shader = SHADER_FLOOR;
+
+		PolyFlags |= PF_ColorMapped;
+	}
 
 	HWR_ProcessPolygon(&Surf, planeVerts, nrPlaneVerts, PolyFlags, shader, false);
 
@@ -706,6 +716,9 @@ FBITFIELD HWR_GetBlendModeFlag(INT32 ast)
 {
 	switch (ast)
 	{
+		case AST_COPY:
+		case AST_OVERLAY:
+			return PF_Masked;
 		case AST_ADD:
 			return PF_Additive;
 		case AST_SUBTRACT:
@@ -744,7 +757,7 @@ UINT8 HWR_GetTranstableAlpha(INT32 transtablenum)
 
 FBITFIELD HWR_SurfaceBlend(INT32 style, INT32 transtablenum, FSurfaceInfo *pSurf)
 {
-	if (!transtablenum)
+	if (!transtablenum || style == AST_COPY || style == AST_OVERLAY)
 	{
 		pSurf->PolyColor.s.alpha = 0xff;
 		return PF_Masked;
@@ -785,8 +798,17 @@ static void HWR_AddTransparentWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, I
 //
 static void HWR_ProjectWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blendmode, INT32 lightlevel, extracolormap_t *wallcolormap)
 {
+	INT32 shader = SHADER_DEFAULT;
+
 	HWR_Lighting(pSurf, lightlevel, wallcolormap);
-	HWR_ProcessPolygon(pSurf, wallVerts, 4, blendmode|PF_Modulated|PF_Occlude, SHADER_WALL, false); // wall shader
+
+	if (HWR_UseShader())
+	{
+		shader = SHADER_WALL;
+		blendmode |= PF_ColorMapped;
+	}
+
+	HWR_ProcessPolygon(pSurf, wallVerts, 4, blendmode|PF_Modulated|PF_Occlude, shader, false);
 }
 
 // ==========================================================================
@@ -831,7 +853,7 @@ static float HWR_ClipViewSegment(INT32 x, polyvertex_t *v1, polyvertex_t *v2)
 //
 // HWR_SplitWall
 //
-static void HWR_SplitWall(sector_t *sector, FOutVector *wallVerts, INT32 texnum, FSurfaceInfo* Surf, INT32 cutflag, ffloor_t *pfloor)
+static void HWR_SplitWall(sector_t *sector, FOutVector *wallVerts, INT32 texnum, FSurfaceInfo* Surf, INT32 cutflag, ffloor_t *pfloor, FBITFIELD polyflags)
 {
 	/* SoM: split up and light walls according to the
 	 lightlist. This may also include leaving out parts
@@ -969,11 +991,11 @@ static void HWR_SplitWall(sector_t *sector, FOutVector *wallVerts, INT32 texnum,
 		wallVerts[1].y = endbot;
 
 		if (cutflag & FF_FOG)
-			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Fog|PF_NoTexture, true, lightnum, colormap);
+			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Fog|PF_NoTexture|polyflags, true, lightnum, colormap);
 		else if (cutflag & FF_TRANSLUCENT)
-			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Translucent, false, lightnum, colormap);
+			HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Translucent|polyflags, false, lightnum, colormap);
 		else
-			HWR_ProjectWall(wallVerts, Surf, PF_Masked, lightnum, colormap);
+			HWR_ProjectWall(wallVerts, Surf, PF_Masked|polyflags, lightnum, colormap);
 
 		top = bot;
 		endtop = endbot;
@@ -998,11 +1020,11 @@ static void HWR_SplitWall(sector_t *sector, FOutVector *wallVerts, INT32 texnum,
 	wallVerts[1].y = endbot;
 
 	if (cutflag & FF_FOG)
-		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Fog|PF_NoTexture, true, lightnum, colormap);
+		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Fog|PF_NoTexture|polyflags, true, lightnum, colormap);
 	else if (cutflag & FF_TRANSLUCENT)
-		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Translucent, false, lightnum, colormap);
+		HWR_AddTransparentWall(wallVerts, Surf, texnum, PF_Translucent|polyflags, false, lightnum, colormap);
 	else
-		HWR_ProjectWall(wallVerts, Surf, PF_Masked, lightnum, colormap);
+		HWR_ProjectWall(wallVerts, Surf, PF_Masked|polyflags, lightnum, colormap);
 }
 
 // HWR_DrawSkyWall
@@ -1183,7 +1205,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			wallVerts[1].y = FIXED_TO_FLOAT(worldhighslope);
 
 			if (gl_frontsector->numlights)
-				HWR_SplitWall(gl_frontsector, wallVerts, gl_toptexture, &Surf, FF_CUTLEVEL, NULL);
+				HWR_SplitWall(gl_frontsector, wallVerts, gl_toptexture, &Surf, FF_CUTLEVEL, NULL, 0);
 			else if (grTex->mipmap.flags & TF_TRANSPARENT)
 				HWR_AddTransparentWall(wallVerts, &Surf, gl_toptexture, PF_Environment, false, lightnum, colormap);
 			else
@@ -1249,7 +1271,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			wallVerts[1].y = FIXED_TO_FLOAT(worldbottomslope);
 
 			if (gl_frontsector->numlights)
-				HWR_SplitWall(gl_frontsector, wallVerts, gl_bottomtexture, &Surf, FF_CUTLEVEL, NULL);
+				HWR_SplitWall(gl_frontsector, wallVerts, gl_bottomtexture, &Surf, FF_CUTLEVEL, NULL, 0);
 			else if (grTex->mipmap.flags & TF_TRANSPARENT)
 				HWR_AddTransparentWall(wallVerts, &Surf, gl_bottomtexture, PF_Environment, false, lightnum, colormap);
 			else
@@ -1465,13 +1487,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					blendmode = HWR_TranstableToAlpha(gl_curline->polyseg->translucency, &Surf);
 			}
 
+			// Render midtextures on two-sided lines with a z-buffer offset.
+			// This will cause the midtexture appear on top, if a FOF overlaps with it.
+			blendmode |= PF_Decal;
+
 			if (gl_frontsector->numlights)
 			{
 				if (!(blendmode & PF_Masked))
-					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_TRANSLUCENT, NULL);
+					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_TRANSLUCENT, NULL, PF_Decal);
 				else
 				{
-					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_CUTLEVEL, NULL);
+					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_CUTLEVEL, NULL, PF_Decal);
 				}
 			}
 			else if (!(blendmode & PF_Masked))
@@ -1554,7 +1580,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			// I don't think that solid walls can use translucent linedef types...
 			if (gl_frontsector->numlights)
-				HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_CUTLEVEL, NULL);
+				HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_CUTLEVEL, NULL, 0);
 			else
 			{
 				if (grTex->mipmap.flags & TF_TRANSPARENT)
@@ -1717,7 +1743,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					Surf.PolyColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel, rover->master->frontsector->extra_colormap);
 
 					if (gl_frontsector->numlights)
-						HWR_SplitWall(gl_frontsector, wallVerts, 0, &Surf, rover->flags, rover);
+						HWR_SplitWall(gl_frontsector, wallVerts, 0, &Surf, rover->flags, rover, 0);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -1732,7 +1758,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					}
 
 					if (gl_frontsector->numlights)
-						HWR_SplitWall(gl_frontsector, wallVerts, texnum, &Surf, rover->flags, rover);
+						HWR_SplitWall(gl_frontsector, wallVerts, texnum, &Surf, rover->flags, rover, 0);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -1829,7 +1855,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					Surf.PolyColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel, rover->master->frontsector->extra_colormap);
 
 					if (gl_backsector->numlights)
-						HWR_SplitWall(gl_backsector, wallVerts, 0, &Surf, rover->flags, rover);
+						HWR_SplitWall(gl_backsector, wallVerts, 0, &Surf, rover->flags, rover, 0);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -1844,7 +1870,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					}
 
 					if (gl_backsector->numlights)
-						HWR_SplitWall(gl_backsector, wallVerts, texnum, &Surf, rover->flags, rover);
+						HWR_SplitWall(gl_backsector, wallVerts, texnum, &Surf, rover->flags, rover, 0);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -2659,30 +2685,30 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 									FBITFIELD blendmode, UINT8 lightlevel, levelflat_t *levelflat, sector_t *FOFsector,
 									UINT8 alpha, extracolormap_t *planecolormap)
 {
-	float           height; //constant y for all points on the convex flat polygon
-	FOutVector      *v3d;
-	INT32             i;
-	float           flatxref,flatyref;
+	FSurfaceInfo Surf;
+	FOutVector *v3d;
+	INT32 shader = SHADER_DEFAULT;
+
+	size_t nrPlaneVerts = polysector->numVertices;
+	INT32 i;
+
+	float height = FIXED_TO_FLOAT(fixedheight); // constant y for all points on the convex flat polygon
+	float flatxref, flatyref;
 	float fflatwidth = 64.0f, fflatheight = 64.0f;
 	INT32 flatflag = 63;
+
 	boolean texflat = false;
+
 	float scrollx = 0.0f, scrolly = 0.0f;
 	angle_t angle = 0;
-	FSurfaceInfo    Surf;
 	fixed_t tempxs, tempyt;
-	size_t nrPlaneVerts;
 
 	static FOutVector *planeVerts = NULL;
 	static UINT16 numAllocedPlaneVerts = 0;
 
-	nrPlaneVerts = polysector->numVertices;
-
-	height = FIXED_TO_FLOAT(fixedheight);
-
-	if (nrPlaneVerts < 3)   //not even a triangle ?
+	if (nrPlaneVerts < 3)   // Not even a triangle?
 		return;
-
-	if (nrPlaneVerts > (size_t)UINT16_MAX) // FIXME: exceeds plVerts size
+	else if (nrPlaneVerts > (size_t)UINT16_MAX) // FIXME: exceeds plVerts size
 	{
 		CONS_Debug(DBG_RENDER, "polygon size of %s exceeds max value of %d vertices\n", sizeu1(nrPlaneVerts), UINT16_MAX);
 		return;
@@ -2834,7 +2860,6 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 		v3d->z = FIXED_TO_FLOAT(polysector->vertices[i]->y);
 	}
 
-
 	HWR_Lighting(&Surf, lightlevel, planecolormap);
 
 	if (blendmode & PF_Translucent)
@@ -2845,7 +2870,13 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	else
 		blendmode |= PF_Masked|PF_Modulated;
 
-	HWR_ProcessPolygon(&Surf, planeVerts, nrPlaneVerts, blendmode, SHADER_FLOOR, false); // floor shader
+	if (HWR_UseShader())
+	{
+		shader = SHADER_FLOOR;
+		blendmode |= PF_ColorMapped;
+	}
+
+	HWR_ProcessPolygon(&Surf, planeVerts, nrPlaneVerts, blendmode, shader, false);
 }
 
 static void HWR_AddPolyObjectPlanes(void)
@@ -3566,6 +3597,8 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	FSurfaceInfo sSurf;
 	float fscale; float fx; float fy; float offset;
 	extracolormap_t *colormap = NULL;
+	FBITFIELD blendmode = PF_Translucent|PF_Modulated;
+	INT32 shader = SHADER_DEFAULT;
 	UINT8 i;
 	SINT8 flip = P_MobjFlip(thing);
 
@@ -3658,14 +3691,20 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	HWR_Lighting(&sSurf, 0, colormap);
 	sSurf.PolyColor.s.alpha = alpha;
 
-	HWR_ProcessPolygon(&sSurf, shadowVerts, 4, PF_Translucent|PF_Modulated, SHADER_SPRITE, false); // sprite shader
+	if (HWR_UseShader())
+	{
+		shader = SHADER_SPRITE;
+		blendmode |= PF_ColorMapped;
+	}
+
+	HWR_ProcessPolygon(&sSurf, shadowVerts, 4, blendmode, shader, false);
 }
 
 // This is expecting a pointer to an array containing 4 wallVerts for a sprite
 static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts, const boolean precip)
 {
 	if (cv_glspritebillboarding.value
-		&& spr && spr->mobj && !(spr->mobj->frame & FF_PAPERSPRITE)
+		&& spr && spr->mobj && !R_ThingIsPaperSprite(spr->mobj)
 		&& wallVerts)
 	{
 		float basey = FIXED_TO_FLOAT(spr->mobj->z);
@@ -3706,8 +3745,8 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	boolean lightset = true;
 	FBITFIELD blend = 0;
 	FBITFIELD occlusion;
+	INT32 shader = SHADER_DEFAULT;
 	boolean use_linkdraw_hack = false;
-	boolean splat = R_ThingIsFloorSprite(spr->mobj);
 	UINT8 alpha;
 
 	INT32 i;
@@ -3766,22 +3805,19 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 		baseWallVerts[0].t = baseWallVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 	}
 
-	if (!splat)
-	{
-		// if it has a dispoffset, push it a little towards the camera
-		if (spr->dispoffset) {
-			float co = -gl_viewcos*(0.05f*spr->dispoffset);
-			float si = -gl_viewsin*(0.05f*spr->dispoffset);
-			baseWallVerts[0].z = baseWallVerts[3].z = baseWallVerts[0].z+si;
-			baseWallVerts[1].z = baseWallVerts[2].z = baseWallVerts[1].z+si;
-			baseWallVerts[0].x = baseWallVerts[3].x = baseWallVerts[0].x+co;
-			baseWallVerts[1].x = baseWallVerts[2].x = baseWallVerts[1].x+co;
-		}
-
-		// Let dispoffset work first since this adjust each vertex
-		HWR_RotateSpritePolyToAim(spr, baseWallVerts, false);
+	// if it has a dispoffset, push it a little towards the camera
+	if (spr->dispoffset) {
+		float co = -gl_viewcos*(0.05f*spr->dispoffset);
+		float si = -gl_viewsin*(0.05f*spr->dispoffset);
+		baseWallVerts[0].z = baseWallVerts[3].z = baseWallVerts[0].z+si;
+		baseWallVerts[1].z = baseWallVerts[2].z = baseWallVerts[1].z+si;
+		baseWallVerts[0].x = baseWallVerts[3].x = baseWallVerts[0].x+co;
+		baseWallVerts[1].x = baseWallVerts[2].x = baseWallVerts[1].x+co;
 	}
 
+	// Let dispoffset work first since this adjust each vertex
+	HWR_RotateSpritePolyToAim(spr, baseWallVerts, false);
+
 	realtop = top = baseWallVerts[3].y;
 	realbot = bot = baseWallVerts[0].y;
 	ttop = baseWallVerts[3].t;
@@ -3817,8 +3853,6 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	else if (spr->mobj->frame & FF_TRANSMASK)
 	{
 		INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
-		if (spr->mobj->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
-			return;
 		blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
 	}
 	else
@@ -3832,6 +3866,12 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 		if (!occlusion) use_linkdraw_hack = true;
 	}
 
+	if (HWR_UseShader())
+	{
+		shader = SHADER_SPRITE;
+		blend |= PF_ColorMapped;
+	}
+
 	alpha = Surf.PolyColor.s.alpha;
 
 	// Start with the lightlevel and colormap from the top of the sprite
@@ -3914,7 +3954,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 
 		// The x and y only need to be adjusted in the case that it's not a papersprite
 		if (cv_glspritebillboarding.value
-			&& spr->mobj && !(spr->mobj->frame & FF_PAPERSPRITE))
+			&& spr->mobj && !R_ThingIsPaperSprite(spr->mobj))
 		{
 			// Get the x and z of the vertices so billboarding draws correctly
 			realheight = realbot - realtop;
@@ -3940,7 +3980,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 
 		Surf.PolyColor.s.alpha = alpha;
 
-		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
+		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, shader, false);
 
 		if (use_linkdraw_hack)
 			HWR_LinkDrawHackAdd(wallVerts, spr);
@@ -3969,7 +4009,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 
 	Surf.PolyColor.s.alpha = alpha;
 
-	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
+	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, shader, false);
 
 	if (use_linkdraw_hack)
 		HWR_LinkDrawHackAdd(wallVerts, spr);
@@ -3983,7 +4023,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 static void HWR_DrawSprite(gl_vissprite_t *spr)
 {
 	FOutVector wallVerts[4];
-	patch_t *gpatch; // sprite patch converted to hardware
+	patch_t *gpatch;
 	FSurfaceInfo Surf;
 	const boolean splat = R_ThingIsFloorSprite(spr->mobj);
 
@@ -4141,6 +4181,11 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 		wallVerts[1].z = wallVerts[2].z = spr->z2;
 	}
 
+	// cache the patch in the graphics card memory
+	//12/12/99: Hurdler: same comment as above (for md2)
+	//Hurdler: 25/04/2000: now support colormap in hardware mode
+	HWR_GetMappedPatch(gpatch, spr->colormap);
+
 	if (spr->flip)
 	{
 		wallVerts[0].s = wallVerts[3].s = ((GLPatch_t *)gpatch->hardware)->max_s;
@@ -4160,11 +4205,6 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 		wallVerts[0].t = wallVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 	}
 
-	// cache the patch in the graphics card memory
-	//12/12/99: Hurdler: same comment as above (for md2)
-	//Hurdler: 25/04/2000: now support colormap in hardware mode
-	HWR_GetMappedPatch(gpatch, spr->colormap);
-
 	if (!splat)
 	{
 		// if it has a dispoffset, push it a little towards the camera
@@ -4219,6 +4259,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 	}
 
 	{
+		INT32 shader = SHADER_DEFAULT;
 		FBITFIELD blend = 0;
 		FBITFIELD occlusion;
 		boolean use_linkdraw_hack = false;
@@ -4244,8 +4285,6 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 		else if (spr->mobj->frame & FF_TRANSMASK)
 		{
 			INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
-			if (spr->mobj->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
-				return;
 			blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
 		}
 		else
@@ -4271,7 +4310,13 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 			if (!occlusion) use_linkdraw_hack = true;
 		}
 
-		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
+		if (HWR_UseShader())
+		{
+			shader = SHADER_SPRITE;
+			blend |= PF_ColorMapped;
+		}
+
+		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, shader, false);
 
 		if (use_linkdraw_hack)
 			HWR_LinkDrawHackAdd(wallVerts, spr);
@@ -4282,9 +4327,10 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 // Sprite drawer for precipitation
 static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 {
+	INT32 shader = SHADER_DEFAULT;
 	FBITFIELD blend = 0;
 	FOutVector wallVerts[4];
-	patch_t *gpatch; // sprite patch converted to hardware
+	patch_t *gpatch;
 	FSurfaceInfo Surf;
 
 	if (!spr->mobj)
@@ -4337,7 +4383,7 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 			// Always use the light at the top instead of whatever I was doing before
 			INT32 light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false);
 
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			if (!R_ThingIsFullBright(spr->mobj))
 				lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel;
 
 			if (*sector->lightlist[light].extra_colormap)
@@ -4345,7 +4391,7 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 		}
 		else
 		{
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			if (!R_ThingIsFullBright(spr->mobj))
 				lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel;
 
 			if (sector->extra_colormap)
@@ -4358,9 +4404,7 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 	if (spr->mobj->frame & FF_TRANSMASK)
 	{
 		INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
-		if (spr->mobj->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
-			return;
-		blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
+		blend = HWR_SurfaceBlend(AST_TRANSLUCENT, trans, &Surf);
 	}
 	else
 	{
@@ -4372,7 +4416,13 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 		blend = HWR_GetBlendModeFlag(spr->mobj->blendmode)|PF_Occlude;
 	}
 
-	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
+	if (HWR_UseShader())
+	{
+		shader = SHADER_SPRITE;
+		blend |= PF_ColorMapped;
+	}
+
+	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, shader, false);
 }
 #endif
 
@@ -4654,7 +4704,7 @@ static void HWR_CreateDrawNodes(void)
 	// that is already lying around. This should all be in some sort of linked list or lists.
 	sortindex = Z_Calloc(sizeof(size_t) * (numplanes + numpolyplanes + numwalls), PU_STATIC, NULL);
 
-	ps_hw_nodesorttime = I_GetTimeMicros();
+	ps_hw_nodesorttime = I_GetPreciseTime();
 
 	for (i = 0; i < numplanes; i++, p++)
 	{
@@ -4709,9 +4759,9 @@ static void HWR_CreateDrawNodes(void)
 		}
 	}
 
-	ps_hw_nodesorttime = I_GetTimeMicros() - ps_hw_nodesorttime;
+	ps_hw_nodesorttime = I_GetPreciseTime() - ps_hw_nodesorttime;
 
-	ps_hw_nodedrawtime = I_GetTimeMicros();
+	ps_hw_nodedrawtime = I_GetPreciseTime();
 
 	// Okay! Let's draw it all! Woo!
 	HWD.pfnSetTransform(&atransform);
@@ -4748,7 +4798,7 @@ static void HWR_CreateDrawNodes(void)
 		}
 	}
 
-	ps_hw_nodedrawtime = I_GetTimeMicros() - ps_hw_nodedrawtime;
+	ps_hw_nodedrawtime = I_GetPreciseTime() - ps_hw_nodedrawtime;
 
 	numwalls = 0;
 	numplanes = 0;
@@ -4921,8 +4971,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	angle_t ang;
 	INT32 heightsec, phs;
-	const boolean papersprite = R_ThingIsPaperSprite(thing);
 	const boolean splat = R_ThingIsFloorSprite(thing);
+	const boolean papersprite = (R_ThingIsPaperSprite(thing) && !splat);
 	angle_t mobjangle = (thing->player ? thing->player->drawangle : thing->angle);
 	float z1, z2;
 
@@ -4939,6 +4989,13 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (thing->spritexscale < 1 || thing->spriteyscale < 1)
 		return;
 
+	// Visibility check by the blend mode.
+	if (thing->frame & FF_TRANSMASK)
+	{
+		if (!R_BlendLevelVisible(thing->blendmode, (thing->frame & FF_TRANSMASK)>>FF_TRANSSHIFT))
+			return;
+	}
+
 	dispoffset = thing->info->dispoffset;
 
 	this_scale = FIXED_TO_FLOAT(thing->scale);
@@ -5295,7 +5352,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 			vis->colormap = R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color ? vis->mobj->color : SKINCOLOR_CYAN, GTC_CACHE);
 	}
 	else
-		vis->colormap = colormaps;
+		vis->colormap = NULL;
 
 	// set top/bottom coords
 	vis->gzt = gzt;
@@ -5325,6 +5382,13 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	unsigned rot = 0;
 	UINT8 flip;
 
+	// Visibility check by the blend mode.
+	if (thing->frame & FF_TRANSMASK)
+	{
+		if (!R_BlendLevelVisible(thing->blendmode, (thing->frame & FF_TRANSMASK)>>FF_TRANSSHIFT))
+			return;
+	}
+
 	// transform the origin point
 	tr_x = FIXED_TO_FLOAT(thing->x) - gl_viewx;
 	tr_y = FIXED_TO_FLOAT(thing->y) - gl_viewy;
@@ -5358,7 +5422,7 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 		return;
 #endif
 
-	sprframe = &sprdef->spriteframes[ thing->frame & FF_FRAMEMASK];
+	sprframe = &sprdef->spriteframes[thing->frame & FF_FRAMEMASK];
 
 	// use single rotation for all views
 	lumpoff = sprframe->lumpid[0];
@@ -5396,7 +5460,7 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	vis->flip = flip;
 	vis->mobj = (mobj_t *)thing;
 
-	vis->colormap = colormaps;
+	vis->colormap = NULL;
 
 	// set top/bottom coords
 	vis->gzt = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset);
@@ -6017,10 +6081,10 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	if (viewnumber == 0) // Only do it if it's the first screen being rendered
 		HWD.pfnClearBuffer(true, false, &ClearColor); // Clear the Color Buffer, stops HOMs. Also seems to fix the skybox issue on Intel GPUs.
 
-	ps_hw_skyboxtime = I_GetTimeMicros();
+	ps_hw_skyboxtime = I_GetPreciseTime();
 	if (skybox && drawsky) // If there's a skybox and we should be drawing the sky, draw the skybox
 		HWR_RenderSkyboxView(viewnumber, player); // This is drawn before everything else so it is placed behind
-	ps_hw_skyboxtime = I_GetTimeMicros() - ps_hw_skyboxtime;
+	ps_hw_skyboxtime = I_GetPreciseTime() - ps_hw_skyboxtime;
 
 	{
 		// do we really need to save player (is it not the same)?
@@ -6132,7 +6196,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	ps_numbspcalls = 0;
 	ps_numpolyobjects = 0;
-	ps_bsptime = I_GetTimeMicros();
+	ps_bsptime = I_GetPreciseTime();
 
 	validcount++;
 
@@ -6170,7 +6234,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	}
 #endif
 
-	ps_bsptime = I_GetTimeMicros() - ps_bsptime;
+	ps_bsptime = I_GetPreciseTime() - ps_bsptime;
 
 	if (cv_glbatching.value)
 		HWR_RenderBatches();
@@ -6186,12 +6250,12 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	// Draw MD2 and sprites
 	ps_numsprites = gl_visspritecount;
-	ps_hw_spritesorttime = I_GetTimeMicros();
+	ps_hw_spritesorttime = I_GetPreciseTime();
 	HWR_SortVisSprites();
-	ps_hw_spritesorttime = I_GetTimeMicros() - ps_hw_spritesorttime;
-	ps_hw_spritedrawtime = I_GetTimeMicros();
+	ps_hw_spritesorttime = I_GetPreciseTime() - ps_hw_spritesorttime;
+	ps_hw_spritedrawtime = I_GetPreciseTime();
 	HWR_DrawSprites();
-	ps_hw_spritedrawtime = I_GetTimeMicros() - ps_hw_spritedrawtime;
+	ps_hw_spritedrawtime = I_GetPreciseTime() - ps_hw_spritedrawtime;
 
 #ifdef NEWCORONAS
 	//Hurdler: they must be drawn before translucent planes, what about gl fog?
@@ -6454,24 +6518,29 @@ void HWR_RenderWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blend,
 	FBITFIELD blendmode = blend;
 	UINT8 alpha = pSurf->PolyColor.s.alpha; // retain the alpha
 
-	int shader;
+	INT32 shader = SHADER_DEFAULT;
 
 	// Lighting is done here instead so that fog isn't drawn incorrectly on transparent walls after sorting
 	HWR_Lighting(pSurf, lightlevel, wallcolormap);
 
 	pSurf->PolyColor.s.alpha = alpha; // put the alpha back after lighting
 
-	shader = SHADER_WALL;	// wall shader
-
 	if (blend & PF_Environment)
 		blendmode |= PF_Occlude;	// PF_Occlude must be used for solid objects
 
-	if (fogwall)
+	if (HWR_UseShader())
 	{
-		blendmode |= PF_Fog;
-		shader = SHADER_FOG;	// fog shader
+		if (fogwall)
+			shader = SHADER_FOG;
+		else
+			shader = SHADER_WALL;
+
+		blendmode |= PF_ColorMapped;
 	}
 
+	if (fogwall)
+		blendmode |= PF_Fog;
+
 	blendmode |= PF_Modulated;	// No PF_Occlude means overlapping (incorrect) transparency
 	HWR_ProcessPolygon(pSurf, wallVerts, 4, blendmode, shader, false);
 }
@@ -6514,7 +6583,7 @@ void HWR_DoPostProcessor(player_t *player)
 
 		Surf.PolyColor.s.alpha = 0xc0; // match software mode
 
-		HWD.pfnDrawPolygon(&Surf, v, 4, PF_Modulated|PF_AdditiveSource|PF_NoTexture|PF_NoDepthTest);
+		HWD.pfnDrawPolygon(&Surf, v, 4, PF_Modulated|PF_Additive|PF_NoTexture|PF_NoDepthTest);
 	}
 
 	// Capture the screen for intermission and screen waving
@@ -6647,7 +6716,6 @@ void HWR_DrawScreenFinalTexture(int width, int height)
     HWD.pfnDrawScreenFinalTexture(width, height);
 }
 
-// jimita 18032019
 static inline UINT16 HWR_FindShaderDefs(UINT16 wadnum)
 {
 	UINT16 i;
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 2ce918408b041780b3a5e0294a9ce059a5326381..4ad09aa3d63b1612239243ca15a0a2d878ea7c8d 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -116,11 +116,11 @@ extern FTransform atransform;
 
 
 // Render stats
-extern int ps_hw_skyboxtime;
-extern int ps_hw_nodesorttime;
-extern int ps_hw_nodedrawtime;
-extern int ps_hw_spritesorttime;
-extern int ps_hw_spritedrawtime;
+extern precise_t ps_hw_skyboxtime;
+extern precise_t ps_hw_nodesorttime;
+extern precise_t ps_hw_nodedrawtime;
+extern precise_t ps_hw_spritesorttime;
+extern precise_t ps_hw_spritedrawtime;
 
 // Render stats for batching
 extern int ps_hw_numpolys;
@@ -130,8 +130,8 @@ extern int ps_hw_numshaders;
 extern int ps_hw_numtextures;
 extern int ps_hw_numpolyflags;
 extern int ps_hw_numcolors;
-extern int ps_hw_batchsorttime;
-extern int ps_hw_batchdrawtime;
+extern precise_t ps_hw_batchsorttime;
+extern precise_t ps_hw_batchdrawtime;
 
 extern boolean gl_init;
 extern boolean gl_maploaded;
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 9c786e67ed5a40e395ceb362b28cc3a58a1254c5..5caf344f75a3859751919338d8cea8b6badf1f92 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -158,7 +158,7 @@ static GLTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 	jmp_buf jmpbuf;
 #endif
 #endif
-	png_FILE_p png_FILE;
+	volatile png_FILE_p png_FILE;
 	//Filename checking fixed ~Monster Iestyn and Golden
 	char *pngfilename = va("%s"PATHSEP"models"PATHSEP"%s", srb2home, filename);
 
@@ -1106,11 +1106,19 @@ static void HWR_GetBlendedTexture(patch_t *patch, patch_t *blendpatch, INT32 ski
 	for (grMipmap = grPatch->mipmap; grMipmap->nextcolormap; )
 	{
 		grMipmap = grMipmap->nextcolormap;
-		if (grMipmap->colormap == colormap)
+		if (grMipmap->colormap && grMipmap->colormap->source == colormap)
 		{
 			if (grMipmap->downloaded && grMipmap->data)
 			{
-				HWD.pfnSetTexture(grMipmap); // found the colormap, set it to the correct texture
+				if (memcmp(grMipmap->colormap->data, colormap, 256 * sizeof(UINT8)))
+				{
+					M_Memcpy(grMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
+					HWR_CreateBlendedTexture(patch, blendpatch, grMipmap, skinnum, color);
+					HWD.pfnUpdateTexture(grMipmap);
+				}
+				else
+					HWD.pfnSetTexture(grMipmap); // found the colormap, set it to the correct texture
+
 				Z_ChangeTag(grMipmap->data, PU_HWRMODELTEXTURE_UNLOCKED);
 				return;
 			}
@@ -1128,7 +1136,10 @@ static void HWR_GetBlendedTexture(patch_t *patch, patch_t *blendpatch, INT32 ski
 	if (newMipmap == NULL)
 		I_Error("%s: Out of memory", "HWR_GetBlendedTexture");
 	grMipmap->nextcolormap = newMipmap;
-	newMipmap->colormap = colormap;
+
+	newMipmap->colormap = Z_Calloc(sizeof(*newMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL);
+	newMipmap->colormap->source = colormap;
+	M_Memcpy(newMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
 
 	HWR_CreateBlendedTexture(patch, blendpatch, newMipmap, skinnum, color);
 
@@ -1303,7 +1314,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 
 			light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before
 
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			if (!R_ThingIsFullBright(spr->mobj))
 				lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel;
 
 			if (*sector->lightlist[light].extra_colormap)
@@ -1311,7 +1322,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		}
 		else
 		{
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			if (!R_ThingIsFullBright(spr->mobj))
 				lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel;
 
 			if (sector->extra_colormap)
@@ -1329,10 +1340,9 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		GLPatch_t *hwrPatch = NULL, *hwrBlendPatch = NULL;
 		INT32 durs = spr->mobj->state->tics;
 		INT32 tics = spr->mobj->tics;
-		//mdlframe_t *next = NULL;
-		const boolean papersprite = (spr->mobj->frame & FF_PAPERSPRITE);
-		const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !(spr->mobj->frame & FF_VERTICALFLIP));
-		const UINT8 hflip = (UINT8)(!(spr->mobj->mirrored) != !(spr->mobj->frame & FF_HORIZONTALFLIP));
+		const boolean papersprite = (R_ThingIsPaperSprite(spr->mobj) && !R_ThingIsFloorSprite(spr->mobj));
+		const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !R_ThingVerticallyFlipped(spr->mobj));
+		const UINT8 hflip = (UINT8)(!(spr->mobj->mirrored) != !R_ThingHorizontallyFlipped(spr->mobj));
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
 		spriteinfo_t *sprinfo;
@@ -1394,6 +1404,11 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 			|| ((!hwrBlendPatch->mipmap->format || !hwrBlendPatch->mipmap->downloaded) && !md2->noblendfile)))
 			md2_loadBlendTexture(md2);
 
+		// Load it again, because it isn't being loaded into blendgpatch after md2_loadblendtexture...
+		blendgpatch = md2->blendgrpatch;
+		if (blendgpatch)
+			hwrBlendPatch = ((GLPatch_t *)blendgpatch->hardware);
+
 		if (md2->error)
 			return false; // we already failed loading this before :(
 		if (!md2->model)
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 8cd948eeadf57a34fa1290479b2f84d26210ba28..af06a198fc3f110278c3894ee99a2e0596860e0c 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -58,8 +58,9 @@ static  GLuint      tex_downloaded  = 0;
 static  GLfloat     fov             = 90.0f;
 static  FBITFIELD   CurrentPolyFlags;
 
-static  FTextureInfo *gl_cachetail = NULL;
-static  FTextureInfo *gl_cachehead = NULL;
+// Linked list of all textures.
+static FTextureInfo *TexCacheTail = NULL;
+static FTextureInfo *TexCacheHead = NULL;
 
 RGBA_t  myPaletteData[256];
 GLint   screen_width    = 0;               // used by Draw2DLine()
@@ -909,7 +910,6 @@ void SetupGLFunc4(void)
 	pgluBuild2DMipmaps = GetGLFunc("gluBuild2DMipmaps");
 }
 
-// jimita
 EXPORT boolean HWRAPI(CompileShaders) (void)
 {
 #ifdef GL_SHADERS
@@ -961,8 +961,6 @@ EXPORT boolean HWRAPI(CompileShaders) (void)
 		}
 	}
 
-	SetShader(SHADER_DEFAULT);
-
 	return true;
 #else
 	return false;
@@ -1287,10 +1285,34 @@ void SetStates(void)
 // -----------------+
 // DeleteTexture    : Deletes a texture from the GPU and frees its data
 // -----------------+
-EXPORT void HWRAPI(DeleteTexture) (FTextureInfo *pTexInfo)
+EXPORT void HWRAPI(DeleteTexture) (GLMipmap_t *pTexInfo)
 {
-	if (pTexInfo->downloaded)
+	FTextureInfo *head = TexCacheHead;
+
+	if (!pTexInfo)
+		return;
+	else if (pTexInfo->downloaded)
 		pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
+
+	while (head)
+	{
+		if (head->downloaded == pTexInfo->downloaded)
+		{
+			if (head->next)
+				head->next->prev = head->prev;
+			else // no next -> tail is being deleted -> update TexCacheTail
+				TexCacheTail = head->prev;
+			if (head->prev)
+				head->prev->next = head->next;
+			else // no prev -> head is being deleted -> update TexCacheHead
+				TexCacheHead = head->next;
+			free(head);
+			break;
+		}
+
+		head = head->next;
+	}
+
 	pTexInfo->downloaded = 0;
 }
 
@@ -1303,23 +1325,26 @@ void Flush(void)
 {
 	//GL_DBG_Printf ("HWR_Flush()\n");
 
-	while (gl_cachehead)
+	while (TexCacheHead)
 	{
-		DeleteTexture(gl_cachehead);
-		gl_cachehead = gl_cachehead->nextmipmap;
-	}
+		FTextureInfo *pTexInfo = TexCacheHead;
+		GLMipmap_t *texture = pTexInfo->texture;
 
-	ClearCacheList(); //Hurdler: well, gl_cachehead is already NULL
-	tex_downloaded = 0;
-}
+		if (pTexInfo->downloaded)
+		{
+			pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
+			pTexInfo->downloaded = 0;
+		}
 
+		if (texture)
+			texture->downloaded = 0;
 
-// -----------------+
-// ClearCacheList   : Clears the texture cache tail and head
-// -----------------+
-EXPORT void HWRAPI(ClearCacheList) (void)
-{
-	gl_cachetail = gl_cachehead = NULL;
+		TexCacheHead = pTexInfo->next;
+		free(pTexInfo);
+	}
+
+	TexCacheTail = TexCacheHead = NULL; //Hurdler: well, TexCacheHead is already NULL
+	tex_downloaded = 0;
 }
 
 
@@ -1554,12 +1579,11 @@ static void SetBlendMode(FBITFIELD flags)
 		case PF_Additive & PF_Blending:
 		case PF_Subtractive & PF_Blending:
 		case PF_ReverseSubtract & PF_Blending:
+			pglBlendFunc(GL_SRC_ALPHA, GL_ONE); // src * alpha + dest
+			break;
 		case PF_Environment & PF_Blending:
 			pglBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
 			break;
-		case PF_AdditiveSource & PF_Blending:
-			pglBlendFunc(GL_SRC_ALPHA, GL_ONE); // src * alpha + dest
-			break;
 		case PF_Multiplicative & PF_Blending:
 			pglBlendFunc(GL_DST_COLOR, GL_ZERO);
 			break;
@@ -1598,7 +1622,6 @@ static void SetBlendMode(FBITFIELD flags)
 			break;
 		case PF_Translucent & PF_Blending:
 		case PF_Additive & PF_Blending:
-		case PF_AdditiveSource & PF_Blending:
 		case PF_Subtractive & PF_Blending:
 		case PF_ReverseSubtract & PF_Blending:
 		case PF_Environment & PF_Blending:
@@ -1718,7 +1741,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 // -----------------+
 // UpdateTexture    : Updates the texture data.
 // -----------------+
-EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
+EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *pTexInfo)
 {
 	// Download a mipmap
 	boolean updatemipmap = true;
@@ -1920,7 +1943,7 @@ EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
 // -----------------+
 // SetTexture       : The mipmap becomes the current texture source
 // -----------------+
-EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
+EXPORT void HWRAPI(SetTexture) (GLMipmap_t *pTexInfo)
 {
 	if (!pTexInfo)
 	{
@@ -1937,17 +1960,25 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 	}
 	else
 	{
+		FTextureInfo *newTex = calloc(1, sizeof (*newTex));
+
 		UpdateTexture(pTexInfo);
-		pTexInfo->nextmipmap = NULL;
+
+		newTex->texture = pTexInfo;
+		newTex->downloaded = (UINT32)pTexInfo->downloaded;
+		newTex->width = (UINT32)pTexInfo->width;
+		newTex->height = (UINT32)pTexInfo->height;
+		newTex->format = (UINT32)pTexInfo->format;
 
 		// insertion at the tail
-		if (gl_cachetail)
+		if (TexCacheTail)
 		{
-			gl_cachetail->nextmipmap = pTexInfo;
-			gl_cachetail = pTexInfo;
+			newTex->prev = TexCacheTail;
+			TexCacheTail->next = newTex;
+			TexCacheTail = newTex;
 		}
 		else // initialization of the linked list
-			gl_cachetail = gl_cachehead = pTexInfo;
+			TexCacheTail = TexCacheHead = newTex;
 	}
 }
 
@@ -2144,32 +2175,34 @@ static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FBITFIELD
 
 	SetBlend(PolyFlags);    //TODO: inline (#pragma..)
 
-	// PolyColor
 	if (pSurf)
 	{
-		// If Modulated, mix the surface colour to the texture
+		// If modulated, mix the surface colour to the texture
 		if (CurrentPolyFlags & PF_Modulated)
-		{
-			// Poly color
-			poly.red    = byte2float[pSurf->PolyColor.s.red];
-			poly.green  = byte2float[pSurf->PolyColor.s.green];
-			poly.blue   = byte2float[pSurf->PolyColor.s.blue];
-			poly.alpha  = byte2float[pSurf->PolyColor.s.alpha];
-
 			pglColor4ubv((GLubyte*)&pSurf->PolyColor.s);
-		}
 
-		// Tint color
-		tint.red   = byte2float[pSurf->TintColor.s.red];
-		tint.green = byte2float[pSurf->TintColor.s.green];
-		tint.blue  = byte2float[pSurf->TintColor.s.blue];
-		tint.alpha = byte2float[pSurf->TintColor.s.alpha];
+		// If the surface is either modulated or colormapped, or both
+		if (CurrentPolyFlags & (PF_Modulated | PF_ColorMapped))
+		{
+			poly.red   = byte2float[pSurf->PolyColor.s.red];
+			poly.green = byte2float[pSurf->PolyColor.s.green];
+			poly.blue  = byte2float[pSurf->PolyColor.s.blue];
+			poly.alpha = byte2float[pSurf->PolyColor.s.alpha];
+		}
 
-		// Fade color
-		fade.red   = byte2float[pSurf->FadeColor.s.red];
-		fade.green = byte2float[pSurf->FadeColor.s.green];
-		fade.blue  = byte2float[pSurf->FadeColor.s.blue];
-		fade.alpha = byte2float[pSurf->FadeColor.s.alpha];
+		// Only if the surface is colormapped
+		if (CurrentPolyFlags & PF_ColorMapped)
+		{
+			tint.red   = byte2float[pSurf->TintColor.s.red];
+			tint.green = byte2float[pSurf->TintColor.s.green];
+			tint.blue  = byte2float[pSurf->TintColor.s.blue];
+			tint.alpha = byte2float[pSurf->TintColor.s.alpha];
+
+			fade.red   = byte2float[pSurf->FadeColor.s.red];
+			fade.green = byte2float[pSurf->FadeColor.s.green];
+			fade.blue  = byte2float[pSurf->FadeColor.s.blue];
+			fade.alpha = byte2float[pSurf->FadeColor.s.alpha];
+		}
 	}
 
 	// this test is added for new coronas' code (without depth buffer)
@@ -2722,7 +2755,7 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	fade.alpha = byte2float[Surface->FadeColor.s.alpha];
 
 	flags = (Surface->PolyFlags | PF_Modulated);
-	if (Surface->PolyFlags & (PF_Additive|PF_AdditiveSource|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative))
+	if (Surface->PolyFlags & (PF_Additive|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative))
 		flags |= PF_Occlude;
 	else if (Surface->PolyColor.s.alpha == 0xFF)
 		flags |= (PF_Occlude | PF_Masked);
@@ -2983,7 +3016,6 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 	pglMatrixMode(GL_PROJECTION);
 	pglLoadIdentity();
 
-	// jimita 14042019
 	// Simulate Software's y-shearing
 	// https://zdoom.org/wiki/Y-shearing
 	if (shearing)
@@ -3011,7 +3043,7 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 
 EXPORT INT32  HWRAPI(GetTextureUsed) (void)
 {
-	FTextureInfo *tmp = gl_cachehead;
+	FTextureInfo *tmp = TexCacheHead;
 	INT32 res = 0;
 
 	while (tmp)
@@ -3028,7 +3060,7 @@ EXPORT INT32  HWRAPI(GetTextureUsed) (void)
 
 		// Add it up!
 		res += tmp->height*tmp->width*bpp;
-		tmp = tmp->nextmipmap;
+		tmp = tmp->next;
 	}
 
 	return res;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 7e9144f98fd995ae3b2218f76472f3a92792db56..7c4f1acf1124b1087dcd7a90cf61b2f883434f18 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -98,6 +98,7 @@ patch_t *emeraldpics[3][8]; // 0 = normal, 1 = tiny, 2 = coinbox
 static patch_t *emblemicon;
 patch_t *tokenicon;
 static patch_t *exiticon;
+static patch_t *nopingicon;
 
 //-------------------------------------------
 //              misc vars
@@ -286,6 +287,7 @@ void HU_LoadGraphics(void)
 	emblemicon = W_CachePatchName("EMBLICON", PU_HUDGFX);
 	tokenicon = W_CachePatchName("TOKNICON", PU_HUDGFX);
 	exiticon = W_CachePatchName("EXITICON", PU_HUDGFX);
+	nopingicon = W_CachePatchName("NOPINGICON", PU_HUDGFX);
 
 	emeraldpics[0][0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
 	emeraldpics[0][1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
@@ -2109,15 +2111,18 @@ void HU_Drawer(void)
 		return;
 
 	// draw the crosshair, not when viewing demos nor with chasecam
-	if (!automapactive && cv_crosshair.value && !demoplayback &&
-		(!camera.chase || ticcmd_ztargetfocus[0])
-	&& !players[displayplayer].spectator)
-		HU_DrawCrosshair();
+	if (LUA_HudEnabled(hud_crosshair))
+	{
+		if (!automapactive && cv_crosshair.value && !demoplayback &&
+			(!camera.chase || ticcmd_ztargetfocus[0])
+		&& !players[displayplayer].spectator)
+			HU_DrawCrosshair();
 
-	if (!automapactive && cv_crosshair2.value && !demoplayback &&
-		(!camera2.chase || ticcmd_ztargetfocus[1])
-	&& !players[secondarydisplayplayer].spectator)
-		HU_DrawCrosshair2();
+		if (!automapactive && cv_crosshair2.value && !demoplayback &&
+			(!camera2.chase || ticcmd_ztargetfocus[1])
+		&& !players[secondarydisplayplayer].spectator)
+			HU_DrawCrosshair2();
+	}
 
 	// draw desynch text
 	if (hu_redownloadinggamestate)
@@ -2243,8 +2248,8 @@ void HU_Erase(void)
 //
 void HU_drawPing(INT32 x, INT32 y, UINT32 ping, boolean notext, INT32 flags)
 {
-	UINT8 numbars = 1; // how many ping bars do we draw?
-	UINT8 barcolor = 35; // color we use for the bars (green, yellow or red)
+	UINT8 numbars = 0; // how many ping bars do we draw?
+	UINT8 barcolor = 31; // color we use for the bars (green, yellow, red or black)
 	SINT8 i = 0;
 	SINT8 yoffset = 6;
 	INT32 dx = x+1 - (V_SmallStringWidth(va("%dms", ping),
@@ -2257,11 +2262,16 @@ void HU_drawPing(INT32 x, INT32 y, UINT32 ping, boolean notext, INT32 flags)
 	}
 	else if (ping < 256)
 	{
-		numbars = 2; // Apparently ternaries w/ multiple statements don't look good in C so I decided against it.
+		numbars = 2;
 		barcolor = 73;
 	}
+	else if (ping < UINT32_MAX)
+	{
+		numbars = 1;
+		barcolor = 35;
+	}
 
-	if (!notext || vid.width >= 640) // how sad, we're using a shit resolution.
+	if (ping < UINT32_MAX && (!notext || vid.width >= 640)) // how sad, we're using a shit resolution.
 		V_DrawSmallString(dx, y+4, V_ALLOWLOWERCASE|flags, va("%dms", ping));
 
 	for (i=0; (i<3); i++) // Draw the ping bar
@@ -2272,6 +2282,9 @@ void HU_drawPing(INT32 x, INT32 y, UINT32 ping, boolean notext, INT32 flags)
 
 		yoffset -= 2;
 	}
+
+	if (ping == UINT32_MAX)
+		V_DrawSmallScaledPatch(x + 4 - nopingicon->width/2, y + 9 - nopingicon->height/2, 0, nopingicon);
 }
 
 //
@@ -2298,16 +2311,17 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 
 		if (!splitscreen) // don't draw it on splitscreen,
 		{
-			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-				HU_drawPing(x+ 253, y, playerpingtable[tab[i].num], false, 0);
+			if (tab[i].num != serverplayer)
+				HU_drawPing(x + 253, y, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], false, 0);
 			//else
 			//	V_DrawSmallString(x+ 246, y+4, V_YELLOWMAP, "SERVER");
 		}
 
-		V_DrawString(x + 20, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? V_60TRANS : 0)
-		             | V_ALLOWLOWERCASE, tab[i].name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 20, y,
+		                 ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+		                 | (greycheck ? V_60TRANS : 0)
+		                 | V_ALLOWLOWERCASE, tab[i].name);
 
 		// Draw emeralds
 		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
@@ -2455,10 +2469,11 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		supercheck = supercheckdef;
 
 		strlcpy(name, tab[i].name, 8);
-		V_DrawString(x + 10, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? 0 : V_TRANSLUCENT)
-		             | V_ALLOWLOWERCASE, name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 10, y,
+			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+			             | (greycheck ? 0 : V_TRANSLUCENT)
+			             | V_ALLOWLOWERCASE, name);
 
 		if (gametyperules & GTR_TEAMFLAGS)
 		{
@@ -2497,10 +2512,10 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
 		if (!splitscreen)
 		{
-			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-				HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0);
-		//else
-			//V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
+			if (tab[i].num != serverplayer)
+				HU_drawPing(x + 135, y+1, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], true, 0);
+			//else
+				//V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
 		}
 	}
 }
@@ -2583,10 +2598,11 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		supercheck = supercheckdef;
 
 		strlcpy(name, tab[i].name, 7);
-		V_DrawString(x + 20, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? V_TRANSLUCENT : 0)
-		             | V_ALLOWLOWERCASE, name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 20, y,
+			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+			             | (greycheck ? V_TRANSLUCENT : 0)
+			             | V_ALLOWLOWERCASE, name);
 
 		if (gametyperules & GTR_TEAMFLAGS)
 		{
@@ -2621,10 +2637,10 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 		if (!splitscreen)
 		{
-			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-				HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0);
-		//else
-		//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
+			if (tab[i].num != serverplayer)
+				HU_drawPing(x+ 113, y, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], false, 0);
+			//else
+			//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
 		}
 	}
 }
@@ -2652,15 +2668,16 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		supercheck = supercheckdef;
 
 		strlcpy(name, tab[i].name, 7);
-		if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-			HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0);
+		if (tab[i].num != serverplayer)
+			HU_drawPing(x+ 113, y, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], false, 0);
 		//else
 		//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
 
-		V_DrawString(x + 20, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? V_TRANSLUCENT : 0)
-		             | V_ALLOWLOWERCASE, name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 20, y,
+			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+			             | (greycheck ? V_TRANSLUCENT : 0)
+			             | V_ALLOWLOWERCASE, name);
 
 		if (G_GametypeUsesLives() && !(G_GametypeUsesCoopLives() && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE, va("%dx", players[tab[i].num].lives));
@@ -2760,16 +2777,17 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
 		strlcpy(name, tab[i].name, 7);
 		if (!splitscreen) // don't draw it on splitscreen,
 		{
-			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-				HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0);
-		//else
-		//	V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
+			if (tab[i].num != serverplayer)
+				HU_drawPing(x+ 135, y+1, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], true, 0);
+			//else
+			//	V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
 		}
 
-		V_DrawString(x + 10, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? 0 : V_TRANSLUCENT)
-		             | V_ALLOWLOWERCASE, name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 10, y,
+			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+			             | (greycheck ? 0 : V_TRANSLUCENT)
+			             | V_ALLOWLOWERCASE, name);
 
 		if (G_GametypeUsesLives()) //show lives
 			V_DrawRightAlignedThinString(x-1, y, V_ALLOWLOWERCASE, va("%d", players[tab[i].num].lives));
diff --git a/src/i_system.h b/src/i_system.h
index dd0b65f6df542d228019f5c3c91d59c5bcd6728c..12f0d751d14eec081175d3a01fdb8a1d03647bb4 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -46,7 +46,13 @@ UINT32 I_GetFreeMem(UINT32 *total);
 */
 tic_t I_GetTime(void);
 
-int I_GetTimeMicros(void);// provides microsecond counter for render stats
+/**	\brief	Returns precise time value for performance measurement.
+  */
+precise_t I_GetPreciseTime(void);
+
+/**	\brief	Returns the difference between precise times as microseconds.
+  */
+int I_PreciseToMicros(precise_t);
 
 /**	\brief	The I_Sleep function
 
diff --git a/src/i_tcp.c b/src/i_tcp.c
index 5180869a53b60772ded94d18495bd17906cef137..ab8a69a9fad3c300c92fa540fa60ae68345aff82 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -20,127 +20,121 @@
 #endif
 
 #ifndef NO_IPV6
-#define HAVE_IPV6
+	#define HAVE_IPV6
 #endif
 
 #ifdef _WIN32
-#define USE_WINSOCK
-#if defined (_WIN64) || defined (HAVE_IPV6)
-#define USE_WINSOCK2
-#else //_WIN64/HAVE_IPV6
-#define USE_WINSOCK1
-#endif
+	#define USE_WINSOCK
+	#if defined (_WIN64) || defined (HAVE_IPV6)
+		#define USE_WINSOCK2
+	#else //_WIN64/HAVE_IPV6
+		#define USE_WINSOCK1
+	#endif
 #endif //WIN32 OS
 
 #ifdef USE_WINSOCK2
-#include <ws2tcpip.h>
+	#include <ws2tcpip.h>
 #endif
 
 #include "doomdef.h"
 
 #if defined (NOMD5) && !defined (NONET)
-//#define NONET
+	//#define NONET
 #endif
 
 #ifdef NONET
-#undef HAVE_MINIUPNPC
+	#undef HAVE_MINIUPNPC
 #else
-#ifdef USE_WINSOCK1
-#include <winsock.h>
-#elif !defined (SCOUW2) && !defined (SCOUW7)
-#ifndef USE_WINSOCK
-#include <arpa/inet.h>
-#endif //normal BSD API
-
-#ifndef USE_WINSOCK
-#ifdef __APPLE_CC__
-#ifndef _BSD_SOCKLEN_T_
-#define _BSD_SOCKLEN_T_
-#endif //_BSD_SOCKLEN_T_
-#endif //__APPLE_CC__
-#include <sys/socket.h>
-#include <netinet/in.h>
-#endif //normal BSD API
-
-#ifndef USE_WINSOCK
-#include <netdb.h>
-#include <sys/ioctl.h>
-#endif //normal BSD API
-
-#include <errno.h>
-#include <time.h>
-
-#if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
-	#include <sys/time.h>
-#endif // UNIXCOMMON
-#endif // !NONET
-
-#ifdef USE_WINSOCK
-	// some undefined under win32
-	#undef errno
-	//#define errno WSAGetLastError() //Alam_GBC: this is the correct way, right?
-	#define errno h_errno // some very strange things happen when not using h_error?!?
-	#ifdef EWOULDBLOCK
-	#undef EWOULDBLOCK
-	#endif
-	#define EWOULDBLOCK WSAEWOULDBLOCK
-	#ifdef EMSGSIZE
-	#undef EMSGSIZE
-	#endif
-	#define EMSGSIZE WSAEMSGSIZE
-	#ifdef ECONNREFUSED
-	#undef ECONNREFUSED
-	#endif
-	#define ECONNREFUSED WSAECONNREFUSED
-	#ifdef ETIMEDOUT
-	#undef ETIMEDOUT
-	#endif
-	#define ETIMEDOUT WSAETIMEDOUT
-	#ifndef IOC_VENDOR
-	#define IOC_VENDOR 0x18000000
+	#ifdef USE_WINSOCK1
+		#include <winsock.h>
+	#else
+		#ifndef USE_WINSOCK
+			#include <arpa/inet.h>
+			#ifdef __APPLE_CC__
+				#ifndef _BSD_SOCKLEN_T_
+					#define _BSD_SOCKLEN_T_
+				#endif //_BSD_SOCKLEN_T_
+			#endif //__APPLE_CC__
+			#include <sys/socket.h>
+			#include <netinet/in.h>
+			#include <netdb.h>
+			#include <sys/ioctl.h>
+		#endif //normal BSD API
+
+		#include <errno.h>
+		#include <time.h>
+
+		#if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
+			#include <sys/time.h>
+		#endif // UNIXCOMMON
 	#endif
-	#ifndef _WSAIOW
-	#define _WSAIOW(x,y) (IOC_IN|(x)|(y))
-	#endif
-	#ifndef SIO_UDP_CONNRESET
-	#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12)
-	#endif
-	#ifndef AI_ADDRCONFIG
-	#define AI_ADDRCONFIG 0x00000400
-	#endif
-	#ifndef STATUS_INVALID_PARAMETER
-	#define STATUS_INVALID_PARAMETER 0xC000000D
-	#endif
-#endif
 
-#ifdef __DJGPP__
-#ifdef WATTCP // Alam_GBC: Wattcp may need this
-#include <tcp.h>
-#define strerror strerror_s
-#else // wattcp
-#include <lsck/lsck.h>
-#endif // libsocket
-#endif // djgpp
-
-typedef union
-{
-	struct sockaddr     any;
-	struct sockaddr_in  ip4;
-#ifdef HAVE_IPV6
-	struct sockaddr_in6 ip6;
-#endif
-} mysockaddr_t;
-
-#ifdef HAVE_MINIUPNPC
-#ifdef STATIC_MINIUPNPC
-#define STATICLIB
-#endif
-#include "miniupnpc/miniwget.h"
-#include "miniupnpc/miniupnpc.h"
-#include "miniupnpc/upnpcommands.h"
-#undef STATICLIB
-static UINT8 UPNP_support = TRUE;
-#endif
+	#ifdef USE_WINSOCK
+		// some undefined under win32
+		#undef errno
+		//#define errno WSAGetLastError() //Alam_GBC: this is the correct way, right?
+		#define errno h_errno // some very strange things happen when not using h_error?!?
+		#ifdef EWOULDBLOCK
+		#undef EWOULDBLOCK
+		#endif
+		#define EWOULDBLOCK WSAEWOULDBLOCK
+		#ifdef EMSGSIZE
+		#undef EMSGSIZE
+		#endif
+		#define EMSGSIZE WSAEMSGSIZE
+		#ifdef ECONNREFUSED
+		#undef ECONNREFUSED
+		#endif
+		#define ECONNREFUSED WSAECONNREFUSED
+		#ifdef ETIMEDOUT
+		#undef ETIMEDOUT
+		#endif
+		#define ETIMEDOUT WSAETIMEDOUT
+		#ifndef IOC_VENDOR
+		#define IOC_VENDOR 0x18000000
+		#endif
+		#ifndef _WSAIOW
+		#define _WSAIOW(x,y) (IOC_IN|(x)|(y))
+		#endif
+		#ifndef SIO_UDP_CONNRESET
+		#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12)
+		#endif
+		#ifndef AI_ADDRCONFIG
+		#define AI_ADDRCONFIG 0x00000400
+		#endif
+		#ifndef STATUS_INVALID_PARAMETER
+		#define STATUS_INVALID_PARAMETER 0xC000000D
+		#endif
+	#endif // USE_WINSOCK
+
+	#ifdef __DJGPP__
+		#ifdef WATTCP // Alam_GBC: Wattcp may need this
+			#include <tcp.h>
+			#define strerror strerror_s
+		#else // wattcp
+			#include <lsck/lsck.h>
+		#endif // libsocket
+	#endif // djgpp
+
+	typedef union
+	{
+		struct sockaddr     any;
+		struct sockaddr_in  ip4;
+	#ifdef HAVE_IPV6
+		struct sockaddr_in6 ip6;
+	#endif
+	} mysockaddr_t;
+
+	#ifdef HAVE_MINIUPNPC
+		#ifdef STATIC_MINIUPNPC
+			#define STATICLIB
+		#endif
+		#include "miniupnpc/miniwget.h"
+		#include "miniupnpc/miniupnpc.h"
+		#include "miniupnpc/upnpcommands.h"
+		#undef STATICLIB
+		static UINT8 UPNP_support = TRUE;
+	#endif // HAVE_MINIUPNC
 
 #endif // !NONET
 
@@ -177,32 +171,32 @@ static UINT8 UPNP_support = TRUE;
 #define DEFAULTPORT "5029"
 
 #if defined (USE_WINSOCK) && !defined (NONET)
-typedef SOCKET SOCKET_TYPE;
-#define ERRSOCKET (SOCKET_ERROR)
+	typedef SOCKET SOCKET_TYPE;
+	#define ERRSOCKET (SOCKET_ERROR)
 #else
-#if (defined (__unix__) && !defined (MSDOS)) || defined (__APPLE__) || defined (__HAIKU__)
-typedef int SOCKET_TYPE;
-#else
-typedef unsigned long SOCKET_TYPE;
-#endif
-#define ERRSOCKET (-1)
-#endif
-
-#if (defined (WATTCP) && !defined (__libsocket_socklen_t)) || defined (USE_WINSOCK1)
-typedef int socklen_t;
+	#if (defined (__unix__) && !defined (MSDOS)) || defined (__APPLE__) || defined (__HAIKU__)
+		typedef int SOCKET_TYPE;
+	#else
+		typedef unsigned long SOCKET_TYPE;
+	#endif
+	#define ERRSOCKET (-1)
 #endif
 
 #ifndef NONET
-static SOCKET_TYPE mysockets[MAXNETNODES+1] = {ERRSOCKET};
-static size_t mysocketses = 0;
-static int myfamily[MAXNETNODES+1] = {0};
-static SOCKET_TYPE nodesocket[MAXNETNODES+1] = {ERRSOCKET};
-static mysockaddr_t clientaddress[MAXNETNODES+1];
-static mysockaddr_t broadcastaddress[MAXNETNODES+1];
-static size_t broadcastaddresses = 0;
-static boolean nodeconnected[MAXNETNODES+1];
-static mysockaddr_t banned[MAXBANS];
-static UINT8 bannedmask[MAXBANS];
+	// define socklen_t in DOS/Windows if it is not already defined
+	#if (defined (WATTCP) && !defined (__libsocket_socklen_t)) || defined (USE_WINSOCK1)
+		typedef int socklen_t;
+	#endif
+	static SOCKET_TYPE mysockets[MAXNETNODES+1] = {ERRSOCKET};
+	static size_t mysocketses = 0;
+	static int myfamily[MAXNETNODES+1] = {0};
+	static SOCKET_TYPE nodesocket[MAXNETNODES+1] = {ERRSOCKET};
+	static mysockaddr_t clientaddress[MAXNETNODES+1];
+	static mysockaddr_t broadcastaddress[MAXNETNODES+1];
+	static size_t broadcastaddresses = 0;
+	static boolean nodeconnected[MAXNETNODES+1];
+	static mysockaddr_t banned[MAXBANS];
+	static UINT8 bannedmask[MAXBANS];
 #endif
 
 static size_t numbans = 0;
diff --git a/src/info.c b/src/info.c
index 29a79b1d6e28ab0e9b1352b0338d9dacc2a06d32..ee836a3728e4bb0ccea8352b790b7dfdb053136e 100644
--- a/src/info.c
+++ b/src/info.c
@@ -150,6 +150,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	"SIGN", // Level end sign
 	"SPIK", // Spike Ball
 	"SFLM", // Spin fire
+	"TFLM", // Spin fire (team)
 	"USPK", // Floor spike
 	"WSPK", // Wall spike
 	"WSPB", // Wall spike base
@@ -1894,6 +1895,13 @@ state_t states[NUMSTATES] =
 	{SPR_SFLM, FF_FULLBRIGHT|4, 2, {NULL}, 0, 0, S_SPINFIRE6}, // S_SPINFIRE5
 	{SPR_SFLM, FF_FULLBRIGHT|5, 2, {NULL}, 0, 0, S_SPINFIRE1}, // S_SPINFIRE6
 
+	{SPR_TFLM, FF_FULLBRIGHT,   2, {NULL}, 0, 0, S_TEAM_SPINFIRE2}, // S_TEAM_SPINFIRE1
+	{SPR_TFLM, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE3}, // S_TEAM_SPINFIRE2
+	{SPR_TFLM, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE4}, // S_TEAM_SPINFIRE3
+	{SPR_TFLM, FF_FULLBRIGHT|3, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE5}, // S_TEAM_SPINFIRE4
+	{SPR_TFLM, FF_FULLBRIGHT|4, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE6}, // S_TEAM_SPINFIRE5
+	{SPR_TFLM, FF_FULLBRIGHT|5, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE1}, // S_TEAM_SPINFIRE6
+
 	// Floor Spike
 	{SPR_USPK, 0,-1, {A_SpikeRetract}, 1, 0, S_SPIKE2}, // S_SPIKE1 -- Fully extended
 	{SPR_USPK, 1, 2, {A_Pain},         0, 0, S_SPIKE3}, // S_SPIKE2
@@ -3291,18 +3299,18 @@ state_t states[NUMSTATES] =
 	{SPR_WZAP, FF_TRANS10|FF_ANIMATE|FF_RANDOMANIM, 4, {NULL}, 3, 2, S_NULL},  // S_WATERZAP
 
 	// Spindash dust
-	{SPR_DUST,            0, 7, {NULL}, 0, 0, S_SPINDUST2}, // S_SPINDUST1
-	{SPR_DUST,            1, 6, {NULL}, 0, 0, S_SPINDUST3}, // S_SPINDUST2
-	{SPR_DUST, FF_TRANS30|2, 4, {NULL}, 0, 0, S_SPINDUST4}, // S_SPINDUST3
-	{SPR_DUST, FF_TRANS60|3, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST4
-	{SPR_BUBL,            0, 7, {NULL}, 0, 0, S_SPINDUST_BUBBLE2}, // S_SPINDUST_BUBBLE1
-	{SPR_BUBL,            0, 6, {NULL}, 0, 0, S_SPINDUST_BUBBLE3}, // S_SPINDUST_BUBBLE2
-	{SPR_BUBL, FF_TRANS30|0, 4, {NULL}, 0, 0, S_SPINDUST_BUBBLE4}, // S_SPINDUST_BUBBLE3
-	{SPR_BUBL, FF_TRANS60|0, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST_BUBBLE4
-	{SPR_FPRT,            0, 7, {NULL}, 0, 0, S_SPINDUST_FIRE2}, // S_SPINDUST_FIRE1
-	{SPR_FPRT,            0, 6, {NULL}, 0, 0, S_SPINDUST_FIRE3}, // S_SPINDUST_FIRE2
-	{SPR_FPRT, FF_TRANS30|0, 4, {NULL}, 0, 0, S_SPINDUST_FIRE4}, // S_SPINDUST_FIRE3
-	{SPR_FPRT, FF_TRANS60|0, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST_FIRE4
+	{SPR_DUST,                          0, 7, {NULL}, 0, 0, S_SPINDUST2}, // S_SPINDUST1
+	{SPR_DUST,                          1, 6, {NULL}, 0, 0, S_SPINDUST3}, // S_SPINDUST2
+	{SPR_DUST,               FF_TRANS30|2, 4, {NULL}, 0, 0, S_SPINDUST4}, // S_SPINDUST3
+	{SPR_DUST,               FF_TRANS60|3, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST4
+	{SPR_BUBL,                          0, 7, {NULL}, 0, 0, S_SPINDUST_BUBBLE2}, // S_SPINDUST_BUBBLE1
+	{SPR_BUBL,                          0, 6, {NULL}, 0, 0, S_SPINDUST_BUBBLE3}, // S_SPINDUST_BUBBLE2
+	{SPR_BUBL,               FF_TRANS30|0, 4, {NULL}, 0, 0, S_SPINDUST_BUBBLE4}, // S_SPINDUST_BUBBLE3
+	{SPR_BUBL,               FF_TRANS60|0, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST_BUBBLE4
+	{SPR_FPRT,            FF_FULLBRIGHT|0, 7, {NULL}, 0, 0, S_SPINDUST_FIRE2}, // S_SPINDUST_FIRE1
+	{SPR_FPRT,            FF_FULLBRIGHT|0, 6, {NULL}, 0, 0, S_SPINDUST_FIRE3}, // S_SPINDUST_FIRE2
+	{SPR_FPRT, FF_FULLBRIGHT|FF_TRANS30|0, 4, {NULL}, 0, 0, S_SPINDUST_FIRE4}, // S_SPINDUST_FIRE3
+	{SPR_FPRT, FF_FULLBRIGHT|FF_TRANS60|0, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST_FIRE4
 
 
 	{SPR_TFOG, FF_FULLBRIGHT|FF_TRANS50,    2, {NULL}, 0, 0, S_FOG2},  // S_FOG1
@@ -3924,9 +3932,7 @@ state_t states[NUMSTATES] =
 	{SPR_BRIB, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 31, 1, S_NULL}, // S_BLUEBRICKDEBRIS
 	{SPR_BRIY, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 31, 1, S_NULL}, // S_YELLOWBRICKDEBRIS
 
-#ifdef SEENAMES
 	{SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_NAMECHECK
-#endif
 };
 
 mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@@ -6458,8 +6464,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_fizzle,     // deathsound
 		10*FRACUNIT,    // speed
-		48*FRACUNIT,    // radius
-		160*FRACUNIT,   // height
+		24*FRACUNIT,    // radius
+		80*FRACUNIT,    // height
 		0,              // display offset
 		DMG_ELECTRIC,   // mass
 		1,              // damage
@@ -21653,7 +21659,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-#ifdef SEENAMES
 	{           // MT_NAMECHECK
 		-1,             // doomednum
 		S_NAMECHECK,    // spawnstate
@@ -21680,7 +21685,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY|MF_NOSECTOR, // flags
 		S_NULL          // raisestate
 	},
-#endif
 };
 
 skincolor_t skincolors[MAXSKINCOLORS] = {
diff --git a/src/info.h b/src/info.h
index 604922bebff2190e3c09296c5fff11772ba3b3e7..60e9702463fb50db9934d6aea9e6eab28a562983 100644
--- a/src/info.h
+++ b/src/info.h
@@ -684,6 +684,7 @@ typedef enum sprite
 	SPR_SIGN, // Level end sign
 	SPR_SPIK, // Spike Ball
 	SPR_SFLM, // Spin fire
+	SPR_TFLM, // Spin fire (team)
 	SPR_USPK, // Floor spike
 	SPR_WSPK, // Wall spike
 	SPR_WSPB, // Wall spike base
@@ -2324,6 +2325,13 @@ typedef enum state
 	S_SPINFIRE5,
 	S_SPINFIRE6,
 
+	S_TEAM_SPINFIRE1,
+	S_TEAM_SPINFIRE2,
+	S_TEAM_SPINFIRE3,
+	S_TEAM_SPINFIRE4,
+	S_TEAM_SPINFIRE5,
+	S_TEAM_SPINFIRE6,
+
 	// Spikes
 	S_SPIKE1,
 	S_SPIKE2,
@@ -4280,9 +4288,7 @@ typedef enum state
 	S_BLUEBRICKDEBRIS, // for CEZ3
 	S_YELLOWBRICKDEBRIS, // for CEZ3
 
-#ifdef SEENAMES
 	S_NAMECHECK,
-#endif
 
 	S_FIRSTFREESLOT,
 	S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
@@ -5082,9 +5088,7 @@ typedef enum mobj_type
 	MT_BLUEBRICKDEBRIS, // for CEZ3
 	MT_YELLOWBRICKDEBRIS, // for CEZ3
 
-#ifdef SEENAMES
 	MT_NAMECHECK,
-#endif
 
 	MT_FIRSTFREESLOT,
 	MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 4667fdbf4a7549ae226075ff8d5f09feeb9cc05f..a59ba546e9bd5e4d9da6206759d05788f456a0ff 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -155,6 +155,8 @@ static const struct {
 	{META_PIVOTLIST,    "spriteframepivot_t[]"},
 	{META_FRAMEPIVOT,   "spriteframepivot_t"},
 
+	{META_TAGLIST,      "taglist"},
+
 	{META_MOBJ,         "mobj_t"},
 	{META_MAPTHING,     "mapthing_t"},
 
@@ -163,6 +165,8 @@ static const struct {
 	{META_SKIN,         "skin_t"},
 	{META_POWERS,       "player_t.powers"},
 	{META_SOUNDSID,     "skin_t.soundsid"},
+	{META_SKINSPRITES,  "skin_t.sprites"},
+	{META_SKINSPRITESLIST,  "skin_t.sprites[]"},
 
 	{META_VERTEX,       "vertex_t"},
 	{META_LINE,         "line_t"},
@@ -184,6 +188,9 @@ static const struct {
 	{META_CVAR,         "consvar_t"},
 
 	{META_SECTORLINES,  "sector_t.lines"},
+#ifdef MUTABLE_TAGS
+	{META_SECTORTAGLIST, "sector_t.taglist"},
+#endif
 	{META_SIDENUM,      "line_t.sidenum"},
 	{META_LINEARGS,     "line_t.args"},
 	{META_LINESTRINGARGS, "line_t.stringargs"},
@@ -425,7 +432,7 @@ static int lib_pAproxDistance(lua_State *L)
 	fixed_t dx = luaL_checkfixed(L, 1);
 	fixed_t dy = luaL_checkfixed(L, 2);
 	//HUDSAFE
-	lua_pushfixed(L, P_AproxDistance(dx, dy));
+	lua_pushfixed(L, R_PointToDist2(0, 0, dx, dy));
 	return 1;
 }
 
@@ -1664,6 +1671,26 @@ static int lib_pSwitchShield(lua_State *L)
 	return 0;
 }
 
+static int lib_pPlayerCanEnterSpinGaps(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	lua_pushboolean(L, P_PlayerCanEnterSpinGaps(player));
+	return 1;
+}
+
+static int lib_pPlayerShouldUseSpinHeight(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	lua_pushboolean(L, P_PlayerShouldUseSpinHeight(player));
+	return 1;
+}
+
 // P_MAP
 ///////////
 
@@ -3865,6 +3892,8 @@ static luaL_Reg lib[] = {
 	{"P_SpawnSpinMobj",lib_pSpawnSpinMobj},
 	{"P_Telekinesis",lib_pTelekinesis},
 	{"P_SwitchShield",lib_pSwitchShield},
+	{"P_PlayerCanEnterSpinGaps",lib_pPlayerCanEnterSpinGaps},
+	{"P_PlayerShouldUseSpinHeight",lib_pPlayerShouldUseSpinHeight},
 
 	// p_map
 	{"P_CheckPosition",lib_pCheckPosition},
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 84bfeaee2c07dbbae2257b083635eb9bb39e94fb..5344fee7617aacf789e577eed98de26b3296818a 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -285,8 +285,8 @@ static void Lua_OnChange(void)
 
 	/// \todo Network this! XD_LUAVAR
 
-	lua_settop(gL, 0); // Just in case...
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
+	lua_insert(gL, 1); // Because LUA_Call wants it at index 1.
 
 	// From CV_OnChange registry field, get the function for this cvar by name.
 	lua_getfield(gL, LUA_REGISTRYINDEX, "CV_OnChange");
@@ -301,6 +301,7 @@ static void Lua_OnChange(void)
 
 	LUA_Call(gL, 1, 0, 1); // call function(cvar)
 	lua_pop(gL, 1); // pop CV_OnChange table
+	lua_remove(gL, 1); // remove LUA_GetErrorMessage
 }
 
 static int lib_cvRegisterVar(lua_State *L)
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 796f3a9d287dcf0e3997d6963949b33111dceb0d..0d631aa4e275259c5bda256e2dcc0e5824283ea4 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -61,6 +61,8 @@ enum hook {
 	hook_GameQuit,
 	hook_PlayerCmd,
 	hook_MusicChange,
+	hook_PlayerHeight,
+	hook_PlayerCanEnterSpinGaps,
 
 	hook_MAX // last hook
 };
@@ -112,11 +114,11 @@ void LUAh_PlayerQuit(player_t *plr, kickreason_t reason); // Hook for player qui
 void LUAh_IntermissionThinker(void); // Hook for Y_Ticker
 boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); // Hook for team switching in... uh....
 UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); // Hook for spy mode
-#ifdef SEENAMES
 boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend); // Hook for MT_NAMECHECK
-#endif
 #define LUAh_PlayerThink(player) LUAh_PlayerHook(player, hook_PlayerThink) // Hook for P_PlayerThink
 boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname); // Hook for whether a jingle of the given music should continue playing
-void LUAh_GameQuit(void); // Hook for game quitting
+void LUAh_GameQuit(boolean quitting); // Hook for game quitting
 boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd); // Hook for building player's ticcmd struct (Ported from SRB2Kart)
-boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping, UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms); // Hook for music changes
\ No newline at end of file
+boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping, UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms); // Hook for music changes
+fixed_t LUAh_PlayerHeight(player_t *player);
+UINT8 LUAh_PlayerCanEnterSpinGaps(player_t *player);
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 117aa48a303e7ba7945f2b970cb20d0292cced2a..1665e36b055ed682bc5bfc594bf7addcfeedc262 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -25,7 +25,7 @@
 
 #include "m_perfstats.h"
 #include "d_netcmd.h" // for cv_perfstats
-#include "i_system.h" // I_GetTimeMicros
+#include "i_system.h" // I_GetPreciseTime
 
 static UINT8 hooksAvailable[(hook_MAX/8)+1];
 
@@ -77,6 +77,8 @@ const char *const hookNames[hook_MAX+1] = {
 	"GameQuit",
 	"PlayerCmd",
 	"MusicChange",
+	"PlayerHeight",
+	"PlayerCanEnterSpinGaps",
 	NULL
 };
 
@@ -221,6 +223,8 @@ static int lib_addHook(lua_State *L)
 	case hook_ShieldSpawn:
 	case hook_ShieldSpecial:
 	case hook_PlayerThink:
+	case hook_PlayerHeight:
+	case hook_PlayerCanEnterSpinGaps:
 		lastp = &playerhooks;
 		break;
 	case hook_LinedefExecute:
@@ -469,7 +473,7 @@ void LUAh_ThinkFrame(void)
 	hook_p hookp;
 	// variables used by perf stats
 	int hook_index = 0;
-	int time_taken = 0;
+	precise_t time_taken = 0;
 	if (!gL || !(hooksAvailable[hook_ThinkFrame/8] & (1<<(hook_ThinkFrame%8))))
 		return;
 
@@ -481,7 +485,7 @@ void LUAh_ThinkFrame(void)
 			continue;
 
 		if (cv_perfstats.value == 3)
-			time_taken = I_GetTimeMicros();
+			time_taken = I_GetPreciseTime();
 		PushHook(gL, hookp);
 		if (lua_pcall(gL, 0, 0, 1)) {
 			if (!hookp->error || cv_debug & DBG_LUA)
@@ -492,7 +496,7 @@ void LUAh_ThinkFrame(void)
 		if (cv_perfstats.value == 3)
 		{
 			lua_Debug ar;
-			time_taken = I_GetTimeMicros() - time_taken;
+			time_taken = I_GetPreciseTime() - time_taken;
 			// we need the function, let's just retrieve it again
 			PushHook(gL, hookp);
 			lua_getinfo(gL, ">S", &ar);
@@ -1754,7 +1758,6 @@ UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean
 }
 
 // Hook for MT_NAMECHECK
-#ifdef SEENAMES
 boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend)
 {
 	hook_p hookp;
@@ -1798,7 +1801,6 @@ boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend)
 
 	return hasSeenPlayer;
 }
-#endif // SEENAMES
 
 boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname)
 {
@@ -1846,7 +1848,7 @@ boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname)
 }
 
 // Hook for game quitting
-void LUAh_GameQuit(void)
+void LUAh_GameQuit(boolean quitting)
 {
 	hook_p hookp;
 	if (!gL || !(hooksAvailable[hook_GameQuit/8] & (1<<(hook_GameQuit%8))))
@@ -1860,7 +1862,8 @@ void LUAh_GameQuit(void)
 			continue;
 
 		PushHook(gL, hookp);
-		if (lua_pcall(gL, 0, 0, 1)) {
+		lua_pushboolean(gL, quitting);
+		if (lua_pcall(gL, 1, 0, 1)) {
 			if (!hookp->error || cv_debug & DBG_LUA)
 				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
@@ -1971,4 +1974,90 @@ boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boo
 	lua_settop(gL, 0);
 	newname[6] = 0;
 	return hooked;
-}
\ No newline at end of file
+}
+
+// Hook for determining player height
+fixed_t LUAh_PlayerHeight(player_t *player)
+{
+	hook_p hookp;
+	fixed_t newheight = -1;
+	if (!gL || !(hooksAvailable[hook_PlayerHeight/8] & (1<<(hook_PlayerHeight%8))))
+		return newheight;
+
+	lua_settop(gL, 0);
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
+	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_PlayerHeight)
+			continue;
+
+		ps_lua_mobjhooks++;
+		if (lua_gettop(gL) == 1)
+			LUA_PushUserdata(gL, player, META_PLAYER);
+		PushHook(gL, hookp);
+		lua_pushvalue(gL, -2);
+		if (lua_pcall(gL, 1, 1, 1)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{
+			fixed_t returnedheight = lua_tonumber(gL, -1);
+			// 0 height has... strange results, but it's not problematic like negative heights are.
+			// when an object's height is set to a negative number directly with lua, it's forced to 0 instead.
+			// here, I think it's better to ignore negatives so that they don't replace any results of previous hooks!
+			if (returnedheight >= 0)
+				newheight = returnedheight;
+		}
+		lua_pop(gL, 1);
+	}
+
+	lua_settop(gL, 0);
+	return newheight;
+}
+
+// Hook for determining whether players are allowed passage through spin gaps
+UINT8 LUAh_PlayerCanEnterSpinGaps(player_t *player)
+{
+	hook_p hookp;
+	UINT8 canEnter = 0; // 0 = default, 1 = force yes, 2 = force no.
+	if (!gL || !(hooksAvailable[hook_PlayerCanEnterSpinGaps/8] & (1<<(hook_PlayerCanEnterSpinGaps%8))))
+		return 0;
+
+	lua_settop(gL, 0);
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
+	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_PlayerCanEnterSpinGaps)
+			continue;
+
+		ps_lua_mobjhooks++;
+		if (lua_gettop(gL) == 1)
+			LUA_PushUserdata(gL, player, META_PLAYER);
+		PushHook(gL, hookp);
+		lua_pushvalue(gL, -2);
+		if (lua_pcall(gL, 1, 1, 1)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, leave canEnter = 0.
+			if (lua_toboolean(gL, -1))
+				canEnter = 1; // Force yes
+			else
+				canEnter = 2; // Force no
+		}
+		lua_pop(gL, 1);
+	}
+
+	lua_settop(gL, 0);
+	return canEnter;
+}
diff --git a/src/lua_hud.h b/src/lua_hud.h
index 4a7c596c8a95e7d343fd9da73b8aa50bcaa344b0..1e9dca00b921abe7e3c0baa2413fc015668e5fa8 100644
--- a/src/lua_hud.h
+++ b/src/lua_hud.h
@@ -13,6 +13,7 @@
 enum hud {
 	hud_stagetitle = 0,
 	hud_textspectator,
+	hud_crosshair,
 	// Singleplayer / Co-op
 	hud_score,
 	hud_time,
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 684e47c381d63dbcd7dee7c376f82ba0777a33a3..8d451e99c5100bc2370974ea977ea055fed04595 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -39,6 +39,7 @@ static UINT8 hudAvailable; // hud hooks field
 static const char *const hud_disable_options[] = {
 	"stagetitle",
 	"textspectator",
+	"crosshair",
 
 	"score",
 	"time",
@@ -896,8 +897,10 @@ static int libd_getColormap(lua_State *L)
 	else if (lua_type(L, 1) == LUA_TNUMBER) // skin number
 	{
 		skinnum = (INT32)luaL_checkinteger(L, 1);
-		if (skinnum < TC_BLINK || skinnum >= MAXSKINS)
-			return luaL_error(L, "skin number %d is out of range (%d - %d)", skinnum, TC_BLINK, MAXSKINS-1);
+		if (skinnum >= MAXSKINS)
+			return luaL_error(L, "skin number %d is out of range (>%d)", skinnum, MAXSKINS-1);
+		else if (skinnum < 0 && skinnum > TC_DEFAULT)
+			return luaL_error(L, "translation colormap index is out of range");
 	}
 	else // skin name
 	{
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 4c6ef35287500d973d85af6d76412a0f4bc12202..6e86f47b7272701efb42497a8fde70692681911a 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -1635,8 +1635,10 @@ static int skincolor_get(lua_State *L)
 		lua_pushinteger(L, info->chatcolor);
 	else if (fastcmp(field,"accessible"))
 		lua_pushboolean(L, info->accessible);
-	else
+	else {
 		CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "skincolor_t", field);
+		return 0;
+	}
 	return 1;
 }
 
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 062a3fe5009fb2beda2c2a5545243ab350a2cdf5..fbe8d48780f35009dfb0ca9b8f7209664297017c 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -12,6 +12,8 @@
 
 extern lua_State *gL;
 
+#define MUTABLE_TAGS
+
 #define LREG_VALID "VALID_USERDATA"
 #define LREG_EXTVARS "LUA_VARS"
 #define LREG_STATEACTION "STATE_ACTION"
@@ -27,6 +29,8 @@ extern lua_State *gL;
 #define META_PIVOTLIST "SPRITEFRAMEPIVOT_T[]"
 #define META_FRAMEPIVOT "SPRITEFRAMEPIVOT_T*"
 
+#define META_TAGLIST "TAGLIST"
+
 #define META_MOBJ "MOBJ_T*"
 #define META_MAPTHING "MAPTHING_T*"
 
@@ -35,6 +39,8 @@ extern lua_State *gL;
 #define META_SKIN "SKIN_T*"
 #define META_POWERS "PLAYER_T*POWERS"
 #define META_SOUNDSID "SKIN_T*SOUNDSID"
+#define META_SKINSPRITES "SKIN_T*SPRITES"
+#define META_SKINSPRITESLIST "SKIN_T*SPRITES[]"
 
 #define META_VERTEX "VERTEX_T*"
 #define META_LINE "LINE_T*"
@@ -56,6 +62,9 @@ extern lua_State *gL;
 #define META_CVAR "CONSVAR_T*"
 
 #define META_SECTORLINES "SECTOR_T*LINES"
+#ifdef MUTABLE_TAGS
+#define META_SECTORTAGLIST "sector_t.taglist"
+#endif
 #define META_SIDENUM "LINE_T*SIDENUM"
 #define META_LINEARGS "LINE_T*ARGS"
 #define META_LINESTRINGARGS "LINE_T*STRINGARGS"
@@ -93,6 +102,7 @@ int LUA_PlayerLib(lua_State *L);
 int LUA_SkinLib(lua_State *L);
 int LUA_ThinkerLib(lua_State *L);
 int LUA_MapLib(lua_State *L);
+int LUA_TagLib(lua_State *L);
 int LUA_PolyObjLib(lua_State *L);
 int LUA_BlockmapLib(lua_State *L);
 int LUA_HudLib(lua_State *L);
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 95cc8c1019e88de52213a0fac5ebff9d42ffa8d0..0161417960fc30556b1fdf11e112c4a7c6f5215f 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -37,6 +37,7 @@ enum sector_e {
 	sector_lightlevel,
 	sector_special,
 	sector_tag,
+	sector_taglist,
 	sector_thinglist,
 	sector_heightsec,
 	sector_camsec,
@@ -55,6 +56,7 @@ static const char *const sector_opt[] = {
 	"lightlevel",
 	"special",
 	"tag",
+	"taglist",
 	"thinglist",
 	"heightsec",
 	"camsec",
@@ -89,6 +91,7 @@ enum line_e {
 	line_flags,
 	line_special,
 	line_tag,
+	line_taglist,
 	line_args,
 	line_stringargs,
 	line_sidenum,
@@ -113,6 +116,7 @@ static const char *const line_opt[] = {
 	"flags",
 	"special",
 	"tag",
+	"taglist",
 	"args",
 	"stringargs",
 	"sidenum",
@@ -579,7 +583,10 @@ static int sector_get(lua_State *L)
 		lua_pushinteger(L, sector->special);
 		return 1;
 	case sector_tag:
-		lua_pushinteger(L, Tag_FGet(&sector->tags));
+		lua_pushinteger(L, (UINT16)Tag_FGet(&sector->tags));
+		return 1;
+	case sector_taglist:
+		LUA_PushUserdata(L, &sector->tags, META_SECTORTAGLIST);
 		return 1;
 	case sector_thinglist: // thinglist
 		lua_pushcfunction(L, lib_iterateSectorThinglist);
@@ -682,6 +689,8 @@ static int sector_set(lua_State *L)
 	case sector_tag:
 		Tag_SectorFSet((UINT32)(sector - sectors), (INT16)luaL_checkinteger(L, 3));
 		break;
+	case sector_taglist:
+		return LUA_ErrSetDirectly(L, "sector_t", "taglist");
 	}
 	return 0;
 }
@@ -819,8 +828,22 @@ static int line_get(lua_State *L)
 		lua_pushinteger(L, line->special);
 		return 1;
 	case line_tag:
+		// HELLO
+		// THIS IS LJ SONIC
+		// HOW IS YOUR DAY?
+		// BY THE WAY WHEN 2.3 OR 3.0 OR 4.0 OR SRB3 OR SRB4 OR WHATEVER IS OUT
+		// YOU SHOULD REMEMBER TO CHANGE THIS SO IT ALWAYS RETURNS A UNSIGNED VALUE
+		// HAVE A NICE DAY
+		//
+		//
+		//
+		//
+		// you are ugly
 		lua_pushinteger(L, Tag_FGet(&line->tags));
 		return 1;
+	case line_taglist:
+		LUA_PushUserdata(L, &line->tags, META_TAGLIST);
+		return 1;
 	case line_args:
 		LUA_PushUserdata(L, line->args, META_LINEARGS);
 		return 1;
@@ -1385,25 +1408,15 @@ static int lib_iterateSectors(lua_State *L)
 
 static int lib_getSector(lua_State *L)
 {
-	int field;
 	INLEVEL
-	lua_settop(L, 2);
-	lua_remove(L, 1); // dummy userdata table is unused.
-	if (lua_isnumber(L, 1))
+	if (lua_isnumber(L, 2))
 	{
-		size_t i = lua_tointeger(L, 1);
+		size_t i = lua_tointeger(L, 2);
 		if (i >= numsectors)
 			return 0;
 		LUA_PushUserdata(L, &sectors[i], META_SECTOR);
 		return 1;
 	}
-	field = luaL_checkoption(L, 1, NULL, array_opt);
-	switch(field)
-	{
-	case 0: // iterate
-		lua_pushcfunction(L, lib_iterateSectors);
-		return 1;
-	}
 	return 0;
 }
 
@@ -1489,25 +1502,15 @@ static int lib_iterateLines(lua_State *L)
 
 static int lib_getLine(lua_State *L)
 {
-	int field;
 	INLEVEL
-	lua_settop(L, 2);
-	lua_remove(L, 1); // dummy userdata table is unused.
-	if (lua_isnumber(L, 1))
+	if (lua_isnumber(L, 2))
 	{
-		size_t i = lua_tointeger(L, 1);
+		size_t i = lua_tointeger(L, 2);
 		if (i >= numlines)
 			return 0;
 		LUA_PushUserdata(L, &lines[i], META_LINE);
 		return 1;
 	}
-	field = luaL_checkoption(L, 1, NULL, array_opt);
-	switch(field)
-	{
-	case 0: // iterate
-		lua_pushcfunction(L, lib_iterateLines);
-		return 1;
-	}
 	return 0;
 }
 
@@ -2189,6 +2192,8 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushinteger(L, header->levelflags);
 	else if (fastcmp(field,"menuflags"))
 		lua_pushinteger(L, header->menuflags);
+	else if (fastcmp(field,"selectheading"))
+		lua_pushstring(L, header->selectheading);
 	else if (fastcmp(field,"startrings"))
 		lua_pushinteger(L, header->startrings);
 	else if (fastcmp(field, "sstimer"))
@@ -2358,15 +2363,13 @@ int LUA_MapLib(lua_State *L)
 		//lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
-	lua_newuserdata(L, 0);
-		lua_createtable(L, 0, 2);
-			lua_pushcfunction(L, lib_getSector);
-			lua_setfield(L, -2, "__index");
-
-			lua_pushcfunction(L, lib_numsectors);
-			lua_setfield(L, -2, "__len");
-		lua_setmetatable(L, -2);
-	lua_setglobal(L, "sectors");
+	LUA_PushTaggableObjectArray(L, "sectors",
+			lib_iterateSectors,
+			lib_getSector,
+			lib_numsectors,
+			tags_sectors,
+			&numsectors, &sectors,
+			sizeof (sector_t), META_SECTOR);
 
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
@@ -2378,15 +2381,13 @@ int LUA_MapLib(lua_State *L)
 		lua_setmetatable(L, -2);
 	lua_setglobal(L, "subsectors");
 
-	lua_newuserdata(L, 0);
-		lua_createtable(L, 0, 2);
-			lua_pushcfunction(L, lib_getLine);
-			lua_setfield(L, -2, "__index");
-
-			lua_pushcfunction(L, lib_numlines);
-			lua_setfield(L, -2, "__len");
-		lua_setmetatable(L, -2);
-	lua_setglobal(L, "lines");
+	LUA_PushTaggableObjectArray(L, "lines",
+			lib_iterateLines,
+			lib_getLine,
+			lib_numlines,
+			tags_lines,
+			&numlines, &lines,
+			sizeof (line_t), META_LINE);
 
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
diff --git a/src/lua_mathlib.c b/src/lua_mathlib.c
index 7cbe7a6cc9bbc579d47ff6f6a03ab3f2ad6b5ef4..b6046ab53b2d37948bc6fdb40c540a49d148be57 100644
--- a/src/lua_mathlib.c
+++ b/src/lua_mathlib.c
@@ -15,6 +15,7 @@
 #include "tables.h"
 #include "p_local.h"
 #include "doomstat.h" // for ALL7EMERALDS
+#include "r_main.h" // for R_PointToDist2
 
 #include "lua_script.h"
 #include "lua_libs.h"
@@ -129,7 +130,7 @@ static int lib_fixedsqrt(lua_State *L)
 
 static int lib_fixedhypot(lua_State *L)
 {
-	lua_pushfixed(L, FixedHypot(luaL_checkfixed(L, 1), luaL_checkfixed(L, 2)));
+	lua_pushfixed(L, R_PointToDist2(0, 0, luaL_checkfixed(L, 1), luaL_checkfixed(L, 2)));
 	return 1;
 }
 
@@ -192,18 +193,30 @@ static luaL_Reg lib[] = {
 	{"cos", lib_finecosine},
 	{"tan", lib_finetangent},
 	{"FixedAngle", lib_fixedangle},
+	{"fixangle"  , lib_fixedangle},
 	{"AngleFixed", lib_anglefixed},
+	{"anglefix"  , lib_anglefixed},
 	{"InvAngle", lib_invangle},
 	{"FixedMul", lib_fixedmul},
+	{"fixmul"  , lib_fixedmul},
 	{"FixedInt", lib_fixedint},
+	{"fixint"  , lib_fixedint},
 	{"FixedDiv", lib_fixeddiv},
+	{"fixdiv"  , lib_fixeddiv},
 	{"FixedRem", lib_fixedrem},
+	{"fixrem"  , lib_fixedrem},
 	{"FixedSqrt", lib_fixedsqrt},
+	{"fixsqrt"  , lib_fixedsqrt},
 	{"FixedHypot", lib_fixedhypot},
+	{"fixhypot"  , lib_fixedhypot},
 	{"FixedFloor", lib_fixedfloor},
+	{"fixfloor"  , lib_fixedfloor},
 	{"FixedTrunc", lib_fixedtrunc},
+	{"fixtrunc"  , lib_fixedtrunc},
 	{"FixedCeil", lib_fixedceil},
+	{"fixceil"  , lib_fixedceil},
 	{"FixedRound", lib_fixedround},
+	{"fixround"  , lib_fixedround},
 	{"GetSecSpecial", lib_getsecspecial},
 	{"All7Emeralds", lib_all7emeralds},
 	{"ColorOpposite", lib_coloropposite},
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 7aae18c90a31f43dd43fefc6e33c6085003d20af..65adceb154cb66424c73b3415253891307a9f706 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -22,8 +22,6 @@
 #include "lua_hud.h" // hud_running errors
 #include "lua_hook.h" // hook_cmd_running errors
 
-static const char *const array_opt[] ={"iterate",NULL};
-
 enum mobj_e {
 	mobj_valid = 0,
 	mobj_x,
@@ -904,6 +902,11 @@ static int mapthing_get(lua_State *L)
 		number = mt->extrainfo;
 	else if(fastcmp(field,"tag"))
 		number = Tag_FGet(&mt->tags);
+	else if(fastcmp(field,"taglist"))
+	{
+		LUA_PushUserdata(L, &mt->tags, META_TAGLIST);
+		return 1;
+	}
 	else if(fastcmp(field,"args"))
 	{
 		LUA_PushUserdata(L, mt->args, META_THINGARGS);
@@ -966,6 +969,8 @@ static int mapthing_set(lua_State *L)
 	}
 	else if (fastcmp(field,"tag"))
 		Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3));
+	else if (fastcmp(field,"taglist"))
+		return LUA_ErrSetDirectly(L, "mapthing_t", "taglist");
 	else if(fastcmp(field,"mobj"))
 		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 	else
@@ -1003,25 +1008,15 @@ static int lib_iterateMapthings(lua_State *L)
 
 static int lib_getMapthing(lua_State *L)
 {
-	int field;
 	INLEVEL
-	lua_settop(L, 2);
-	lua_remove(L, 1); // dummy userdata table is unused.
-	if (lua_isnumber(L, 1))
+	if (lua_isnumber(L, 2))
 	{
-		size_t i = lua_tointeger(L, 1);
+		size_t i = lua_tointeger(L, 2);
 		if (i >= nummapthings)
 			return 0;
 		LUA_PushUserdata(L, &mapthings[i], META_MAPTHING);
 		return 1;
 	}
-	field = luaL_checkoption(L, 1, NULL, array_opt);
-	switch(field)
-	{
-	case 0: // iterate
-		lua_pushcfunction(L, lib_iterateMapthings);
-		return 1;
-	}
 	return 0;
 }
 
@@ -1068,14 +1063,13 @@ int LUA_MobjLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
-	lua_newuserdata(L, 0);
-		lua_createtable(L, 0, 2);
-			lua_pushcfunction(L, lib_getMapthing);
-			lua_setfield(L, -2, "__index");
+	LUA_PushTaggableObjectArray(L, "mapthings",
+			lib_iterateMapthings,
+			lib_getMapthing,
+			lib_nummapthings,
+			tags_mapthings,
+			&nummapthings, &mapthings,
+			sizeof (mapthing_t), META_MAPTHING);
 
-			lua_pushcfunction(L, lib_nummapthings);
-			lua_setfield(L, -2, "__len");
-		lua_setmetatable(L, -2);
-	lua_setglobal(L, "mapthings");
 	return 0;
 }
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 412dc3eff6b1f0927e2d0483878a38110acf3b05..0eb54808fb21a90de8ee5b6aaa3d046eff8cfdd1 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -158,6 +158,10 @@ static int player_get(lua_State *L)
 		lua_pushinteger(L, plr->flashpal);
 	else if (fastcmp(field,"skincolor"))
 		lua_pushinteger(L, plr->skincolor);
+	else if (fastcmp(field,"skin"))
+		lua_pushinteger(L, plr->skin);
+	else if (fastcmp(field,"availabilities"))
+		lua_pushinteger(L, plr->availabilities);
 	else if (fastcmp(field,"score"))
 		lua_pushinteger(L, plr->score);
 	else if (fastcmp(field,"dashspeed"))
@@ -469,6 +473,10 @@ static int player_set(lua_State *L)
 			return luaL_error(L, "player.skincolor %d out of range (0 - %d).", newcolor, numskincolors-1);
 		plr->skincolor = newcolor;
 	}
+	else if (fastcmp(field,"skin"))
+		return NOSET;
+	else if (fastcmp(field,"availabilities"))
+		return NOSET;
 	else if (fastcmp(field,"score"))
 		plr->score = (UINT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"dashspeed"))
diff --git a/src/lua_polyobjlib.c b/src/lua_polyobjlib.c
index 365d970563dd504cc896abaac756fffd6cd2458f..2a5bcfbf19a54458e63960d5b06c1466f96487de 100644
--- a/src/lua_polyobjlib.c
+++ b/src/lua_polyobjlib.c
@@ -417,7 +417,7 @@ static int lib_getPolyObject(lua_State *L)
 	{
 		i = luaL_checkinteger(L, 2);
 		if (i < 0 || i >= numPolyObjects)
-			return luaL_error(L, "PolyObjects[] index %d out of range (0 - %d)", i, numPolyObjects-1);
+			return luaL_error(L, "polyobjects[] index %d out of range (0 - %d)", i, numPolyObjects-1);
 		LUA_PushUserdata(L, &PolyObjects[i], META_POLYOBJ);
 		return 1;
 	}
@@ -481,6 +481,6 @@ int LUA_PolyObjLib(lua_State *L)
 			lua_pushcfunction(L, lib_numPolyObjects);
 			lua_setfield(L, -2, "__len");
 		lua_setmetatable(L, -2);
-	lua_setglobal(L, "PolyObjects");
+	lua_setglobal(L, "polyobjects");
 	return 0;
 }
diff --git a/src/lua_script.c b/src/lua_script.c
index eb4737f7655d018ced5cec7bd83625768b9b8caf..7fd5a98e6f71620ad4c89eff58e71e222773b622 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -53,6 +53,7 @@ static lua_CFunction liblist[] = {
 	LUA_SkinLib, // skin_t, skins[]
 	LUA_ThinkerLib, // thinker_t
 	LUA_MapLib, // line_t, side_t, sector_t, subsector_t
+	LUA_TagLib, // tags
 	LUA_PolyObjLib, // polyobj_t
 	LUA_BlockmapLib, // blockmap stuff
 	LUA_HudLib, // HUD stuff
@@ -332,7 +333,7 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 		return 1;
 	// local player variables, by popular request
 	} else if (fastcmp(word,"consoleplayer")) { // player controlling console (aka local player 1)
-		if (consoleplayer < 0 || !playeringame[consoleplayer])
+		if (!addedtogame || consoleplayer < 0 || !playeringame[consoleplayer])
 			return 0;
 		LUA_PushUserdata(L, &players[consoleplayer], META_PLAYER);
 		return 1;
@@ -739,25 +740,37 @@ void LUA_PushLightUserdata (lua_State *L, void *data, const char *meta)
 // Pushes it to the stack and stores it in the registry.
 void LUA_PushUserdata(lua_State *L, void *data, const char *meta)
 {
+	if (LUA_RawPushUserdata(L, data) == LPUSHED_NEW)
+	{
+		luaL_getmetatable(L, meta);
+		lua_setmetatable(L, -2);
+	}
+}
+
+// Same as LUA_PushUserdata but don't set a metatable yet.
+lpushed_t LUA_RawPushUserdata(lua_State *L, void *data)
+{
+	lpushed_t status = LPUSHED_NIL;
+
 	void **userdata;
 
 	if (!data) { // push a NULL
 		lua_pushnil(L);
-		return;
+		return status;
 	}
 
 	lua_getfield(L, LUA_REGISTRYINDEX, LREG_VALID);
 	I_Assert(lua_istable(L, -1));
+
 	lua_pushlightuserdata(L, data);
 	lua_rawget(L, -2);
+
 	if (lua_isnil(L, -1)) { // no userdata? deary me, we'll have to make one.
 		lua_pop(L, 1); // pop the nil
 
 		// create the userdata
 		userdata = lua_newuserdata(L, sizeof(void *));
 		*userdata = data;
-		luaL_getmetatable(L, meta);
-		lua_setmetatable(L, -2);
 
 		// Set it in the registry so we can find it again
 		lua_pushlightuserdata(L, data); // k (store the userdata via the data's pointer)
@@ -765,8 +778,15 @@ void LUA_PushUserdata(lua_State *L, void *data, const char *meta)
 		lua_rawset(L, -4);
 
 		// stack is left with the userdata on top, as if getting it had originally succeeded.
+
+		status = LPUSHED_NEW;
 	}
+	else
+		status = LPUSHED_EXISTING;
+
 	lua_remove(L, -2); // remove LREG_VALID
+
+	return status;
 }
 
 // When userdata is freed, use this function to remove it from Lua.
@@ -826,6 +846,7 @@ void LUA_InvalidateLevel(void)
 	{
 		LUA_InvalidateUserdata(&sectors[i]);
 		LUA_InvalidateUserdata(&sectors[i].lines);
+		LUA_InvalidateUserdata(&sectors[i].tags);
 		if (sectors[i].ffloors)
 		{
 			for (rover = sectors[i].ffloors; rover; rover = rover->next)
@@ -835,6 +856,7 @@ void LUA_InvalidateLevel(void)
 	for (i = 0; i < numlines; i++)
 	{
 		LUA_InvalidateUserdata(&lines[i]);
+		LUA_InvalidateUserdata(&lines[i].tags);
 		LUA_InvalidateUserdata(lines[i].sidenum);
 	}
 	for (i = 0; i < numsides; i++)
@@ -866,7 +888,10 @@ void LUA_InvalidateMapthings(void)
 		return;
 
 	for (i = 0; i < nummapthings; i++)
+	{
 		LUA_InvalidateUserdata(&mapthings[i]);
+		LUA_InvalidateUserdata(&mapthings[i].tags);
+	}
 }
 
 void LUA_InvalidatePlayer(player_t *player)
@@ -1681,3 +1706,36 @@ int Lua_optoption(lua_State *L, int narg,
 			return i;
 	return -1;
 }
+
+void LUA_PushTaggableObjectArray
+(		lua_State *L,
+		const char *field,
+		lua_CFunction iterator,
+		lua_CFunction indexer,
+		lua_CFunction counter,
+		taggroup_t *garray[],
+		size_t * max_elements,
+		void * element_array,
+		size_t sizeof_element,
+		const char *meta)
+{
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_createtable(L, 0, 2);
+				lua_pushcfunction(L, iterator);
+				lua_setfield(L, -2, "iterate");
+
+				LUA_InsertTaggroupIterator(L, garray,
+						max_elements, element_array, sizeof_element, meta);
+
+				lua_createtable(L, 0, 1);
+					lua_pushcfunction(L, indexer);
+					lua_setfield(L, -2, "__index");
+				lua_setmetatable(L, -2);
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, counter);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, field);
+}
diff --git a/src/lua_script.h b/src/lua_script.h
index 79ba0bb38a5e1aeb6af476fb4c5b01b39aeb63f2..77fbb7c1d125f682db270b7d4f051d6673290d43 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -10,10 +10,14 @@
 /// \file  lua_script.h
 /// \brief Lua scripting basics
 
+#ifndef LUA_SCRIPT_H
+#define LUA_SCRIPT_H
+
 #include "m_fixed.h"
 #include "doomtype.h"
 #include "d_player.h"
 #include "g_state.h"
+#include "taglist.h"
 
 #include "blua/lua.h"
 #include "blua/lualib.h"
@@ -46,12 +50,6 @@ void LUA_LoadLump(UINT16 wad, UINT16 lump, boolean noresults);
 void LUA_DumpFile(const char *filename);
 #endif
 fixed_t LUA_EvalMath(const char *word);
-void LUA_PushLightUserdata(lua_State *L, void *data, const char *meta);
-void LUA_PushUserdata(lua_State *L, void *data, const char *meta);
-void LUA_InvalidateUserdata(void *data);
-void LUA_InvalidateLevel(void);
-void LUA_InvalidateMapthings(void);
-void LUA_InvalidatePlayer(player_t *player);
 void LUA_Step(void);
 void LUA_Archive(void);
 void LUA_UnArchive(void);
@@ -63,11 +61,49 @@ int Lua_optoption(lua_State *L, int narg,
 	const char *def, const char *const lst[]);
 void LUAh_NetArchiveHook(lua_CFunction archFunc);
 
+void LUA_PushTaggableObjectArray
+(		lua_State *L,
+		const char *field,
+		lua_CFunction iterator,
+		lua_CFunction indexer,
+		lua_CFunction counter,
+		taggroup_t *garray[],
+		size_t * max_elements,
+		void * element_array,
+		size_t sizeof_element,
+		const char *meta);
+
+void LUA_InsertTaggroupIterator
+(		lua_State *L,
+		taggroup_t *garray[],
+		size_t * max_elements,
+		void * element_array,
+		size_t sizeof_element,
+		const char * meta);
+
+typedef enum {
+	LPUSHED_NIL,
+	LPUSHED_NEW,
+	LPUSHED_EXISTING,
+} lpushed_t;
+
+void LUA_PushLightUserdata(lua_State *L, void *data, const char *meta);
+void LUA_PushUserdata(lua_State *L, void *data, const char *meta);
+lpushed_t LUA_RawPushUserdata(lua_State *L, void *data);
+
+void LUA_InvalidateUserdata(void *data);
+
+void LUA_InvalidateLevel(void);
+void LUA_InvalidateMapthings(void);
+void LUA_InvalidatePlayer(player_t *player);
+
 // Console wrapper
 void COM_Lua_f(void);
 
 #define LUA_ErrInvalid(L, type) luaL_error(L, "accessed " type " doesn't exist anymore, please check 'valid' before using " type ".");
 
+#define LUA_ErrSetDirectly(L, type, field) luaL_error(L, type " field " LUA_QL(field) " cannot be set directly.")
+
 // Deprecation warnings
 // Shows once upon use. Then doesn't show again.
 #define LUA_Deprecated(L,this_func,use_instead)\
@@ -98,3 +134,5 @@ void COM_Lua_f(void);
 
 #define INLEVEL if (! ISINLEVEL)\
 return luaL_error(L, "This can only be used in a level!");
+
+#endif/*LUA_SCRIPT_H*/
diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c
index 3e4ddb9f0806d4debc27867ddbec50eaae66ec1d..56be6bf4f40504420e7e67cb0c6b60c739a6dfc7 100644
--- a/src/lua_skinlib.c
+++ b/src/lua_skinlib.c
@@ -21,7 +21,6 @@
 enum skin {
 	skin_valid = 0,
 	skin_name,
-	skin_spritedef,
 	skin_wadnum,
 	skin_flags,
 	skin_realname,
@@ -54,12 +53,12 @@ enum skin {
 	skin_contspeed,
 	skin_contangle,
 	skin_soundsid,
-	skin_availability
+	skin_availability,
+	skin_sprites
 };
 static const char *const skin_opt[] = {
 	"valid",
 	"name",
-	"spritedef",
 	"wadnum",
 	"flags",
 	"realname",
@@ -93,6 +92,7 @@ static const char *const skin_opt[] = {
 	"contangle",
 	"soundsid",
 	"availability",
+	"sprites",
 	NULL};
 
 #define UNIMPLEMENTED luaL_error(L, LUA_QL("skin_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", skin_opt[field])
@@ -113,8 +113,6 @@ static int skin_get(lua_State *L)
 	case skin_name:
 		lua_pushstring(L, skin->name);
 		break;
-	case skin_spritedef:
-		return UNIMPLEMENTED;
 	case skin_wadnum:
 		// !!WARNING!! May differ between clients due to music wads, therefore NOT NETWORK SAFE
 		return UNIMPLEMENTED;
@@ -214,6 +212,9 @@ static int skin_get(lua_State *L)
 	case skin_availability:
 		lua_pushinteger(L, skin->availability);
 		break;
+	case skin_sprites:
+		LUA_PushLightUserdata(L, skin->sprites, META_SKINSPRITES);
+		break;
 	}
 	return 1;
 }
@@ -324,6 +325,49 @@ static int soundsid_num(lua_State *L)
 	return 1;
 }
 
+enum spritesopt {
+	numframes = 0
+};
+
+static const char *const sprites_opt[] = {
+	"numframes",
+	NULL};
+
+// skin.sprites[i] -> sprites[i]
+static int lib_getSkinSprite(lua_State *L)
+{
+	spritedef_t *sprites = (spritedef_t *)luaL_checkudata(L, 1, META_SKINSPRITES);
+	playersprite_t i = luaL_checkinteger(L, 2);
+
+	if (i < 0 || i >= NUMPLAYERSPRITES*2)
+		return luaL_error(L, LUA_QL("skin_t") " field 'sprites' index %d out of range (0 - %d)", i, (NUMPLAYERSPRITES*2)-1);
+
+	LUA_PushLightUserdata(L, &sprites[i], META_SKINSPRITESLIST);
+	return 1;
+}
+
+// #skin.sprites -> NUMPLAYERSPRITES*2
+static int lib_numSkinsSprites(lua_State *L)
+{
+	lua_pushinteger(L, NUMPLAYERSPRITES*2);
+	return 1;
+}
+
+static int sprite_get(lua_State *L)
+{
+	spritedef_t *sprite = (spritedef_t *)luaL_checkudata(L, 1, META_SKINSPRITESLIST);
+	enum spritesopt field = luaL_checkoption(L, 2, NULL, sprites_opt);
+
+	switch (field)
+	{
+	case numframes:
+		lua_pushinteger(L, sprite->numframes);
+		break;
+	}
+	return 1;
+}
+
+
 int LUA_SkinLib(lua_State *L)
 {
 	luaL_newmetatable(L, META_SKIN);
@@ -345,6 +389,19 @@ int LUA_SkinLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
+	luaL_newmetatable(L, META_SKINSPRITES);
+		lua_pushcfunction(L, lib_getSkinSprite);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, lib_numSkinsSprites);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L,1);
+
+	luaL_newmetatable(L, META_SKINSPRITESLIST);
+		lua_pushcfunction(L, sprite_get);
+		lua_setfield(L, -2, "__index");
+	lua_pop(L,1);
+
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
 			lua_pushcfunction(L, lib_getSkin);
diff --git a/src/lua_taglib.c b/src/lua_taglib.c
new file mode 100644
index 0000000000000000000000000000000000000000..c9f320fe8f230524b0b42b10b04022b8d79a5d10
--- /dev/null
+++ b/src/lua_taglib.c
@@ -0,0 +1,451 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by James R.
+// Copyright (C) 2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  lua_taglib.c
+/// \brief tag list iterator for Lua scripting
+
+#include "doomdef.h"
+#include "taglist.h"
+#include "r_state.h"
+
+#include "lua_script.h"
+#include "lua_libs.h"
+
+#ifdef MUTABLE_TAGS
+#include "z_zone.h"
+#endif
+
+static int tag_iterator(lua_State *L)
+{
+	INT32 tag = lua_isnil(L, 2) ? -1 : lua_tonumber(L, 2);
+	do
+	{
+		if (++tag >= MAXTAGS)
+			return 0;
+	}
+	while (! in_bit_array(tags_available, tag)) ;
+	lua_pushnumber(L, tag);
+	return 1;
+}
+
+enum {
+#define UPVALUE lua_upvalueindex
+	up_garray         = UPVALUE(1),
+	up_max_elements   = UPVALUE(2),
+	up_element_array  = UPVALUE(3),
+	up_sizeof_element = UPVALUE(4),
+	up_meta           = UPVALUE(5),
+#undef UPVALUE
+};
+
+static INT32 next_element(lua_State *L, const mtag_t tag, const size_t p)
+{
+	taggroup_t ** garray = lua_touserdata(L, up_garray);
+	const size_t * max_elements = lua_touserdata(L, up_max_elements);
+	return Taggroup_Iterate(garray, *max_elements, tag, p);
+}
+
+static void push_element(lua_State *L, void *element)
+{
+	if (LUA_RawPushUserdata(L, element) == LPUSHED_NEW)
+	{
+		lua_pushvalue(L, up_meta);
+		lua_setmetatable(L, -2);
+	}
+}
+
+static void push_next_element(lua_State *L, const INT32 element)
+{
+	char * element_array = *(char **)lua_touserdata(L, up_element_array);
+	const size_t sizeof_element = lua_tonumber(L, up_sizeof_element);
+	push_element(L, &element_array[element * sizeof_element]);
+}
+
+struct element_iterator_state {
+	mtag_t tag;
+	size_t p;
+};
+
+static int element_iterator(lua_State *L)
+{
+	struct element_iterator_state * state = lua_touserdata(L, 1);
+	if (lua_isnoneornil(L, 3))
+		state->p = 0;
+	lua_pushnumber(L, ++state->p);
+	lua_gettable(L, 1);
+	return 1;
+}
+
+static int lib_iterateTags(lua_State *L)
+{
+	if (lua_gettop(L) < 2)
+	{
+		lua_pushcfunction(L, tag_iterator);
+		return 1;
+	}
+	else
+		return tag_iterator(L);
+}
+
+static int lib_numTags(lua_State *L)
+{
+	lua_pushnumber(L, num_tags);
+	return 1;
+}
+
+static int lib_getTaggroup(lua_State *L)
+{
+	struct element_iterator_state *state;
+
+	mtag_t tag;
+
+	if (lua_gettop(L) > 1)
+		return luaL_error(L, "too many arguments");
+
+	if (lua_isnoneornil(L, 1))
+	{
+		tag = MTAG_GLOBAL;
+	}
+	else
+	{
+		tag = lua_tonumber(L, 1);
+		luaL_argcheck(L, tag >= -1, 1, "tag out of range");
+	}
+
+	state = lua_newuserdata(L, sizeof *state);
+	state->tag = tag;
+	state->p = 0;
+
+	lua_pushvalue(L, lua_upvalueindex(1));
+	lua_setmetatable(L, -2);
+
+	return 1;
+}
+
+static int lib_getTaggroupElement(lua_State *L)
+{
+	const size_t p = luaL_checknumber(L, 2) - 1;
+	const mtag_t tag = *(mtag_t *)lua_touserdata(L, 1);
+	const INT32 element = next_element(L, tag, p);
+
+	if (element == -1)
+		return 0;
+	else
+	{
+		push_next_element(L, element);
+		return 1;
+	}
+}
+
+static int lib_numTaggroupElements(lua_State *L)
+{
+	const mtag_t tag = *(mtag_t *)lua_touserdata(L, 1);
+	if (tag == MTAG_GLOBAL)
+		lua_pushnumber(L, *(size_t *)lua_touserdata(L, up_max_elements));
+	else
+	{
+		const taggroup_t ** garray = lua_touserdata(L, up_garray);
+		lua_pushnumber(L, Taggroup_Count(garray[tag]));
+	}
+	return 1;
+}
+
+#ifdef MUTABLE_TAGS
+static int meta_ref[2];
+#endif
+
+static int has_valid_field(lua_State *L)
+{
+	int equal;
+	lua_rawgeti(L, LUA_ENVIRONINDEX, 1);
+	equal = lua_rawequal(L, 2, -1);
+	lua_pop(L, 1);
+	return equal;
+}
+
+static taglist_t * valid_taglist(lua_State *L, int idx, boolean getting)
+{
+	taglist_t *list = *(taglist_t **)lua_touserdata(L, idx);
+
+	if (list == NULL)
+	{
+		if (getting && has_valid_field(L))
+			lua_pushboolean(L, 0);
+		else
+			LUA_ErrInvalid(L, "taglist");/* doesn't actually return */
+		return NULL;
+	}
+	else
+		return list;
+}
+
+static taglist_t * check_taglist(lua_State *L, int idx)
+{
+	if (lua_isuserdata(L, idx) && lua_getmetatable(L, idx))
+	{
+		lua_getref(L, meta_ref[0]);
+		lua_getref(L, meta_ref[1]);
+
+		if (lua_rawequal(L, -3, -2) || lua_rawequal(L, -3, -1))
+		{
+			lua_pop(L, 3);
+			return valid_taglist(L, idx, false);
+		}
+	}
+
+	return luaL_argerror(L, idx, "must be a tag list"), NULL;
+}
+
+static int taglist_get(lua_State *L)
+{
+	const taglist_t *list = valid_taglist(L, 1, true);
+
+	if (list == NULL)/* valid check */
+		return 1;
+
+	if (lua_isnumber(L, 2))
+	{
+		const size_t i = lua_tonumber(L, 2);
+
+		if (list && i <= list->count)
+		{
+			lua_pushnumber(L, list->tags[i - 1]);
+			return 1;
+		}
+		else
+			return 0;
+	}
+	else if (has_valid_field(L))
+	{
+		lua_pushboolean(L, 1);
+		return 1;
+	}
+	else
+	{
+		lua_getmetatable(L, 1);
+		lua_replace(L, 1);
+		lua_rawget(L, 1);
+		return 1;
+	}
+}
+
+static int taglist_len(lua_State *L)
+{
+	const taglist_t *list = valid_taglist(L, 1, false);
+	lua_pushnumber(L, list->count);
+	return 1;
+}
+
+static int taglist_equal(lua_State *L)
+{
+	const taglist_t *lhs = check_taglist(L, 1);
+	const taglist_t *rhs = check_taglist(L, 2);
+	lua_pushboolean(L, Tag_Compare(lhs, rhs));
+	return 1;
+}
+
+static int taglist_iterator(lua_State *L)
+{
+	const taglist_t *list = valid_taglist(L, 1, false);
+	const size_t i = 1 + lua_tonumber(L, lua_upvalueindex(1));
+	if (i <= list->count)
+	{
+		lua_pushnumber(L, list->tags[i - 1]);
+		/* watch me exploit an upvalue as a control because
+			I want to use the control as the value */
+		lua_pushnumber(L, i);
+		lua_replace(L, lua_upvalueindex(1));
+		return 1;
+	}
+	else
+		return 0;
+}
+
+static int taglist_iterate(lua_State *L)
+{
+	check_taglist(L, 1);
+	lua_pushnumber(L, 0);
+	lua_pushcclosure(L, taglist_iterator, 1);
+	lua_pushvalue(L, 1);
+	return 2;
+}
+
+static int taglist_find(lua_State *L)
+{
+	const taglist_t *list = check_taglist(L, 1);
+	const mtag_t tag = luaL_checknumber(L, 2);
+	lua_pushboolean(L, Tag_Find(list, tag));
+	return 1;
+}
+
+static int taglist_shares(lua_State *L)
+{
+	const taglist_t *lhs = check_taglist(L, 1);
+	const taglist_t *rhs = check_taglist(L, 2);
+	lua_pushboolean(L, Tag_Share(lhs, rhs));
+	return 1;
+}
+
+/* only sector tags are mutable... */
+
+#ifdef MUTABLE_TAGS
+static size_t sector_of_taglist(taglist_t *list)
+{
+	return (sector_t *)((char *)list - offsetof (sector_t, tags)) - sectors;
+}
+
+static int this_taglist(lua_State *L)
+{
+	lua_settop(L, 1);
+	return 1;
+}
+
+static int taglist_add(lua_State *L)
+{
+	taglist_t *list = *(taglist_t **)luaL_checkudata(L, 1, META_SECTORTAGLIST);
+	const mtag_t tag = luaL_checknumber(L, 2);
+
+	if (! Tag_Find(list, tag))
+	{
+		Taggroup_Add(tags_sectors, tag, sector_of_taglist(list));
+		Tag_Add(list, tag);
+	}
+
+	return this_taglist(L);
+}
+
+static int taglist_remove(lua_State *L)
+{
+	taglist_t *list = *(taglist_t **)luaL_checkudata(L, 1, META_SECTORTAGLIST);
+	const mtag_t tag = luaL_checknumber(L, 2);
+
+	size_t i;
+
+	for (i = 0; i < list->count; ++i)
+	{
+		if (list->tags[i] == tag)
+		{
+			if (list->count > 1)
+			{
+				memmove(&list->tags[i], &list->tags[i + 1],
+						(list->count - 1 - i) * sizeof (mtag_t));
+				list->tags = Z_Realloc(list->tags,
+						(--list->count) * sizeof (mtag_t), PU_LEVEL, NULL);
+				Taggroup_Remove(tags_sectors, tag, sector_of_taglist(list));
+			}
+			else/* reset to default tag */
+				Tag_SectorFSet(sector_of_taglist(list), 0);
+			break;
+		}
+	}
+
+	return this_taglist(L);
+}
+#endif/*MUTABLE_TAGS*/
+
+void LUA_InsertTaggroupIterator
+(		lua_State *L,
+		taggroup_t *garray[],
+		size_t * max_elements,
+		void * element_array,
+		size_t sizeof_element,
+		const char * meta)
+{
+	lua_createtable(L, 0, 3);
+		lua_pushlightuserdata(L, garray);
+		lua_pushlightuserdata(L, max_elements);
+
+		lua_pushvalue(L, -2);
+		lua_pushvalue(L, -2);
+		lua_pushlightuserdata(L, element_array);
+		lua_pushnumber(L, sizeof_element);
+		luaL_getmetatable(L, meta);
+		lua_pushcclosure(L, lib_getTaggroupElement, 5);
+		lua_setfield(L, -4, "__index");
+
+		lua_pushcclosure(L, lib_numTaggroupElements, 2);
+		lua_setfield(L, -2, "__len");
+
+		lua_pushcfunction(L, element_iterator);
+		lua_setfield(L, -2, "__call");
+	lua_pushcclosure(L, lib_getTaggroup, 1);
+	lua_setfield(L, -2, "tagged");
+}
+
+static luaL_Reg taglist_lib[] = {
+	{"iterate", taglist_iterate},
+	{"find", taglist_find},
+	{"shares", taglist_shares},
+#ifdef MUTABLE_TAGS
+	{"add", taglist_add},
+	{"remove", taglist_remove},
+#endif
+	{0}
+};
+
+static void open_taglist(lua_State *L)
+{
+	luaL_register(L, "taglist", taglist_lib);
+
+	lua_getfield(L, -1, "find");
+	lua_setfield(L, -2, "has");
+}
+
+#define new_literal(L, s) \
+	(lua_pushliteral(L, s), luaL_ref(L, -2))
+
+#ifdef MUTABLE_TAGS
+static int
+#else
+static void
+#endif
+set_taglist_metatable(lua_State *L, const char *meta)
+{
+	luaL_newmetatable(L, meta);
+		lua_pushcfunction(L, taglist_get);
+		lua_createtable(L, 0, 1);
+			new_literal(L, "valid");
+		lua_setfenv(L, -2);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, taglist_len);
+		lua_setfield(L, -2, "__len");
+
+		lua_pushcfunction(L, taglist_equal);
+		lua_setfield(L, -2, "__eq");
+#ifdef MUTABLE_TAGS
+	return luaL_ref(L, LUA_REGISTRYINDEX);
+#endif
+}
+
+int LUA_TagLib(lua_State *L)
+{
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_createtable(L, 0, 1);
+				lua_pushcfunction(L, lib_iterateTags);
+				lua_setfield(L, -2, "iterate");
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, lib_numTags);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, "tags");
+
+	open_taglist(L);
+
+#ifdef MUTABLE_TAGS
+	meta_ref[0] = set_taglist_metatable(L, META_TAGLIST);
+	meta_ref[1] = set_taglist_metatable(L, META_SECTORTAGLIST);
+#else
+	set_taglist_metatable(L, META_TAGLIST);
+#endif
+
+	return 0;
+}
diff --git a/src/m_anigif.c b/src/m_anigif.c
index dbc8d3422366f4c8cf255cde5e66ea55cb9cbddf..41f99254eff59fea3d80f7592168723d9de7a975 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -18,7 +18,7 @@
 #include "z_zone.h"
 #include "v_video.h"
 #include "i_video.h"
-#include "i_system.h" // I_GetTimeMicros
+#include "i_system.h" // I_GetPreciseTime
 #include "m_misc.h"
 #include "st_stuff.h" // st_palette
 
@@ -53,8 +53,8 @@ static RGBA_t *gif_framepalette = NULL;
 
 static FILE *gif_out = NULL;
 static INT32 gif_frames = 0;
-static UINT32 gif_prevframeus = 0; // "us" is microseconds
-static UINT32 gif_delayus = 0;
+static precise_t gif_prevframetime = 0;
+static UINT32 gif_delayus = 0; // "us" is microseconds
 static UINT8 gif_writeover = 0;
 
 
@@ -608,7 +608,7 @@ static void GIF_framewrite(void)
 		{
 			// golden's attempt at creating a "dynamic delay"
 			UINT16 mingifdelay = 10; // minimum gif delay in milliseconds (keep at 10 because gifs can't get more precise).
-			gif_delayus += (I_GetTimeMicros() - gif_prevframeus); // increase delay by how much time was spent between last measurement
+			gif_delayus += I_PreciseToMicros(I_GetPreciseTime() - gif_prevframetime); // increase delay by how much time was spent between last measurement
 
 			if (gif_delayus/1000 >= mingifdelay) // delay is big enough to be able to effect gif frame delay?
 			{
@@ -621,7 +621,7 @@ static void GIF_framewrite(void)
 		{
 			float delayf = ceil(100.0f/NEWTICRATE);
 
-			delay = (UINT16)((I_GetTimeMicros() - gif_prevframeus)/10/1000);
+			delay = (UINT16)I_PreciseToMicros((I_GetPreciseTime() - gif_prevframetime))/10/1000;
 
 			if (delay < (UINT16)(delayf))
 				delay = (UINT16)(delayf);
@@ -711,7 +711,7 @@ static void GIF_framewrite(void)
 	}
 	fwrite(gifframe_data, 1, (p - gifframe_data), gif_out);
 	++gif_frames;
-	gif_prevframeus = I_GetTimeMicros();
+	gif_prevframetime = I_GetPreciseTime();
 }
 
 
@@ -739,7 +739,7 @@ INT32 GIF_open(const char *filename)
 
 	GIF_headwrite();
 	gif_frames = 0;
-	gif_prevframeus = I_GetTimeMicros();
+	gif_prevframetime = I_GetPreciseTime();
 	gif_delayus = 0;
 	return 1;
 }
diff --git a/src/m_menu.c b/src/m_menu.c
index 77648f877c7adc9ff2b3b5c4fc9f0be7eb532a05..0fca39801baf9d975a257d2829f52599af5907b8 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1356,9 +1356,7 @@ static menuitem_t OP_VideoOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Score/Time/Rings",          &cv_timetic,          71},
 	{IT_STRING | IT_CVAR, NULL, "Show Powerups",             &cv_powerupdisplay,   76},
 	{IT_STRING | IT_CVAR, NULL, "Local ping display",		&cv_showping,			81}, // shows ping next to framerate if we want to.
-#ifdef SEENAMES
 	{IT_STRING | IT_CVAR, NULL, "Show player names",         &cv_seenames,         86},
-#endif
 
 	{IT_HEADER, NULL, "Console", NULL, 95},
 	{IT_STRING | IT_CVAR, NULL, "Background color",          &cons_backcolor,      101},
@@ -1551,18 +1549,19 @@ static menuitem_t OP_ScreenshotOptionsMenu[] =
 	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bits,           57},
 
 	{IT_HEADER, NULL, "Movie Mode (F9)", NULL, 64},
-	{IT_STRING|IT_CVAR, NULL, "Storage Location",  &cv_movie_option,              70},
-	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_movie_folder, 	  75},
-	{IT_STRING|IT_CVAR, NULL, "Capture Mode",      &cv_moviemode,                 90},
+	{IT_STRING|IT_CVAR, NULL, "Storage Location",  &cv_movie_option,               70},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_movie_folder, 	   75},
+	{IT_STRING|IT_CVAR, NULL, "Capture Mode",      &cv_moviemode,                  90},
 
-	{IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize,              95},
-	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_gif_downscale,             100},
+	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_gif_downscale,              95},
+	{IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize,              100},
 	{IT_STRING|IT_CVAR, NULL, "Local Color Table", &cv_gif_localcolortable,       105},
 
-	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memorya,              95},
-	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela,               100},
-	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategya,            105},
-	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bitsa,         110},
+	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_apng_downscale,             95},
+	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memorya,              100},
+	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela,               105},
+	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategya,            110},
+	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bitsa,         115},
 };
 
 enum
@@ -1575,7 +1574,7 @@ enum
 	op_screenshot_gif_start = 13,
 	op_screenshot_gif_end = 15,
 	op_screenshot_apng_start = 16,
-	op_screenshot_apng_end = 19,
+	op_screenshot_apng_end = 20,
 };
 
 static menuitem_t OP_EraseDataMenu[] =
@@ -1613,53 +1612,54 @@ static menuitem_t OP_ServerOptionsMenu[] =
 	{IT_STRING | IT_CVAR,    NULL, "Max Players",                      &cv_maxplayers,          21},
 	{IT_STRING | IT_CVAR,    NULL, "Allow Add-on Downloading",         &cv_downloading,         26},
 	{IT_STRING | IT_CVAR,    NULL, "Allow players to join",            &cv_allownewplayer,      31},
+	{IT_STRING | IT_CVAR,    NULL, "Minutes for reconnecting",         &cv_rejointimeout,       36},
 #endif
-	{IT_STRING | IT_CVAR,    NULL, "Map progression",                  &cv_advancemap,          36},
-	{IT_STRING | IT_CVAR,    NULL, "Intermission Timer",               &cv_inttime,             41},
+	{IT_STRING | IT_CVAR,    NULL, "Map progression",                  &cv_advancemap,          41},
+	{IT_STRING | IT_CVAR,    NULL, "Intermission Timer",               &cv_inttime,             46},
 
-	{IT_HEADER, NULL, "Characters", NULL, 50},
-	{IT_STRING | IT_CVAR,    NULL, "Force a character",                &cv_forceskin,           56},
-	{IT_STRING | IT_CVAR,    NULL, "Restrict character changes",       &cv_restrictskinchange,  61},
+	{IT_HEADER, NULL, "Characters", NULL, 55},
+	{IT_STRING | IT_CVAR,    NULL, "Force a character",                &cv_forceskin,           61},
+	{IT_STRING | IT_CVAR,    NULL, "Restrict character changes",       &cv_restrictskinchange,  66},
 
-	{IT_HEADER, NULL, "Items", NULL, 70},
-	{IT_STRING | IT_CVAR,    NULL, "Item respawn delay",               &cv_itemrespawntime,     76},
-	{IT_STRING | IT_SUBMENU, NULL, "Mystery Item Monitor Toggles...",  &OP_MonitorToggleDef,    81},
+	{IT_HEADER, NULL, "Items", NULL, 75},
+	{IT_STRING | IT_CVAR,    NULL, "Item respawn delay",               &cv_itemrespawntime,     81},
+	{IT_STRING | IT_SUBMENU, NULL, "Mystery Item Monitor Toggles...",  &OP_MonitorToggleDef,    86},
 
-	{IT_HEADER, NULL, "Cooperative", NULL, 90},
-	{IT_STRING | IT_CVAR,    NULL, "Players required for exit",        &cv_playersforexit,      96},
-	{IT_STRING | IT_CVAR,    NULL, "Starposts",                        &cv_coopstarposts,      101},
-	{IT_STRING | IT_CVAR,    NULL, "Life sharing",                     &cv_cooplives,          106},
-	{IT_STRING | IT_CVAR,    NULL, "Post-goal free roaming",           &cv_exitmove,           111},
+	{IT_HEADER, NULL, "Cooperative", NULL, 95},
+	{IT_STRING | IT_CVAR,    NULL, "Players required for exit",        &cv_playersforexit,     101},
+	{IT_STRING | IT_CVAR,    NULL, "Starposts",                        &cv_coopstarposts,      106},
+	{IT_STRING | IT_CVAR,    NULL, "Life sharing",                     &cv_cooplives,          111},
+	{IT_STRING | IT_CVAR,    NULL, "Post-goal free roaming",           &cv_exitmove,           116},
 
-	{IT_HEADER, NULL, "Race, Competition", NULL, 120},
-	{IT_STRING | IT_CVAR,    NULL, "Level completion countdown",       &cv_countdowntime,      126},
-	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_competitionboxes,   131},
+	{IT_HEADER, NULL, "Race, Competition", NULL, 125},
+	{IT_STRING | IT_CVAR,    NULL, "Level completion countdown",       &cv_countdowntime,      131},
+	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_competitionboxes,   136},
 
-	{IT_HEADER, NULL, "Ringslinger (Match, CTF, Tag, H&S)", NULL, 140},
-	{IT_STRING | IT_CVAR,    NULL, "Time Limit",                       &cv_timelimit,          146},
-	{IT_STRING | IT_CVAR,    NULL, "Score Limit",                      &cv_pointlimit,         151},
-	{IT_STRING | IT_CVAR,    NULL, "Overtime on Tie",                  &cv_overtime,           156},
-	{IT_STRING | IT_CVAR,    NULL, "Player respawn delay",             &cv_respawntime,        161},
+	{IT_HEADER, NULL, "Ringslinger (Match, CTF, Tag, H&S)", NULL, 145},
+	{IT_STRING | IT_CVAR,    NULL, "Time Limit",                       &cv_timelimit,          151},
+	{IT_STRING | IT_CVAR,    NULL, "Score Limit",                      &cv_pointlimit,         156},
+	{IT_STRING | IT_CVAR,    NULL, "Overtime on Tie",                  &cv_overtime,           161},
+	{IT_STRING | IT_CVAR,    NULL, "Player respawn delay",             &cv_respawntime,        166},
 
-	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_matchboxes,         171},
-	{IT_STRING | IT_CVAR,    NULL, "Weapon Rings",                     &cv_specialrings,       176},
-	{IT_STRING | IT_CVAR,    NULL, "Power Stones",                     &cv_powerstones,        181},
+	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_matchboxes,         176},
+	{IT_STRING | IT_CVAR,    NULL, "Weapon Rings",                     &cv_specialrings,       181},
+	{IT_STRING | IT_CVAR,    NULL, "Power Stones",                     &cv_powerstones,        186},
 
-	{IT_STRING | IT_CVAR,    NULL, "Flag respawn delay",               &cv_flagtime,           191},
-	{IT_STRING | IT_CVAR,    NULL, "Hiding time",                      &cv_hidetime,           196},
+	{IT_STRING | IT_CVAR,    NULL, "Flag respawn delay",               &cv_flagtime,           196},
+	{IT_STRING | IT_CVAR,    NULL, "Hiding time",                      &cv_hidetime,           201},
 
-	{IT_HEADER, NULL, "Teams", NULL, 205},
-	{IT_STRING | IT_CVAR,    NULL, "Autobalance sizes",                &cv_autobalance,        211},
-	{IT_STRING | IT_CVAR,    NULL, "Scramble on Map Change",           &cv_scrambleonchange,   216},
+	{IT_HEADER, NULL, "Teams", NULL, 210},
+	{IT_STRING | IT_CVAR,    NULL, "Autobalance sizes",                &cv_autobalance,        216},
+	{IT_STRING | IT_CVAR,    NULL, "Scramble on Map Change",           &cv_scrambleonchange,   221},
 
 #ifndef NONET
-	{IT_HEADER, NULL, "Advanced", NULL, 225},
-	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server",        &cv_masterserver,       231},
+	{IT_HEADER, NULL, "Advanced", NULL, 230},
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server",        &cv_masterserver,       236},
 
-	{IT_STRING | IT_CVAR,    NULL, "Join delay",                       &cv_joindelay,          246},
-	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",        &cv_resynchattempts,    251},
+	{IT_STRING | IT_CVAR,    NULL, "Join delay",                       &cv_joindelay,          251},
+	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",        &cv_resynchattempts,    256},
 
-	{IT_STRING | IT_CVAR,    NULL, "Show IP Address of Joiners",       &cv_showjoinaddress,    256},
+	{IT_STRING | IT_CVAR,    NULL, "Show IP Address of Joiners",       &cv_showjoinaddress,    261},
 #endif
 };
 
@@ -6937,8 +6937,7 @@ static void M_SelectableClearMenus(INT32 choice)
 static void M_UltimateCheat(INT32 choice)
 {
 	(void)choice;
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(true);
 	I_Quit();
 }
 
@@ -8423,7 +8422,7 @@ static void M_DrawLoadGameData(void)
 				sprdef = &charbotskin->sprites[SPR2_SIGN];
 				if (!sprdef->numframes)
 					goto skipbot;
-				colormap = R_GetTranslationColormap(savegameinfo[savetodraw].botskin-1, charbotskin->prefcolor, 0);
+				colormap = R_GetTranslationColormap(savegameinfo[savetodraw].botskin-1, charbotskin->prefcolor, GTC_CACHE);
 				sprframe = &sprdef->spriteframes[0];
 				patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 
@@ -8433,8 +8432,6 @@ static void M_DrawLoadGameData(void)
 					charbotskin->highresscale,
 					0, patch, colormap);
 
-				Z_Free(colormap);
-
 				tempx -= (20<<FRACBITS);
 				//flip = V_FLIP;
 			}
@@ -8443,7 +8440,7 @@ skipbot:
 			if (!charskin) // shut up compiler
 				goto skipsign;
 			sprdef = &charskin->sprites[SPR2_SIGN];
-			colormap = R_GetTranslationColormap(savegameinfo[savetodraw].skinnum, charskin->prefcolor, 0);
+			colormap = R_GetTranslationColormap(savegameinfo[savetodraw].skinnum, charskin->prefcolor, GTC_CACHE);
 			if (!sprdef->numframes)
 				goto skipsign;
 			sprframe = &sprdef->spriteframes[0];
@@ -8483,8 +8480,6 @@ skipsign:
 				charskin->highresscale/2,
 				0, patch, colormap);
 skiplife:
-			if (colormap)
-				Z_Free(colormap);
 
 			patch = W_CachePatchName("STLIVEX", PU_PATCH);
 
@@ -11424,9 +11419,9 @@ static void M_ServerOptions(INT32 choice)
 		OP_ServerOptionsMenu[ 2].status = IT_GRAYEDOUT; // Max players
 		OP_ServerOptionsMenu[ 3].status = IT_GRAYEDOUT; // Allow add-on downloading
 		OP_ServerOptionsMenu[ 4].status = IT_GRAYEDOUT; // Allow players to join
-		OP_ServerOptionsMenu[35].status = IT_GRAYEDOUT; // Master server
-		OP_ServerOptionsMenu[36].status = IT_GRAYEDOUT; // Minimum delay between joins
-		OP_ServerOptionsMenu[37].status = IT_GRAYEDOUT; // Attempts to resynchronise
+		OP_ServerOptionsMenu[36].status = IT_GRAYEDOUT; // Master server
+		OP_ServerOptionsMenu[37].status = IT_GRAYEDOUT; // Minimum delay between joins
+		OP_ServerOptionsMenu[38].status = IT_GRAYEDOUT; // Attempts to resynchronise
 	}
 	else
 	{
@@ -11434,11 +11429,11 @@ static void M_ServerOptions(INT32 choice)
 		OP_ServerOptionsMenu[ 2].status = IT_STRING | IT_CVAR;
 		OP_ServerOptionsMenu[ 3].status = IT_STRING | IT_CVAR;
 		OP_ServerOptionsMenu[ 4].status = IT_STRING | IT_CVAR;
-		OP_ServerOptionsMenu[35].status = (netgame
+		OP_ServerOptionsMenu[36].status = (netgame
 			? IT_GRAYEDOUT
 			: (IT_STRING | IT_CVAR | IT_CV_STRING));
-		OP_ServerOptionsMenu[36].status = IT_STRING | IT_CVAR;
 		OP_ServerOptionsMenu[37].status = IT_STRING | IT_CVAR;
+		OP_ServerOptionsMenu[38].status = IT_STRING | IT_CVAR;
 	}
 #endif
 
@@ -11755,7 +11750,7 @@ static void M_DrawSetupMultiPlayerMenu(void)
 		goto faildraw;
 
 	// ok, draw player sprite for sure now
-	colormap = R_GetTranslationColormap(setupm_fakeskin, setupm_fakecolor->color, 0);
+	colormap = R_GetTranslationColormap(setupm_fakeskin, setupm_fakecolor->color, GTC_CACHE);
 
 	if (multi_frame >= sprdef->numframes)
 		multi_frame = 0;
@@ -11773,7 +11768,6 @@ static void M_DrawSetupMultiPlayerMenu(void)
 		FixedDiv(skins[setupm_fakeskin].highresscale, skins[setupm_fakeskin].shieldscale),
 		flags, patch, colormap);
 
-	Z_Free(colormap);
 	goto colordraw;
 
 faildraw:
@@ -13372,8 +13366,7 @@ void M_QuitResponse(INT32 ch)
 
 	if (ch != 'y' && ch != KEY_ENTER)
 		return;
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(true);
 	if (!(netgame || cv_debug))
 	{
 		S_ResetCaptions();
diff --git a/src/m_misc.c b/src/m_misc.c
index d97d8f94be36972a82880f79316ac8d2f7149131..17a398b83f9b26a19d756a5fdfafe8b8a63652c5 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -163,6 +163,11 @@ consvar_t cv_zlib_levela = CVAR_INIT ("apng_compress_level", "4", CV_SAVE, zlib_
 consvar_t cv_zlib_strategya = CVAR_INIT ("apng_strategy", "RLE", CV_SAVE, zlib_strategy_t, NULL);
 consvar_t cv_zlib_window_bitsa = CVAR_INIT ("apng_window_size", "32k", CV_SAVE, zlib_window_bits_t, NULL);
 consvar_t cv_apng_delay = CVAR_INIT ("apng_speed", "1x", CV_SAVE, apng_delay_t, NULL);
+consvar_t cv_apng_downscale = CVAR_INIT ("apng_downscale", "On", CV_SAVE, CV_OnOff, NULL);
+
+#ifdef USE_APNG
+static boolean apng_downscale = false; // So nobody can do something dumb like changing cvars mid output
+#endif
 
 boolean takescreenshot = false; // Take a screenshot this tic
 
@@ -981,25 +986,38 @@ static inline boolean M_PNGLib(void)
 
 static void M_PNGFrame(png_structp png_ptr, png_infop png_info_ptr, png_bytep png_buf)
 {
+	png_uint_16 downscale = apng_downscale ? vid.dupx : 1;
+
 	png_uint_32 pitch = png_get_rowbytes(png_ptr, png_info_ptr);
-	PNG_CONST png_uint_32 height = vid.height;
-	png_bytepp row_pointers = png_malloc(png_ptr, height* sizeof (png_bytep));
-	png_uint_32 y;
+	PNG_CONST png_uint_32 width = vid.width / downscale;
+	PNG_CONST png_uint_32 height = vid.height / downscale;
+	png_bytepp row_pointers = png_malloc(png_ptr, height * sizeof (png_bytep));
+	png_uint_32 x, y;
 	png_uint_16 framedelay = (png_uint_16)cv_apng_delay.value;
 
 	apng_frames++;
 
 	for (y = 0; y < height; y++)
 	{
-		row_pointers[y] = png_buf;
-		png_buf += pitch;
+		row_pointers[y] = malloc(pitch * sizeof(png_byte));
+		for (x = 0; x < width; x++)
+			row_pointers[y][x] = png_buf[x * downscale];
+		png_buf += pitch * (downscale * downscale);
 	}
+		//for (x = 0; x < width; x++)
+		//{
+		//	printf("%d", x);
+		//	row_pointers[y][x] = 0;
+		//}
+	/*	row_pointers[y] = calloc(1, sizeof(png_bytep));
+		png_buf += pitch * 2;
+	}*/
 
 #ifndef PNG_STATIC
 	if (aPNG_write_frame_head)
 #endif
 		aPNG_write_frame_head(apng_ptr, apng_info_ptr, row_pointers,
-			vid.width, /* width */
+			width,     /* width */
 			height,    /* height */
 			0,         /* x offset */
 			0,         /* y offset */
@@ -1030,6 +1048,12 @@ static void M_PNGfix_acTL(png_structp png_ptr, png_infop png_info_ptr,
 
 static boolean M_SetupaPNG(png_const_charp filename, png_bytep pal)
 {
+	png_uint_16 downscale;
+
+	apng_downscale = (!!cv_apng_downscale.value);
+
+	downscale = apng_downscale ? vid.dupx : 1;
+
 	apng_FILE = fopen(filename,"wb+"); // + mode for reading
 	if (!apng_FILE)
 	{
@@ -1080,7 +1104,7 @@ static boolean M_SetupaPNG(png_const_charp filename, png_bytep pal)
 	png_set_compression_strategy(apng_ptr, cv_zlib_strategya.value);
 	png_set_compression_window_bits(apng_ptr, cv_zlib_window_bitsa.value);
 
-	M_PNGhdr(apng_ptr, apng_info_ptr, vid.width, vid.height, pal);
+	M_PNGhdr(apng_ptr, apng_info_ptr, vid.width / downscale, vid.height / downscale, pal);
 
 	M_PNGText(apng_ptr, apng_info_ptr, true);
 
diff --git a/src/m_misc.h b/src/m_misc.h
index dbded37d0a47fdc81c0488098e5bf7ac8e677143..c5ef9f9f2548e339ef76e96ae02f835b82dec23c 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -33,7 +33,7 @@ extern consvar_t cv_screenshot_option, cv_screenshot_folder, cv_screenshot_color
 extern consvar_t cv_moviemode, cv_movie_folder, cv_movie_option;
 extern consvar_t cv_zlib_memory, cv_zlib_level, cv_zlib_strategy, cv_zlib_window_bits;
 extern consvar_t cv_zlib_memorya, cv_zlib_levela, cv_zlib_strategya, cv_zlib_window_bitsa;
-extern consvar_t cv_apng_delay;
+extern consvar_t cv_apng_delay, cv_apng_downscale;
 
 void M_StartMovie(void);
 void M_SaveFrame(void);
diff --git a/src/m_perfstats.c b/src/m_perfstats.c
index 085adda80dac925917e2f86ec3a14ec4ec5c0590..b58599b6d934651ab4d8a0e45f71713477921a52 100644
--- a/src/m_perfstats.c
+++ b/src/m_perfstats.c
@@ -22,32 +22,37 @@
 #include "hardware/hw_main.h"
 #endif
 
-int ps_tictime = 0;
-
-int ps_playerthink_time = 0;
-int ps_thinkertime = 0;
-
-int ps_thlist_times[NUM_THINKERLISTS];
-static const char* thlist_names[] = {
-	"Polyobjects:     %d",
-	"Main:            %d",
-	"Mobjs:           %d",
-	"Dynamic slopes:  %d",
-	"Precipitation:   %d",
-	NULL
+struct perfstatcol;
+struct perfstatrow;
+
+typedef struct perfstatcol perfstatcol_t;
+typedef struct perfstatrow perfstatrow_t;
+
+struct perfstatcol {
+	INT32 lores_x;
+	INT32 hires_x;
+	INT32 color;
+	perfstatrow_t * rows;
 };
-static const char* thlist_shortnames[] = {
-	"plyobjs %d",
-	"main    %d",
-	"mobjs   %d",
-	"dynslop %d",
-	"precip  %d",
-	NULL
+
+struct perfstatrow {
+	const char * lores_label;
+	const char * hires_label;
+	void       * value;
 };
 
+static precise_t ps_frametime = 0;
+
+precise_t ps_tictime = 0;
+
+precise_t ps_playerthink_time = 0;
+precise_t ps_thinkertime = 0;
+
+precise_t ps_thlist_times[NUM_THINKERLISTS];
+
 int ps_checkposition_calls = 0;
 
-int ps_lua_thinkframe_time = 0;
+precise_t ps_lua_thinkframe_time = 0;
 int ps_lua_mobjhooks = 0;
 
 // dynamically allocated resizeable array for thinkframe hook stats
@@ -55,7 +60,9 @@ ps_hookinfo_t *thinkframe_hooks = NULL;
 int thinkframe_hooks_length = 0;
 int thinkframe_hooks_capacity = 16;
 
-void PS_SetThinkFrameHookInfo(int index, UINT32 time_taken, char* short_src)
+static INT32 draw_row;
+
+void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src)
 {
 	if (!thinkframe_hooks)
 	{
@@ -76,380 +83,414 @@ void PS_SetThinkFrameHookInfo(int index, UINT32 time_taken, char* short_src)
 	thinkframe_hooks_length = index + 1;
 }
 
-void M_DrawPerfStats(void)
+static void PS_SetFrameTime(void)
 {
-	char s[100];
-	int currenttime = I_GetTimeMicros();
-	int frametime = currenttime - ps_prevframetime;
+	precise_t currenttime = I_GetPreciseTime();
+	ps_frametime = currenttime - ps_prevframetime;
 	ps_prevframetime = currenttime;
+}
 
-	if (cv_perfstats.value == 1) // rendering
+static boolean M_HighResolution(void)
+{
+	return (vid.width >= 640 && vid.height >= 400);
+}
+
+enum {
+	PERF_TIME,
+	PERF_COUNT,
+};
+
+static void M_DrawPerfString(perfstatcol_t *col, int type)
+{
+	const boolean hires = M_HighResolution();
+
+	INT32 draw_flags = V_MONOSPACE | col->color;
+
+	perfstatrow_t * row;
+
+	int value;
+
+	if (hires)
+		draw_flags |= V_ALLOWLOWERCASE;
+
+	for (row = col->rows; row->lores_label; ++row)
 	{
-		if (vid.width < 640 || vid.height < 400) // low resolution
+		if (type == PERF_TIME)
+			value = I_PreciseToMicros(*(precise_t *)row->value);
+		else
+			value = *(int *)row->value;
+
+		if (hires)
 		{
-			snprintf(s, sizeof s - 1, "frmtime %d", frametime);
-			V_DrawThinString(20, 10, V_MONOSPACE | V_YELLOWMAP, s);
-			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
-			{
-				snprintf(s, sizeof s - 1, "ui      %d", ps_uitime);
-				V_DrawThinString(20, 18, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "finupdt %d", ps_swaptime);
-				V_DrawThinString(20, 26, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "logic   %d", ps_tictime);
-				V_DrawThinString(20, 38, V_MONOSPACE | V_GRAYMAP, s);
-				return;
-			}
-			snprintf(s, sizeof s - 1, "drwtime %d", ps_rendercalltime);
-			V_DrawThinString(20, 18, V_MONOSPACE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "bspcall %d", ps_numbspcalls);
-			V_DrawThinString(90, 10, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "sprites %d", ps_numsprites);
-			V_DrawThinString(90, 18, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "drwnode %d", ps_numdrawnodes);
-			V_DrawThinString(90, 26, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "plyobjs %d", ps_numpolyobjects);
-			V_DrawThinString(90, 34, V_MONOSPACE | V_BLUEMAP, s);
-#ifdef HWRENDER
-			if (rendermode == render_opengl) // OpenGL specific stats
-			{
-				snprintf(s, sizeof s - 1, "skybox  %d", ps_hw_skyboxtime);
-				V_DrawThinString(24, 26, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "bsptime %d", ps_bsptime);
-				V_DrawThinString(24, 34, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "nodesrt %d", ps_hw_nodesorttime);
-				V_DrawThinString(24, 42, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "nodedrw %d", ps_hw_nodedrawtime);
-				V_DrawThinString(24, 50, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "sprsort %d", ps_hw_spritesorttime);
-				V_DrawThinString(24, 58, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "sprdraw %d", ps_hw_spritedrawtime);
-				V_DrawThinString(24, 66, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "other   %d",
-					ps_rendercalltime - ps_hw_skyboxtime - ps_bsptime - ps_hw_nodesorttime
-					- ps_hw_nodedrawtime - ps_hw_spritesorttime - ps_hw_spritedrawtime
-					- ps_hw_batchsorttime - ps_hw_batchdrawtime);
-				V_DrawThinString(24, 74, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "ui      %d", ps_uitime);
-				V_DrawThinString(20, 82, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "finupdt %d", ps_swaptime);
-				V_DrawThinString(20, 90, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "logic   %d", ps_tictime);
-				V_DrawThinString(20, 102, V_MONOSPACE | V_GRAYMAP, s);
-				if (cv_glbatching.value)
-				{
-					snprintf(s, sizeof s - 1, "batsort %d", ps_hw_batchsorttime);
-					V_DrawThinString(90, 46, V_MONOSPACE | V_REDMAP, s);
-					snprintf(s, sizeof s - 1, "batdraw %d", ps_hw_batchdrawtime);
-					V_DrawThinString(90, 54, V_MONOSPACE | V_REDMAP, s);
-
-					snprintf(s, sizeof s - 1, "polygon %d", ps_hw_numpolys);
-					V_DrawThinString(155, 10, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "drwcall %d", ps_hw_numcalls);
-					V_DrawThinString(155, 18, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "shaders %d", ps_hw_numshaders);
-					V_DrawThinString(155, 26, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "vertex  %d", ps_hw_numverts);
-					V_DrawThinString(155, 34, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "texture %d", ps_hw_numtextures);
-					V_DrawThinString(220, 10, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "polyflg %d", ps_hw_numpolyflags);
-					V_DrawThinString(220, 18, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "colors  %d", ps_hw_numcolors);
-					V_DrawThinString(220, 26, V_MONOSPACE | V_PURPLEMAP, s);
-				}
-				else
-				{
-					// reset these vars so the "other" measurement isn't off
-					ps_hw_batchsorttime = 0;
-					ps_hw_batchdrawtime = 0;
-				}
-			}
-			else // software specific stats
-#endif
-			{
-				snprintf(s, sizeof s - 1, "bsptime %d", ps_bsptime);
-				V_DrawThinString(24, 26, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "sprclip %d", ps_sw_spritecliptime);
-				V_DrawThinString(24, 34, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "portals %d", ps_sw_portaltime);
-				V_DrawThinString(24, 42, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "planes  %d", ps_sw_planetime);
-				V_DrawThinString(24, 50, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "masked  %d", ps_sw_maskedtime);
-				V_DrawThinString(24, 58, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "other   %d",
-					ps_rendercalltime - ps_bsptime - ps_sw_spritecliptime
-					- ps_sw_portaltime - ps_sw_planetime - ps_sw_maskedtime);
-				V_DrawThinString(24, 66, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "ui      %d", ps_uitime);
-				V_DrawThinString(20, 74, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "finupdt %d", ps_swaptime);
-				V_DrawThinString(20, 82, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "logic   %d", ps_tictime);
-				V_DrawThinString(20, 94, V_MONOSPACE | V_GRAYMAP, s);
-			}
+			V_DrawSmallString(col->hires_x, draw_row, draw_flags,
+					va("%s %d", row->hires_label, value));
+
+			draw_row += 5;
 		}
-		else // high resolution
+		else
 		{
-			snprintf(s, sizeof s - 1, "Frame time:     %d", frametime);
-			V_DrawSmallString(20, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
-			{
-				snprintf(s, sizeof s - 1, "UI render:      %d", ps_uitime);
-				V_DrawSmallString(20, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "I_FinishUpdate: %d", ps_swaptime);
-				V_DrawSmallString(20, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Game logic:     %d", ps_tictime);
-				V_DrawSmallString(20, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_GRAYMAP, s);
-				return;
-			}
-			snprintf(s, sizeof s - 1, "3d rendering:   %d", ps_rendercalltime);
-			V_DrawSmallString(20, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "BSP calls:    %d", ps_numbspcalls);
-			V_DrawSmallString(115, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Sprites:      %d", ps_numsprites);
-			V_DrawSmallString(115, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Drawnodes:    %d", ps_numdrawnodes);
-			V_DrawSmallString(115, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Polyobjects:  %d", ps_numpolyobjects);
-			V_DrawSmallString(115, 25, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-#ifdef HWRENDER
-			if (rendermode == render_opengl) // OpenGL specific stats
-			{
-				snprintf(s, sizeof s - 1, "Skybox render:  %d", ps_hw_skyboxtime);
-				V_DrawSmallString(24, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "RenderBSPNode:  %d", ps_bsptime);
-				V_DrawSmallString(24, 25, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Drwnode sort:   %d", ps_hw_nodesorttime);
-				V_DrawSmallString(24, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Drwnode render: %d", ps_hw_nodedrawtime);
-				V_DrawSmallString(24, 35, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Sprite sort:    %d", ps_hw_spritesorttime);
-				V_DrawSmallString(24, 40, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Sprite render:  %d", ps_hw_spritedrawtime);
-				V_DrawSmallString(24, 45, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				// Remember to update this calculation when adding more 3d rendering stats!
-				snprintf(s, sizeof s - 1, "Other:          %d",
-					ps_rendercalltime - ps_hw_skyboxtime - ps_bsptime - ps_hw_nodesorttime
-					- ps_hw_nodedrawtime - ps_hw_spritesorttime - ps_hw_spritedrawtime
-					- ps_hw_batchsorttime - ps_hw_batchdrawtime);
-				V_DrawSmallString(24, 50, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "UI render:      %d", ps_uitime);
-				V_DrawSmallString(20, 55, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "I_FinishUpdate: %d", ps_swaptime);
-				V_DrawSmallString(20, 60, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Game logic:     %d", ps_tictime);
-				V_DrawSmallString(20, 70, V_MONOSPACE | V_ALLOWLOWERCASE | V_GRAYMAP, s);
-				if (cv_glbatching.value)
-				{
-					snprintf(s, sizeof s - 1, "Batch sort:   %d", ps_hw_batchsorttime);
-					V_DrawSmallString(115, 35, V_MONOSPACE | V_ALLOWLOWERCASE | V_REDMAP, s);
-					snprintf(s, sizeof s - 1, "Batch render: %d", ps_hw_batchdrawtime);
-					V_DrawSmallString(115, 40, V_MONOSPACE | V_ALLOWLOWERCASE | V_REDMAP, s);
-
-					snprintf(s, sizeof s - 1, "Polygons:   %d", ps_hw_numpolys);
-					V_DrawSmallString(200, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Vertices:   %d", ps_hw_numverts);
-					V_DrawSmallString(200, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Draw calls: %d", ps_hw_numcalls);
-					V_DrawSmallString(200, 25, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Shaders:    %d", ps_hw_numshaders);
-					V_DrawSmallString(200, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Textures:   %d", ps_hw_numtextures);
-					V_DrawSmallString(200, 35, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Polyflags:  %d", ps_hw_numpolyflags);
-					V_DrawSmallString(200, 40, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "Colors:     %d", ps_hw_numcolors);
-					V_DrawSmallString(200, 45, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-				}
-				else
-				{
-					// reset these vars so the "other" measurement isn't off
-					ps_hw_batchsorttime = 0;
-					ps_hw_batchdrawtime = 0;
-				}
-			}
-			else // software specific stats
-#endif
-			{
-				snprintf(s, sizeof s - 1, "RenderBSPNode:  %d", ps_bsptime);
-				V_DrawSmallString(24, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "R_ClipSprites:  %d", ps_sw_spritecliptime);
-				V_DrawSmallString(24, 25, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Portals+Skybox: %d", ps_sw_portaltime);
-				V_DrawSmallString(24, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "R_DrawPlanes:   %d", ps_sw_planetime);
-				V_DrawSmallString(24, 35, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "R_DrawMasked:   %d", ps_sw_maskedtime);
-				V_DrawSmallString(24, 40, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				// Remember to update this calculation when adding more 3d rendering stats!
-				snprintf(s, sizeof s - 1, "Other:          %d",
-					ps_rendercalltime - ps_bsptime - ps_sw_spritecliptime
-					- ps_sw_portaltime - ps_sw_planetime - ps_sw_maskedtime);
-				V_DrawSmallString(24, 45, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "UI render:      %d", ps_uitime);
-				V_DrawSmallString(20, 50, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "I_FinishUpdate: %d", ps_swaptime);
-				V_DrawSmallString(20, 55, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "Game logic:     %d", ps_tictime);
-				V_DrawSmallString(20, 65, V_MONOSPACE | V_ALLOWLOWERCASE | V_GRAYMAP, s);
-			}
+			V_DrawThinString(col->lores_x, draw_row, draw_flags,
+					va("%s %d", row->lores_label, value));
+
+			draw_row += 8;
 		}
 	}
-	else if (cv_perfstats.value == 2) // logic
+}
+
+static void M_DrawPerfTiming(perfstatcol_t *col)
+{
+	M_DrawPerfString(col, PERF_TIME);
+}
+
+static void M_DrawPerfCount(perfstatcol_t *col)
+{
+	M_DrawPerfString(col, PERF_COUNT);
+}
+
+static void M_DrawRenderStats(void)
+{
+	const boolean hires = M_HighResolution();
+
+	const int half_row = hires ? 5 : 4;
+
+	precise_t extrarendertime;
+
+	perfstatrow_t frametime_row[] = {
+		{"frmtime", "Frame time:    ", &ps_frametime},
+		{0}
+	};
+
+	perfstatrow_t rendercalltime_row[] = {
+		{"drwtime", "3d rendering:  ", &ps_rendercalltime},
+		{0}
+	};
+
+	perfstatrow_t opengltime_row[] = {
+		{"skybox ", "Skybox render: ", &ps_hw_skyboxtime},
+		{"bsptime", "RenderBSPNode: ", &ps_bsptime},
+		{"nodesrt", "Drwnode sort:  ", &ps_hw_nodesorttime},
+		{"nodedrw", "Drwnode render:", &ps_hw_nodedrawtime},
+		{"sprsort", "Sprite sort:   ", &ps_hw_spritesorttime},
+		{"sprdraw", "Sprite render: ", &ps_hw_spritedrawtime},
+		{"other  ", "Other:         ", &extrarendertime},
+		{0}
+	};
+
+	perfstatrow_t softwaretime_row[] = {
+		{"bsptime", "RenderBSPNode: ", &ps_bsptime},
+		{"sprclip", "R_ClipSprites: ", &ps_sw_spritecliptime},
+		{"portals", "Portals+Skybox:", &ps_sw_portaltime},
+		{"planes ", "R_DrawPlanes:  ", &ps_sw_planetime},
+		{"masked ", "R_DrawMasked:  ", &ps_sw_maskedtime},
+		{"other  ", "Other:         ", &extrarendertime},
+		{0}
+	};
+
+	perfstatrow_t uiswaptime_row[] = {
+		{"ui     ", "UI render:     ", &ps_uitime},
+		{"finupdt", "I_FinishUpdate:", &ps_swaptime},
+		{0}
+	};
+
+	perfstatrow_t tictime_row[] = {
+		{"logic  ", "Game logic:    ", &ps_tictime},
+		{0}
+	};
+
+	perfstatrow_t rendercalls_row[] = {
+		{"bspcall", "BSP calls:   ", &ps_numbspcalls},
+		{"sprites", "Sprites:     ", &ps_numsprites},
+		{"drwnode", "Drawnodes:   ", &ps_numdrawnodes},
+		{"plyobjs", "Polyobjects: ", &ps_numpolyobjects},
+		{0}
+	};
+
+	perfstatrow_t batchtime_row[] = {
+		{"batsort", "Batch sort:  ", &ps_hw_batchsorttime},
+		{"batdraw", "Batch render:", &ps_hw_batchdrawtime},
+		{0}
+	};
+
+	perfstatrow_t batchcount_row[] = {
+		{"polygon", "Polygons:  ", &ps_hw_numpolys},
+		{"vertex ", "Vertices:  ", &ps_hw_numverts},
+		{0}
+	};
+
+	perfstatrow_t batchcalls_row[] = {
+		{"drwcall", "Draw calls:", &ps_hw_numcalls},
+		{"shaders", "Shaders:   ", &ps_hw_numshaders},
+		{"texture", "Textures:  ", &ps_hw_numtextures},
+		{"polyflg", "Polyflags: ", &ps_hw_numpolyflags},
+		{"colors ", "Colors:    ", &ps_hw_numcolors},
+		{0}
+	};
+
+	perfstatcol_t      frametime_col =  {20,  20, V_YELLOWMAP,      frametime_row};
+	perfstatcol_t rendercalltime_col =  {20,  20, V_YELLOWMAP, rendercalltime_row};
+
+	perfstatcol_t     opengltime_col =  {24,  24, V_YELLOWMAP,     opengltime_row};
+	perfstatcol_t   softwaretime_col =  {24,  24, V_YELLOWMAP,   softwaretime_row};
+
+	perfstatcol_t     uiswaptime_col =  {20,  20, V_YELLOWMAP,     uiswaptime_row};
+	perfstatcol_t        tictime_col =  {20,  20, V_GRAYMAP,          tictime_row};
+
+	perfstatcol_t    rendercalls_col =  {90, 115, V_BLUEMAP,      rendercalls_row};
+
+	perfstatcol_t      batchtime_col =  {90, 115, V_REDMAP,         batchtime_row};
+
+	perfstatcol_t     batchcount_col = {155, 200, V_PURPLEMAP,     batchcount_row};
+	perfstatcol_t     batchcalls_col = {220, 200, V_PURPLEMAP,     batchcalls_row};
+
+
+	boolean rendering = (
+			gamestate == GS_LEVEL ||
+			(gamestate == GS_TITLESCREEN && titlemapinaction)
+	);
+
+	draw_row = 10;
+	M_DrawPerfTiming(&frametime_col);
+
+	if (rendering)
 	{
-		int i = 0;
-		thinker_t *thinker;
-		int thinkercount = 0;
-		int polythcount = 0;
-		int mainthcount = 0;
-		int mobjcount = 0;
-		int nothinkcount = 0;
-		int scenerycount = 0;
-		int dynslopethcount = 0;
-		int precipcount = 0;
-		int removecount = 0;
-		// y offset for drawing columns
-		int yoffset1 = 0;
-		int yoffset2 = 0;
-
-		for (i = 0; i < NUM_THINKERLISTS; i++)
+		M_DrawPerfTiming(&rendercalltime_col);
+
+		// Remember to update this calculation when adding more 3d rendering stats!
+		extrarendertime = ps_rendercalltime - ps_bsptime;
+
+#ifdef HWRENDER
+		if (rendermode == render_opengl)
 		{
-			for (thinker = thlist[i].next; thinker != &thlist[i]; thinker = thinker->next)
+			extrarendertime -=
+				ps_hw_skyboxtime +
+				ps_hw_nodesorttime +
+				ps_hw_nodedrawtime +
+				ps_hw_spritesorttime +
+				ps_hw_spritedrawtime;
+
+			if (cv_glbatching.value)
 			{
-				thinkercount++;
-				if (thinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-					removecount++;
-				else if (i == THINK_POLYOBJ)
-					polythcount++;
-				else if (i == THINK_MAIN)
-					mainthcount++;
-				else if (i == THINK_MOBJ)
-				{
-					if (thinker->function.acp1 == (actionf_p1)P_MobjThinker)
-					{
-						mobj_t *mobj = (mobj_t*)thinker;
-						mobjcount++;
-						if (mobj->flags & MF_NOTHINK)
-							nothinkcount++;
-						else if (mobj->flags & MF_SCENERY)
-							scenerycount++;
-					}
-				}
-				else if (i == THINK_DYNSLOPE)
-					dynslopethcount++;
-				else if (i == THINK_PRECIP)
-					precipcount++;
+				extrarendertime -=
+					ps_hw_batchsorttime +
+					ps_hw_batchdrawtime;
 			}
+
+			M_DrawPerfTiming(&opengltime_col);
+		}
+		else
+#endif
+		{
+			extrarendertime -=
+				ps_sw_spritecliptime +
+				ps_sw_portaltime +
+				ps_sw_planetime +
+				ps_sw_maskedtime;
+
+			M_DrawPerfTiming(&softwaretime_col);
 		}
+	}
 
-		if (vid.width < 640 || vid.height < 400) // low resolution
+	M_DrawPerfTiming(&uiswaptime_col);
+
+	draw_row += half_row;
+	M_DrawPerfTiming(&tictime_col);
+
+	if (rendering)
+	{
+		draw_row = 10;
+		M_DrawPerfCount(&rendercalls_col);
+
+#ifdef HWRENDER
+		if (rendermode == render_opengl && cv_glbatching.value)
 		{
-			snprintf(s, sizeof s - 1, "logic   %d", ps_tictime);
-			V_DrawThinString(20, 10, V_MONOSPACE | V_YELLOWMAP, s);
-			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
-				return;
-			snprintf(s, sizeof s - 1, "plrthnk %d", ps_playerthink_time);
-			V_DrawThinString(24, 18, V_MONOSPACE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "thnkers %d", ps_thinkertime);
-			V_DrawThinString(24, 26, V_MONOSPACE | V_YELLOWMAP, s);
-			for (i = 0; i < NUM_THINKERLISTS; i++)
-			{
-				yoffset1 += 8;
-				snprintf(s, sizeof s - 1, thlist_shortnames[i], ps_thlist_times[i]);
-				V_DrawThinString(28, 26+yoffset1, V_MONOSPACE | V_YELLOWMAP, s);
-			}
-			snprintf(s, sizeof s - 1, "lthinkf %d", ps_lua_thinkframe_time);
-			V_DrawThinString(24, 34+yoffset1, V_MONOSPACE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "other   %d",
-				ps_tictime - ps_playerthink_time - ps_thinkertime - ps_lua_thinkframe_time);
-			V_DrawThinString(24, 42+yoffset1, V_MONOSPACE | V_YELLOWMAP, s);
-
-			snprintf(s, sizeof s - 1, "thnkers %d", thinkercount);
-			V_DrawThinString(90, 10, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "plyobjs %d", polythcount);
-			V_DrawThinString(94, 18, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "main    %d", mainthcount);
-			V_DrawThinString(94, 26, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "mobjs   %d", mobjcount);
-			V_DrawThinString(94, 34, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "regular %d", mobjcount - scenerycount - nothinkcount);
-			V_DrawThinString(98, 42, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "scenery %d", scenerycount);
-			V_DrawThinString(98, 50, V_MONOSPACE | V_BLUEMAP, s);
-			if (nothinkcount)
-			{
-				snprintf(s, sizeof s - 1, "nothink %d", nothinkcount);
-				V_DrawThinString(98, 58, V_MONOSPACE | V_BLUEMAP, s);
-				yoffset2 += 8;
-			}
-			snprintf(s, sizeof s - 1, "dynslop %d", dynslopethcount);
-			V_DrawThinString(94, 58+yoffset2, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "precip  %d", precipcount);
-			V_DrawThinString(94, 66+yoffset2, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "remove  %d", removecount);
-			V_DrawThinString(94, 74+yoffset2, V_MONOSPACE | V_BLUEMAP, s);
-
-			snprintf(s, sizeof s - 1, "lmhooks %d", ps_lua_mobjhooks);
-			V_DrawThinString(170, 10, V_MONOSPACE | V_PURPLEMAP, s);
-			snprintf(s, sizeof s - 1, "chkpos  %d", ps_checkposition_calls);
-			V_DrawThinString(170, 18, V_MONOSPACE | V_PURPLEMAP, s);
+			draw_row += half_row;
+			M_DrawPerfTiming(&batchtime_col);
+
+			draw_row = 10;
+			M_DrawPerfCount(&batchcount_col);
+
+			if (hires)
+				draw_row += half_row;
+			else
+				draw_row  = 10;
+
+			M_DrawPerfCount(&batchcalls_col);
 		}
-		else // high resolution
+#endif
+	}
+}
+
+static void M_DrawTickStats(void)
+{
+	int i = 0;
+	thinker_t *thinker;
+	int thinkercount = 0;
+	int polythcount = 0;
+	int mainthcount = 0;
+	int mobjcount = 0;
+	int nothinkcount = 0;
+	int scenerycount = 0;
+	int regularcount = 0;
+	int dynslopethcount = 0;
+	int precipcount = 0;
+	int removecount = 0;
+
+	precise_t extratime =
+		ps_tictime -
+		ps_playerthink_time -
+		ps_thinkertime -
+		ps_lua_thinkframe_time;
+
+	perfstatrow_t tictime_row[] = {
+		{"logic  ", "Game logic:     ", &ps_tictime},
+		{0}
+	};
+
+	perfstatrow_t thinker_time_row[] = {
+		{"plrthnk", "P_PlayerThink:  ", &ps_playerthink_time},
+		{"thnkers", "P_RunThinkers:  ", &ps_thinkertime},
+		{0}
+	};
+
+	perfstatrow_t detailed_thinker_time_row[] = {
+		{"plyobjs", "Polyobjects:    ", &ps_thlist_times[THINK_POLYOBJ]},
+		{"main   ", "Main:           ", &ps_thlist_times[THINK_MAIN]},
+		{"mobjs  ", "Mobjs:          ", &ps_thlist_times[THINK_MOBJ]},
+		{"dynslop", "Dynamic slopes: ", &ps_thlist_times[THINK_DYNSLOPE]},
+		{"precip ", "Precipitation:  ", &ps_thlist_times[THINK_PRECIP]},
+		{0}
+	};
+
+	perfstatrow_t extra_thinker_time_row[] = {
+		{"lthinkf", "LUAh_ThinkFrame:", &ps_lua_thinkframe_time},
+		{"other  ", "Other:          ", &extratime},
+		{0}
+	};
+
+	perfstatrow_t thinkercount_row[] = {
+		{"thnkers", "Thinkers:       ", &thinkercount},
+		{0}
+	};
+
+	perfstatrow_t detailed_thinkercount_row[] = {
+		{"plyobjs", "Polyobjects:    ", &polythcount},
+		{"main   ", "Main:           ", &mainthcount},
+		{"mobjs  ", "Mobjs:          ", &mobjcount},
+		{0}
+	};
+
+	perfstatrow_t mobjthinkercount_row[] = {
+		{"regular", "Regular:        ", &regularcount},
+		{"scenery", "Scenery:        ", &scenerycount},
+		{0}
+	};
+
+	perfstatrow_t nothinkcount_row[] = {
+		{"nothink", "Nothink:        ", &nothinkcount},
+		{0}
+	};
+
+	perfstatrow_t detailed_thinkercount_row2[] = {
+		{"dynslop", "Dynamic slopes: ", &dynslopethcount},
+		{"precip ", "Precipitation:  ", &precipcount},
+		{"remove ", "Pending removal:", &removecount},
+		{0}
+	};
+
+	perfstatrow_t misc_calls_row[] = {
+		{"lmhook", "Lua mobj hooks: ", &ps_lua_mobjhooks},
+		{"chkpos", "P_CheckPosition:", &ps_checkposition_calls},
+		{0}
+	};
+
+	perfstatcol_t               tictime_col  =  {20,  20, V_YELLOWMAP,               tictime_row};
+	perfstatcol_t          thinker_time_col  =  {24,  24, V_YELLOWMAP,          thinker_time_row};
+	perfstatcol_t detailed_thinker_time_col  =  {28,  28, V_YELLOWMAP, detailed_thinker_time_row};
+	perfstatcol_t    extra_thinker_time_col  =  {24,  24, V_YELLOWMAP,    extra_thinker_time_row};
+
+	perfstatcol_t          thinkercount_col  =  {90, 115, V_BLUEMAP,            thinkercount_row};
+	perfstatcol_t detailed_thinkercount_col  =  {94, 119, V_BLUEMAP,   detailed_thinkercount_row};
+	perfstatcol_t      mobjthinkercount_col  =  {98, 123, V_BLUEMAP,        mobjthinkercount_row};
+	perfstatcol_t          nothinkcount_col  =  {98, 123, V_BLUEMAP,            nothinkcount_row};
+	perfstatcol_t detailed_thinkercount_col2 =  {94, 119, V_BLUEMAP,   detailed_thinkercount_row2};
+	perfstatcol_t            misc_calls_col  = {170, 216, V_PURPLEMAP,            misc_calls_row};
+
+	for (i = 0; i < NUM_THINKERLISTS; i++)
+	{
+		for (thinker = thlist[i].next; thinker != &thlist[i]; thinker = thinker->next)
 		{
-			snprintf(s, sizeof s - 1, "Game logic:      %d", ps_tictime);
-			V_DrawSmallString(20, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
-				return;
-			snprintf(s, sizeof s - 1, "P_PlayerThink:   %d", ps_playerthink_time);
-			V_DrawSmallString(24, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "P_RunThinkers:   %d", ps_thinkertime);
-			V_DrawSmallString(24, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			for (i = 0; i < NUM_THINKERLISTS; i++)
+			thinkercount++;
+			if (thinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				removecount++;
+			else if (i == THINK_POLYOBJ)
+				polythcount++;
+			else if (i == THINK_MAIN)
+				mainthcount++;
+			else if (i == THINK_MOBJ)
 			{
-				yoffset1 += 5;
-				snprintf(s, sizeof s - 1, thlist_names[i], ps_thlist_times[i]);
-				V_DrawSmallString(28, 20+yoffset1, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			}
-			snprintf(s, sizeof s - 1, "LUAh_ThinkFrame: %d", ps_lua_thinkframe_time);
-			V_DrawSmallString(24, 25+yoffset1, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "Other:           %d",
-				ps_tictime - ps_playerthink_time - ps_thinkertime - ps_lua_thinkframe_time);
-			V_DrawSmallString(24, 30+yoffset1, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, s);
-
-			snprintf(s, sizeof s - 1, "Thinkers:        %d", thinkercount);
-			V_DrawSmallString(115, 10+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Polyobjects:     %d", polythcount);
-			V_DrawSmallString(119, 15+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Main:            %d", mainthcount);
-			V_DrawSmallString(119, 20+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Mobjs:           %d", mobjcount);
-			V_DrawSmallString(119, 25+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Regular:         %d", mobjcount - scenerycount - nothinkcount);
-			V_DrawSmallString(123, 30+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Scenery:         %d", scenerycount);
-			V_DrawSmallString(123, 35+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			if (nothinkcount)
-			{
-				snprintf(s, sizeof s - 1, "Nothink:         %d", nothinkcount);
-				V_DrawSmallString(123, 40+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-				yoffset2 += 5;
+				if (thinker->function.acp1 == (actionf_p1)P_MobjThinker)
+				{
+					mobj_t *mobj = (mobj_t*)thinker;
+					mobjcount++;
+					if (mobj->flags & MF_NOTHINK)
+						nothinkcount++;
+					else if (mobj->flags & MF_SCENERY)
+						scenerycount++;
+					else
+						regularcount++;
+				}
 			}
-			snprintf(s, sizeof s - 1, "Dynamic slopes:  %d", dynslopethcount);
-			V_DrawSmallString(119, 40+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Precipitation:   %d", precipcount);
-			V_DrawSmallString(119, 45+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "Pending removal: %d", removecount);
-			V_DrawSmallString(119, 50+yoffset2, V_MONOSPACE | V_ALLOWLOWERCASE | V_BLUEMAP, s);
-
-			snprintf(s, sizeof s - 1, "Calls:");
-			V_DrawSmallString(212, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-			snprintf(s, sizeof s - 1, "Lua mobj hooks:  %d", ps_lua_mobjhooks);
-			V_DrawSmallString(216, 15, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
-			snprintf(s, sizeof s - 1, "P_CheckPosition: %d", ps_checkposition_calls);
-			V_DrawSmallString(216, 20, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, s);
+			else if (i == THINK_DYNSLOPE)
+				dynslopethcount++;
+			else if (i == THINK_PRECIP)
+				precipcount++;
 		}
 	}
+
+	draw_row = 10;
+	M_DrawPerfTiming(&tictime_col);
+	M_DrawPerfTiming(&thinker_time_col);
+	M_DrawPerfTiming(&detailed_thinker_time_col);
+	M_DrawPerfTiming(&extra_thinker_time_col);
+
+	draw_row = 10;
+	M_DrawPerfCount(&thinkercount_col);
+	M_DrawPerfCount(&detailed_thinkercount_col);
+	M_DrawPerfCount(&mobjthinkercount_col);
+
+	if (nothinkcount)
+		M_DrawPerfCount(&nothinkcount_col);
+
+	M_DrawPerfCount(&detailed_thinkercount_col2);
+
+	if (M_HighResolution())
+	{
+		V_DrawSmallString(212, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, "Calls:");
+
+		draw_row = 15;
+	}
+	else
+	{
+		draw_row = 10;
+	}
+
+	M_DrawPerfCount(&misc_calls_col);
+}
+
+void M_DrawPerfStats(void)
+{
+	char s[100];
+
+	PS_SetFrameTime();
+
+	if (cv_perfstats.value == 1) // rendering
+	{
+		M_DrawRenderStats();
+	}
+	else if (cv_perfstats.value == 2) // logic
+	{
+		M_DrawTickStats();
+	}
 	else if (cv_perfstats.value == 3) // lua thinkframe
 	{
 		if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
@@ -524,7 +565,7 @@ void M_DrawPerfStats(void)
 				len = (int)strlen(str);
 				if (len > 20)
 					str += len - 20;
-				snprintf(s, sizeof s - 1, "%20s: %u", str, thinkframe_hooks[i].time_taken);
+				snprintf(s, sizeof s - 1, "%20s: %d", str, I_PreciseToMicros(thinkframe_hooks[i].time_taken));
 				V_DrawSmallString(x, y, V_MONOSPACE | V_ALLOWLOWERCASE | text_color, s);
 				y += 4; // repeated code!
 				if (y > 192)
diff --git a/src/m_perfstats.h b/src/m_perfstats.h
index 01a818c1c360c3cdc6231069686edcb7bf3544ee..1ca71957fcb7581eaa9248a6905e83358b1abcb0 100644
--- a/src/m_perfstats.h
+++ b/src/m_perfstats.h
@@ -16,25 +16,25 @@
 #include "lua_script.h"
 #include "p_local.h"
 
-extern int ps_tictime;
+extern precise_t ps_tictime;
 
-extern int ps_playerthink_time;
-extern int ps_thinkertime;
+extern precise_t ps_playerthink_time;
+extern precise_t ps_thinkertime;
 
-extern int ps_thlist_times[];
+extern precise_t ps_thlist_times[];
 
-extern int ps_checkposition_calls;
+extern int       ps_checkposition_calls;
 
-extern int ps_lua_thinkframe_time;
-extern int ps_lua_mobjhooks;
+extern precise_t ps_lua_thinkframe_time;
+extern int       ps_lua_mobjhooks;
 
 typedef struct
 {
-	UINT32 time_taken;
+	precise_t time_taken;
 	char short_src[LUA_IDSIZE];
 } ps_hookinfo_t;
 
-void PS_SetThinkFrameHookInfo(int index, UINT32 time_taken, char* short_src);
+void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src);
 
 void M_DrawPerfStats(void);
 
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 22de9bc67325b1b2b85d076808d24345d04c27de..59176d6cc8ba44cc31d710ef6a87da801a1e62d8 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -3924,6 +3924,10 @@ 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);
@@ -4053,6 +4057,10 @@ bossjustdie:
 		}
 		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;
@@ -4904,7 +4912,7 @@ void A_ThrownRing(mobj_t *actor)
 		}
 
 		if (actor->tracer && (actor->tracer->health)
-			&& (actor->tracer->player->powers[pw_shield] & SH_PROTECTELECTRIC))// Already found someone to follow.
+			&& (actor->tracer->player && actor->tracer->player->powers[pw_shield] & SH_PROTECTELECTRIC))// Already found someone to follow.
 		{
 			const INT32 temp = actor->threshold;
 			actor->threshold = 32000;
@@ -5912,13 +5920,18 @@ void A_DetonChase(mobj_t *actor)
 
 	if (actor->reactiontime == -42)
 	{
-		fixed_t xyspeed;
+		fixed_t xyspeed, speed;
+
+		if (actor->target->player)
+			speed = actor->target->player->normalspeed;
+		else
+			speed = actor->target->info->speed;
 
 		actor->reactiontime = -42;
 
 		exact = actor->movedir>>ANGLETOFINESHIFT;
-		xyspeed = FixedMul(FixedMul(actor->tracer->player->normalspeed,3*FRACUNIT/4), FINECOSINE(exact));
-		actor->momz = FixedMul(FixedMul(actor->tracer->player->normalspeed,3*FRACUNIT/4), FINESINE(exact));
+		xyspeed = FixedMul(FixedMul(speed,3*FRACUNIT/4), FINECOSINE(exact));
+		actor->momz = FixedMul(FixedMul(speed,3*FRACUNIT/4), FINESINE(exact));
 
 		exact = actor->angle>>ANGLETOFINESHIFT;
 		actor->momx = FixedMul(xyspeed, FINECOSINE(exact));
@@ -7513,7 +7526,7 @@ void A_Boss2PogoTarget(mobj_t *actor)
 	}
 
 	// Target hit, retreat!
-	if (actor->target->player->powers[pw_flashing] > TICRATE || actor->flags2 & MF2_FRET)
+	if ((actor->target->player && actor->target->player->powers[pw_flashing] > TICRATE) || actor->flags2 & MF2_FRET)
 	{
 		UINT8 prandom = P_RandomByte();
 		actor->z++; // unstick from the floor
@@ -7524,7 +7537,7 @@ void A_Boss2PogoTarget(mobj_t *actor)
 	// Try to land on top of the player.
 	else if (P_AproxDistance(actor->x-actor->target->x, actor->y-actor->target->y) < FixedMul(512*FRACUNIT, actor->scale))
 	{
-		fixed_t airtime, gravityadd, zoffs;
+		fixed_t airtime, gravityadd, zoffs, height;
 
 		// check gravity in the sector (for later math)
 		P_CheckGravity(actor, true);
@@ -7546,7 +7559,13 @@ void A_Boss2PogoTarget(mobj_t *actor)
 		// Remember, kids!
 		// Reduced down Calculus lets you avoid bad 'logic math' loops!
 		//airtime = FixedDiv(-actor->momz<<1, gravityadd)<<1; // going from 0 to 0 is much simpler
-		zoffs = (P_GetPlayerHeight(actor->target->player)>>1) + (actor->target->floorz - actor->floorz); // offset by the difference in floor height plus half the player height,
+
+		if (actor->target->player)
+			height = P_GetPlayerHeight(actor->target->player) >> 1;
+		else
+			height = actor->target->height >> 1;
+
+		zoffs = height + (actor->target->floorz - actor->floorz); // offset by the difference in floor height plus half the player height,
 		airtime = FixedDiv((-actor->momz - FixedSqrt(FixedMul(actor->momz,actor->momz)+zoffs)), gravityadd)<<1; // to try and land on their head rather than on their feet
 
 		actor->angle = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
@@ -9860,22 +9879,23 @@ void A_Custom3DRotate(mobj_t *actor)
 	if (LUA_CallAction(A_CUSTOM3DROTATE, actor))
 		return;
 
-	if (actor->target->health == 0)
+	if (!actor->target) // Ensure we actually have a target first.
 	{
+		CONS_Printf("Error: A_Custom3DRotate: Object has no target.\n");
 		P_RemoveMobj(actor);
 		return;
 	}
 
-	if (!actor->target) // This should NEVER happen.
+	if (actor->target->health == 0)
 	{
-		if (cv_debug)
-			CONS_Printf("Error: Object has no target\n");
 		P_RemoveMobj(actor);
 		return;
 	}
+
 	if (hspeed==0 && vspeed==0)
 	{
-		CONS_Printf("Error: A_Custom3DRotate: Object has no speed.\n");
+		if (cv_debug)
+			CONS_Printf("Error: A_Custom3DRotate: Object has no speed.\n");
 		return;
 	}
 
diff --git a/src/p_floor.c b/src/p_floor.c
index ed49b03a38ef8b4381ad968a4540c09952d1d018..7c26065b59b6e9ef0f002bf20907e3eae75856cc 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -1064,9 +1064,7 @@ static mobj_t *SearchMarioNode(msecnode_t *node)
 		case MT_HOOP:
 		case MT_HOOPCOLLIDE:
 		case MT_NIGHTSCORE:
-#ifdef SEENAMES
 		case MT_NAMECHECK: // DEFINITELY not this, because it is client-side.
-#endif
 			continue;
 		default:
 			break;
diff --git a/src/p_inter.c b/src/p_inter.c
index 415c679e4922b1d55920bc5f937c39fee1927e30..e9a16a3dd143128a06fa5658cb8ad2d7fb35f4c2 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -470,14 +470,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 				{
 					fixed_t setmomz = -toucher->momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
-					
+
 					if (elementalpierce == 2) // Reset bubblewrap, part 1
 						P_DoBubbleBounce(player);
 					toucher->momz = setmomz;
 					if (elementalpierce == 2) // Reset bubblewrap, part 2
 					{
 						boolean underwater = toucher->eflags & MFE_UNDERWATER;
-							
+
 						if (underwater)
 							toucher->momz /= 2;
 						toucher->momz -= (toucher->momz/(underwater ? 8 : 4)); // Cap the height!
@@ -1388,6 +1388,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (player->bot)
 					return;
 
+				// Initialize my junk
+				junk.tags.tags = NULL;
+				junk.tags.count = 0;
+
 				Tag_FSet(&junk.tags, LE_AXE);
 				EV_DoElevator(&junk, bridgeFall, false);
 
@@ -1613,7 +1617,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 				if (special->tracer && !(special->tracer->flags2 & MF2_STRONGBOX))
 					macespin = true;
-				
+
 				if (macespin ? (player->powers[pw_ignorelatch] & (1<<15)) : (player->powers[pw_ignorelatch]))
 					return;
 
diff --git a/src/p_local.h b/src/p_local.h
index 8a508496208b82699b4bdde458f7639f7823805e..8568dd4f8c2d58481c1b15e7b5a9e65a864f77bc 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -143,6 +143,8 @@ angle_t P_GetLocalAngle(player_t *player);
 void P_SetLocalAngle(player_t *player, angle_t angle);
 void P_ForceLocalAngle(player_t *player, angle_t angle);
 boolean P_PlayerFullbright(player_t *player);
+boolean P_PlayerCanEnterSpinGaps(player_t *player);
+boolean P_PlayerShouldUseSpinHeight(player_t *player);
 
 boolean P_IsObjectInGoop(mobj_t *mo);
 boolean P_IsObjectOnGround(mobj_t *mo);
@@ -326,9 +328,7 @@ mobj_t *P_SpawnPointMissile(mobj_t *source, fixed_t xa, fixed_t ya, fixed_t za,
 mobj_t *P_SpawnAlteredDirectionMissile(mobj_t *source, mobjtype_t type, fixed_t x, fixed_t y, fixed_t z, INT32 shiftingAngle);
 mobj_t *P_SPMAngle(mobj_t *source, mobjtype_t type, angle_t angle, UINT8 aimtype, UINT32 flags2);
 #define P_SpawnPlayerMissile(s,t,f) P_SPMAngle(s,t,s->angle,true,f)
-#ifdef SEENAMES
 #define P_SpawnNameFinder(s,t) P_SPMAngle(s,t,s->angle,true,0)
-#endif
 void P_ColorTeamMissile(mobj_t *missile, player_t *source);
 SINT8 P_MobjFlip(mobj_t *mobj);
 fixed_t P_GetMobjGravity(mobj_t *mo);
diff --git a/src/p_map.c b/src/p_map.c
index 922c0d9ec06c8a7f6c74d73b209ab961cc8ec571..bf668ba3e8562db44f524066553350306f206ae1 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -727,9 +727,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	|| (thing->player && thing->player->spectator))
 		return true;
 
-#ifdef SEENAMES
-  // Do name checks all the way up here
-  // So that NOTHING ELSE can see MT_NAMECHECK because it is client-side.
+	// Do name checks all the way up here
+	// So that NOTHING ELSE can see MT_NAMECHECK because it is client-side.
 	if (tmthing->type == MT_NAMECHECK)
 	{
 		// Ignore things that aren't players, ignore spectators, ignore yourself.
@@ -753,7 +752,6 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		seenplayer = thing->player;
 		return false;
 	}
-#endif
 
 	// Metal Sonic destroys tiny baby objects.
 	if (tmthing->type == MT_METALSONIC_RACE
@@ -982,7 +980,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	if (thing->type == MT_SALOONDOOR && tmthing->player)
 	{
 		mobj_t *ref = (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)) ? tmthing->tracer : tmthing;
-		if ((thing->flags2 & MF2_AMBUSH) || ref != tmthing)
+		if (((thing->flags2 & MF2_AMBUSH) && (tmthing->z <= thing->z + thing->height) && (tmthing->z + tmthing->height >= thing->z))
+			|| ref != tmthing)
 		{
 			fixed_t dm = min(FixedHypot(ref->momx, ref->momy), 16*FRACUNIT);
 			angle_t ang = R_PointToAngle2(0, 0, ref->momx, ref->momy) - thing->angle;
@@ -995,7 +994,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	if (thing->type == MT_SALOONDOORCENTER && tmthing->player)
 	{
-		if ((thing->flags2 & MF2_AMBUSH) || (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)))
+		if (((thing->flags2 & MF2_AMBUSH) && (tmthing->z <= thing->z + thing->height) && (tmthing->z + tmthing->height >= thing->z))
+			|| (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)))
 			return true;
 	}
 
@@ -1683,7 +1683,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 					if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 					{
 						fixed_t setmomz = -*momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
-					
+
 						if (elementalpierce == 2) // Reset bubblewrap, part 1
 							P_DoBubbleBounce(player);
 						*momz = setmomz; // Therefore, you should be thrust in the opposite direction, vertically.
@@ -1692,7 +1692,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 						if (elementalpierce == 2) // Reset bubblewrap, part 2
 						{
 							boolean underwater = tmthing->eflags & MFE_UNDERWATER;
-							
+
 							if (underwater)
 								*momz /= 2;
 							*momz -= (*momz/(underwater ? 8 : 4)); // Cap the height!
@@ -2257,6 +2257,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 			{
 				if (!P_BlockThingsIterator(bx, by, PIT_CheckThing))
 					blockval = false;
+				else
+					tmhitthing = tmfloorthing;
 				if (P_MobjWasRemoved(tmthing))
 					return false;
 			}
@@ -2721,7 +2723,10 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 			if (thing->type == MT_SKIM)
 				maxstep = 0;
 
-			if (tmceilingz - tmfloorz < thing->height)
+			if (tmceilingz - tmfloorz < thing->height
+				|| (thing->player
+					&& tmceilingz - tmfloorz < P_GetPlayerHeight(thing->player)
+					&& !P_PlayerCanEnterSpinGaps(thing->player)))
 			{
 				if (tmfloorthing)
 					tmhitthing = tmfloorthing;
@@ -3329,6 +3334,11 @@ static boolean PTR_LineIsBlocking(line_t *li)
 	if (openbottom - slidemo->z > FixedMul(MAXSTEPMOVE, slidemo->scale))
 		return true; // too big a step up
 
+	if (slidemo->player
+		&& openrange < P_GetPlayerHeight(slidemo->player)
+		&& !P_PlayerCanEnterSpinGaps(slidemo->player))
+			return true; // nonspin character should not take this path
+
 	return false;
 }
 
@@ -3443,9 +3453,17 @@ static boolean PTR_SlideTraverse(intercept_t *in)
 			P_ProcessSpecialSector(slidemo->player, slidemo->subsector->sector, li->polyobj->lines[0]->backsector);
 	}
 
-	if (slidemo->player && slidemo->player->charability == CA_GLIDEANDCLIMB
-		&& (slidemo->player->pflags & PF_GLIDING || slidemo->player->climbing))
-		PTR_GlideClimbTraverse(li);
+	if (slidemo->player)
+	{
+		if (slidemo->player->charability == CA_GLIDEANDCLIMB
+			&& (slidemo->player->pflags & PF_GLIDING || slidemo->player->climbing))
+			PTR_GlideClimbTraverse(li);
+		else
+		{
+			slidemo->player->lastsidehit = li->sidenum[P_PointOnLineSide(slidemo->x, slidemo->y, li)];
+			slidemo->player->lastlinehit = (INT16)(li - lines);
+		}
+	}
 
 	if (in->frac < bestslidefrac && (!slidemo->player || !slidemo->player->climbing))
 	{
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 7ba6d1fad979ff68c563f00693c1ba94b5fbdf93..49db6daee9916632abb3a9fedb3c18a6f6871e82 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3188,13 +3188,16 @@ boolean P_SceneryZMovement(mobj_t *mo)
 //
 boolean P_CanRunOnWater(player_t *player, ffloor_t *rover)
 {
-	fixed_t topheight = P_GetFFloorTopZAt(rover, player->mo->x, player->mo->y);
+	boolean flip = player->mo->eflags & MFE_VERTICALFLIP;
+	fixed_t surfaceheight = flip ? P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y) : P_GetFFloorTopZAt(rover, player->mo->x, player->mo->y);
+	fixed_t playerbottom = flip ? (player->mo->z + player->mo->height) : player->mo->z;
+	boolean doifit = flip ? (surfaceheight - player->mo->floorz >= player->mo->height) : (player->mo->ceilingz - surfaceheight >= player->mo->height);
 
 	if (!player->powers[pw_carry] && !player->homing
-		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= DASHMODE_THRESHOLD) && player->mo->ceilingz-topheight >= player->mo->height)
+		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= DASHMODE_THRESHOLD) && doifit)
 		&& (rover->flags & FF_SWIMMABLE) && !(player->pflags & PF_SPINNING) && player->speed > FixedMul(player->runspeed, player->mo->scale)
 		&& !(player->pflags & PF_SLIDING)
-		&& abs(player->mo->z - topheight) < FixedMul(30*FRACUNIT, player->mo->scale))
+		&& abs(playerbottom - surfaceheight) < FixedMul(30*FRACUNIT, player->mo->scale))
 		return true;
 
 	return false;
@@ -3366,7 +3369,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 			}
 
 			// skipping stone!
-			if (p && (p->charability2 == CA2_SPINDASH) && p->speed/2 > abs(mobj->momz)
+			if (p && p->speed/2 > abs(mobj->momz)
 				&& ((p->pflags & (PF_SPINNING|PF_JUMPED)) == PF_SPINNING)
 				&& ((!(mobj->eflags & MFE_VERTICALFLIP) && thingtop - mobj->momz > mobj->watertop)
 				|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z - mobj->momz < mobj->waterbottom)))
@@ -5654,14 +5657,10 @@ static void P_Boss9Thinker(mobj_t *mobj)
 				if (P_RandomRange(1,(dist>>FRACBITS)/16) == 1)
 					break;
 			}
-			if (spawner)
+			if (spawner && dist)
 			{
 				mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER);
-
-				if (dist == 0)
-					missile->fuse = 0;
-				else
-					missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
+				missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
 
 				if (missile->fuse > mobj->fuse)
 					P_RemoveMobj(missile);
@@ -7937,7 +7936,7 @@ static boolean P_MobjPushableThink(mobj_t *mobj)
 	P_PushableThinker(mobj);
 
 	// Extinguish fire objects in water. (Yes, it's extraordinarily rare to have a pushable flame object, but Brak uses such a case.)
-	if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
+	if ((mobj->flags & MF_FIRE) && !(mobj->eflags & MFE_TOUCHLAVA)
 		&& (mobj->eflags & (MFE_UNDERWATER | MFE_TOUCHWATER)))
 	{
 		P_KillMobj(mobj, NULL, NULL, 0);
@@ -9651,6 +9650,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 		break;
 	}
 	case MT_SALOONDOOR:
+		if (!mobj->tracer) // Door center is gone or not spawned?
+		{
+			P_RemoveMobj(mobj); // Die
+			return false;
+		}
+
 		P_SaloonDoorThink(mobj);
 		break;
 	case MT_MINECARTSPAWNER:
@@ -9705,7 +9710,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 		P_MobjCheckWater(mobj);
 
 		// Extinguish fire objects in water
-		if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
+		if ((mobj->flags & MF_FIRE) && !(mobj->eflags & MFE_TOUCHLAVA)
 			&& (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
 		{
 			P_KillMobj(mobj, NULL, NULL, 0);
@@ -9835,7 +9840,7 @@ static void P_FlagFuseThink(mobj_t *mobj)
 	if (mobj->type == MT_REDFLAG)
 	{
 		if (!(mobj->flags2 & MF2_JUSTATTACKED))
-			CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x85, M_GetText("Red flag"), 0x80);
+			CONS_Printf(M_GetText("The \205Red flag\200 has returned to base.\n"));
 
 		// Assumedly in splitscreen players will be on opposing teams
 		if (players[consoleplayer].ctfteam == 1 || splitscreen)
@@ -9848,7 +9853,7 @@ static void P_FlagFuseThink(mobj_t *mobj)
 	else // MT_BLUEFLAG
 	{
 		if (!(mobj->flags2 & MF2_JUSTATTACKED))
-			CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x84, M_GetText("Blue flag"), 0x80);
+			CONS_Printf(M_GetText("The \204Blue flag\200 has returned to base.\n"));
 
 		// Assumedly in splitscreen players will be on opposing teams
 		if (players[consoleplayer].ctfteam == 2 || splitscreen)
@@ -10481,9 +10486,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 	P_SetThingPosition(mobj);
 	I_Assert(mobj->subsector != NULL);
 
-	// Make sure scale matches destscale immediately when spawned
-	P_SetScale(mobj, mobj->destscale);
-
 	mobj->floorz   = P_GetSectorFloorZAt  (mobj->subsector->sector, x, y);
 	mobj->ceilingz = P_GetSectorCeilingZAt(mobj->subsector->sector, x, y);
 
@@ -11397,6 +11399,10 @@ void P_SpawnPlayer(INT32 playernum)
 		p->jumpfactor = skins[p->skin].jumpfactor;
 	}
 
+	// Clear lastlinehit and lastsidehit
+	p->lastsidehit = -1;
+	p->lastlinehit = -1;
+
 	//awayview stuff
 	p->awayviewmobj = NULL;
 	p->awayviewtics = 0;
@@ -11792,7 +11798,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 		if (!(G_CoopGametype() || (mthing->options & MTF_EXTRA)))
 			return false; // she doesn't hang out here
 
-		if (!mariomode && !(netgame || multiplayer) && players[consoleplayer].skin == 3)
+		if (!(netgame || multiplayer) && players[consoleplayer].skin == 3)
 			return false; // no doubles
 
 		break;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index adedea049108ecbd1d44b8931bb0854365128005..03229e740bc0b5cddc3940ee8df0b96d1843347d 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1506,7 +1506,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 {
 	const mobj_t *mobj = (const mobj_t *)th;
 	UINT32 diff;
-	UINT16 diff2;
+	UINT32 diff2;
 
 	// Ignore stationary hoops - these will be respawned from mapthings.
 	if (mobj->type == MT_HOOP)
@@ -1638,7 +1638,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		diff2 |= MD2_SHADOWSCALE;
 	if (mobj->renderflags)
 		diff2 |= MD2_RENDERFLAGS;
-	if (mobj->renderflags)
+	if (mobj->blendmode != AST_TRANSLUCENT)
 		diff2 |= MD2_BLENDMODE;
 	if (mobj->spritexscale != FRACUNIT)
 		diff2 |= MD2_SPRITEXSCALE;
@@ -1646,6 +1646,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		diff2 |= MD2_SPRITEYSCALE;
 	if (mobj->spritexoffset)
 		diff2 |= MD2_SPRITEXOFFSET;
+	if (mobj->spriteyoffset)
+		diff2 |= MD2_SPRITEYOFFSET;
 	if (mobj->floorspriteslope)
 	{
 		pslope_t *slope = mobj->floorspriteslope;
@@ -1667,7 +1669,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, type);
 	WRITEUINT32(save_p, diff);
 	if (diff & MD_MORE)
-		WRITEUINT16(save_p, diff2);
+		WRITEUINT32(save_p, diff2);
 
 	// save pointer, at load time we will search this pointer to reinitilize pointers
 	WRITEUINT32(save_p, (size_t)mobj);
@@ -2615,14 +2617,14 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 	thinker_t *next;
 	mobj_t *mobj;
 	UINT32 diff;
-	UINT16 diff2;
+	UINT32 diff2;
 	INT32 i;
 	fixed_t z, floorz, ceilingz;
 	ffloor_t *floorrover = NULL, *ceilingrover = NULL;
 
 	diff = READUINT32(save_p);
 	if (diff & MD_MORE)
-		diff2 = READUINT16(save_p);
+		diff2 = READUINT32(save_p);
 	else
 		diff2 = 0;
 
@@ -2843,10 +2845,16 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		mobj->renderflags = READUINT32(save_p);
 	if (diff2 & MD2_BLENDMODE)
 		mobj->blendmode = READINT32(save_p);
+	else
+		mobj->blendmode = AST_TRANSLUCENT;
 	if (diff2 & MD2_SPRITEXSCALE)
 		mobj->spritexscale = READFIXED(save_p);
+	else
+		mobj->spritexscale = FRACUNIT;
 	if (diff2 & MD2_SPRITEYSCALE)
 		mobj->spriteyscale = READFIXED(save_p);
+	else
+		mobj->spriteyscale = FRACUNIT;
 	if (diff2 & MD2_SPRITEXOFFSET)
 		mobj->spritexoffset = READFIXED(save_p);
 	if (diff2 & MD2_SPRITEYOFFSET)
diff --git a/src/p_setup.c b/src/p_setup.c
index 918ffbd4e6955348e4597963520245fd2ef353a6..40dd1a28477a50c3216372314facfd5efdab8984 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1621,6 +1621,14 @@ 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))
+	{
+		size_t argnum = param[3] - '0';
+		if (argnum >= NUMLINESTRINGARGS)
+			return;
+		lines[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
+		M_Memcpy(lines[i].stringargs[argnum], val, strlen(val) + 1);
+	}
 	else if (fastncmp(param, "arg", 3) && strlen(param) > 3)
 	{
 		size_t argnum = atol(param + 3);
@@ -1628,14 +1636,6 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 			return;
 		lines[i].args[argnum] = atol(val);
 	}
-	else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9)
-	{
-		size_t argnum = param[9] - '0';
-		if (argnum >= NUMLINESTRINGARGS)
-			return;
-		lines[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
-		M_Memcpy(lines[i].stringargs[argnum], val, strlen(val) + 1);
-	}
 	else if (fastcmp(param, "sidefront"))
 		lines[i].sidenum[0] = atol(val);
 	else if (fastcmp(param, "sideback"))
@@ -1720,6 +1720,14 @@ static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
 	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))
+	{
+		size_t argnum = param[3] - '0';
+		if (argnum >= NUMMAPTHINGSTRINGARGS)
+			return;
+		mapthings[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
+		M_Memcpy(mapthings[i].stringargs[argnum], val, strlen(val) + 1);
+	}
 	else if (fastncmp(param, "arg", 3) && strlen(param) > 3)
 	{
 		size_t argnum = atol(param + 3);
@@ -1727,14 +1735,6 @@ static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
 			return;
 		mapthings[i].args[argnum] = atol(val);
 	}
-	else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9)
-	{
-		size_t argnum = param[9] - '0';
-		if (argnum >= NUMMAPTHINGSTRINGARGS)
-			return;
-		mapthings[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
-		M_Memcpy(mapthings[i].stringargs[argnum], val, strlen(val) + 1);
-	}
 }
 
 /** From a given position table, run a specified parser function through a {}-encapsuled text.
@@ -3171,7 +3171,7 @@ static void P_ConvertBinaryMap(void)
 		switch (mapthings[i].type)
 		{
 		case 750:
-			Tag_Add(&mapthings[i].tags, mapthings[i].angle);
+			Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
 			break;
 		case 760:
 		case 761:
@@ -4135,7 +4135,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 
 #ifdef HWRENDER
 	// Free GPU textures before freeing patches.
-	if (vid.glstate == VID_GL_LIBRARY_LOADED)
+	if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
 		HWR_ClearAllTextures();
 #endif
 
@@ -4500,7 +4500,7 @@ boolean P_AddWadFile(const char *wadfilename)
 
 #ifdef HWRENDER
 	// Free GPU textures before freeing patches.
-	if (vid.glstate == VID_GL_LIBRARY_LOADED)
+	if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
 		HWR_ClearAllTextures();
 #endif
 
diff --git a/src/p_setup.h b/src/p_setup.h
index 34de9c93da1c4a91f7c46cc25af8107136df530e..5d13ae7d429d94d5c1fed55b08ff275fe3d3b353 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -80,6 +80,7 @@ typedef struct
 	UINT8 *picture;
 #ifdef HWRENDER
 	void *mipmap;
+	void *mippic;
 #endif
 } levelflat_t;
 
diff --git a/src/p_spec.c b/src/p_spec.c
index a1afdd00ddb12c4c4253e4dfe4547a8e3391734d..226e58d154423037c980835f61447a2ca8bb500d 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4304,7 +4304,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			if (leveltime % (TICRATE/2) == 0 && player->rings > 0)
 			{
 				player->rings--;
-				S_StartSound(player->mo, sfx_itemup);
+				S_StartSound(player->mo, sfx_antiri);
 			}
 			break;
 		case 11: // Special Stage Damage
@@ -4420,15 +4420,19 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			// clear the special so you can't push the button twice.
 			sector->special = 0;
 
+			// Initialize my junk
+			junk.tags.tags = NULL;
+			junk.tags.count = 0;
+
 			// Move the button down
-			Tag_FSet(&junk.tags, 680);
+			Tag_FSet(&junk.tags, LE_CAPSULE0);
 			EV_DoElevator(&junk, elevateDown, false);
 
 			// Open the top FOF
-			Tag_FSet(&junk.tags, 681);
+			Tag_FSet(&junk.tags, LE_CAPSULE1);
 			EV_DoFloor(&junk, raiseFloorToNearestFast);
 			// Open the bottom FOF
-			Tag_FSet(&junk.tags, 682);
+			Tag_FSet(&junk.tags, LE_CAPSULE2);
 			EV_DoCeiling(&junk, lowerToLowestFast);
 
 			// Mark all players with the time to exit thingy!
@@ -4503,7 +4507,7 @@ DoneSection2:
 
 				P_InstaThrust(player->mo, player->mo->angle, linespeed);
 
-				if ((lines[i].flags & ML_EFFECT5) && (player->charability2 == CA2_SPINDASH)) // Roll!
+				if (lines[i].flags & ML_EFFECT5) // Roll!
 				{
 					if (!(player->pflags & PF_SPINNING))
 						player->pflags |= PF_SPINNING;
@@ -4600,7 +4604,7 @@ DoneSection2:
 
 					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
 					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sBLUE FLAG%s.\\\\\\\\"), "\x85", player_names[player-players], "\x80", "\x84", "\x80"));
+					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);
@@ -4633,7 +4637,7 @@ DoneSection2:
 
 					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
 					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sRED FLAG%s.\\\\\\\\"), "\x84", player_names[player-players], "\x80", "\x85", "\x80"));
+					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);
@@ -4669,7 +4673,7 @@ DoneSection2:
 			break;
 
 		case 7: // Make player spin
-			if (!(player->pflags & PF_SPINNING) && P_IsObjectOnGround(player->mo) && (player->charability2 == CA2_SPINDASH))
+			if (!(player->pflags & PF_SPINNING))
 			{
 				player->pflags |= PF_SPINNING;
 				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
@@ -4823,6 +4827,8 @@ DoneSection2:
 
 					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);
 
diff --git a/src/p_tick.c b/src/p_tick.c
index da2a980c480a54f22d2dc2c3ef2904e96c07a1e8..c0a1c5700727eecd19ce386d7ea0e708cb28a8bb 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -22,7 +22,7 @@
 #include "lua_script.h"
 #include "lua_hook.h"
 #include "m_perfstats.h"
-#include "i_system.h" // I_GetTimeMicros
+#include "i_system.h" // I_GetPreciseTime
 
 // Object place
 #include "m_cheat.h"
@@ -323,7 +323,7 @@ static inline void P_RunThinkers(void)
 	size_t i;
 	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		ps_thlist_times[i] = I_GetTimeMicros();
+		ps_thlist_times[i] = I_GetPreciseTime();
 		for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = currentthinker->next)
 		{
 #ifdef PARANOIA
@@ -331,7 +331,7 @@ static inline void P_RunThinkers(void)
 #endif
 			currentthinker->function.acp1(currentthinker);
 		}
-		ps_thlist_times[i] = I_GetTimeMicros() - ps_thlist_times[i];
+		ps_thlist_times[i] = I_GetPreciseTime() - ps_thlist_times[i];
 	}
 
 }
@@ -658,11 +658,11 @@ void P_Ticker(boolean run)
 
 		LUAh_PreThinkFrame();
 
-		ps_playerthink_time = I_GetTimeMicros();
+		ps_playerthink_time = I_GetPreciseTime();
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
 				P_PlayerThink(&players[i]);
-		ps_playerthink_time = I_GetTimeMicros() - ps_playerthink_time;
+		ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time;
 	}
 
 	// Keep track of how long they've been playing!
@@ -677,18 +677,18 @@ void P_Ticker(boolean run)
 
 	if (run)
 	{
-		ps_thinkertime = I_GetTimeMicros();
+		ps_thinkertime = I_GetPreciseTime();
 		P_RunThinkers();
-		ps_thinkertime = I_GetTimeMicros() - ps_thinkertime;
+		ps_thinkertime = I_GetPreciseTime() - ps_thinkertime;
 
 		// Run any "after all the other thinkers" stuff
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
 				P_PlayerAfterThink(&players[i]);
 
-		ps_lua_thinkframe_time = I_GetTimeMicros();
+		ps_lua_thinkframe_time = I_GetPreciseTime();
 		LUAh_ThinkFrame();
-		ps_lua_thinkframe_time = I_GetTimeMicros() - ps_lua_thinkframe_time;
+		ps_lua_thinkframe_time = I_GetPreciseTime() - ps_lua_thinkframe_time;
 	}
 
 	// Run shield positioning
diff --git a/src/p_user.c b/src/p_user.c
index c5f919c78ec4f6ee73f8535f730ef7883e549cc3..4413cc6cdfe583ddf18c3ada79ed8c4a6a39bc66 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -190,7 +190,7 @@ fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move)
 boolean P_AutoPause(void)
 {
 	// Don't pause even on menu-up or focus-lost in netgames or record attack
-	if (netgame || modeattacking || gamestate == GS_TITLESCREEN)
+	if (netgame || modeattacking || gamestate == GS_TITLESCREEN || (marathonmode && gamestate == GS_INTERMISSION))
 		return false;
 
 	return (menuactive || ( window_notinfocus && cv_pauseifunfocused.value ));
@@ -1341,7 +1341,7 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 	// Transformation animation
 	P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_TRANS1);
 
-	if (giverings)
+	if (giverings && player->rings < 50)
 		player->rings = 50;
 
 	// Just in case.
@@ -1491,10 +1491,10 @@ void P_PlayLivesJingle(player_t *player)
 	if (player && !P_IsLocalPlayer(player))
 		return;
 
-	if (use1upSound || cv_1upsound.value)
-		S_StartSound(NULL, sfx_oneup);
-	else if (mariomode)
+	if (mariomode)
 		S_StartSound(NULL, sfx_marioa);
+	else if (use1upSound || cv_1upsound.value)
+		S_StartSound(NULL, sfx_oneup);
 	else
 	{
 		P_PlayJingle(player, JT_1UP);
@@ -2329,7 +2329,8 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 			P_MobjCheckWater(player->mo);
 			if (player->pflags & PF_SPINNING)
 			{
-				if (player->mo->state-states != S_PLAY_ROLL && !(player->pflags & PF_STARTDASH))
+				if (!(player->pflags & PF_STARTDASH) && player->panim != PA_ROLL && player->panim != PA_ETC
+				&& player->panim != PA_ABILITY && player->panim != PA_ABILITY2)
 				{
 					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 					S_StartSound(player->mo, sfx_spin);
@@ -2612,10 +2613,10 @@ static void P_CheckBustableBlocks(player_t *player)
 
 	if ((netgame || multiplayer) && player->spectator)
 		return;
-	
+
 	oldx = player->mo->x;
 	oldy = player->mo->y;
-	
+
 	if (!(player->pflags & PF_BOUNCING)) // Bouncers only get to break downwards, not sideways
 	{
 		P_UnsetThingPosition(player->mo);
@@ -2634,7 +2635,7 @@ static void P_CheckBustableBlocks(player_t *player)
 
 		if (!node->m_sector->ffloors)
 			continue;
-		
+
 		for (rover = node->m_sector->ffloors; rover; rover = rover->next)
 		{
 			if (!P_PlayerCanBust(player, rover))
@@ -4498,7 +4499,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 	if (twodlevel || (player->mo->flags2 & MF2_TWOD))
 		factor += player->jumpfactor / 10;
 
-	if (player->charflags & SF_MULTIABILITY && player->charability == CA_DOUBLEJUMP)
+	if (player->charflags & SF_MULTIABILITY && player->charability == CA_DOUBLEJUMP && (player->actionspd >> FRACBITS) != -1)
 		factor -= max(0, player->secondjump * player->jumpfactor / ((player->actionspd >> FRACBITS) + 1)); // Reduce the jump height each time
 
 	//if (maptol & TOL_NIGHTS)
@@ -4525,6 +4526,9 @@ void P_DoJump(player_t *player, boolean soundandstate)
 
 	player->pflags |= P_GetJumpFlags(player);;
 
+	if (player->charflags & SF_NOJUMPDAMAGE)
+		player->pflags &= ~PF_SPINNING;
+
 	if (soundandstate)
 	{
 		if (!player->spectator)
@@ -4876,22 +4880,28 @@ void P_DoBubbleBounce(player_t *player)
 //
 void P_DoAbilityBounce(player_t *player, boolean changemomz)
 {
-	fixed_t prevmomz;
 	if (player->mo->state-states == S_PLAY_BOUNCE_LANDING)
 		return;
+
 	if (changemomz)
 	{
-		fixed_t minmomz;
-		prevmomz = player->mo->momz;
+		fixed_t prevmomz = player->mo->momz, minmomz;
+
 		if (P_MobjFlip(player->mo)*prevmomz < 0)
 			prevmomz = 0;
 		else if (player->mo->eflags & MFE_UNDERWATER)
 			prevmomz /= 2;
+
 		P_DoJump(player, false);
 		player->pflags &= ~(PF_STARTJUMP|PF_JUMPED);
 		minmomz = FixedMul(player->mo->momz, 3*FRACUNIT/2);
-		player->mo->momz = max(minmomz, (minmomz + prevmomz)/2);
+
+		if (player->mo->eflags & MFE_VERTICALFLIP) // Use "min" or "max" depending on if the player is flipped
+			player->mo->momz = min(minmomz, (minmomz + prevmomz)/2);
+		else
+			player->mo->momz = max(minmomz, (minmomz + prevmomz)/2);
 	}
+
 	S_StartSound(player->mo, sfx_boingf);
 	P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE_LANDING);
 	player->pflags |= PF_BOUNCING|PF_THOKKED;
@@ -5020,7 +5030,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 	if ((player->powers[pw_shield] & SH_NOSTACK) && !player->powers[pw_super] && !(player->pflags & PF_SPINDOWN)
 		&& ((!(player->pflags & PF_THOKKED) || (((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP || (player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT) && player->secondjump == UINT8_MAX) ))) // thokked is optional if you're bubblewrapped / 3dblasted
 	{
-		if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT)
+		if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT && !(player->charflags & SF_NOSHIELDABILITY))
 		{
 			if ((lockonshield = P_LookForEnemies(player, false, false)))
 			{
@@ -5043,7 +5053,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 				}
 			}
 		}
-		if (cmd->buttons & BT_SPIN && !LUAh_ShieldSpecial(player)) // Spin button effects
+		if ((!(player->charflags & SF_NOSHIELDABILITY)) && (cmd->buttons & BT_SPIN && !LUAh_ShieldSpecial(player))) // Spin button effects
 		{
 			// Force stop
 			if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
@@ -5491,7 +5501,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						break;
 				}
 		}
-		else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_WHIRLWIND && !player->powers[pw_super])
+		else if ((!(player->charflags & SF_NOSHIELDABILITY)) && ((player->powers[pw_shield] & SH_NOSTACK) == SH_WHIRLWIND && !player->powers[pw_super] && !LUAh_ShieldSpecial(player)))
 			P_DoJumpShield(player);
 	}
 
@@ -7752,6 +7762,11 @@ void P_ElementalFire(player_t *player, boolean cropcircle)
 			flame->eflags = (flame->eflags & ~MFE_VERTICALFLIP)|(player->mo->eflags & MFE_VERTICALFLIP);
 			P_InstaThrust(flame, flame->angle, FixedMul(3*FRACUNIT, flame->scale));
 			P_SetObjectMomZ(flame, 3*FRACUNIT, false);
+			if (!(gametyperules & GTR_FRIENDLY))
+			{
+				P_SetMobjState(flame, S_TEAM_SPINFIRE1);
+				flame->color = player->mo->color;
+			}
 		}
 #undef limitangle
 #undef numangles
@@ -7779,6 +7794,11 @@ void P_ElementalFire(player_t *player, boolean cropcircle)
 			flame->destscale = player->mo->scale;
 			P_SetScale(flame, player->mo->scale);
 			flame->eflags = (flame->eflags & ~MFE_VERTICALFLIP)|(player->mo->eflags & MFE_VERTICALFLIP);
+			if (!(gametyperules & GTR_FRIENDLY))
+			{
+				P_SetMobjState(flame, S_TEAM_SPINFIRE1);
+				flame->color = player->mo->color;
+			}
 
 			flame->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
 			P_XYMovement(flame);
@@ -8605,12 +8625,6 @@ void P_MovePlayer(player_t *player)
 		player->climbing--;
 	}
 
-	if (!player->climbing)
-	{
-		player->lastsidehit = -1;
-		player->lastlinehit = -1;
-	}
-
 	// Make sure you're not teetering when you shouldn't be.
 	if (player->panim == PA_EDGE
 	&& (player->mo->momx || player->mo->momy || player->mo->momz))
@@ -8635,41 +8649,47 @@ void P_MovePlayer(player_t *player)
 		P_DoFiring(player, cmd);
 
 	{
+		boolean atspinheight = false;
 		fixed_t oldheight = player->mo->height;
+		fixed_t luaheight = LUAh_PlayerHeight(player);
 
+		if (luaheight != -1)
+		{
+			player->mo->height = luaheight;
+			if (luaheight <= P_GetPlayerSpinHeight(player))
+				atspinheight = true; // spinning will not save you from being crushed
+		}
 		// Less height while spinning. Good for spinning under things...?
-		if ((player->mo->state == &states[player->mo->info->painstate])
-		|| ((player->pflags & PF_JUMPED) && !(player->pflags & PF_NOJUMPDAMAGE))
-		|| (player->pflags & PF_SPINNING)
-		|| player->powers[pw_tailsfly] || player->pflags & PF_GLIDING
-		|| (player->charability == CA_GLIDEANDCLIMB && player->mo->state-states == S_PLAY_GLIDE_LANDING)
-		|| (player->charability == CA_FLY && player->mo->state-states == S_PLAY_FLY_TIRED))
+		else if (P_PlayerShouldUseSpinHeight(player))
+		{
 			player->mo->height = P_GetPlayerSpinHeight(player);
+			atspinheight = true;
+		}
 		else
 			player->mo->height = P_GetPlayerHeight(player);
 
 		if (player->mo->eflags & MFE_VERTICALFLIP && player->mo->height != oldheight) // adjust z height for reverse gravity, similar to how it's done for scaling
 			player->mo->z -= player->mo->height - oldheight;
-	}
 
-	// Crush test...
-	if ((player->mo->ceilingz - player->mo->floorz < player->mo->height)
-		&& !(player->mo->flags & MF_NOCLIP))
-	{
-		if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_SPINNING))
-		{
-			player->pflags |= PF_SPINNING;
-			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-		}
-		else if (player->mo->ceilingz - player->mo->floorz < player->mo->height)
+		// Crush test...
+		if ((player->mo->ceilingz - player->mo->floorz < player->mo->height)
+			&& !(player->mo->flags & MF_NOCLIP))
 		{
-			if ((netgame || multiplayer) && player->spectator)
-				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPECTATOR); // Respawn crushed spectators
-			else
-				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_CRUSHED);
+			if (!atspinheight)
+			{
+				player->pflags |= PF_SPINNING;
+				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+			}
+			else if (player->mo->ceilingz - player->mo->floorz < player->mo->height)
+			{
+				if ((netgame || multiplayer) && player->spectator)
+					P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPECTATOR); // Respawn crushed spectators
+				else
+					P_DamageMobj(player->mo, NULL, NULL, 1, DMG_CRUSHED);
 
-			if (player->playerstate == PST_DEAD)
-				return;
+				if (player->playerstate == PST_DEAD)
+					return;
+			}
 		}
 	}
 
@@ -11344,7 +11364,7 @@ static void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 	angle_t angle = player->drawangle;
 	fixed_t dist;
 	panim_t panim = player->panim;
-	tic_t dashmode = player->dashmode;
+	tic_t dashmode = min(player->dashmode, DASHMODE_MAX);
 	boolean underwater = mo->eflags & MFE_UNDERWATER;
 	statenum_t stat = fume->state-states;
 
@@ -11482,7 +11502,6 @@ void P_PlayerThink(player_t *player)
 		}
 	}
 
-#ifdef SEENAMES
 	if (netgame && player == &players[displayplayer] && !(leveltime % (TICRATE/5)))
 	{
 		seenplayer = NULL;
@@ -11507,7 +11526,6 @@ void P_PlayerThink(player_t *player)
 			}
 		}
 	}
-#endif
 
 	if (player->awayviewmobj && P_MobjWasRemoved(player->awayviewmobj))
 	{
@@ -12578,13 +12596,16 @@ void P_PlayerAfterThink(player_t *player)
 					player->powers[pw_carry] = CR_NONE;
 				else
 				{
-					P_TryMove(player->mo, tails->x + P_ReturnThrustX(tails, tails->player->drawangle, 4*FRACUNIT), tails->y + P_ReturnThrustY(tails, tails->player->drawangle, 4*FRACUNIT), true);
+					if (tails->player)
+						P_TryMove(player->mo, tails->x + P_ReturnThrustX(tails, tails->player->drawangle, 4*FRACUNIT), tails->y + P_ReturnThrustY(tails, tails->player->drawangle, 4*FRACUNIT), true);
+					else
+						P_TryMove(player->mo, tails->x + P_ReturnThrustX(tails, tails->angle, 4*FRACUNIT), tails->y + P_ReturnThrustY(tails, tails->angle, 4*FRACUNIT), true);
 					player->mo->momx = tails->momx;
 					player->mo->momy = tails->momy;
 					player->mo->momz = tails->momz;
 				}
 
-				if (G_CoopGametype() && (!tails->player || tails->player->bot != 1))
+				if (G_CoopGametype() && tails->player && tails->player->bot != 1)
 				{
 					player->mo->angle = tails->angle;
 
@@ -12599,7 +12620,7 @@ void P_PlayerAfterThink(player_t *player)
 				{
 					if (player->mo->state-states != S_PLAY_RIDE)
 						P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
-					if ((tails->skin && ((skin_t *)(tails->skin))->sprites[SPR2_SWIM].numframes) && (tails->eflags & MFE_UNDERWATER))
+					if (tails->player && (tails->skin && ((skin_t *)(tails->skin))->sprites[SPR2_SWIM].numframes) && (tails->eflags & MFE_UNDERWATER))
 						tails->player->powers[pw_tailsfly] = 0;
 				}
 				else
@@ -12934,3 +12955,33 @@ boolean P_PlayerFullbright(player_t *player)
 			|| !(player->mo->state >= &states[S_PLAY_NIGHTS_TRANS1]
 			&& player->mo->state < &states[S_PLAY_NIGHTS_TRANS6])))); // Note the < instead of <=
 }
+
+#define JUMPCURLED(player) ((player->pflags & PF_JUMPED)\
+	&& (!(player->charflags & SF_NOJUMPSPIN))\
+	&& (player->panim == PA_JUMP || player->panim == PA_ROLL))\
+
+// returns true if the player can enter a sector that they could not if standing at their skin's full height
+boolean P_PlayerCanEnterSpinGaps(player_t *player)
+{
+	UINT8 canEnter = LUAh_PlayerCanEnterSpinGaps(player);
+	if (canEnter == 1)
+		return true;
+	else if (canEnter == 2)
+		return false;
+
+	return ((player->pflags & (PF_SPINNING|PF_GLIDING)) // players who are spinning or gliding
+		|| (player->charability == CA_GLIDEANDCLIMB && player->mo->state-states == S_PLAY_GLIDE_LANDING) // players who are landing from a glide
+		|| JUMPCURLED(player)); // players who are jumpcurled, but only if they would normally jump that way
+}
+
+// returns true if the player should use their skin's spinheight instead of their skin's height
+boolean P_PlayerShouldUseSpinHeight(player_t *player)
+{
+	return ((player->pflags & (PF_SPINNING|PF_GLIDING))
+		|| (player->mo->state == &states[player->mo->info->painstate])
+		|| (player->panim == PA_ROLL)
+		|| ((player->powers[pw_tailsfly] || (player->charability == CA_FLY && player->mo->state-states == S_PLAY_FLY_TIRED))
+			&& !(player->charflags & SF_NOJUMPSPIN))
+		|| (player->charability == CA_GLIDEANDCLIMB && player->mo->state-states == S_PLAY_GLIDE_LANDING)
+		|| JUMPCURLED(player));
+}
diff --git a/src/r_data.c b/src/r_data.c
index af672f6dc024ee2c6840818982d586b17ceacb07..2cfe9cb7ace3139d40a32a9d8dd0ba21afa9b794 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -30,10 +30,6 @@
 #include "byteptr.h"
 #include "dehacked.h"
 
-#ifdef _WIN32
-#include <malloc.h> // alloca(sizeof)
-#endif
-
 //
 // Graphics.
 // SRB2 graphics for walls and sprites
diff --git a/src/r_draw.c b/src/r_draw.c
index d9ea942a2f22b301bdbd1762e0635f31ba085d6e..9a835ee5840c7e087256109f87ffef9f01bcf22c 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -134,19 +134,51 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 #define DEFAULT_STARTTRANSCOLOR 96
 #define NUM_PALETTE_ENTRIES 256
 
-static UINT8** translationtablecache[MAXSKINS + 7] = {NULL};
+static UINT8 **translationtablecache[MAXSKINS + 7] = {NULL};
 UINT8 skincolor_modified[MAXSKINCOLORS];
 
-CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
+static INT32 SkinToCacheIndex(INT32 skinnum)
+{
+	switch (skinnum)
+	{
+		case TC_DEFAULT:    return DEFAULT_TT_CACHE_INDEX;
+		case TC_BOSS:       return BOSS_TT_CACHE_INDEX;
+		case TC_METALSONIC: return METALSONIC_TT_CACHE_INDEX;
+		case TC_ALLWHITE:   return ALLWHITE_TT_CACHE_INDEX;
+		case TC_RAINBOW:    return RAINBOW_TT_CACHE_INDEX;
+		case TC_BLINK:      return BLINK_TT_CACHE_INDEX;
+		case TC_DASHMODE:   return DASHMODE_TT_CACHE_INDEX;
+		     default:       break;
+	}
+
+	return skinnum;
+}
+
+static INT32 CacheIndexToSkin(INT32 ttc)
+{
+	switch (ttc)
+	{
+		case DEFAULT_TT_CACHE_INDEX:    return TC_DEFAULT;
+		case BOSS_TT_CACHE_INDEX:       return TC_BOSS;
+		case METALSONIC_TT_CACHE_INDEX: return TC_METALSONIC;
+		case ALLWHITE_TT_CACHE_INDEX:   return TC_ALLWHITE;
+		case RAINBOW_TT_CACHE_INDEX:    return TC_RAINBOW;
+		case BLINK_TT_CACHE_INDEX:      return TC_BLINK;
+		case DASHMODE_TT_CACHE_INDEX:   return TC_DASHMODE;
+		     default:                   break;
+	}
+
+	return ttc;
+}
 
-#define TRANSTAB_AMTMUL10 (256.0f / 10.0f)
+CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
 
 /** \brief Initializes the translucency tables used by the Software renderer.
 */
 void R_InitTranslucencyTables(void)
 {
-	// Load here the transparency lookup tables 'TINTTAB'
-	// NOTE: the TINTTAB resource MUST BE aligned on 64k for the asm
+	// Load here the transparency lookup tables 'TRANSx0'
+	// NOTE: the TRANSx0 resources MUST BE aligned on 64k for the asm
 	// optimised code (in other words, transtables pointer low word is 0)
 	transtables = Z_MallocAlign(NUMTRANSTABLES*0x10000, PU_STATIC,
 		NULL, 16);
@@ -164,42 +196,43 @@ void R_InitTranslucencyTables(void)
 	R_GenerateBlendTables();
 }
 
-void R_GenerateBlendTables(void)
+static colorlookup_t transtab_lut;
+
+static void BlendTab_Translucent(UINT8 *table, int style, UINT8 blendamt)
 {
-	INT32 i;
+	INT16 bg, fg;
 
-	for (i = 0; i < NUMBLENDMAPS; i++)
-	{
-		if (i == blendtab_modulate)
-			continue;
-		blendtables[i] = Z_MallocAlign((NUMTRANSTABLES + 1) * 0x10000, PU_STATIC, NULL, 16);
-	}
+	if (table == NULL)
+		I_Error("BlendTab_Translucent: input table was NULL!");
 
-	for (i = 0; i <= 9; i++)
+	for (bg = 0; bg < 0xFF; bg++)
 	{
-		const size_t offs = (0x10000 * i);
-		const UINT8 alpha = TRANSTAB_AMTMUL10 * i;
+		for (fg = 0; fg < 0xFF; fg++)
+		{
+			RGBA_t backrgba = V_GetMasterColor(bg);
+			RGBA_t frontrgba = V_GetMasterColor(fg);
+			RGBA_t result;
 
-		R_GenerateTranslucencyTable(blendtables[blendtab_add] + offs, AST_ADD, alpha);
-		R_GenerateTranslucencyTable(blendtables[blendtab_subtract] + offs, AST_SUBTRACT, alpha);
-		R_GenerateTranslucencyTable(blendtables[blendtab_reversesubtract] + offs, AST_REVERSESUBTRACT, alpha);
-	}
+			result.rgba = ASTBlendPixel(backrgba, frontrgba, style, 0xFF);
+			result.rgba = ASTBlendPixel(result, frontrgba, AST_TRANSLUCENT, blendamt);
 
-	// Modulation blending only requires a single table
-	blendtables[blendtab_modulate] = Z_MallocAlign(0x10000, PU_STATIC, NULL, 16);
-	R_GenerateTranslucencyTable(blendtables[blendtab_modulate], AST_MODULATE, 0);
+			table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut, result.s.red, result.s.green, result.s.blue);
+		}
+	}
 }
 
-static colorlookup_t transtab_lut;
-
-void R_GenerateTranslucencyTable(UINT8 *table, int style, UINT8 blendamt)
+static void BlendTab_Subtractive(UINT8 *table, int style, UINT8 blendamt)
 {
 	INT16 bg, fg;
 
 	if (table == NULL)
-		I_Error("R_GenerateTranslucencyTable: input table was NULL!");
+		I_Error("BlendTab_Subtractive: input table was NULL!");
 
-	InitColorLUT(&transtab_lut, pMasterPalette, false);
+	if (blendamt == 0xFF)
+	{
+		memset(table, GetColorLUT(&transtab_lut, 0, 0, 0), 0x10000);
+		return;
+	}
 
 	for (bg = 0; bg < 0xFF; bg++)
 	{
@@ -209,12 +242,94 @@ void R_GenerateTranslucencyTable(UINT8 *table, int style, UINT8 blendamt)
 			RGBA_t frontrgba = V_GetMasterColor(fg);
 			RGBA_t result;
 
-			result.rgba = ASTBlendPixel(backrgba, frontrgba, style, blendamt);
+			result.rgba = ASTBlendPixel(backrgba, frontrgba, style, 0xFF);
+			result.s.red = max(0, result.s.red - blendamt);
+			result.s.green = max(0, result.s.green - blendamt);
+			result.s.blue = max(0, result.s.blue - blendamt);
+
+			table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut, result.s.red, result.s.green, result.s.blue);
+		}
+	}
+}
+
+static void BlendTab_Modulative(UINT8 *table)
+{
+	INT16 bg, fg;
+
+	if (table == NULL)
+		I_Error("BlendTab_Modulative: input table was NULL!");
+
+	for (bg = 0; bg < 0xFF; bg++)
+	{
+		for (fg = 0; fg < 0xFF; fg++)
+		{
+			RGBA_t backrgba = V_GetMasterColor(bg);
+			RGBA_t frontrgba = V_GetMasterColor(fg);
+			RGBA_t result;
+			result.rgba = ASTBlendPixel(backrgba, frontrgba, AST_MODULATE, 0);
 			table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut, result.s.red, result.s.green, result.s.blue);
 		}
 	}
 }
 
+static INT32 BlendTab_Count[NUMBLENDMAPS] =
+{
+	NUMTRANSTABLES+1, // blendtab_add
+	NUMTRANSTABLES+1, // blendtab_subtract
+	NUMTRANSTABLES+1, // blendtab_reversesubtract
+	1                 // blendtab_modulate
+};
+
+static INT32 BlendTab_FromStyle[] =
+{
+	0,                        // AST_COPY
+	0,                        // AST_TRANSLUCENT
+	blendtab_add,             // AST_ADD
+	blendtab_subtract,        // AST_SUBTRACT
+	blendtab_reversesubtract, // AST_REVERSESUBTRACT
+	blendtab_modulate,        // AST_MODULATE
+	0                         // AST_OVERLAY
+};
+
+static void BlendTab_GenerateMaps(INT32 tab, INT32 style, void (*genfunc)(UINT8 *, int, UINT8))
+{
+	INT32 i = 0, num = BlendTab_Count[tab];
+	const float amtmul = (256.0f / (float)(NUMTRANSTABLES + 1));
+	for (; i < num; i++)
+	{
+		const size_t offs = (0x10000 * i);
+		const UINT16 alpha = min(amtmul * i, 0xFF);
+		genfunc(blendtables[tab] + offs, style, alpha);
+	}
+}
+
+void R_GenerateBlendTables(void)
+{
+	INT32 i;
+
+	for (i = 0; i < NUMBLENDMAPS; i++)
+		blendtables[i] = Z_MallocAlign(BlendTab_Count[i] * 0x10000, PU_STATIC, NULL, 16);
+
+	InitColorLUT(&transtab_lut, pMasterPalette, false);
+
+	// Additive
+	BlendTab_GenerateMaps(blendtab_add, AST_ADD, BlendTab_Translucent);
+
+	// Subtractive
+#if 1
+	BlendTab_GenerateMaps(blendtab_subtract, AST_SUBTRACT, BlendTab_Subtractive);
+#else
+	BlendTab_GenerateMaps(blendtab_subtract, AST_SUBTRACT, BlendTab_Translucent);
+#endif
+
+	// Reverse subtractive
+	BlendTab_GenerateMaps(blendtab_reversesubtract, AST_REVERSESUBTRACT, BlendTab_Translucent);
+
+	// Modulative blending only requires a single table
+	BlendTab_Modulative(blendtables[blendtab_modulate]);
+}
+
+#define ClipBlendLevel(style, trans) max(min((trans), BlendTab_Count[BlendTab_FromStyle[style]]-1), 0)
 #define ClipTransLevel(trans) max(min((trans), NUMTRANSMAPS-2), 0)
 
 UINT8 *R_GetTranslucencyTable(INT32 alphalevel)
@@ -224,7 +339,12 @@ UINT8 *R_GetTranslucencyTable(INT32 alphalevel)
 
 UINT8 *R_GetBlendTable(int style, INT32 alphalevel)
 {
-	size_t offs = (ClipTransLevel(alphalevel) << FF_TRANSSHIFT);
+	size_t offs;
+
+	if (style == AST_COPY || style == AST_OVERLAY)
+		return NULL;
+
+	offs = (ClipBlendLevel(style, alphalevel) << FF_TRANSSHIFT);
 
 	// Lactozilla: Returns the equivalent to AST_TRANSLUCENT
 	// if no alpha style matches any of the blend tables.
@@ -249,6 +369,14 @@ UINT8 *R_GetBlendTable(int style, INT32 alphalevel)
 		return NULL;
 }
 
+boolean R_BlendLevelVisible(INT32 blendmode, INT32 alphalevel)
+{
+	if (blendmode == AST_COPY || blendmode == AST_SUBTRACT || blendmode == AST_MODULATE || blendmode == AST_OVERLAY)
+		return true;
+
+	return (alphalevel < BlendTab_Count[BlendTab_FromStyle[blendmode]]);
+}
+
 // Define for getting accurate color brightness readings according to how the human eye sees them.
 // https://en.wikipedia.org/wiki/Relative_luminance
 // 0.2126 to red
@@ -308,7 +436,7 @@ static void R_RainbowColormap(UINT8 *dest_colormap, UINT16 skincolor)
 /**	\brief	Generates a translation colormap.
 
 	\param	dest_colormap	colormap to populate
-	\param	skinnum		number of skin, TC_DEFAULT or TC_BOSS
+	\param	skinnum		skin number, or a translation mode
 	\param	color		translation color
 
 	\return	void
@@ -412,6 +540,9 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 	if (color >= numskincolors)
 		I_Error("Invalid skin color #%hu.", (UINT16)color);
 
+	if (skinnum < 0 && skinnum > TC_DEFAULT)
+		I_Error("Invalid translation colormap index %d.", skinnum);
+
 	starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum].starttranscolor : DEFAULT_STARTTRANSCOLOR;
 
 	if (starttranscolor >= NUM_PALETTE_ENTRIES)
@@ -448,25 +579,11 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags)
 {
 	UINT8* ret;
-	INT32 skintableindex;
+	INT32 skintableindex = SkinToCacheIndex(skinnum); // Adjust if we want the default colormap
 	INT32 i;
 
-	// Adjust if we want the default colormap
-	switch (skinnum)
-	{
-		case TC_DEFAULT:    skintableindex = DEFAULT_TT_CACHE_INDEX; break;
-		case TC_BOSS:       skintableindex = BOSS_TT_CACHE_INDEX; break;
-		case TC_METALSONIC: skintableindex = METALSONIC_TT_CACHE_INDEX; break;
-		case TC_ALLWHITE:   skintableindex = ALLWHITE_TT_CACHE_INDEX; break;
-		case TC_RAINBOW:    skintableindex = RAINBOW_TT_CACHE_INDEX; break;
-		case TC_BLINK:      skintableindex = BLINK_TT_CACHE_INDEX; break;
-		case TC_DASHMODE:   skintableindex = DASHMODE_TT_CACHE_INDEX; break;
-		     default:       skintableindex = skinnum; break;
-	}
-
 	if (flags & GTC_CACHE)
 	{
-
 		// Allocate table for skin if necessary
 		if (!translationtablecache[skintableindex])
 			translationtablecache[skintableindex] = Z_Calloc(MAXSKINCOLORS * sizeof(UINT8**), PU_STATIC, NULL);
@@ -479,7 +596,8 @@ UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags
 		{
 			for (i = 0; i < (INT32)(sizeof(translationtablecache) / sizeof(translationtablecache[0])); i++)
 				if (translationtablecache[i] && translationtablecache[i][color])
-					R_GenerateTranslationColormap(translationtablecache[i][color], i>=MAXSKINS ? MAXSKINS-i-1 : i, color);
+					R_GenerateTranslationColormap(translationtablecache[i][color], CacheIndexToSkin(i), color);
+
 			skincolor_modified[color] = false;
 		}
 	}
diff --git a/src/r_draw.h b/src/r_draw.h
index 9957541ca33bcfd84c37b03f4375184a722da459..caf43fd89d7ba5451eed376ef08591c71a25efa8 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -106,13 +106,17 @@ extern lumpnum_t viewborderlump[8];
 
 #define GTC_CACHE 1
 
-#define TC_DEFAULT    -1
-#define TC_BOSS       -2
-#define TC_METALSONIC -3 // For Metal Sonic battle
-#define TC_ALLWHITE   -4 // For Cy-Brak-demon
-#define TC_RAINBOW    -5 // For single colour
-#define TC_BLINK      -6 // For item blinking, according to kart
-#define TC_DASHMODE   -7 // For Metal Sonic's dashmode
+enum
+{
+	TC_BOSS       = INT8_MIN,
+	TC_METALSONIC, // For Metal Sonic battle
+	TC_ALLWHITE,   // For Cy-Brak-demon
+	TC_RAINBOW,    // For single colour
+	TC_BLINK,      // For item blinking, according to kart
+	TC_DASHMODE,   // For Metal Sonic's dashmode
+
+	TC_DEFAULT
+};
 
 // Custom player skin translation
 // Initialize color translation tables, for player rendering etc.
@@ -136,11 +140,12 @@ extern UINT8 *blendtables[NUMBLENDMAPS];
 
 void R_InitTranslucencyTables(void);
 void R_GenerateBlendTables(void);
-void R_GenerateTranslucencyTable(UINT8 *table, int style, UINT8 blendamt);
 
 UINT8 *R_GetTranslucencyTable(INT32 alphalevel);
 UINT8 *R_GetBlendTable(int style, INT32 alphalevel);
 
+boolean R_BlendLevelVisible(INT32 blendmode, INT32 alphalevel);
+
 // Color ramp modification should force a recache
 extern UINT8 skincolor_modified[];
 
diff --git a/src/r_draw8.c b/src/r_draw8.c
index e78ba8a6c49b8f39a9c54fe0af814340c9462641..1f451115eba2108c59b6188232d5b88bd3aacb74 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -1447,10 +1447,7 @@ void R_DrawFloorSprite_8 (void)
 		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
 		// have the uber complicated math to calculate it now, so that was a memory write we didn't
 		// need!
-		//
-		// <Callum> 4194303 = (2048x2048)-1 (2048x2048 is maximum flat size)
 		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
-		val &= 4194303;
 		val = source[val];
 		if (val & 0xFF00)
 			dest[0] = colormap[translation[val & 0xFF]];
@@ -1458,7 +1455,6 @@ void R_DrawFloorSprite_8 (void)
 		yposition += ystep;
 
 		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
-		val &= 4194303;
 		val = source[val];
 		if (val & 0xFF00)
 			dest[1] = colormap[translation[val & 0xFF]];
@@ -1466,7 +1462,6 @@ void R_DrawFloorSprite_8 (void)
 		yposition += ystep;
 
 		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
-		val &= 4194303;
 		val = source[val];
 		if (val & 0xFF00)
 			dest[2] = colormap[translation[val & 0xFF]];
@@ -1474,7 +1469,6 @@ void R_DrawFloorSprite_8 (void)
 		yposition += ystep;
 
 		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
-		val &= 4194303;
 		val = source[val];
 		if (val & 0xFF00)
 			dest[3] = colormap[translation[val & 0xFF]];
@@ -1482,7 +1476,6 @@ void R_DrawFloorSprite_8 (void)
 		yposition += ystep;
 
 		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
-		val &= 4194303;
 		val = source[val];
 		if (val & 0xFF00)
 			dest[4] = colormap[translation[val & 0xFF]];
@@ -1490,7 +1483,6 @@ void R_DrawFloorSprite_8 (void)
 		yposition += ystep;
 
 		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
-		val &= 4194303;
 		val = source[val];
 		if (val & 0xFF00)
 			dest[5] = colormap[translation[val & 0xFF]];
@@ -1498,7 +1490,6 @@ void R_DrawFloorSprite_8 (void)
 		yposition += ystep;
 
 		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
-		val &= 4194303;
 		val = source[val];
 		if (val & 0xFF00)
 			dest[6] = colormap[translation[val & 0xFF]];
@@ -1506,7 +1497,6 @@ void R_DrawFloorSprite_8 (void)
 		yposition += ystep;
 
 		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
-		val &= 4194303;
 		val = source[val];
 		if (val & 0xFF00)
 			dest[7] = colormap[translation[val & 0xFF]];
diff --git a/src/r_main.c b/src/r_main.c
index 165f74a7975515011d81b4ad8a0d2346b0ad4600..04bdebc58282642cdca3329e9b3f801c10f4c6c3 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -34,7 +34,7 @@
 #include "m_random.h" // quake camera shake
 #include "r_portal.h"
 #include "r_main.h"
-#include "i_system.h" // I_GetTimeMicros
+#include "i_system.h" // I_GetPreciseTime
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -100,17 +100,17 @@ lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ];
 extracolormap_t *extra_colormaps = NULL;
 
 // Render stats
-int ps_prevframetime = 0;
-int ps_rendercalltime = 0;
-int ps_uitime = 0;
-int ps_swaptime = 0;
+precise_t ps_prevframetime = 0;
+precise_t ps_rendercalltime = 0;
+precise_t ps_uitime = 0;
+precise_t ps_swaptime = 0;
 
-int ps_bsptime = 0;
+precise_t ps_bsptime = 0;
 
-int ps_sw_spritecliptime = 0;
-int ps_sw_portaltime = 0;
-int ps_sw_planetime = 0;
-int ps_sw_maskedtime = 0;
+precise_t ps_sw_spritecliptime = 0;
+precise_t ps_sw_portaltime = 0;
+precise_t ps_sw_planetime = 0;
+precise_t ps_sw_maskedtime = 0;
 
 int ps_numbspcalls = 0;
 int ps_numsprites = 0;
@@ -1089,8 +1089,6 @@ subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y)
 // 18/08/18: (No it's actually 16*viewheight, thanks Jimita for finding this out)
 static void R_SetupFreelook(player_t *player, boolean skybox)
 {
-	INT32 dy = 0;
-
 #ifndef HWRENDER
 	(void)player;
 	(void)skybox;
@@ -1109,14 +1107,15 @@ static void R_SetupFreelook(player_t *player, boolean skybox)
 		G_SoftwareClipAimingPitch((INT32 *)&aimingangle);
 	}
 
+	centeryfrac = (viewheight/2)<<FRACBITS;
+
 	if (rendermode == render_soft)
-	{
-		dy = (AIMINGTODY(aimingangle)>>FRACBITS) * viewwidth/BASEVIDWIDTH;
-		yslope = &yslopetab[viewheight*8 - (viewheight/2 + dy)];
-	}
+		centeryfrac += FixedMul(AIMINGTODY(aimingangle), FixedDiv(viewwidth<<FRACBITS, BASEVIDWIDTH<<FRACBITS));
 
-	centery = (viewheight/2) + dy;
-	centeryfrac = centery<<FRACBITS;
+	centery = FixedInt(FixedRound(centeryfrac));
+
+	if (rendermode == render_soft)
+		yslope = &yslopetab[viewheight*8 - centery];
 }
 
 void R_SetupFrame(player_t *player)
@@ -1498,9 +1497,9 @@ void R_RenderPlayerView(player_t *player)
 	ProfZeroTimer();
 #endif
 	ps_numbspcalls = ps_numpolyobjects = ps_numdrawnodes = 0;
-	ps_bsptime = I_GetTimeMicros();
+	ps_bsptime = I_GetPreciseTime();
 	R_RenderBSPNode((INT32)numnodes - 1);
-	ps_bsptime = I_GetTimeMicros() - ps_bsptime;
+	ps_bsptime = I_GetPreciseTime() - ps_bsptime;
 	ps_numsprites = visspritecount;
 #ifdef TIMING
 	RDMSR(0x10, &mycount);
@@ -1511,9 +1510,9 @@ void R_RenderPlayerView(player_t *player)
 //profile stuff ---------------------------------------------------------
 	Mask_Post(&masks[nummasks - 1]);
 
-	ps_sw_spritecliptime = I_GetTimeMicros();
+	ps_sw_spritecliptime = I_GetPreciseTime();
 	R_ClipSprites(drawsegs, NULL);
-	ps_sw_spritecliptime = I_GetTimeMicros() - ps_sw_spritecliptime;
+	ps_sw_spritecliptime = I_GetPreciseTime() - ps_sw_spritecliptime;
 
 
 	// Add skybox portals caused by sky visplanes.
@@ -1521,7 +1520,7 @@ void R_RenderPlayerView(player_t *player)
 		Portal_AddSkyboxPortals();
 
 	// Portal rendering. Hijacks the BSP traversal.
-	ps_sw_portaltime = I_GetTimeMicros();
+	ps_sw_portaltime = I_GetPreciseTime();
 	if (portal_base)
 	{
 		portal_t *portal;
@@ -1561,17 +1560,17 @@ void R_RenderPlayerView(player_t *player)
 			Portal_Remove(portal);
 		}
 	}
-	ps_sw_portaltime = I_GetTimeMicros() - ps_sw_portaltime;
+	ps_sw_portaltime = I_GetPreciseTime() - ps_sw_portaltime;
 
-	ps_sw_planetime = I_GetTimeMicros();
+	ps_sw_planetime = I_GetPreciseTime();
 	R_DrawPlanes();
-	ps_sw_planetime = I_GetTimeMicros() - ps_sw_planetime;
+	ps_sw_planetime = I_GetPreciseTime() - ps_sw_planetime;
 
 	// draw mid texture and sprite
 	// And now 3D floors/sides!
-	ps_sw_maskedtime = I_GetTimeMicros();
+	ps_sw_maskedtime = I_GetPreciseTime();
 	R_DrawMasked(masks, nummasks);
-	ps_sw_maskedtime = I_GetTimeMicros() - ps_sw_maskedtime;
+	ps_sw_maskedtime = I_GetPreciseTime() - ps_sw_maskedtime;
 
 	free(masks);
 }
diff --git a/src/r_main.h b/src/r_main.h
index f1cc9621f00f320ffdbb363b08b783af89fda75b..2ac6abf5a1e45ca7b0062a1e663e221ef9825931 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -78,17 +78,17 @@ boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixe
 
 // Render stats
 
-extern int ps_prevframetime;// time when previous frame was rendered
-extern int ps_rendercalltime;
-extern int ps_uitime;
-extern int ps_swaptime;
+extern precise_t ps_prevframetime;// time when previous frame was rendered
+extern precise_t ps_rendercalltime;
+extern precise_t ps_uitime;
+extern precise_t ps_swaptime;
 
-extern int ps_bsptime;
+extern precise_t ps_bsptime;
 
-extern int ps_sw_spritecliptime;
-extern int ps_sw_portaltime;
-extern int ps_sw_planetime;
-extern int ps_sw_maskedtime;
+extern precise_t ps_sw_spritecliptime;
+extern precise_t ps_sw_portaltime;
+extern precise_t ps_sw_planetime;
+extern precise_t ps_sw_maskedtime;
 
 extern int ps_numbspcalls;
 extern int ps_numsprites;
diff --git a/src/r_picformats.c b/src/r_picformats.c
index 02f1de4ab20d1f0edaeb8753459eacc8c452de23..f87362c76ef895097b7e21329367c6b082fa705e 100644
--- a/src/r_picformats.c
+++ b/src/r_picformats.c
@@ -544,21 +544,22 @@ void *Picture_GetPatchPixel(
 	UINT16 *s16 = NULL;
 	UINT32 *s32 = NULL;
 	softwarepatch_t *doompatch = (softwarepatch_t *)patch;
+	boolean isdoompatch = Picture_IsDoomPatchFormat(informat);
 	INT16 width;
 
 	if (patch == NULL)
 		I_Error("Picture_GetPatchPixel: patch == NULL");
 
-	width = (Picture_IsDoomPatchFormat(informat) ? patch->width : SHORT(patch->width));
+	width = (isdoompatch ? SHORT(doompatch->width) : patch->width);
 
 	if (x >= 0 && x < width)
 	{
 		INT32 colx = (flags & PICFLAGS_XFLIP) ? (width-1)-x : x;
 		INT32 topdelta, prevdelta = -1;
-		INT32 colofs = (Picture_IsDoomPatchFormat(informat) ? LONG(patch->columnofs[colx]) : patch->columnofs[colx]);
+		INT32 colofs = (isdoompatch ? LONG(doompatch->columnofs[colx]) : patch->columnofs[colx]);
 
-		// Column offsets are pointers so no casting required
-		if (Picture_IsDoomPatchFormat(informat))
+		// Column offsets are pointers, so no casting is required.
+		if (isdoompatch)
 			column = (column_t *)((UINT8 *)doompatch + colofs);
 		else
 			column = (column_t *)((UINT8 *)patch->columns + colofs);
diff --git a/src/r_plane.c b/src/r_plane.c
index c54b32382eb178f86b8d19a5a36d6dbe07189a39..ea4dfa4e8a8c13d4aad4ed8260d6da37e8fea00a 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -154,14 +154,10 @@ static void R_UpdatePlaneRipple(void)
 // R_MapPlane
 //
 // Uses global vars:
+//  planeheight
 //  basexscale
 //  baseyscale
 //  centerx
-//  viewx
-//  viewy
-//  viewsin
-//  viewcos
-//  viewheight
 
 void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 {
@@ -580,7 +576,7 @@ void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop)
 //
 void R_MakeSpans(INT32 x, INT32 t1, INT32 b1, INT32 t2, INT32 b2)
 {
-	//    Alam: from r_splats's R_RenderFloorSplat
+	//    Alam: from r_splats's R_RasterizeFloorSplat
 	if (t1 >= vid.height) t1 = vid.height-1;
 	if (b1 >= vid.height) b1 = vid.height-1;
 	if (t2 >= vid.height) t2 = vid.height-1;
@@ -705,9 +701,9 @@ void R_CalculateSlopeVectors(pslope_t *slope, fixed_t planeviewx, fixed_t planev
 	n.z = -xscale * cos(ang);
 
 	ang = ANG2RAD(planeangle);
-	temp = P_GetSlopeZAt(slope, planeviewx + yscale * FLOAT_TO_FIXED(sin(ang)), planeviewy + yscale * FLOAT_TO_FIXED(cos(ang)));
+	temp = P_GetSlopeZAt(slope, planeviewx + FLOAT_TO_FIXED(yscale * sin(ang)), planeviewy + FLOAT_TO_FIXED(yscale * cos(ang)));
 	m.y = FIXED_TO_FLOAT(temp) - zeroheight;
-	temp = P_GetSlopeZAt(slope, planeviewx + xscale * FLOAT_TO_FIXED(cos(ang)), planeviewy - xscale * FLOAT_TO_FIXED(sin(ang)));
+	temp = P_GetSlopeZAt(slope, planeviewx + FLOAT_TO_FIXED(xscale * cos(ang)), planeviewy - FLOAT_TO_FIXED(xscale * sin(ang)));
 	n.y = FIXED_TO_FLOAT(temp) - zeroheight;
 
 	if (ds_powersoftwo)
@@ -788,7 +784,6 @@ void R_DrawSinglePlane(visplane_t *pl)
 	ffloor_t *rover;
 	int type;
 	int spanfunctype = BASEDRAWFUNC;
-	angle_t viewang = viewangle;
 
 	if (!(pl->minx <= pl->maxx))
 		return;
@@ -1153,8 +1148,6 @@ using the palette colors.
 		}
 	}
 #endif
-
-	viewangle = viewang;
 }
 
 void R_PlaneBounds(visplane_t *plane)
diff --git a/src/r_segs.c b/src/r_segs.c
index 7c913e023d0a9c17b6382953f9d5136766517cba..834f00b47be6b4d2f841b92e87a251da3a5e7a5b 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -540,7 +540,7 @@ static boolean R_IsFFloorTranslucent(visffloor_t *pfloor)
 
 	// Polyobjects have no ffloors, and they're handled in the conditional above.
 	if (pfloor->ffloor != NULL)
-		return (pfloor->ffloor->flags & FF_TRANSLUCENT);
+		return (pfloor->ffloor->flags & (FF_TRANSLUCENT|FF_FOG));
 
 	return false;
 }
@@ -1191,7 +1191,7 @@ static void R_RenderSegLoop (void)
 
 							// Lactozilla: Cull part of the column by the 3D floor if it can't be seen
 							// "bottom" is the top pixel of the floor column
-							if (ffbottom >= bottom-1 && R_FFloorCanClip(&ffloor[i]))
+							if (ffbottom >= bottom-1 && R_FFloorCanClip(&ffloor[i]) && !curline->polyseg)
 							{
 								rw_floormarked = true;
 								floorclip[rw_x] = fftop;
@@ -1239,7 +1239,7 @@ static void R_RenderSegLoop (void)
 
 							// Lactozilla: Cull part of the column by the 3D floor if it can't be seen
 							// "top" is the height of the ceiling column
-							if (fftop <= top+1 && R_FFloorCanClip(&ffloor[i]))
+							if (fftop <= top+1 && R_FFloorCanClip(&ffloor[i]) && !curline->polyseg)
 							{
 								rw_ceilingmarked = true;
 								ceilingclip[rw_x] = ffbottom;
@@ -1657,23 +1657,26 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		// left
 		temp = xtoviewangle[start]+viewangle;
 
+#define FIXED_TO_DOUBLE(x) (((double)(x)) / ((double)FRACUNIT))
+#define DOUBLE_TO_FIXED(x) (fixed_t)((x) * ((double)FRACUNIT))
+
 		{
 			// Both lines can be written in slope-intercept form, so figure out line intersection
-			float a1, b1, c1, a2, b2, c2, det; // 1 is the seg, 2 is the view angle vector...
-			///TODO: convert to FPU
+			double a1, b1, c1, a2, b2, c2, det; // 1 is the seg, 2 is the view angle vector...
+			///TODO: convert to fixed point
 
-			a1 = FIXED_TO_FLOAT(curline->v2->y-curline->v1->y);
-			b1 = FIXED_TO_FLOAT(curline->v1->x-curline->v2->x);
-			c1 = a1*FIXED_TO_FLOAT(curline->v1->x) + b1*FIXED_TO_FLOAT(curline->v1->y);
+			a1 = FIXED_TO_DOUBLE(curline->v2->y-curline->v1->y);
+			b1 = FIXED_TO_DOUBLE(curline->v1->x-curline->v2->x);
+			c1 = a1*FIXED_TO_DOUBLE(curline->v1->x) + b1*FIXED_TO_DOUBLE(curline->v1->y);
 
-			a2 = -FIXED_TO_FLOAT(FINESINE(temp>>ANGLETOFINESHIFT));
-			b2 = FIXED_TO_FLOAT(FINECOSINE(temp>>ANGLETOFINESHIFT));
-			c2 = a2*FIXED_TO_FLOAT(viewx) + b2*FIXED_TO_FLOAT(viewy);
+			a2 = -FIXED_TO_DOUBLE(FINESINE(temp>>ANGLETOFINESHIFT));
+			b2 = FIXED_TO_DOUBLE(FINECOSINE(temp>>ANGLETOFINESHIFT));
+			c2 = a2*FIXED_TO_DOUBLE(viewx) + b2*FIXED_TO_DOUBLE(viewy);
 
 			det = a1*b2 - a2*b1;
 
-			ds_p->leftpos.x = segleft.x = FLOAT_TO_FIXED((b2*c1 - b1*c2)/det);
-			ds_p->leftpos.y = segleft.y = FLOAT_TO_FIXED((a1*c2 - a2*c1)/det);
+			ds_p->leftpos.x = segleft.x = DOUBLE_TO_FIXED((b2*c1 - b1*c2)/det);
+			ds_p->leftpos.y = segleft.y = DOUBLE_TO_FIXED((a1*c2 - a2*c1)/det);
 		}
 
 		// right
@@ -1681,22 +1684,26 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 		{
 			// Both lines can be written in slope-intercept form, so figure out line intersection
-			float a1, b1, c1, a2, b2, c2, det; // 1 is the seg, 2 is the view angle vector...
-			///TODO: convert to FPU
+			double a1, b1, c1, a2, b2, c2, det; // 1 is the seg, 2 is the view angle vector...
+			///TODO: convert to fixed point
 
-			a1 = FIXED_TO_FLOAT(curline->v2->y-curline->v1->y);
-			b1 = FIXED_TO_FLOAT(curline->v1->x-curline->v2->x);
-			c1 = a1*FIXED_TO_FLOAT(curline->v1->x) + b1*FIXED_TO_FLOAT(curline->v1->y);
+			a1 = FIXED_TO_DOUBLE(curline->v2->y-curline->v1->y);
+			b1 = FIXED_TO_DOUBLE(curline->v1->x-curline->v2->x);
+			c1 = a1*FIXED_TO_DOUBLE(curline->v1->x) + b1*FIXED_TO_DOUBLE(curline->v1->y);
 
-			a2 = -FIXED_TO_FLOAT(FINESINE(temp>>ANGLETOFINESHIFT));
-			b2 = FIXED_TO_FLOAT(FINECOSINE(temp>>ANGLETOFINESHIFT));
-			c2 = a2*FIXED_TO_FLOAT(viewx) + b2*FIXED_TO_FLOAT(viewy);
+			a2 = -FIXED_TO_DOUBLE(FINESINE(temp>>ANGLETOFINESHIFT));
+			b2 = FIXED_TO_DOUBLE(FINECOSINE(temp>>ANGLETOFINESHIFT));
+			c2 = a2*FIXED_TO_DOUBLE(viewx) + b2*FIXED_TO_DOUBLE(viewy);
 
 			det = a1*b2 - a2*b1;
 
-			ds_p->rightpos.x = segright.x = FLOAT_TO_FIXED((b2*c1 - b1*c2)/det);
-			ds_p->rightpos.y = segright.y = FLOAT_TO_FIXED((a1*c2 - a2*c1)/det);
+			ds_p->rightpos.x = segright.x = DOUBLE_TO_FIXED((b2*c1 - b1*c2)/det);
+			ds_p->rightpos.y = segright.y = DOUBLE_TO_FIXED((a1*c2 - a2*c1)/det);
 		}
+
+#undef FIXED_TO_DOUBLE
+#undef DOUBLE_TO_FIXED
+
 	}
 
 
diff --git a/src/r_skins.c b/src/r_skins.c
index 25904e95e3ff2c1c89e5fcd5c60c564efedaf84b..6f150f234ed9908f9c58bd42ecbe65ac5e5a534a 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -511,6 +511,10 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
 	GETFLAG(MULTIABILITY)
 	GETFLAG(NONIGHTSROTATION)
 	GETFLAG(NONIGHTSSUPER)
+	GETFLAG(NOSUPERSPRITES)
+	GETFLAG(NOSUPERJUMPBOOST)
+	GETFLAG(CANBUSTWALLS)
+	GETFLAG(NOSHIELDABILITY)
 #undef GETFLAG
 
 	else // let's check if it's a sound, otherwise error out
diff --git a/src/r_splats.c b/src/r_splats.c
index a3fad82d81730ff1d0ff967f0a4419c60791e59a..72cac9fd937f6bc35e98a343bb4bf98c4a6a4b8a 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -28,11 +28,12 @@ static void prepare_rastertab(void);
 //                                                               FLOOR SPLATS
 // ==========================================================================
 
+static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis);
+
 #ifdef USEASM
 void ASMCALL rasterize_segment_tex_asm(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir);
 #endif
 
-// Lactozilla
 static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir)
 {
 #ifdef USEASM
@@ -137,7 +138,7 @@ static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32
 	}
 }
 
-void R_DrawFloorSprite(vissprite_t *spr)
+void R_DrawFloorSplat(vissprite_t *spr)
 {
 	floorsplat_t splat;
 	mobj_t *mobj = spr->mobj;
@@ -187,7 +188,7 @@ void R_DrawFloorSprite(vissprite_t *spr)
 	if (spr->rotateflags & SRF_3D || renderflags & RF_NOSPLATBILLBOARD)
 		splatangle = mobj->angle;
 	else
-		splatangle = viewangle;
+		splatangle = spr->viewangle;
 
 	if (!(spr->cut & SC_ISROTATED))
 		splatangle += mobj->rollangle;
@@ -265,7 +266,6 @@ void R_DrawFloorSprite(vissprite_t *spr)
 
 	if (splat.tilted)
 	{
-		// Lactozilla: Just copy the entire slope LMFAOOOO
 		pslope_t *s = &splat.slope;
 
 		s->o.x = slope->o.x;
@@ -330,7 +330,7 @@ void R_DrawFloorSprite(vissprite_t *spr)
 		v2d[i].y = (centeryfrac + FixedMul(rot_z, yscale))>>FRACBITS;
 	}
 
-	R_RenderFloorSplat(&splat, v2d, spr);
+	R_RasterizeFloorSplat(&splat, v2d, spr);
 }
 
 // --------------------------------------------------------------------------
@@ -338,7 +338,7 @@ void R_DrawFloorSprite(vissprite_t *spr)
 // fill the polygon with linear interpolation, call span drawer for each
 // scan line
 // --------------------------------------------------------------------------
-void R_RenderFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis)
+static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis)
 {
 	// rasterizing
 	INT32 miny = viewheight + 1, maxy = 0;
@@ -416,11 +416,10 @@ void R_RenderFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis
 	if (R_CheckPowersOfTwo())
 		R_CheckFlatLength(ds_flatwidth * ds_flatheight);
 
-	// Lactozilla: I don't know what I'm doing
 	if (pSplat->tilted)
 	{
 		R_SetTiltedSpan(0);
-		R_CalculateSlopeVectors(&pSplat->slope, viewx, viewy, viewz, pSplat->xscale, pSplat->yscale, -pSplat->verts[0].x, pSplat->verts[0].y, viewangle, pSplat->angle, 1.0f);
+		R_CalculateSlopeVectors(&pSplat->slope, viewx, viewy, viewz, pSplat->xscale, pSplat->yscale, -pSplat->verts[0].x, pSplat->verts[0].y, vis->viewangle, pSplat->angle, 1.0f);
 		spanfunctype = SPANDRAWFUNC_TILTEDSPRITE;
 	}
 	else
@@ -533,7 +532,7 @@ void R_RenderFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis
 			fixed_t xstep, ystep;
 			fixed_t distance, span;
 
-			angle_t angle = (viewangle + pSplat->angle)>>ANGLETOFINESHIFT;
+			angle_t angle = (vis->viewangle + pSplat->angle)>>ANGLETOFINESHIFT;
 			angle_t planecos = FINECOSINE(angle);
 			angle_t planesin = FINESINE(angle);
 
@@ -543,17 +542,13 @@ void R_RenderFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis
 				distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
 				span = abs(centery - y);
 
-				if (span) // don't divide by zero
+				if (span) // Don't divide by zero
 				{
 					xstep = FixedMul(planesin, planeheight) / span;
 					ystep = FixedMul(planecos, planeheight) / span;
 				}
 				else
-				{
-					// ah
-					xstep = FRACUNIT;
-					ystep = FRACUNIT;
-				}
+					xstep = ystep = FRACUNIT;
 
 				cachedxstep[y] = xstep;
 				cachedystep[y] = ystep;
diff --git a/src/r_splats.h b/src/r_splats.h
index e1f836f489bab54513dafd5b867ebfd7dbc79f44..05d8b66b06a8b04f569ae1739861660d425df92c 100644
--- a/src/r_splats.h
+++ b/src/r_splats.h
@@ -42,7 +42,6 @@ typedef struct floorsplat_s
 	mobj_t *mobj; // Mobj it is tied to
 } floorsplat_t;
 
-void R_DrawFloorSprite(vissprite_t *spr);
-void R_RenderFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis);
+void R_DrawFloorSplat(vissprite_t *spr);
 
 #endif /*__R_SPLATS_H__*/
diff --git a/src/r_textures.c b/src/r_textures.c
index 9de9649e222a9628f0917592570c899917e62722..d5da69018cb39c81a1dcac1fe01736ee67abab65 100644
--- a/src/r_textures.c
+++ b/src/r_textures.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -28,11 +28,6 @@
 #include "byteptr.h"
 #include "dehacked.h"
 
-// I don't know what this is even for, but r_data.c had it.
-#ifdef _WIN32
-#include <malloc.h> // alloca(sizeof)
-#endif
-
 #ifdef HWRENDER
 #include "hardware/hw_glob.h" // HWR_LoadMapTextures
 #endif
@@ -604,7 +599,7 @@ void *R_GetLevelFlat(levelflat_t *levelflat)
 				levelflat->height = ds_flatheight = SHORT(patch->height);
 
 				levelflat->picture = Z_Malloc(levelflat->width * levelflat->height, PU_LEVEL, NULL);
-				converted = Picture_FlatConvert(PICFMT_DOOMPATCH, patch, PICFMT_FLAT, 0, &size, levelflat->width, levelflat->height, patch->topoffset, patch->leftoffset, 0);
+				converted = Picture_FlatConvert(PICFMT_DOOMPATCH, patch, PICFMT_FLAT, 0, &size, levelflat->width, levelflat->height, SHORT(patch->topoffset), SHORT(patch->leftoffset), 0);
 				M_Memcpy(levelflat->picture, converted, size);
 				Z_Free(converted);
 			}
@@ -626,7 +621,7 @@ void *R_GetLevelFlat(levelflat_t *levelflat)
 //
 // R_CheckPowersOfTwo
 //
-// Self-explanatory?
+// Sets ds_powersoftwo true if the flat's dimensions are powers of two, and returns that.
 //
 boolean R_CheckPowersOfTwo(void)
 {
diff --git a/src/r_things.c b/src/r_things.c
index 08337392742fe3775f3faa2d1f0a073937736fc6..5c0e5fda95b2052e376f16279abae72ba29581f1 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 1999-2021 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -230,7 +230,7 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 	UINT8 rotation;
 	lumpinfo_t *lumpinfo;
 	softwarepatch_t patch;
-	UINT8 numadded = 0;
+	UINT16 numadded = 0;
 
 	memset(sprtemp,0xFF, sizeof (sprtemp));
 	maxframe = (size_t)-1;
@@ -796,7 +796,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	INT32 pwidth;
 	fixed_t frac;
 	patch_t *patch = vis->patch;
-	fixed_t this_scale = vis->mobj->scale;
+	fixed_t this_scale = vis->thingscale;
 	INT32 x1, x2;
 	INT64 overflow_test;
 
@@ -1078,6 +1078,14 @@ static void R_SplitSprite(vissprite_t *sprite)
 		sprite->sz = cutfrac;
 		newsprite->szt = (INT16)(sprite->sz - 1);
 
+		if (testheight < sprite->pzt && testheight > sprite->pz)
+			sprite->pz = newsprite->pzt = testheight;
+		else
+		{
+			newsprite->pz = newsprite->gz;
+			newsprite->pzt = newsprite->gzt;
+		}
+
 		newsprite->szt -= 8;
 
 		newsprite->cut |= SC_TOP;
@@ -1300,12 +1308,16 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	shadow->patch = patch;
 	shadow->heightsec = vis->heightsec;
 
+	shadow->thingheight = FRACUNIT;
+	shadow->pz = groundz + (isflipped ? -shadow->thingheight : 0);
+	shadow->pzt = shadow->pz + shadow->thingheight;
+
 	shadow->mobjflags = 0;
 	shadow->sortscale = vis->sortscale;
 	shadow->dispoffset = vis->dispoffset - 5;
 	shadow->gx = thing->x;
 	shadow->gy = thing->y;
-	shadow->gzt = groundz + patch->height * shadowyscale / 2;
+	shadow->gzt = (isflipped ? shadow->pzt : shadow->pz) + patch->height * shadowyscale / 2;
 	shadow->gz = shadow->gzt - patch->height * shadowyscale;
 	shadow->texturemid = FixedMul(thing->scale, FixedDiv(shadow->gzt - viewz, shadowyscale));
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
@@ -1320,6 +1332,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 
 	shadow->xscale = FixedMul(xscale, shadowxscale); //SoM: 4/17/2000
 	shadow->scale = FixedMul(yscale, shadowyscale);
+	shadow->thingscale = thing->scale;
 	shadow->sector = vis->sector;
 	shadow->szt = (INT16)((centeryfrac - FixedMul(shadow->gzt - viewz, yscale))>>FRACBITS);
 	shadow->sz = (INT16)((centeryfrac - FixedMul(shadow->gz - viewz, yscale))>>FRACBITS);
@@ -1411,7 +1424,7 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	fixed_t sheartan = 0;
 	fixed_t shadowscale = FRACUNIT;
-	fixed_t basetx; // drop shadows
+	fixed_t basetx, basetz; // drop shadows
 
 	boolean shadowdraw, shadoweffects, shadowskew;
 	boolean splat = R_ThingIsFloorSprite(thing);
@@ -1441,7 +1454,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	tr_x = thing->x - viewx;
 	tr_y = thing->y - viewy;
 
-	tz = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin); // near/far distance
+	basetz = tz = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin); // near/far distance
 
 	// thing is behind view plane?
 	if (!papersprite && (tz < FixedMul(MINZ, this_scale))) // papersprite clipping is handled later
@@ -1790,7 +1803,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	else if (oldthing->frame & FF_TRANSMASK)
 	{
 		trans = (oldthing->frame & FF_TRANSMASK) >> FF_TRANSSHIFT;
-		if (oldthing->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
+		if (!R_BlendLevelVisible(oldthing->blendmode, trans))
 			return;
 	}
 	else
@@ -1935,11 +1948,15 @@ static void R_ProjectSprite(mobj_t *thing)
 	vis->gy = thing->y;
 	vis->gz = gz;
 	vis->gzt = gzt;
+	vis->thingheight = thing->height;
+	vis->pz = thing->z;
+	vis->pzt = vis->pz + vis->thingheight;
 	vis->texturemid = FixedDiv(gzt - viewz, spriteyscale);
 	vis->scalestep = scalestep;
 	vis->paperoffset = paperoffset;
 	vis->paperdistance = paperdistance;
 	vis->centerangle = centerangle;
+	vis->viewangle = viewangle;
 	vis->shear.tan = sheartan;
 	vis->shear.offset = 0;
 
@@ -1960,6 +1977,7 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	vis->xscale = FixedMul(spritexscale, xscale); //SoM: 4/17/2000
 	vis->scale = FixedMul(spriteyscale, yscale); //<<detailshift;
+	vis->thingscale = oldthing->scale;
 
 	vis->spritexscale = spritexscale;
 	vis->spriteyscale = spriteyscale;
@@ -2037,7 +2055,7 @@ static void R_ProjectSprite(mobj_t *thing)
 		R_SplitSprite(vis);
 
 	if (oldthing->shadowscale && cv_shadow.value)
-		R_ProjectDropShadow(oldthing, vis, oldthing->shadowscale, basetx, tz);
+		R_ProjectDropShadow(oldthing, vis, oldthing->shadowscale, basetx, basetz);
 
 	// Debug
 	++objectsdrawn;
@@ -2151,6 +2169,9 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 	vis->gy = thing->y;
 	vis->gz = gz;
 	vis->gzt = gzt;
+	vis->thingheight = 4*FRACUNIT;
+	vis->pz = thing->z;
+	vis->pzt = vis->pz + vis->thingheight;
 	vis->texturemid = vis->gzt - viewz;
 	vis->scalestep = 0;
 	vis->paperdistance = 0;
@@ -2179,7 +2200,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 
 	// specific translucency
 	if (thing->frame & FF_TRANSMASK)
-		vis->transmap = (thing->frame & FF_TRANSMASK) - 0x10000 + transtables;
+		vis->transmap = R_GetTranslucencyTable((thing->frame & FF_TRANSMASK) >> FF_TRANSSHIFT);
 	else
 		vis->transmap = NULL;
 
@@ -2544,15 +2565,19 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 				planeobjectz = P_GetZAt(r2->plane->slope, rover->gx, rover->gy, r2->plane->height);
 				planecameraz = P_GetZAt(r2->plane->slope,     viewx,     viewy, r2->plane->height);
 
-				// bird: if any part of the sprite peeks in front the plane
-				if (planecameraz < viewz)
+				if (rover->mobjflags & MF_NOCLIPHEIGHT)
 				{
-					if (rover->gzt >= planeobjectz)
+					//Objects with NOCLIPHEIGHT can appear halfway in.
+					if (planecameraz < viewz && rover->pz+(rover->thingheight/2) >= planeobjectz)
+						continue;
+					if (planecameraz > viewz && rover->pzt-(rover->thingheight/2) <= planeobjectz)
 						continue;
 				}
-				else if (planecameraz > viewz)
+				else
 				{
-					if (rover->gz <= planeobjectz)
+					if (planecameraz < viewz && rover->pz >= planeobjectz)
+						continue;
+					if (planecameraz > viewz && rover->pzt <= planeobjectz)
 						continue;
 				}
 
@@ -2585,7 +2610,7 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 			}
 			else if (r2->thickseg)
 			{
-				//fixed_t topplaneobjectz, topplanecameraz, botplaneobjectz, botplanecameraz;
+				fixed_t topplaneobjectz, topplanecameraz, botplaneobjectz, botplanecameraz;
 				if (rover->x1 > r2->thickseg->x2 || rover->x2 < r2->thickseg->x1)
 					continue;
 
@@ -2596,11 +2621,6 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 				if (scale <= rover->sortscale)
 					continue;
 
-				// bird: Always sort sprites behind segs. This helps the plane
-				// sorting above too. Basically if the sprite gets sorted behind
-				// the seg here, it will be behind the plane too, since planes
-				// are added after segs in the list.
-#if 0
 				topplaneobjectz = P_GetFFloorTopZAt   (r2->ffloor, rover->gx, rover->gy);
 				topplanecameraz = P_GetFFloorTopZAt   (r2->ffloor,     viewx,     viewy);
 				botplaneobjectz = P_GetFFloorBottomZAt(r2->ffloor, rover->gx, rover->gy);
@@ -2609,7 +2629,6 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 				if ((topplanecameraz > viewz && botplanecameraz < viewz) ||
 				    (topplanecameraz < viewz && rover->gzt < topplaneobjectz) ||
 				    (botplanecameraz > viewz && rover->gz > botplaneobjectz))
-#endif
 				{
 					entry = R_CreateDrawNode(NULL);
 					(entry->prev = r2->prev)->next = entry;
@@ -2650,11 +2669,23 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 
 					if (!behind)
 					{
-						// FIXME: calculate gz and gzt for splats properly and use that
-						if (rover->mobj->z < viewz)
-							infront = (r2->sprite->mobj->z >= rover->mobj->z);
+						fixed_t z1 = 0, z2 = 0;
+
+						if (rover->mobj->z - viewz > 0)
+						{
+							z1 = rover->pz;
+							z2 = r2->sprite->pz;
+						}
 						else
-							infront = (r2->sprite->mobj->z <= rover->mobj->z);
+						{
+							z1 = r2->sprite->pz;
+							z2 = rover->pz;
+						}
+
+						z1 -= viewz;
+						z2 -= viewz;
+
+						infront = (z1 >= z2);
 					}
 				}
 				else
@@ -2753,7 +2784,7 @@ static void R_DrawSprite(vissprite_t *spr)
 	mceilingclip = spr->cliptop;
 
 	if (spr->cut & SC_SPLAT)
-		R_DrawFloorSprite(spr);
+		R_DrawFloorSplat(spr);
 	else
 		R_DrawVisSprite(spr);
 }
diff --git a/src/r_things.h b/src/r_things.h
index d15ae818c4c273dee396fe9c6b7919a95c87b98b..95b4215afa132ede0a11346036a3fffb4d6e7da9 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -151,10 +151,12 @@ typedef struct vissprite_s
 	INT32 x1, x2;
 
 	fixed_t gx, gy; // for line side calculation
-	fixed_t gz, gzt; // global bottom/top for silhouette clipping and sorting with 3D floors
+	fixed_t gz, gzt; // global bottom/top for silhouette clipping
+	fixed_t pz, pzt; // physical bottom/top for sorting with 3D floors
 
 	fixed_t startfrac; // horizontal position of x1
-	fixed_t scale;
+	fixed_t xscale, scale; // projected horizontal and vertical scales
+	fixed_t thingscale; // the object's scale
 	fixed_t sortscale; // sortscale only differs from scale for paper sprites, floor sprites, and MF2_LINKDRAW
 	fixed_t sortsplat; // the sortscale from behind the floor sprite
 	fixed_t scalestep; // only for paper sprites, 0 otherwise
@@ -162,6 +164,7 @@ typedef struct vissprite_s
 	fixed_t xiscale; // negative if flipped
 
 	angle_t centerangle; // for paper sprites
+	angle_t viewangle; // for floor sprites, the viewpoint's current angle
 
 	struct {
 		fixed_t tan; // The amount to shear the sprite vertically per row
@@ -182,9 +185,8 @@ typedef struct vissprite_s
 
 	extracolormap_t *extra_colormap; // global colormaps
 
-	fixed_t xscale;
-
 	// Precalculated top and bottom screen coords for the sprite.
+	fixed_t thingheight; // The actual height of the thing (for 3D floors)
 	sector_t *sector; // The sector containing the thing.
 	INT16 sz, szt;
 
diff --git a/src/s_sound.c b/src/s_sound.c
index 36bd454c104b02867c29d3f16f22f639ed06dc97..392a5b45328abd8c9667e745050ce2aa732d000a 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -2143,7 +2143,7 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 static lumpnum_t S_GetMusicLumpNum(const char *mname)
 {
 	boolean midipref = cv_musicpref.value;
-	
+
 	if (S_PrefAvailable(midipref, mname))
 		return W_GetNumForName(va(midipref ? "d_%s":"o_%s", mname));
 	else if (S_PrefAvailable(!midipref, mname))
@@ -2291,7 +2291,7 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32
 		I_FadeSong(0, prefadems, S_ChangeMusicToQueue);
 		return;
 	}
-	else if (strnicmp(music_name, newmusic, 6) || (mflags & MUSIC_FORCERESET) || 
+	else if (strnicmp(music_name, newmusic, 6) || (mflags & MUSIC_FORCERESET) ||
 		(midipref != currentmidi && S_PrefAvailable(midipref, newmusic)))
  	{
 		CONS_Debug(DBG_DETAILED, "Now playing song %s\n", newmusic);
diff --git a/src/screen.c b/src/screen.c
index 9d36eee39cb1da8c2ce14e5fc1392344a9dbefc9..d37724390dfa2148fe31c863a72c32a55552e596 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -33,6 +33,11 @@
 #include "s_sound.h" // ditto
 #include "g_game.h" // ditto
 #include "p_local.h" // P_AutoPause()
+#ifdef HWRENDER
+#include "hardware/hw_main.h"
+#include "hardware/hw_light.h"
+#include "hardware/hw_model.h"
+#endif
 
 
 #if defined (USEASM) && !defined (NORUSEASM)//&& (!defined (_MSC_VER) || (_MSC_VER <= 1200))
@@ -217,7 +222,7 @@ void SCR_SetMode(void)
 
 	// Set the video mode in the video interface.
 	if (setmodeneeded)
-		VID_SetMode(--setmodeneeded);
+		VID_SetMode(setmodeneeded - 1);
 
 	V_SetPalette(0);
 
@@ -423,6 +428,10 @@ void SCR_ChangeRenderer(void)
 			CONS_Alert(CONS_ERROR, "OpenGL never loaded\n");
 		return;
 	}
+
+	if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED)) // Clear these out before switching to software
+		HWR_ClearAllTextures();
+
 #endif
 
 	// Set the new render mode
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index 3985086626c4d17f157a63668627d210737d1546..96e3d7d6926ef23771c8dcf489b4d8d2a16c0a1c 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -90,7 +90,6 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(ReadRect);
 	GETFUNC(GClipRect);
 	GETFUNC(ClearMipMapCache);
-	GETFUNC(ClearCacheList);
 	GETFUNC(SetSpecialState);
 	GETFUNC(GetTextureUsed);
 	GETFUNC(DrawModel);
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 10c0747bf18b4e8fc725f4cfd3984a9183e79c56..a0dd6e1da707a1e521f53623d2de42b01e6f7dfa 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -54,12 +54,6 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #include <fcntl.h>
 #endif
 
-#if defined (_WIN32)
-DWORD TimeFunction(int requested_frequency);
-#else
-int TimeFunction(int requested_frequency);
-#endif
-
 #include <stdio.h>
 #ifdef _WIN32
 #include <conio.h>
@@ -143,6 +137,12 @@ int TimeFunction(int requested_frequency);
 #include <errno.h>
 #endif
 
+#if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)
+#include <execinfo.h>
+#include <time.h>
+#define UNIXBACKTRACE
+#endif
+
 // Locations for searching the srb2.pk3
 #if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)
 #define DEFAULTWADLOCATION1 "/usr/local/share/games/SRB2"
@@ -244,6 +244,71 @@ SDL_bool framebuffer = SDL_FALSE;
 
 UINT8 keyboard_started = false;
 
+#ifdef UNIXBACKTRACE
+#define STDERR_WRITE(string) if (fd != -1) I_OutputMsg("%s", string)
+#define CRASHLOG_WRITE(string) if (fd != -1) write(fd, string, strlen(string))
+#define CRASHLOG_STDERR_WRITE(string) \
+	if (fd != -1)\
+		write(fd, string, strlen(string));\
+	I_OutputMsg("%s", string)
+
+static void write_backtrace(INT32 signal)
+{
+	int fd = -1;
+	size_t size;
+	time_t rawtime;
+	struct tm timeinfo;
+
+	enum { BT_SIZE = 1024, STR_SIZE = 32 };
+	void *array[BT_SIZE];
+	char timestr[STR_SIZE];
+
+	const char *error = "An error occurred within SRB2! Send this stack trace to someone who can help!\n";
+	const char *error2 = "(Or find crash-log.txt in your SRB2 directory.)\n"; // Shown only to stderr.
+
+	fd = open(va("%s" PATHSEP "%s", srb2home, "crash-log.txt"), O_CREAT|O_APPEND|O_RDWR, S_IRUSR|S_IWUSR);
+
+	if (fd == -1)
+		I_OutputMsg("\nWARNING: Couldn't open crash log for writing! Make sure your permissions are correct. Please save the below report!\n");
+
+	// Get the current time as a string.
+	time(&rawtime);
+	localtime_r(&rawtime, &timeinfo);
+	strftime(timestr, STR_SIZE, "%a, %d %b %Y %T %z", &timeinfo);
+
+	CRASHLOG_WRITE("------------------------\n"); // Nice looking seperator
+
+	CRASHLOG_STDERR_WRITE("\n"); // Newline to look nice for both outputs.
+	CRASHLOG_STDERR_WRITE(error); // "Oops, SRB2 crashed" message
+	STDERR_WRITE(error2); // Tell the user where the crash log is.
+
+	// Tell the log when we crashed.
+	CRASHLOG_WRITE("Time of crash: ");
+	CRASHLOG_WRITE(timestr);
+	CRASHLOG_WRITE("\n");
+
+	// Give the crash log the cause and a nice 'Backtrace:' thing
+	// The signal is given to the user when the parent process sees we crashed.
+	CRASHLOG_WRITE("Cause: ");
+	CRASHLOG_WRITE(strsignal(signal));
+	CRASHLOG_WRITE("\n"); // Newline for the signal name
+
+	CRASHLOG_STDERR_WRITE("\nBacktrace:\n");
+
+	// Flood the output and log with the backtrace
+	size = backtrace(array, BT_SIZE);
+	backtrace_symbols_fd(array, size, fd);
+	backtrace_symbols_fd(array, size, STDERR_FILENO);
+
+	CRASHLOG_WRITE("\n"); // Write another newline to the log so it looks nice :)
+
+	close(fd);
+}
+#undef STDERR_WRITE
+#undef CRASHLOG_WRITE
+#undef CRASHLOG_STDERR_WRITE
+#endif // UNIXBACKTRACE
+
 static void I_ReportSignal(int num, int coredumped)
 {
 	//static char msg[] = "oh no! back to reality!\r\n";
@@ -303,6 +368,9 @@ FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num)
 {
 	D_QuitNetGame(); // Fix server freezes
 	CL_AbortDownloadResume();
+#ifdef UNIXBACKTRACE
+	write_backtrace(num);
+#endif
 	I_ReportSignal(num, 0);
 	I_ShutdownSystem();
 	signal(num, SIG_DFL);               //default signal action
@@ -693,6 +761,28 @@ static void I_RegisterSignals (void)
 #endif
 }
 
+#ifdef NEWSIGNALHANDLER
+static void signal_handler_child(INT32 num)
+{
+#ifdef UNIXBACKTRACE
+	write_backtrace(num);
+#endif
+
+	signal(num, SIG_DFL);               //default signal action
+	raise(num);
+}
+
+static void I_RegisterChildSignals(void)
+{
+	// If these defines don't exist,
+	// then compilation would have failed above us...
+	signal(SIGILL , signal_handler_child);
+	signal(SIGSEGV , signal_handler_child);
+	signal(SIGABRT , signal_handler_child);
+	signal(SIGFPE , signal_handler_child);
+}
+#endif
+
 //
 //I_OutputMsg
 //
@@ -2044,112 +2134,36 @@ ticcmd_t *I_BaseTiccmd2(void)
 	return &emptycmd2;
 }
 
-#if defined (_WIN32)
-static HMODULE winmm = NULL;
-static DWORD starttickcount = 0; // hack for win2k time bug
-static p_timeGetTime pfntimeGetTime = NULL;
-
-// ---------
-// I_GetTime
-// Use the High Resolution Timer if available,
-// else use the multimedia timer which has 1 millisecond precision on Windowz 95,
-// but lower precision on Windows NT
-// ---------
-
-DWORD TimeFunction(int requested_frequency)
-{
-	DWORD newtics = 0;
-	// this var acts as a multiplier if sub-millisecond precision is asked but is not available
-	int excess_frequency = requested_frequency / 1000;
-
-	if (!starttickcount) // high precision timer
-	{
-		LARGE_INTEGER currtime; // use only LowPart if high resolution counter is not available
-		static LARGE_INTEGER basetime = {{0, 0}};
-
-		// use this if High Resolution timer is found
-		static LARGE_INTEGER frequency;
-
-		if (!basetime.LowPart)
-		{
-			if (!QueryPerformanceFrequency(&frequency))
-				frequency.QuadPart = 0;
-			else
-				QueryPerformanceCounter(&basetime);
-		}
-
-		if (frequency.LowPart && QueryPerformanceCounter(&currtime))
-		{
-			newtics = (INT32)((currtime.QuadPart - basetime.QuadPart) * requested_frequency
-				/ frequency.QuadPart);
-		}
-		else if (pfntimeGetTime)
-		{
-			currtime.LowPart = pfntimeGetTime();
-			if (!basetime.LowPart)
-				basetime.LowPart = currtime.LowPart;
-			if (requested_frequency > 1000)
-				newtics = currtime.LowPart - basetime.LowPart * excess_frequency;
-			else
-				newtics = (currtime.LowPart - basetime.LowPart)/(1000/requested_frequency);
-		}
-	}
-	else
-	{
-		if (requested_frequency > 1000)
-			newtics = (GetTickCount() - starttickcount) * excess_frequency;
-		else
-			newtics = (GetTickCount() - starttickcount)/(1000/requested_frequency);
-	}
-
-	return newtics;
-}
-
-static void I_ShutdownTimer(void)
-{
-	pfntimeGetTime = NULL;
-	if (winmm)
-	{
-		p_timeEndPeriod pfntimeEndPeriod = (p_timeEndPeriod)(LPVOID)GetProcAddress(winmm, "timeEndPeriod");
-		if (pfntimeEndPeriod)
-			pfntimeEndPeriod(1);
-		FreeLibrary(winmm);
-		winmm = NULL;
-	}
-}
-#else
 //
 // I_GetTime
 // returns time in 1/TICRATE second tics
 //
 
-// millisecond precision only
-int TimeFunction(int requested_frequency)
-{
-	static Uint64 basetime = 0;
-		   Uint64 ticks = SDL_GetTicks();
+static Uint64 timer_frequency;
 
-	if (!basetime)
-		basetime = ticks;
+static double tic_frequency;
+static Uint64 tic_epoch;
 
-	ticks -= basetime;
+tic_t I_GetTime(void)
+{
+	static double elapsed;
 
-	ticks = (ticks*requested_frequency);
+	const Uint64 now = SDL_GetPerformanceCounter();
 
-	ticks = (ticks/1000);
+	elapsed += (now - tic_epoch) / tic_frequency;
+	tic_epoch = now; // moving epoch
 
-	return ticks;
+	return (tic_t)elapsed;
 }
-#endif
 
-tic_t I_GetTime(void)
+precise_t I_GetPreciseTime(void)
 {
-	return TimeFunction(NEWTICRATE);
+	return SDL_GetPerformanceCounter();
 }
 
-int I_GetTimeMicros(void)
+int I_PreciseToMicros(precise_t d)
 {
-	return TimeFunction(1000000);
+	return (int)(d / (timer_frequency / 1000000.0));
 }
 
 //
@@ -2157,26 +2171,11 @@ int I_GetTimeMicros(void)
 //
 void I_StartupTimer(void)
 {
-#ifdef _WIN32
-	// for win2k time bug
-	if (M_CheckParm("-gettickcount"))
-	{
-		starttickcount = GetTickCount();
-		CONS_Printf("%s", M_GetText("Using GetTickCount()\n"));
-	}
-	winmm = LoadLibraryA("winmm.dll");
-	if (winmm)
-	{
-		p_timeEndPeriod pfntimeBeginPeriod = (p_timeEndPeriod)(LPVOID)GetProcAddress(winmm, "timeBeginPeriod");
-		if (pfntimeBeginPeriod)
-			pfntimeBeginPeriod(1);
-		pfntimeGetTime = (p_timeGetTime)(LPVOID)GetProcAddress(winmm, "timeGetTime");
-	}
-	I_AddExitFunc(I_ShutdownTimer);
-#endif
-}
-
+	timer_frequency = SDL_GetPerformanceFrequency();
+	tic_epoch       = SDL_GetPerformanceCounter();
 
+	tic_frequency   = timer_frequency / (double)NEWTICRATE;
+}
 
 void I_Sleep(void)
 {
@@ -2220,6 +2219,7 @@ static void I_Fork(void)
 			newsignalhandler_Warn("fork()");
 			break;
 		case 0:
+			I_RegisterChildSignals();
 			break;
 		default:
 			if (logstream)
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index b8b3b9d34e8ae97648d3f1f6178511c251b9253e..0ed10463fc79734e004abdfdb960d56630168abf 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1057,8 +1057,7 @@ void I_GetEvent(void)
 					M_SetupJoystickMenu(0);
 			 	break;
 			case SDL_QUIT:
-				if (Playing())
-					LUAh_GameQuit();
+				LUAh_GameQuit(true);
 				I_Quit();
 				break;
 		}
@@ -1863,7 +1862,6 @@ void VID_StartupOpenGL(void)
 		HWD.pfnReadRect         = hwSym("ReadRect",NULL);
 		HWD.pfnGClipRect        = hwSym("GClipRect",NULL);
 		HWD.pfnClearMipMapCache = hwSym("ClearMipMapCache",NULL);
-		HWD.pfnClearCacheList   = hwSym("ClearCacheList",NULL);
 		HWD.pfnSetSpecialState  = hwSym("SetSpecialState",NULL);
 		HWD.pfnSetPalette       = hwSym("SetPalette",NULL);
 		HWD.pfnGetTextureUsed   = hwSym("GetTextureUsed",NULL);
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index c64164caafce5b52698e564a2514d57fa7a7eeda..412a21ea0098415b46eea3565630f6f9ce99bfe8 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -1298,6 +1298,10 @@ boolean I_PlaySong(boolean looping)
 	if (gme)
 	{
 		gme_equalizer_t eq = {GME_TREBLE, GME_BASS, 0,0,0,0,0,0,0,0};
+#if defined (GME_VERSION) && GME_VERSION >= 0x000603
+		if (looping)
+			gme_set_autoload_playback_limit(gme, 0);
+#endif        
 		gme_set_equalizer(gme, &eq);
 		gme_start_track(gme, 0);
 		current_track = 0;
diff --git a/src/st_stuff.c b/src/st_stuff.c
index b25538d883276cdac46784489462b2c32f349fc9..a1fbbec03ae7321455d7fe93ff8545d6aa542e7d 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -299,10 +299,6 @@ void ST_LoadGraphics(void)
 	gravboots = W_CachePatchName("TVGVICON", PU_HUDGFX);
 
 	tagico = W_CachePatchName("TAGICO", PU_HUDGFX);
-	rflagico = W_CachePatchName("RFLAGICO", PU_HUDGFX);
-	bflagico = W_CachePatchName("BFLAGICO", PU_HUDGFX);
-	rmatcico = W_CachePatchName("RMATCICO", PU_HUDGFX);
-	bmatcico = W_CachePatchName("BMATCICO", PU_HUDGFX);
 	gotrflag = W_CachePatchName("GOTRFLAG", PU_HUDGFX);
 	gotbflag = W_CachePatchName("GOTBFLAG", PU_HUDGFX);
 	fnshico = W_CachePatchName("FNSHICO", PU_HUDGFX);
@@ -2363,27 +2359,29 @@ static inline void ST_drawRaceHUD(void)
 
 static void ST_drawTeamHUD(void)
 {
-	patch_t *p;
 #define SEP 20
 
 	if (F_GetPromptHideHud(0)) // y base is 0
 		return;
 
-	if (gametyperules & GTR_TEAMFLAGS)
-		p = bflagico;
-	else
-		p = bmatcico;
-
-	if (LUA_HudEnabled(hud_teamscores))
-		V_DrawSmallScaledPatch(BASEVIDWIDTH/2 - SEP - (p->width / 4), 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
-
-	if (gametyperules & GTR_TEAMFLAGS)
-		p = rflagico;
-	else
-		p = rmatcico;
+	rflagico = W_CachePatchName("RFLAGICO", PU_HUDGFX);
+	bflagico = W_CachePatchName("BFLAGICO", PU_HUDGFX);
+	rmatcico = W_CachePatchName("RMATCICO", PU_HUDGFX);
+	bmatcico = W_CachePatchName("BMATCICO", PU_HUDGFX);
 
 	if (LUA_HudEnabled(hud_teamscores))
-		V_DrawSmallScaledPatch(BASEVIDWIDTH/2 + SEP - (p->width / 4), 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
+	{
+		if (gametyperules & GTR_TEAMFLAGS)
+		{
+			V_DrawSmallScaledPatch(BASEVIDWIDTH/2 - SEP - (bflagico->width / 4), 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, bflagico);
+			V_DrawSmallScaledPatch(BASEVIDWIDTH/2 + SEP - (rflagico->width / 4), 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, rflagico);
+		}
+		else
+		{
+			V_DrawSmallScaledPatch(BASEVIDWIDTH/2 - SEP - (bmatcico->width / 4), 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, bmatcico);
+			V_DrawSmallScaledPatch(BASEVIDWIDTH/2 + SEP - (rmatcico->width / 4), 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, rmatcico);
+		}
+	}
 
 	if (!(gametyperules & GTR_TEAMFLAGS))
 		goto num;
@@ -2751,7 +2749,6 @@ static void ST_overlayDrawer(void)
 
 void ST_Drawer(void)
 {
-#ifdef SEENAMES
 	if (cv_seenames.value && cv_allowseenames.value && displayplayer == consoleplayer && seenplayer && seenplayer->mo)
 	{
 		INT32 c = 0;
@@ -2775,7 +2772,6 @@ void ST_Drawer(void)
 
 		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF|c, player_names[seenplayer-players]);
 	}
-#endif
 
 	// Doom's status bar only updated if necessary.
 	// However, ours updates every frame regardless, so the "refresh" param was removed
diff --git a/src/taglist.c b/src/taglist.c
index b11216b6cf7b3e7c709b73750c2d46e8e9155c40..a759f4d02bfc0658ea68a6b6becd20dffaad7700 100644
--- a/src/taglist.c
+++ b/src/taglist.c
@@ -15,6 +15,11 @@
 #include "z_zone.h"
 #include "r_data.h"
 
+// Bit array of whether a tag exists for sectors/lines/things.
+bitarray_t tags_available[BIT_ARRAY_SIZE (MAXTAGS)];
+
+size_t num_tags;
+
 // Taggroups are used to list elements of the same tag, for iteration.
 // Since elements can now have multiple tags, it means an element may appear
 // in several taggroups at the same time. These are built on level load.
@@ -105,6 +110,39 @@ size_t Taggroup_Find (const taggroup_t *group, const size_t id)
 	return -1;
 }
 
+/// group->count, but also checks for NULL
+size_t Taggroup_Count (const taggroup_t *group)
+{
+	return group ? group->count : 0;
+}
+
+/// Iterate thru elements in a global taggroup.
+INT32 Taggroup_Iterate
+(		taggroup_t *garray[],
+		const size_t max_elements,
+		const mtag_t tag,
+		const size_t p)
+{
+	const taggroup_t *group;
+
+	if (tag == MTAG_GLOBAL)
+	{
+		if (p < max_elements)
+			return p;
+		return -1;
+	}
+
+	group = garray[(UINT16)tag];
+
+	if (group)
+	{
+		if (p < group->count)
+			return group->elements[p];
+		return -1;
+	}
+	return -1;
+}
+
 /// Add an element to a global taggroup.
 void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id)
 {
@@ -120,6 +158,12 @@ void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id)
 	if (Taggroup_Find(group, id) != (size_t)-1)
 		return;
 
+	if (! in_bit_array(tags_available, tag))
+	{
+		num_tags++;
+		set_bit_array(tags_available, tag);
+	}
+
 	// Create group if empty.
 	if (!group)
 	{
@@ -133,25 +177,34 @@ void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id)
 		for (i = 0; i < group->count; i++)
 			if (group->elements[i] > id)
 				break;
+	}
 
-		group->elements = Z_Realloc(group->elements, (group->count + 1) * sizeof(size_t), PU_LEVEL, NULL);
+	group->elements = Z_Realloc(group->elements, (group->count + 1) * sizeof(size_t), PU_LEVEL, NULL);
 
-		// Offset existing elements to make room for the new one.
-		if (i < group->count)
-			memmove(&group->elements[i + 1], &group->elements[i], group->count - i);
-	}
+	// Offset existing elements to make room for the new one.
+	if (i < group->count)
+		memmove(&group->elements[i + 1], &group->elements[i], group->count - i);
 
 	group->count++;
-	group->elements = Z_Realloc(group->elements, group->count * sizeof(size_t), PU_LEVEL, NULL);
 	group->elements[i] = id;
 }
 
+static size_t total_elements_with_tag (const mtag_t tag)
+{
+	return
+		(
+				Taggroup_Count(tags_sectors[tag]) +
+				Taggroup_Count(tags_lines[tag]) +
+				Taggroup_Count(tags_mapthings[tag])
+		);
+}
+
 /// Remove an element from a global taggroup.
 void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id)
 {
 	taggroup_t *group;
 	size_t rempos;
-	size_t newcount;
+	size_t oldcount;
 
 	if (tag == MTAG_GLOBAL)
 		return;
@@ -161,8 +214,14 @@ void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id)
 	if ((rempos = Taggroup_Find(group, id)) == (size_t)-1)
 		return;
 
+	if (group->count == 1 && total_elements_with_tag(tag) == 1)
+	{
+		num_tags--;
+		unset_bit_array(tags_available, tag);
+	}
+
 	// Strip away taggroup if no elements left.
-	if (!(newcount = --group->count))
+	if (!(oldcount = group->count--))
 	{
 		Z_Free(group->elements);
 		Z_Free(group);
@@ -170,19 +229,18 @@ void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id)
 	}
 	else
 	{
-		size_t *newelements = Z_Malloc(newcount * sizeof(size_t), PU_LEVEL, NULL);
+		size_t *newelements = Z_Malloc(group->count * sizeof(size_t), PU_LEVEL, NULL);
 		size_t i;
 
 		// Copy the previous entries save for the one to remove.
 		for (i = 0; i < rempos; i++)
 			newelements[i] = group->elements[i];
 
-		for (i = rempos + 1; i < group->count; i++)
+		for (i = rempos + 1; i < oldcount; i++)
 			newelements[i - 1] = group->elements[i];
 
 		Z_Free(group->elements);
 		group->elements = newelements;
-		group->count = newcount;
 	}
 }
 
@@ -209,6 +267,9 @@ void Taglist_InitGlobalTables(void)
 {
 	size_t i, j;
 
+	memset(tags_available, 0, sizeof tags_available);
+	num_tags = 0;
+
 	for (i = 0; i < MAXTAGS; i++)
 	{
 		tags_sectors[i] = NULL;
@@ -236,56 +297,17 @@ void Taglist_InitGlobalTables(void)
 
 INT32 Tag_Iterate_Sectors (const mtag_t tag, const size_t p)
 {
-	if (tag == MTAG_GLOBAL)
-	{
-		if (p < numsectors)
-			return p;
-		return -1;
-	}
-
-	if (tags_sectors[(UINT16)tag])
-	{
-		if (p < tags_sectors[(UINT16)tag]->count)
-			return tags_sectors[(UINT16)tag]->elements[p];
-		return -1;
-	}
-	return -1;
+	return Taggroup_Iterate(tags_sectors, numsectors, tag, p);
 }
 
 INT32 Tag_Iterate_Lines (const mtag_t tag, const size_t p)
 {
-	if (tag == MTAG_GLOBAL)
-	{
-		if (p < numlines)
-			return p;
-		return -1;
-	}
-
-	if (tags_lines[(UINT16)tag])
-	{
-		if (p < tags_lines[(UINT16)tag]->count)
-			return tags_lines[(UINT16)tag]->elements[p];
-		return -1;
-	}
-	return -1;
+	return Taggroup_Iterate(tags_lines, numlines, tag, p);
 }
 
 INT32 Tag_Iterate_Things (const mtag_t tag, const size_t p)
 {
-	if (tag == MTAG_GLOBAL)
-	{
-		if (p < nummapthings)
-			return p;
-		return -1;
-	}
-
-	if (tags_mapthings[(UINT16)tag])
-	{
-		if (p < tags_mapthings[(UINT16)tag]->count)
-			return tags_mapthings[(UINT16)tag]->elements[p];
-		return -1;
-	}
-	return -1;
+	return Taggroup_Iterate(tags_mapthings, nummapthings, tag, p);
 }
 
 INT32 Tag_FindLineSpecial(const INT16 special, const mtag_t tag)
diff --git a/src/taglist.h b/src/taglist.h
index 0e6d9f8422bbc150dbbabe3c6048d6e66c448f05..a0529ab6b6afae4fe4252e61fd5848e2bd875ef6 100644
--- a/src/taglist.h
+++ b/src/taglist.h
@@ -43,6 +43,10 @@ typedef struct
 	size_t count;
 } taggroup_t;
 
+extern bitarray_t tags_available[];
+
+extern size_t num_tags;
+
 extern taggroup_t* tags_sectors[];
 extern taggroup_t* tags_lines[];
 extern taggroup_t* tags_mapthings[];
@@ -50,6 +54,13 @@ extern taggroup_t* tags_mapthings[];
 void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id);
 void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id);
 size_t Taggroup_Find (const taggroup_t *group, const size_t id);
+size_t Taggroup_Count (const taggroup_t *group);
+
+INT32 Taggroup_Iterate
+(		taggroup_t *garray[],
+		const size_t max_elements,
+		const mtag_t tag,
+		const size_t p);
 
 void Taglist_InitGlobalTables(void);
 
diff --git a/src/w_wad.c b/src/w_wad.c
index aca530fa518c7134636349b769f6760590cc79e3..6149aec6e756adea77cf9e0662de22042930b17a 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -66,6 +66,7 @@
 #include "p_setup.h" // P_ScanThings
 #endif
 #include "m_misc.h" // M_MapNumber
+#include "g_game.h" // G_SetGameModified
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -683,9 +684,9 @@ static UINT16 W_InitFileError (const char *filename, boolean exitworthy)
 	if (exitworthy)
 	{
 #ifdef _DEBUG
-		CONS_Error("A WAD file was not found or not valid.\nCheck the log to see which ones.\n");
+		CONS_Error(va("%s was not found or not valid.\nCheck the log for more details.\n", filename));
 #else
-		I_Error("A WAD file was not found or not valid.\nCheck the log to see which ones.\n");
+		I_Error("%s was not found or not valid.\nCheck the log for more details.\n", filename);
 #endif
 	}
 	else
@@ -716,7 +717,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 #endif
 	size_t packetsize;
 	UINT8 md5sum[16];
-	boolean important;
+	int important;
 
 	if (!(refreshdirmenu & REFRESHDIR_ADDFILE))
 		refreshdirmenu = REFRESHDIR_NORMAL|REFRESHDIR_ADDFILE; // clean out cons_alerts that happened earlier
@@ -746,10 +747,18 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 	if ((handle = W_OpenWadFile(&filename, true)) == NULL)
 		return W_InitFileError(filename, startup);
 
+	important = W_VerifyNMUSlumps(filename, startup);
+
+	if (important == -1)
+	{
+		fclose(handle);
+		return INT16_MAX;
+	}
+
 	// Check if wad files will overflow fileneededbuffer. Only the filename part
 	// is send in the packet; cf.
 	// see PutFileNeeded in d_netfil.c
-	if ((important = !W_VerifyNMUSlumps(filename)))
+	if ((important = !important))
 	{
 		packetsize = packetsizetally + nameonlylength(filename) + 22;
 
@@ -811,6 +820,12 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 		return W_InitFileError(filename, startup);
 	}
 
+	if (important && !mainfile)
+	{
+		//G_SetGameModified(true);
+		modifiedgame = true; // avoid savemoddata being set to false
+	}
+
 	//
 	// link wad file to search files
 	//
@@ -1670,26 +1685,12 @@ void *W_CacheSoftwarePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 
 		// read the lump in full
 		W_ReadLumpHeaderPwad(wad, lump, lumpdata, 0, 0);
+		ptr = lumpdata;
 
 #ifndef NO_PNG_LUMPS
-		// lump is a png so convert it
 		if (Picture_IsLumpPNG((UINT8 *)lumpdata, len))
-		{
-			size_t newlen;
-			void *converted = Picture_PNGConvert((UINT8 *)lumpdata, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, len, &newlen, 0);
-			ptr = Z_Malloc(newlen, PU_STATIC, NULL);
-			M_Memcpy(ptr, converted, newlen);
-			Z_Free(converted);
-			len = newlen;
-		}
-		else // just copy it into the patch cache
+			ptr = Picture_PNGConvert((UINT8 *)lumpdata, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, len, &len, 0);
 #endif
-		{
-			ptr = Z_Malloc(len, PU_STATIC, NULL);
-			M_Memcpy(ptr, lumpdata, len);
-		}
-
-		Z_Free(lumpdata);
 
 		dest = Z_Calloc(sizeof(patch_t), tag, &lumpcache[lump]);
 		Patch_Create(ptr, len, dest);
@@ -1735,6 +1736,9 @@ void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag)
 
 void W_UnlockCachedPatch(void *patch)
 {
+	if (!patch)
+		return;
+
 	// The hardware code does its own memory management, as its patches
 	// have different lifetimes from software's.
 #ifdef HWRENDER
@@ -1919,8 +1923,16 @@ static lumpchecklist_t folderblacklist[] =
 static int
 W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 {
+	int verified = true;
+
     zend_t zend;
     zentry_t zentry;
+    zlentry_t zlentry;
+
+	long file_size;/* size of zip file */
+	long data_size;/* size of data inside zip file */
+
+	long old_position;
 
 	UINT16 numlumps;
 	size_t i;
@@ -1936,6 +1948,8 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 	// Central directory bullshit
 
 	fseek(fp, 0, SEEK_END);
+	file_size = ftell(fp);
+
 	if (!ResFindSignature(fp, pat_end, max(0, ftell(fp) - (22 + 65536))))
 		return true;
 
@@ -1943,6 +1957,8 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 	if (fread(&zend, 1, sizeof zend, fp) < sizeof zend)
 		return true;
 
+	data_size = sizeof zend;
+
 	numlumps = zend.entries;
 
 	fseek(fp, zend.cdiroffset, SEEK_SET);
@@ -1957,40 +1973,79 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 		if (memcmp(zentry.signature, pat_central, 4))
 			return true;
 
-		fullname = malloc(zentry.namelen + 1);
-		if (fgets(fullname, zentry.namelen + 1, fp) != fullname)
-			return true;
+		if (verified == true)
+		{
+			fullname = malloc(zentry.namelen + 1);
+			if (fgets(fullname, zentry.namelen + 1, fp) != fullname)
+				return true;
 
-		// Strip away file address and extension for the 8char name.
-		if ((trimname = strrchr(fullname, '/')) != 0)
-			trimname++;
-		else
-			trimname = fullname; // Care taken for root files.
+			// Strip away file address and extension for the 8char name.
+			if ((trimname = strrchr(fullname, '/')) != 0)
+				trimname++;
+			else
+				trimname = fullname; // Care taken for root files.
 
-		if (*trimname) // Ignore directories, well kinda
-		{
-			if ((dotpos = strrchr(trimname, '.')) == 0)
-				dotpos = fullname + strlen(fullname); // Watch for files without extension.
+			if (*trimname) // Ignore directories, well kinda
+			{
+				if ((dotpos = strrchr(trimname, '.')) == 0)
+					dotpos = fullname + strlen(fullname); // Watch for files without extension.
+
+				memset(lumpname, '\0', 9); // Making sure they're initialized to 0. Is it necessary?
+				strncpy(lumpname, trimname, min(8, dotpos - trimname));
+
+				if (! W_VerifyName(lumpname, checklist, status))
+					verified = false;
 
-			memset(lumpname, '\0', 9); // Making sure they're initialized to 0. Is it necessary?
-			strncpy(lumpname, trimname, min(8, dotpos - trimname));
+				// Check for directories next, if it's blacklisted it will return false
+				else if (W_VerifyName(fullname, folderblacklist, status))
+					verified = false;
+			}
 
-			if (! W_VerifyName(lumpname, checklist, status))
-				return false;
+			free(fullname);
 
-			// Check for directories next, if it's blacklisted it will return false
-			if (W_VerifyName(fullname, folderblacklist, status))
-				return false;
+			// skip and ignore comments/extra fields
+			if (fseek(fp, zentry.xtralen + zentry.commlen, SEEK_CUR) != 0)
+				return true;
+		}
+		else
+		{
+			if (fseek(fp, zentry.namelen + zentry.xtralen + zentry.commlen, SEEK_CUR) != 0)
+				return true;
 		}
 
-		free(fullname);
+		data_size +=
+			sizeof zentry + zentry.namelen + zentry.xtralen + zentry.commlen;
 
-		// skip and ignore comments/extra fields
-		if (fseek(fp, zentry.xtralen + zentry.commlen, SEEK_CUR) != 0)
+		old_position = ftell(fp);
+
+		if (fseek(fp, zentry.offset, SEEK_SET) != 0)
+			return true;
+
+		if (fread(&zlentry, 1, sizeof(zlentry_t), fp) < sizeof (zlentry_t))
 			return true;
+
+		data_size +=
+			sizeof zlentry + zlentry.namelen + zlentry.xtralen + zlentry.compsize;
+
+		fseek(fp, old_position, SEEK_SET);
 	}
 
-	return true;
+	if (data_size < file_size)
+	{
+		const char * error = "ZIP file has holes (%ld extra bytes)\n";
+		CONS_Alert(CONS_ERROR, error, (file_size - data_size));
+		return -1;
+	}
+	else if (data_size > file_size)
+	{
+		const char * error = "Reported size of ZIP file contents exceeds file size (%ld extra bytes)\n";
+		CONS_Alert(CONS_ERROR, error, (data_size - file_size));
+		return -1;
+	}
+	else
+	{
+		return verified;
+	}
 }
 
 // Note: This never opens lumps themselves and therefore doesn't have to
@@ -2029,12 +2084,13 @@ static int W_VerifyFile(const char *filename, lumpchecklist_t *checklist,
   * be sent.
   *
   * \param filename Filename of the wad to check.
+  * \param exit_on_error Whether to exit upon file error.
   * \return 1 if file contains only music/sound lumps, 0 if it contains other
   *         stuff (maps, sprites, dehacked lumps, and so on). -1 if there no
   *         file exists with that filename
   * \author Alam Arias
   */
-int W_VerifyNMUSlumps(const char *filename)
+int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error)
 {
 	// MIDI, MOD/S3M/IT/XM/OGG/MP3/WAV, WAVE SFX
 	// ENDOOM text and palette lumps
@@ -2080,7 +2136,7 @@ int W_VerifyNMUSlumps(const char *filename)
 		{"LT", 2}, // Titlecard changes
 
 		{"SLID", 4}, // Continue
-		{"CONT", 4}, 
+		{"CONT", 4},
 
 		{"MINICAPS", 8}, // NiGHTS graphics here and below
 		{"BLUESTAT", 8}, // Sphere status
@@ -2108,7 +2164,13 @@ int W_VerifyNMUSlumps(const char *filename)
 
 		{NULL, 0},
 	};
-	return W_VerifyFile(filename, NMUSlist, false);
+
+	int status = W_VerifyFile(filename, NMUSlist, false);
+
+	if (status == -1)
+		W_InitFileError(filename, exit_on_error);
+
+	return status;
 }
 
 /** \brief Generates a virtual resource used for level data loading.
diff --git a/src/w_wad.h b/src/w_wad.h
index 1e86eea5a6b2ac991d26d47b98cf3416f4de5b2b..d0a86bcb44817b678fdda16fd426692cc222c89a 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -206,6 +206,6 @@ void W_UnlockCachedPatch(void *patch);
 
 void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5);
 
-int W_VerifyNMUSlumps(const char *filename);
+int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error);
 
 #endif // __W_WAD__
diff --git a/src/win32/Makefile.cfg b/src/win32/Makefile.cfg
index 486616f2d79b477ec7474c5f9982b72d8d663ae3..702ae3765ce52effaccb48b7d3c3e196a7ac9214 100644
--- a/src/win32/Makefile.cfg
+++ b/src/win32/Makefile.cfg
@@ -56,14 +56,6 @@ ifndef GCC44
 	#OPTS+=-mms-bitfields
 endif
 
-ifndef SDL
-	OPTS+=-D_WINDOWS
-endif
-
-ifndef SDL
-	LIBS+=-lmingw32 -mwindows -ldinput -ldxguid -lgdi32 -lwinmm
-endif
-
 	LIBS+=-ladvapi32 -lkernel32 -lmsvcrt -luser32
 ifdef MINGW64
 	LIBS+=-lws2_32
@@ -76,11 +68,7 @@ endif
 endif
 
 	# name of the exefile
-ifdef SDL
 	EXENAME?=srb2win.exe
-else
-	EXENAME?=srb2dd.exe
-endif
 
 ifdef SDL
 	i_system_o+=$(OBJDIR)/SRB2.res
@@ -88,22 +76,6 @@ ifdef SDL
 ifndef NOHW
 	OPTS+=-DUSE_WGL_SWAP
 endif
-else
-	D_FILES+=$(D_DIR)/fmodex.dll
-	CFLAGS+=-I../libs/fmodex/inc
-	LDFLAGS+=-L../libs/fmodex/lib
-ifdef MINGW64
-	LIBS+=-lfmodex64_vc
-else
-	LIBS+=-lfmodex_vc
-endif
-	i_cdmus_o=$(OBJDIR)/win_cd.o
-	i_net_o=$(OBJDIR)/win_net.o
-	i_system_o=$(OBJDIR)/win_sys.o $(OBJDIR)/SRB2.res
-	i_sound_o=$(OBJDIR)/win_snd.o
-	i_main_o=$(OBJDIR)/win_main.o
-	#i_main_o+=$(OBJDIR)/win_dbg.o
-	OBJS=$(OBJDIR)/dx_error.o $(OBJDIR)/fabdxlib.o $(OBJDIR)/win_vid.o $(OBJDIR)/win_dll.o
 endif
 
 
@@ -161,4 +133,4 @@ ifdef MINGW64
 else
 	CURL_LDFLAGS+=-L../libs/curl/lib32 -lcurl
 endif #MINGW64
-endif
\ No newline at end of file
+endif
diff --git a/src/win32/win_dll.c b/src/win32/win_dll.c
index d942d8cd406ad55b85b0e1bdd768631588f244ec..4743cec34b2e6af738caeec60d7c179e58ec14d1 100644
--- a/src/win32/win_dll.c
+++ b/src/win32/win_dll.c
@@ -111,7 +111,6 @@ static loadfunc_t hwdFuncTable[] = {
 	{"ReadRect@24",         &hwdriver.pfnReadRect},
 	{"GClipRect@20",        &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache@0",  &hwdriver.pfnClearMipMapCache},
-	{"ClearCacheList@0",    &hwdriver.pfnClearCacheList},
 	{"SetSpecialState@8",   &hwdriver.pfnSetSpecialState},
 	{"DrawModel@16",        &hwdriver.pfnDrawModel},
 	{"SetTransform@4",      &hwdriver.pfnSetTransform},
@@ -145,7 +144,6 @@ static loadfunc_t hwdFuncTable[] = {
 	{"ReadRect",            &hwdriver.pfnReadRect},
 	{"GClipRect",           &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache",    &hwdriver.pfnClearMipMapCache},
-	{"ClearCacheList",      &hwdriver.pfnClearCacheList},
 	{"SetSpecialState",     &hwdriver.pfnSetSpecialState},
 	{"DrawModel",           &hwdriver.pfnDrawModel},
 	{"SetTransform",        &hwdriver.pfnSetTransform},
diff --git a/src/y_inter.c b/src/y_inter.c
index 061cbb5e1d45ed5659593b3f88944bba74e82e4d..4354a1677e706b293a3370382f1bf8fbf6c1c19f 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -215,6 +215,89 @@ static void Y_IntermissionTokenDrawer(void)
 		V_DrawCroppedPatch(32<<FRACBITS, y<<FRACBITS, FRACUNIT/2, 0, tokenicon, 0, 0, tokenicon->width, calc);
 }
 
+
+//
+// Y_LoadIntermissionData
+//
+// Load patches for drawing the intermission, if acceptable
+//
+void Y_LoadIntermissionData(void)
+{
+	INT32 i;
+
+	if (dedicated)
+		return;
+
+	switch (intertype)
+	{
+		case int_coop:
+		{
+			for (i = 0; i < 4; ++i)
+			{
+				if (strlen(data.coop.bonuses[i].patch))
+					data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_PATCH);
+			}
+			data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_PATCH);
+
+			// get background patches
+			bgpatch = W_CachePatchName("INTERSCR", PU_PATCH);
+
+			// grab an interscreen if appropriate
+			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
+				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
+			break;
+		}
+		case int_spec:
+		{
+			for (i = 0; i < 2; ++i)
+				data.spec.bonuspatches[i] = W_CachePatchName(data.spec.bonuses[i].patch, PU_PATCH);
+
+			data.spec.pscore = W_CachePatchName("YB_SCORE", PU_PATCH);
+			data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_PATCH);
+
+			// get background tile
+			bgtile = W_CachePatchName("SPECTILE", PU_PATCH);
+
+			// grab an interscreen if appropriate
+			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
+				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
+			break;
+		}
+		case int_ctf:
+		case int_teammatch:
+		{
+			if (!rflagico) //prevent a crash if we haven't cached our team graphics yet
+			{
+				rflagico = W_CachePatchName("RFLAGICO", PU_HUDGFX);
+				bflagico = W_CachePatchName("BFLAGICO", PU_HUDGFX);
+				rmatcico = W_CachePatchName("RMATCICO", PU_HUDGFX);
+				bmatcico = W_CachePatchName("BMATCICO", PU_HUDGFX);
+			}
+
+			data.match.redflag = (intertype == int_ctf) ? rflagico : rmatcico;
+			data.match.blueflag = (intertype == int_ctf) ? bflagico : bmatcico;
+		}
+		/* FALLTHRU */
+		case int_match:
+		case int_race:
+		case int_comp:
+		{
+			if (intertype == int_match || intertype == int_race)
+			{
+				// get RESULT header
+				data.match.result = W_CachePatchName("RESULT", PU_PATCH);
+			}
+
+			// get background tile
+			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
+			break;
+		}
+		case int_none:
+		default:
+			break;
+	}
+}
+
 //
 // Y_ConsiderScreenBuffer
 //
@@ -1181,10 +1264,9 @@ void Y_DetermineIntermissionType(void)
 //
 // Called by G_DoCompleted. Sets up data for intermission drawer/ticker.
 //
+//
 void Y_StartIntermission(void)
 {
-	INT32 i;
-
 	intertic = -1;
 
 #ifdef PARANOIA
@@ -1228,20 +1310,12 @@ void Y_StartIntermission(void)
 			// setup time data
 			data.coop.tics = players[consoleplayer].realtime;
 
-			for (i = 0; i < 4; ++i)
-				data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_PATCH);
-			data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_PATCH);
-
 			// get act number
 			data.coop.actnum = mapheaderinfo[gamemap-1]->actnum;
 
-			// get background patches
-			bgpatch = W_CachePatchName("INTERSCR", PU_PATCH);
-
 			// grab an interscreen if appropriate
 			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
 			{
-				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
 				useinterpic = true;
 				usebuffer = false;
 			}
@@ -1298,21 +1372,9 @@ void Y_StartIntermission(void)
 			// give out ring bonuses
 			Y_AwardSpecialStageBonus();
 
-			for (i = 0; i < 2; ++i)
-				data.spec.bonuspatches[i] = W_CachePatchName(data.spec.bonuses[i].patch, PU_PATCH);
-
-			data.spec.pscore = W_CachePatchName("YB_SCORE", PU_PATCH);
-			data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_PATCH);
-
-			// get background tile
-			bgtile = W_CachePatchName("SPECTILE", PU_PATCH);
-
 			// grab an interscreen if appropriate
 			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
-			{
-				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
 				useinterpic = true;
-			}
 			else
 				useinterpic = false;
 
@@ -1405,11 +1467,6 @@ void Y_StartIntermission(void)
 
 			data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';
 
-			// get RESULT header
-			data.match.result =
-				W_CachePatchName("RESULT", PU_PATCH);
-
-			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
 			usetile = true;
 			useinterpic = false;
 			break;
@@ -1434,10 +1491,6 @@ void Y_StartIntermission(void)
 
 			data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';
 
-			// get RESULT header
-			data.match.result = W_CachePatchName("RESULT", PU_PATCH);
-
-			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
 			usetile = true;
 			useinterpic = false;
 			break;
@@ -1463,18 +1516,6 @@ void Y_StartIntermission(void)
 
 			data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';
 
-			if (intertype == int_ctf)
-			{
-				data.match.redflag = rflagico;
-				data.match.blueflag = bflagico;
-			}
-			else // team match
-			{
-				data.match.redflag = rmatcico;
-				data.match.blueflag = bmatcico;
-			}
-
-			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
 			usetile = true;
 			useinterpic = false;
 			break;
@@ -1499,8 +1540,6 @@ void Y_StartIntermission(void)
 
 			data.competition.levelstring[sizeof data.competition.levelstring - 1] = '\0';
 
-			// get background tile
-			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
 			usetile = true;
 			useinterpic = false;
 			break;
@@ -1733,7 +1772,6 @@ static void Y_SetNullBonus(player_t *player, y_bonus_t *bstruct)
 {
 	(void)player;
 	memset(bstruct, 0, sizeof(y_bonus_t));
-	strncpy(bstruct->patch, "MISSING", sizeof(bstruct->patch));
 }
 
 //
@@ -2031,7 +2069,8 @@ static void Y_AwardSpecialStageBonus(void)
 //
 void Y_EndIntermission(void)
 {
-	Y_UnloadData();
+	if (!dedicated)
+		Y_UnloadData();
 
 	endtic = -1;
 	intertype = int_none;
diff --git a/src/y_inter.h b/src/y_inter.h
index 859144b1d4ad71f7a98a797327537a213e0970b2..7268b1a473f14764701cd3a13a91f459447e6bf6 100644
--- a/src/y_inter.h
+++ b/src/y_inter.h
@@ -14,6 +14,7 @@ extern boolean usebuffer;
 void Y_IntermissionDrawer(void);
 void Y_Ticker(void);
 
+void Y_LoadIntermissionData(void);
 void Y_StartIntermission(void);
 void Y_EndIntermission(void);
 
diff --git a/src/z_zone.c b/src/z_zone.c
index ad64a3a07f04f01d40ac291cfa4e77f26bd37e88..d7da17e51d1608cf068a0c929bbdbd982d3a935c 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -813,12 +813,12 @@ static void Command_Memfree_f(void)
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
 	{
-		CONS_Printf(M_GetText("Patch info headers: %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPATCHINFO)>>10));
-		CONS_Printf(M_GetText("Mipmap patches    : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPATCHCOLMIPMAP)>>10));
-		CONS_Printf(M_GetText("HW Texture cache  : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRCACHE)>>10));
-		CONS_Printf(M_GetText("Plane polygons    : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPLANE)>>10));
-		CONS_Printf(M_GetText("HW model textures : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRMODELTEXTURE)>>10));
-		CONS_Printf(M_GetText("HW Texture used   : %7d KB\n"), HWR_GetTextureUsed()>>10);
+		CONS_Printf(M_GetText("Patch info headers     : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPATCHINFO)>>10));
+		CONS_Printf(M_GetText("Cached textures        : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRCACHE)>>10));
+		CONS_Printf(M_GetText("Texture colormaps      : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPATCHCOLMIPMAP)>>10));
+		CONS_Printf(M_GetText("Model textures         : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRMODELTEXTURE)>>10));
+		CONS_Printf(M_GetText("Plane polygons         : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPLANE)>>10));
+		CONS_Printf(M_GetText("All GPU textures       : %7d KB\n"), HWR_GetTextureUsed()>>10);
 	}
 #endif
 
diff --git a/src/z_zone.h b/src/z_zone.h
index e80a45e7fb4f2ed6223868fc78d18649e75ab4ad..7b58be8f3fce52c01642e20bee0de00c1795bece 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -68,8 +68,7 @@ enum
 	PU_HWRCACHE_UNLOCKED     = 102, // 'unlocked' PU_HWRCACHE memory:
 									// 'second-level' cache for graphics
                                     // stored in hardware format and downloaded as needed
-	PU_HWRPATCHINFO_UNLOCKED    = 103, // 'unlocked' PU_HWRPATCHINFO memory
-	PU_HWRMODELTEXTURE_UNLOCKED = 104, // 'unlocked' PU_HWRMODELTEXTURE memory
+	PU_HWRMODELTEXTURE_UNLOCKED = 103, // 'unlocked' PU_HWRMODELTEXTURE memory
 };
 
 //