diff --git a/extras/conf/SRB2-22.cfg b/extras/conf/SRB2-22.cfg
index 9aeada89b6c470c9986347a7ee4e72136604d1aa..405923c6c9e28d6079b8f250ded2c1c5d6d603fc 100644
--- a/extras/conf/SRB2-22.cfg
+++ b/extras/conf/SRB2-22.cfg
@@ -1097,6 +1097,7 @@ linedeftypes
 		{
 			title = "Water, Opaque";
 			prefix = "(120)";
+			flags2text = "[1] Make lava intangible";
 			flags8text = "[3] Slope skew sides";
 			flags64text = "[6] Use two light levels";
 			flags512text = "[9] Use target light level";
@@ -1112,6 +1113,7 @@ linedeftypes
 		{
 			title = "Water, Translucent";
 			prefix = "(121)";
+			flags2text = "[1] Make lava intangible";
 			flags8text = "[3] Slope skew sides";
 			flags64text = "[6] Use two light levels";
 			flags512text = "[9] Use target light level";
@@ -1128,6 +1130,7 @@ linedeftypes
 		{
 			title = "Water, Opaque, No Sides";
 			prefix = "(122)";
+			flags2text = "[1] Make lava intangible";
 			flags64text = "[6] Use two light levels";
 			flags512text = "[9] Use target light level";
 			flags1024text = "[10] Ripple effect";
@@ -1142,6 +1145,7 @@ linedeftypes
 		{
 			title = "Water, Translucent, No Sides";
 			prefix = "(123)";
+			flags2text = "[1] Make lava intangible";
 			flags64text = "[6] Use two light levels";
 			flags512text = "[9] Use target light level";
 			flags1024text = "[10] Ripple effect";
@@ -1157,6 +1161,7 @@ linedeftypes
 		{
 			title = "Goo Water, Translucent";
 			prefix = "(124)";
+			flags2text = "[1] Make lava intangible";
 			flags8text = "[3] Slope skew sides";
 			flags64text = "[6] Use two light levels";
 			flags512text = "[9] Use target light level";
@@ -1173,6 +1178,7 @@ linedeftypes
 		{
 			title = "Goo Water, Translucent, No Sides";
 			prefix = "(125)";
+			flags2text = "[1] Make lava intangible";
 			flags64text = "[6] Use two light levels";
 			flags512text = "[9] Use target light level";
 			flags1024text = "[10] Ripple effect";
@@ -1675,12 +1681,14 @@ linedeftypes
 		{
 			title = "Continuous";
 			prefix = "(300)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		301
 		{
 			title = "Each Time";
 			prefix = "(301)";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags16384text = "[14] Also trigger on exit";
 		}
 
@@ -1688,6 +1696,7 @@ linedeftypes
 		{
 			title = "Once";
 			prefix = "(302)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		303
@@ -1697,6 +1706,7 @@ linedeftypes
 			flags2text = "[1] Rings greater or equal";
 			flags64text = "[6] Rings less or equal";
 			flags512text = "[9] Consider all players";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		304
@@ -1706,18 +1716,21 @@ linedeftypes
 			flags2text = "[1] Rings greater or equal";
 			flags64text = "[6] Rings less or equal";
 			flags512text = "[9] Consider all players";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		305
 		{
 			title = "Character Ability - Continuous";
 			prefix = "(305)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		306
 		{
 			title = "Character Ability - Each Time";
 			prefix = "(306)";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags16384text = "[14] Also trigger on exit";
 		}
 
@@ -1725,24 +1738,28 @@ linedeftypes
 		{
 			title = "Character Ability - Once";
 			prefix = "(307)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		308
 		{
 			title = "Race Only - Once";
 			prefix = "(308)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		309
 		{
 			title = "CTF Red Team - Continuous";
 			prefix = "(309)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		310
 		{
 			title = "CTF Red Team - Each Time";
 			prefix = "(310)";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags16384text = "[14] Also trigger on exit";
 		}
 
@@ -1750,12 +1767,14 @@ linedeftypes
 		{
 			title = "CTF Blue Team - Continuous";
 			prefix = "(311)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		312
 		{
 			title = "CTF Blue Team - Each Time";
 			prefix = "(312)";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags16384text = "[14] Also trigger on exit";
 		}
 
@@ -1763,6 +1782,7 @@ linedeftypes
 		{
 			title = "No More Enemies - Once";
 			prefix = "(313)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		314
@@ -1771,6 +1791,7 @@ linedeftypes
 			prefix = "(314)";
 			flags64text = "[6] Number greater or equal";
 			flags512text = "[9] Number less";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		315
@@ -1779,30 +1800,35 @@ linedeftypes
 			prefix = "(315)";
 			flags64text = "[6] Number greater or equal";
 			flags512text = "[9] Number less";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		317
 		{
 			title = "Condition Set Trigger - Continuous";
 			prefix = "(317)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		318
 		{
 			title = "Condition Set Trigger - Once";
 			prefix = "(318)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		319
 		{
 			title = "Unlockable - Continuous";
 			prefix = "(319)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		320
 		{
 			title = "Unlockable - Once";
 			prefix = "(320)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		321
@@ -1810,6 +1836,7 @@ linedeftypes
 			title = "Trigger After X Calls - Continuous";
 			prefix = "(321)";
 			flags64text = "[6] Trigger more than once";
+			flags1024text = "[10] Use faster, unordered execution";
 
 		}
 
@@ -1818,6 +1845,7 @@ linedeftypes
 			title = "Trigger After X Calls - Each Time";
 			prefix = "(322)";
 			flags64text = "[6] Trigger more than once";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		323
@@ -1832,6 +1860,7 @@ linedeftypes
 			flags128text = "[7] Lap >= Front Y Offset";
 			flags256text = "[8] Count laps from Bonus Time";
 			flags512text = "[9] Count from triggering player";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags16384text = "[14] Run if no more mares";
 			flags32768text = "[15] Run if player is not NiGHTS";
 		}
@@ -1839,6 +1868,7 @@ linedeftypes
 		324
 		{
 			title = "NiGHTSerize - Once";
+			prefix = "(324)";
 			flags2text = "[1] Mare >= Front X Offset";
 			flags8text = "[3] Run only if player is NiGHTS";
 			flags16text = "[4] Count from lowest of players";
@@ -1847,14 +1877,15 @@ linedeftypes
 			flags128text = "[7] Lap >= Front Y Offset";
 			flags256text = "[8] Count laps from Bonus Time";
 			flags512text = "[9] Count from triggering player";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags16384text = "[14] Run if no more mares";
 			flags32768text = "[15] Run if player is not NiGHTS";
-			prefix = "(324)";
 		}
 
 		325
 		{
 			title = "De-NiGHTSerize - Each Time";
+			prefix = "(325)";
 			flags2text = "[1] Mare >= Front X Offset";
 			flags8text = "[3] Run if anyone is NiGHTS";
 			flags16text = "[4] Count from lowest of players";
@@ -1863,13 +1894,14 @@ linedeftypes
 			flags128text = "[7] Lap >= Front Y Offset";
 			flags256text = "[8] Count laps from Bonus Time";
 			flags512text = "[9] Count from triggering player";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags32768text = "[15] Run if no one is NiGHTS";
-			prefix = "(325)";
 		}
 
 		326
 		{
 			title = "De-NiGHTSerize - Once";
+			prefix = "(326)";
 			flags2text = "[1] Mare >= Front X Offset";
 			flags8text = "[3] Run if anyone is NiGHTS";
 			flags16text = "[4] Count from lowest of players";
@@ -1878,13 +1910,14 @@ linedeftypes
 			flags128text = "[7] Lap >= Front Y Offset";
 			flags256text = "[8] Count laps from Bonus Time";
 			flags512text = "[9] Count from triggering player";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags32768text = "[15] Run if no one is NiGHTS";
-			prefix = "(326)";
 		}
 
 		327
 		{
 			title = "NiGHTS Lap - Each Time";
+			prefix = "(327)";
 			flags2text = "[1] Mare >= Front X Offset";
 			flags16text = "[4] Count from lowest of players";
 			flags32text = "[5] Lap <= Front Y Offset";
@@ -1892,12 +1925,13 @@ linedeftypes
 			flags128text = "[7] Lap >= Front Y Offset";
 			flags256text = "[8] Count laps from Bonus Time";
 			flags512text = "[9] Count from triggering player";
-			prefix = "(327)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		328
 		{
 			title = "NiGHTS Lap - Once";
+			prefix = "(328)";
 			flags2text = "[1] Mare >= Front X Offset";
 			flags16text = "[4] Count from lowest of players";
 			flags32text = "[5] Lap <= Front Y Offset";
@@ -1905,12 +1939,13 @@ linedeftypes
 			flags128text = "[7] Lap >= Front Y Offset";
 			flags256text = "[8] Count laps from Bonus Time";
 			flags512text = "[9] Count from triggering player";
-			prefix = "(328)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		329
 		{
 			title = "Ideya Capture Touch - Each Time";
+			prefix = "(329)";
 			flags2text = "[1] Mare >= Front X Offset";
 			flags8text = "[3] Run regardless of spheres";
 			flags16text = "[4] Count from lowest of players";
@@ -1919,14 +1954,15 @@ linedeftypes
 			flags128text = "[7] Lap >= Front Y Offset";
 			flags256text = "[8] Count laps from Bonus Time";
 			flags512text = "[9] Count from triggering player";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags16384text = "[14] Only if not enough spheres";
 			flags32768text = "[15] Run when entering Capture";
-			prefix = "(329)";
 		}
 
 		330
 		{
 			title = "Ideya Capture Touch - Once";
+			prefix = "(330)";
 			flags2text = "[1] Mare >= Front X Offset";
 			flags8text = "[3] Run regardless of spheres";
 			flags16text = "[4] Count from lowest of players";
@@ -1935,51 +1971,57 @@ linedeftypes
 			flags128text = "[7] Lap >= Front Y Offset";
 			flags256text = "[8] Count laps from Bonus Time";
 			flags512text = "[9] Count from triggering player";
+			flags1024text = "[10] Use faster, unordered execution";
 			flags16384text = "[14] Only if not enough spheres";
 			flags32768text = "[15] Run when entering Capture";
-			prefix = "(330)";
 		}
 
 		331
 		{
 			title = "Player Skin - Continuous";
-			flags64text = "[6] Disable for this skin";
 			prefix = "(331)";
+			flags64text = "[6] Disable for this skin";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		332
 		{
 			title = "Player Skin - Each Time";
-			flags64text = "[6] Disable for this skin";
 			prefix = "(332)";
+			flags64text = "[6] Disable for this skin";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		333
 		{
 			title = "Player Skin - Once";
-			flags64text = "[6] Disable for this skin";
 			prefix = "(333)";
+			flags64text = "[6] Disable for this skin";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		334
 		{
 			title = "Object Dye - Continuous";
-			flags64text = "[6] Disable for this color";
 			prefix = "(334)";
+			flags64text = "[6] Disable for this color";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		335
 		{
 			title = "Object Dye - Each Time";
-			flags64text = "[6] Disable for this color";
 			prefix = "(335)";
+			flags64text = "[6] Disable for this color";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		336
 		{
 			title = "Object Dye - Once";
-			flags64text = "[6] Disable for this color";
 			prefix = "(336)";
+			flags64text = "[6] Disable for this color";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 
 		337
@@ -2028,6 +2070,7 @@ linedeftypes
 		{
 			title = "Level Load";
 			prefix = "(399)";
+			flags1024text = "[10] Use faster, unordered execution";
 		}
 	}
 
@@ -2730,7 +2773,7 @@ linedeftypes
 
 		502
 		{
-			title = "Scroll Tagged Wall";
+			title = "Scroll Tagged Walls";
 			prefix = "(502)";
 			flags128text = "[7] Use texture offsets";
 			flags256text = "[8] Scroll back side";
@@ -2738,7 +2781,7 @@ linedeftypes
 
 		503
 		{
-			title = "Scroll Tagged Wall (Accelerative)";
+			title = "Scroll Tagged Walls (Accelerative)";
 			prefix = "(503)";
 			flags128text = "[7] Use texture offsets";
 			flags256text = "[8] Scroll back side";
@@ -2746,7 +2789,7 @@ linedeftypes
 
 		504
 		{
-			title = "Scroll Tagged Wall (Displacement)";
+			title = "Scroll Tagged Walls (Displacement)";
 			prefix = "(504)";
 			flags128text = "[7] Use texture offsets";
 			flags256text = "[8] Scroll back side";
@@ -3180,7 +3223,7 @@ linedeftypes
 		723
 		{
 			title = "Copy Backside Floor Slope from Line Tag";
-			prefix = "(720)";
+			prefix = "(723)";
 			slope = "copy";
 			slopeargs = 4;
 		}
@@ -3188,7 +3231,7 @@ linedeftypes
 		724
 		{
 			title = "Copy Backside Ceiling Slope from Line Tag";
-			prefix = "(721)";
+			prefix = "(724)";
 			slope = "copy";
 			slopeargs = 8;
 		}
@@ -3196,7 +3239,7 @@ linedeftypes
 		725
 		{
 			title = "Copy Backside Floor and Ceiling Slope from Line Tag";
-			prefix = "(722)";
+			prefix = "(725)";
 			slope = "copy";
 			slopeargs = 12;
 		}
@@ -3258,7 +3301,7 @@ linedeftypes
 
 	transwall
 	{
-		title = "Translucent Wall";
+		title = "Translucent Walls";
 
 		900
 		{
@@ -3319,6 +3362,192 @@ linedeftypes
 			title = "Fog Wall";
 			prefix = "(909)";
 		}
+
+		910
+		{
+			title = "100% Additive";
+			prefix = "(910)";
+		}
+
+		911
+		{
+			title = "90% Additive";
+			prefix = "(911)";
+		}
+
+		912
+		{
+			title = "80% Additive";
+			prefix = "(912)";
+		}
+
+		913
+		{
+			title = "70% Additive";
+			prefix = "(913)";
+		}
+
+		914
+		{
+			title = "60% Additive";
+			prefix = "(914)";
+		}
+
+		915
+		{
+			title = "50% Additive";
+			prefix = "(915)";
+		}
+
+		916
+		{
+			title = "40% Additive";
+			prefix = "(916)";
+		}
+
+		917
+		{
+			title = "30% Additive";
+			prefix = "(917)";
+		}
+
+		918
+		{
+			title = "20% Additive";
+			prefix = "(918)";
+		}
+
+		919
+		{
+			title = "10% Additive";
+			prefix = "(919)";
+		}
+
+		920
+		{
+			title = "100% Subtractive";
+			prefix = "(920)";
+		}
+
+		921
+		{
+			title = "90% Subtractive";
+			prefix = "(921)";
+		}
+
+		922
+		{
+			title = "80% Subtractive";
+			prefix = "(922)";
+		}
+
+		923
+		{
+			title = "70% Subtractive";
+			prefix = "(923)";
+		}
+
+		924
+		{
+			title = "60% Subtractive";
+			prefix = "(924)";
+		}
+
+		925
+		{
+			title = "50% Subtractive";
+			prefix = "(925)";
+		}
+
+		926
+		{
+			title = "40% Subtractive";
+			prefix = "(926)";
+		}
+
+		927
+		{
+			title = "30% Subtractive";
+			prefix = "(927)";
+		}
+
+		928
+		{
+			title = "20% Subtractive";
+			prefix = "(928)";
+		}
+
+		929
+		{
+			title = "10% Subtractive";
+			prefix = "(929)";
+		}
+
+		930
+		{
+			title = "100% Reverse Subtractive";
+			prefix = "(930)";
+		}
+
+		931
+		{
+			title = "90% Reverse Subtractive";
+			prefix = "(931)";
+		}
+
+		932
+		{
+			title = "80% Reverse Subtractive";
+			prefix = "(932)";
+		}
+
+		933
+		{
+			title = "70% Reverse Subtractive";
+			prefix = "(933)";
+		}
+
+		934
+		{
+			title = "60% Reverse Subtractive";
+			prefix = "(934)";
+		}
+
+		935
+		{
+			title = "50% Reverse Subtractive";
+			prefix = "(935)";
+		}
+
+		936
+		{
+			title = "40% Reverse Subtractive";
+			prefix = "(936)";
+		}
+
+		937
+		{
+			title = "30% Reverse Subtractive";
+			prefix = "(937)";
+		}
+
+		938
+		{
+			title = "20% Reverse Subtractive";
+			prefix = "(938)";
+		}
+
+		939
+		{
+			title = "10% Reverse Subtractive";
+			prefix = "(939)";
+		}
+
+		940
+		{
+			title = "Modulate";
+			prefix = "(940)";
+		}
 	}
 }
 
@@ -3848,6 +4077,7 @@ thingtypes
 			sprite = "BUMBA1";
 			width = 16;
 			height = 32;
+			flags8text = "[8] Cannot move";
 		}
 		124
 		{
@@ -3878,7 +4108,6 @@ thingtypes
 			width = 24;
 			height = 76;
 			flags4text = "[4] End level on death";
-			flags8text = "[8] Alternate laser attack";
 		}
 		201
 		{
@@ -3913,6 +4142,7 @@ thingtypes
 			height = 60;
 			flags1text = "[1] Grayscale mode";
 			flags4text = "[4] End level on death";
+			flags8text = "[8] Skip intro";
 		}
 		206
 		{
@@ -5114,7 +5344,7 @@ thingtypes
 			width = 8;
 			height = 16;
 			hangs = 1;
-			angletext = "Dripping interval";
+			angletext = "Dripping delay";
 			fixedrotation = 1;
 		}
 		1003
@@ -5456,6 +5686,8 @@ thingtypes
 			width = 20;
 			height = 72;
 			arrow = 1;
+			flags4text = "[4] Move right";
+			flags8text = "[8] Move left";
 		}
 		1128
 		{
@@ -5608,6 +5840,7 @@ thingtypes
 			width = 24;
 			height = 63;
 			arrow = 1;
+			flags8text = "[8] Not pushable";
 		}
 		1217
 		{
@@ -5743,6 +5976,7 @@ thingtypes
 			height = 32;
 			angletext = "Initial delay";
 			fixedrotation = 1;
+			hangs = 1;
 			flags8text = "[8] Double size";
 		}
 		1305
diff --git a/extras/conf/udb/Includes/SRB222_common.cfg b/extras/conf/udb/Includes/SRB222_common.cfg
index d67835aeb3c67a6b8dee404664aa0bafd6cf312e..b752e36544590c7a0a2a5fd7af9e33cc97acda30 100644
--- a/extras/conf/udb/Includes/SRB222_common.cfg
+++ b/extras/conf/udb/Includes/SRB222_common.cfg
@@ -264,10 +264,10 @@ mapformat_udmf
 	}
 
 	// LINEDEF RENDERSTYLES
-	/*linedefrenderstyles
+	linedefrenderstyles
 	{
 		include("SRB222_misc.cfg", "linedefrenderstyles");
-	}*/
+	}
 
 	// THING FLAGS
 	thingflags
diff --git a/extras/conf/udb/Includes/SRB222_linedefs.cfg b/extras/conf/udb/Includes/SRB222_linedefs.cfg
index 6255859ab4a9b0bf4be23a440af193db1ff54d74..69f265b3c632b64f580ea0d17b410f5c31b24337 100644
--- a/extras/conf/udb/Includes/SRB222_linedefs.cfg
+++ b/extras/conf/udb/Includes/SRB222_linedefs.cfg
@@ -1575,6 +1575,161 @@ doom
 			title = "Fog Wall";
 			prefix = "(909)";
 		}
+		910
+		{
+			title = "100% Additive";
+			prefix = "(910)";
+		}
+		911
+		{
+			title = "90% Additive";
+			prefix = "(911)";
+		}
+		912
+		{
+			title = "80% Additive";
+			prefix = "(912)";
+		}
+		913
+		{
+			title = "70% Additive";
+			prefix = "(913)";
+		}
+		914
+		{
+			title = "60% Additive";
+			prefix = "(914)";
+		}
+		915
+		{
+			title = "50% Additive";
+			prefix = "(915)";
+		}
+		916
+		{
+			title = "40% Additive";
+			prefix = "(916)";
+		}
+		917
+		{
+			title = "30% Additive";
+			prefix = "(917)";
+		}
+		918
+		{
+			title = "20% Additive";
+			prefix = "(918)";
+		}
+		919
+		{
+			title = "10% Additive";
+			prefix = "(919)";
+		}
+		920
+		{
+			title = "100% Subtractive";
+			prefix = "(920)";
+		}
+		921
+		{
+			title = "90% Subtractive";
+			prefix = "(921)";
+		}
+		922
+		{
+			title = "80% Subtractive";
+			prefix = "(922)";
+		}
+		923
+		{
+			title = "70% Subtractive";
+			prefix = "(923)";
+		}
+		924
+		{
+			title = "60% Subtractive";
+			prefix = "(924)";
+		}
+		925
+		{
+			title = "50% Subtractive";
+			prefix = "(925)";
+		}
+		926
+		{
+			title = "40% Subtractive";
+			prefix = "(926)";
+		}
+		927
+		{
+			title = "30% Subtractive";
+			prefix = "(927)";
+		}
+		928
+		{
+			title = "20% Subtractive";
+			prefix = "(928)";
+		}
+		929
+		{
+			title = "10% Subtractive";
+			prefix = "(929)";
+		}
+		930
+		{
+			title = "100% Reverse Subtractive";
+			prefix = "(930)";
+		}
+		931
+		{
+			title = "90% Reverse Subtractive";
+			prefix = "(931)";
+		}
+		932
+		{
+			title = "80% Reverse Subtractive";
+			prefix = "(932)";
+		}
+		933
+		{
+			title = "70% Reverse Subtractive";
+			prefix = "(933)";
+		}
+		934
+		{
+			title = "60% Reverse Subtractive";
+			prefix = "(934)";
+		}
+		935
+		{
+			title = "50% Reverse Subtractive";
+			prefix = "(935)";
+		}
+		936
+		{
+			title = "40% Reverse Subtractive";
+			prefix = "(936)";
+		}
+		937
+		{
+			title = "30% Reverse Subtractive";
+			prefix = "(937)";
+		}
+		938
+		{
+			title = "20% Reverse Subtractive";
+			prefix = "(938)";
+		}
+		939
+		{
+			title = "10% Reverse Subtractive";
+			prefix = "(939)";
+		}
+		940
+		{
+			title = "Modulate";
+			prefix = "(940)";
+		}
 	}
 }
 
@@ -2061,6 +2216,12 @@ udmf
 				default = 255;
 			}
 			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
 			{
 				title = "Appearance";
 				type = 12;
@@ -2074,7 +2235,7 @@ udmf
 					32 = "Cut cyan flat pixels";
 				}
 			}
-			arg3
+			arg4
 			{
 				title = "Tangibility";
 				type = 12;
@@ -2097,6 +2258,12 @@ udmf
 				default = 128;
 			}
 			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
 			{
 				title = "Flags";
 				type = 12;
@@ -2164,12 +2331,18 @@ udmf
 				default = 255;
 			}
 			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
 			{
 				title = "Tangibility";
 				type = 12;
 				enum = "tangibility";
 			}
-			arg3
+			arg4
 			{
 				title = "Flags";
 				type = 12;
@@ -2199,6 +2372,12 @@ udmf
 				default = 255;
 			}
 			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
 			{
 				title = "Appearance";
 				type = 12;
@@ -2212,17 +2391,17 @@ udmf
 					32 = "Cut cyan flat pixels";
 				}
 			}
-			arg3
+			arg4
 			{
 				title = "Tangibility";
 				type = 12;
 				enum = "tangibility";
 			}
-			arg4
+			arg5
 			{
 				title = "Speed";
 			}
-			arg5
+			arg6
 			{
 				title = "Flags";
 				type = 12;
@@ -2277,6 +2456,12 @@ udmf
 				default = 255;
 			}
 			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
 			{
 				title = "Appearance";
 				type = 12;
@@ -2363,6 +2548,12 @@ udmf
 				default = 255;
 			}
 			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
 			{
 				title = "Bustable type";
 				type = 11;
@@ -2374,7 +2565,7 @@ udmf
 					3 = "Strong";
 				}
 			}
-			arg3
+			arg4
 			{
 				title = "Flags";
 				type = 12;
@@ -2386,7 +2577,7 @@ udmf
 					8 = "Cut cyan flat pixels";
 				}
 			}
-			arg4
+			arg5
 			{
 				title = "Linedef executor tag";
 				type = 15;
@@ -2433,6 +2624,12 @@ udmf
 				default = 128;
 			}
 			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
 			{
 				title = "Flags";
 				type = 12;
@@ -2459,6 +2656,12 @@ udmf
 				default = 255;
 			}
 			arg2
+			{
+				title = "Blending mode";
+				type = 11;
+				enum = "blendmodes";
+			}
+			arg3
 			{
 				title = "Flags";
 				type = 12;
diff --git a/extras/conf/udb/Includes/SRB222_misc.cfg b/extras/conf/udb/Includes/SRB222_misc.cfg
index f4a60581af8008a88dbd07f2e9f3a816d0270c10..919bb66b8d3cf656b861bb97eaa2c09f9b3c30ba 100644
--- a/extras/conf/udb/Includes/SRB222_misc.cfg
+++ b/extras/conf/udb/Includes/SRB222_misc.cfg
@@ -63,11 +63,15 @@ linedefflags_udmf
 	transfer = "Transfer Line";
 }
 
-/*linedefrenderstyles
+linedefrenderstyles
 {
 	translucent = "Translucent";
+	add = "Add";
+	subtract = "Subtract";
+	reversesubtract = "Reverse subtract";
+	modulate = "Modulate";
 	fog = "Fog";
-}*/
+}
 
 sectorflags
 {
@@ -242,6 +246,16 @@ universalfields
 			type = 0;
 			default = 0;
 		}
+		arg8
+		{
+			type = 0;
+			default = 0;
+		}
+		arg9
+		{
+			type = 0;
+			default = 0;
+		}
 		stringarg0
 		{
 			type = 2;
@@ -270,6 +284,41 @@ universalfields
 
 	thing
 	{
+		arg5
+		{
+			type = 0;
+			default = 0;
+		}
+		arg6
+		{
+			type = 0;
+			default = 0;
+		}
+		arg7
+		{
+			type = 0;
+			default = 0;
+		}
+		arg8
+		{
+			type = 0;
+			default = 0;
+		}
+		arg9
+		{
+			type = 0;
+			default = 0;
+		}
+		stringarg0
+		{
+			type = 2;
+			default = "";
+		}
+		stringarg1
+		{
+			type = 2;
+			default = "";
+		}
 	}
 }
 
@@ -703,4 +752,4 @@ flats
 		start = "F_START";
 		end = "FF_END";
 	}
-}
\ No newline at end of file
+}
diff --git a/src/b_bot.c b/src/b_bot.c
index cdd74fc0757522ac2a7c30dfe3dec50237c446ea..bf2dbbb68586aab8390557568622d2e9e6f89e0a 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -29,11 +29,16 @@ void B_UpdateBotleader(player_t *player)
 	{
 		if (players[i].bot || players[i].playerstate != PST_LIVE || players[i].spectator || !players[i].mo)
 			continue;
-		if (!player->mo) //Can't do distance calculations if there's no player object, so we'll just take the first we find
+		
+		if (!player->botleader)
 		{
-			player->botleader = &players[i];
+			player->botleader = &players[i]; // set default
 			return;
 		}
+
+		if (!player->mo)
+			return;
+
 		//Update best candidate based on nearest distance
 		dist = R_PointToDist2(player->mo->x, player->mo->y, players[i].mo->x, players[i].mo->y);
 		if (neardist > dist)
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index cca3102d085aac427b5a3469dd00f3085a3fee3d..fe7e7678fe447cfc186b9eaf4134cb6eeeb6db27 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -3763,7 +3763,7 @@ static void Command_ListWADS_f(void)
 		nameonly(tempname = va("%s", wadfiles[i]->filename));
 		if (!i)
 			CONS_Printf("\x82 IWAD\x80: %s\n", tempname);
-		else if (i <= mainwads)
+		else if (i < mainwads)
 			CONS_Printf("\x82 * %.2d\x80: %s\n", i, tempname);
 		else if (!wadfiles[i]->important)
 			CONS_Printf("\x86   %.2d: %s\n", i, tempname);
diff --git a/src/deh_tables.c b/src/deh_tables.c
index eda0258d6d3f091f4d52b8fa9ef9867885585ff6..5ea30a8fd6c3145df10fad64a5ece0500a5ce34f 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -4843,9 +4843,18 @@ struct int_const_s const INT_CONST[] = {
 	{"FF_RANDOMANIM",FF_RANDOMANIM},
 	{"FF_GLOBALANIM",FF_GLOBALANIM},
 	{"FF_FULLBRIGHT",FF_FULLBRIGHT},
+	{"FF_SEMIBRIGHT",FF_SEMIBRIGHT},
+	{"FF_FULLDARK",FF_FULLDARK},
 	{"FF_VERTICALFLIP",FF_VERTICALFLIP},
 	{"FF_HORIZONTALFLIP",FF_HORIZONTALFLIP},
 	{"FF_PAPERSPRITE",FF_PAPERSPRITE},
+	{"FF_FLOORSPRITE",FF_FLOORSPRITE},
+	{"FF_BLENDMASK",FF_BLENDMASK},
+	{"FF_BLENDSHIFT",FF_BLENDSHIFT},
+	{"FF_ADD",FF_ADD},
+	{"FF_SUBTRACT",FF_SUBTRACT},
+	{"FF_REVERSESUBTRACT",FF_REVERSESUBTRACT},
+	{"FF_MODULATE",FF_MODULATE},
 	{"FF_TRANSMASK",FF_TRANSMASK},
 	{"FF_TRANSSHIFT",FF_TRANSSHIFT},
 	// new preshifted translucency (used in source)
@@ -4889,6 +4898,7 @@ struct int_const_s const INT_CONST[] = {
 	{"AST_REVERSESUBTRACT",AST_REVERSESUBTRACT},
 	{"AST_MODULATE",AST_MODULATE},
 	{"AST_OVERLAY",AST_OVERLAY},
+	{"AST_FOG",AST_FOG},
 
 	// Render flags
 	{"RF_HORIZONTALFLIP",RF_HORIZONTALFLIP},
@@ -4900,9 +4910,10 @@ struct int_const_s const INT_CONST[] = {
 	{"RF_OBJECTSLOPESPLAT",RF_OBJECTSLOPESPLAT},
 	{"RF_NOSPLATBILLBOARD",RF_NOSPLATBILLBOARD},
 	{"RF_NOSPLATROLLANGLE",RF_NOSPLATROLLANGLE},
-	{"RF_BLENDMASK",RF_BLENDMASK},
+	{"RF_BRIGHTMASK",RF_BRIGHTMASK},
 	{"RF_FULLBRIGHT",RF_FULLBRIGHT},
 	{"RF_FULLDARK",RF_FULLDARK},
+	{"RF_SEMIBRIGHT",RF_SEMIBRIGHT},
 	{"RF_NOCOLORMAPS",RF_NOCOLORMAPS},
 	{"RF_SPRITETYPEMASK",RF_SPRITETYPEMASK},
 	{"RF_PAPERSPRITE",RF_PAPERSPRITE},
@@ -5088,6 +5099,7 @@ struct int_const_s const INT_CONST[] = {
 	{"PAL_MIXUP",PAL_MIXUP},
 	{"PAL_RECYCLE",PAL_RECYCLE},
 	{"PAL_NUKE",PAL_NUKE},
+	{"PAL_INVERT",PAL_INVERT},
 	// for P_DamageMobj
 	//// Damage types
 	{"DMG_WATER",DMG_WATER},
@@ -5400,9 +5412,12 @@ struct int_const_s const INT_CONST[] = {
 	{"V_HUDTRANSHALF",V_HUDTRANSHALF},
 	{"V_HUDTRANS",V_HUDTRANS},
 	{"V_HUDTRANSDOUBLE",V_HUDTRANSDOUBLE},
-	{"V_AUTOFADEOUT",V_AUTOFADEOUT},
-	{"V_RETURN8",V_RETURN8},
-	{"V_OFFSET",V_OFFSET},
+	{"V_BLENDSHIFT",V_BLENDSHIFT},
+	{"V_BLENDMASK",V_BLENDMASK},
+	{"V_ADD",V_ADD},
+	{"V_SUBTRACT",V_SUBTRACT},
+	{"V_REVERSESUBTRACT",V_REVERSESUBTRACT},
+	{"V_MODULATE",V_MODULATE},
 	{"V_ALLOWLOWERCASE",V_ALLOWLOWERCASE},
 	{"V_FLIP",V_FLIP},
 	{"V_CENTERNAMETAG",V_CENTERNAMETAG},
@@ -5410,8 +5425,8 @@ struct int_const_s const INT_CONST[] = {
 	{"V_SNAPTOBOTTOM",V_SNAPTOBOTTOM},
 	{"V_SNAPTOLEFT",V_SNAPTOLEFT},
 	{"V_SNAPTORIGHT",V_SNAPTORIGHT},
-	{"V_WRAPX",V_WRAPX},
-	{"V_WRAPY",V_WRAPY},
+	{"V_AUTOFADEOUT",V_AUTOFADEOUT},
+	{"V_RETURN8",V_RETURN8},
 	{"V_NOSCALESTART",V_NOSCALESTART},
 	{"V_PERPLAYER",V_PERPLAYER},
 
diff --git a/src/doomdata.h b/src/doomdata.h
index 7ba159a7cbf20086e2df037836598b5bcfb8e3e4..630e18615603ee8a1786e1e3f22463f010cec75a 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -196,7 +196,7 @@ typedef struct
 #pragma pack()
 #endif
 
-#define NUMMAPTHINGARGS 8
+#define NUMMAPTHINGARGS 10
 #define NUMMAPTHINGSTRINGARGS 2
 
 // Thing definition, position, orientation and type,
diff --git a/src/g_game.c b/src/g_game.c
index 3955834b2170fa203222a5c76f8f9ead60e74fa6..44860bbbc5a19190e5ef705dff024d70dc6d1445 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -346,8 +346,8 @@ consvar_t cv_analog[2] = {
 	CVAR_INIT ("sessionanalog2", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog2_OnChange),
 };
 consvar_t cv_useranalog[2] = {
-	CVAR_INIT ("configanalog", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog_OnChange),
-	CVAR_INIT ("configanalog2", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog2_OnChange),
+	CVAR_INIT ("configanalog", "On", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog_OnChange),
+	CVAR_INIT ("configanalog2", "On", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog2_OnChange),
 };
 
 // deez New User eXperiences
@@ -362,8 +362,8 @@ consvar_t cv_autobrake2 = CVAR_INIT ("autobrake2", "On", CV_SAVE|CV_CALL, CV_OnO
 // hi here's some new controls
 static CV_PossibleValue_t zerotoone_cons_t[] = {{0, "MIN"}, {FRACUNIT, "MAX"}, {0, NULL}};
 consvar_t cv_cam_shiftfacing[2] = {
-	CVAR_INIT ("cam_shiftfacingchar", "0.33", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
-	CVAR_INIT ("cam2_shiftfacingchar", "0.33", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam_shiftfacingchar", "0.375", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam2_shiftfacingchar", "0.375", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
 };
 consvar_t cv_cam_turnfacing[2] = {
 	CVAR_INIT ("cam_turnfacingchar", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
@@ -374,12 +374,12 @@ consvar_t cv_cam_turnfacingability[2] = {
 	CVAR_INIT ("cam2_turnfacingability", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
 };
 consvar_t cv_cam_turnfacingspindash[2] = {
-	CVAR_INIT ("cam_turnfacingspindash", "0.5", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
-	CVAR_INIT ("cam2_turnfacingspindash", "0.5", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam_turnfacingspindash", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam2_turnfacingspindash", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
 };
 consvar_t cv_cam_turnfacinginput[2] = {
-	CVAR_INIT ("cam_turnfacinginput", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
-	CVAR_INIT ("cam2_turnfacinginput", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam_turnfacinginput", "0.375", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam2_turnfacinginput", "0.375", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
 };
 
 static CV_PossibleValue_t centertoggle_cons_t[] = {{0, "Hold"}, {1, "Toggle"}, {2, "Sticky Hold"}, {0, NULL}};
@@ -403,28 +403,28 @@ static CV_PossibleValue_t lockedassist_cons_t[] = {
 	{0, NULL}
 };
 consvar_t cv_cam_lockonboss[2] = {
-	CVAR_INIT ("cam_lockaimassist", "Bosses", CV_SAVE, lockedassist_cons_t, NULL),
-	CVAR_INIT ("cam2_lockaimassist", "Bosses", CV_SAVE, lockedassist_cons_t, NULL),
+	CVAR_INIT ("cam_lockaimassist", "Full", CV_SAVE, lockedassist_cons_t, NULL),
+	CVAR_INIT ("cam2_lockaimassist", "Full", CV_SAVE, lockedassist_cons_t, NULL),
 };
 
-consvar_t cv_turnaxis = CVAR_INIT ("joyaxis_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_moveaxis = CVAR_INIT ("joyaxis_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_sideaxis = CVAR_INIT ("joyaxis_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_lookaxis = CVAR_INIT ("joyaxis_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_turnaxis = CVAR_INIT ("joyaxis_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_jumpaxis = CVAR_INIT ("joyaxis_jump", "None", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_spinaxis = CVAR_INIT ("joyaxis_spin", "None", CV_SAVE, joyaxis_cons_t, NULL);
-consvar_t cv_fireaxis = CVAR_INIT ("joyaxis_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_fireaxis = CVAR_INIT ("joyaxis_fire", "Z-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_firenaxis = CVAR_INIT ("joyaxis_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_deadzone = CVAR_INIT ("joy_deadzone", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL);
 consvar_t cv_digitaldeadzone = CVAR_INIT ("joy_digdeadzone", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL);
 
-consvar_t cv_turnaxis2 = CVAR_INIT ("joyaxis2_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_moveaxis2 = CVAR_INIT ("joyaxis2_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_sideaxis2 = CVAR_INIT ("joyaxis2_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_lookaxis2 = CVAR_INIT ("joyaxis2_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_turnaxis2 = CVAR_INIT ("joyaxis2_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_jumpaxis2 = CVAR_INIT ("joyaxis2_jump", "None", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_spinaxis2 = CVAR_INIT ("joyaxis2_spin", "None", CV_SAVE, joyaxis_cons_t, NULL);
-consvar_t cv_fireaxis2 = CVAR_INIT ("joyaxis2_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_fireaxis2 = CVAR_INIT ("joyaxis2_fire", "Z-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
 consvar_t cv_firenaxis2 = CVAR_INIT ("joyaxis2_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL);
 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);
@@ -1551,8 +1551,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	{
 		// Fix offset angle for P2-controlled Tailsbot when P2's controls are set to non-Strafe
 		cmd->angleturn = (INT16)((localangle - *myangle) >> 16);
-	}	
-	
+	}
+
 	*myangle += (cmd->angleturn<<16);
 
 	if (controlstyle == CS_LMAOGALOG) {
@@ -2335,7 +2335,7 @@ void G_Ticker(boolean run)
 			else if (players[i].bot == BOT_MPAI) {
 				B_BuildTiccmd(&players[i], &players[i].cmd);
 			}
-			
+
 			// Do angle adjustments.
 			if (players[i].bot == BOT_NONE || players[i].bot == BOT_2PHUMAN)
 			{
@@ -2350,7 +2350,7 @@ void G_Ticker(boolean run)
     				P_ForceLocalAngle(&players[i], players[i].angleturn << 16);
     			else
     				players[i].cmd.angleturn = players[i].angleturn;
-    
+
     			players[i].cmd.angleturn &= ~TICCMD_RECEIVED;
 				// Use the leveltime sent in the player's ticcmd to determine control lag
     			players[i].cmd.latency = min(((leveltime & 0xFF) - players[i].cmd.latency) & 0xFF, MAXPREDICTTICS-1);
@@ -3056,15 +3056,15 @@ void G_DoReborn(INT32 playernum)
 
 		return;
 	}
-	
+
 	// Additional players (e.g. independent bots) in Single Player
-	if (playernum != consoleplayer && !(netgame || multiplayer)) 
-	{		
+	if (playernum != consoleplayer && !(netgame || multiplayer))
+	{
 		mobj_t *oldmo = NULL;
 		// Do nothing if out of lives
 		if (player->lives <= 0)
 			return;
-		
+
 		// Otherwise do respawn, starting by removing the player object
 		if (player->mo)
 		{
@@ -3075,7 +3075,7 @@ void G_DoReborn(INT32 playernum)
 		G_SpawnPlayer(playernum);
 		if (oldmo)
 			G_ChangePlayerReferences(oldmo, players[playernum].mo);
-		
+
 		return; //Exit function to avoid proccing other SP related mechanics
 	}
 
@@ -3300,7 +3300,7 @@ boolean G_EnoughPlayersFinished(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (!playeringame[i] || players[i].spectator || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN)
+		if (!playeringame[i] || players[i].spectator || players[i].bot)
 			continue;
 		if (players[i].quittime > 30 * TICRATE)
 			continue;
diff --git a/src/g_input.c b/src/g_input.c
index 6383c3f0068a3c47007f6fc50422f46b9e4bbb2c..250a2477223659fe3bc9988aa9fa4010deb14f87 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -684,14 +684,18 @@ void G_DefineDefaultControls(void)
 	gamecontroldefault[gcs_fps][GC_LOOKDOWN   ][0] = KEY_DOWNARROW;
 	gamecontroldefault[gcs_fps][GC_TURNLEFT   ][0] = KEY_LEFTARROW;
 	gamecontroldefault[gcs_fps][GC_TURNRIGHT  ][0] = KEY_RIGHTARROW;
-	gamecontroldefault[gcs_fps][GC_CENTERVIEW ][0] = KEY_END;
+	gamecontroldefault[gcs_fps][GC_CENTERVIEW ][0] = KEY_LCTRL;
 	gamecontroldefault[gcs_fps][GC_JUMP       ][0] = KEY_SPACE;
 	gamecontroldefault[gcs_fps][GC_SPIN       ][0] = KEY_LSHIFT;
 	gamecontroldefault[gcs_fps][GC_FIRE       ][0] = KEY_RCTRL;
 	gamecontroldefault[gcs_fps][GC_FIRE       ][1] = KEY_MOUSE1+0;
-	gamecontroldefault[gcs_fps][GC_FIRENORMAL ][0] = 'c';
+	gamecontroldefault[gcs_fps][GC_FIRENORMAL ][0] = KEY_RALT;
+	gamecontroldefault[gcs_fps][GC_FIRENORMAL ][1] = KEY_MOUSE1+1;
+	gamecontroldefault[gcs_fps][GC_CUSTOM1    ][0] = 'z';
+	gamecontroldefault[gcs_fps][GC_CUSTOM2    ][0] = 'x';
+	gamecontroldefault[gcs_fps][GC_CUSTOM3    ][0] = 'c';
 
-	// Platform game controls (arrow keys)
+	// Platform game controls (arrow keys), currently unused
 	gamecontroldefault[gcs_platform][GC_FORWARD    ][0] = KEY_UPARROW;
 	gamecontroldefault[gcs_platform][GC_BACKWARD   ][0] = KEY_DOWNARROW;
 	gamecontroldefault[gcs_platform][GC_STRAFELEFT ][0] = 'a';
@@ -734,34 +738,36 @@ void G_DefineDefaultControls(void)
 		gamecontroldefault[i][GC_VIEWPOINT  ][0] = KEY_F12;
 
 		// Gamepad controls -- same for both schemes
-		gamecontroldefault[i][GC_WEAPONNEXT ][1] = KEY_JOY1+1; // B
-		gamecontroldefault[i][GC_WEAPONPREV ][1] = KEY_JOY1+2; // X
-		gamecontroldefault[i][GC_TOSSFLAG   ][1] = KEY_JOY1+0; // A
-		gamecontroldefault[i][GC_SPIN       ][1] = KEY_JOY1+4; // LB
-		gamecontroldefault[i][GC_CAMTOGGLE  ][1] = KEY_HAT1+0; // D-Pad Up
-		gamecontroldefault[i][GC_CAMRESET   ][1] = KEY_JOY1+3; // Y
+		gamecontroldefault[i][GC_JUMP       ][1] = KEY_JOY1+0; // A
+		gamecontroldefault[i][GC_SPIN       ][1] = KEY_JOY1+2; // X
+		gamecontroldefault[i][GC_CUSTOM1    ][1] = KEY_JOY1+1; // B
+		gamecontroldefault[i][GC_CUSTOM2    ][1] = KEY_JOY1+3; // Y
+		gamecontroldefault[i][GC_CUSTOM3    ][1] = KEY_JOY1+8; // Left Stick
 		gamecontroldefault[i][GC_CENTERVIEW ][1] = KEY_JOY1+9; // Right Stick
-		gamecontroldefault[i][GC_TALKKEY    ][1] = KEY_HAT1+2; // D-Pad Left
-		gamecontroldefault[i][GC_SCORES     ][1] = KEY_HAT1+3; // D-Pad Right
-		gamecontroldefault[i][GC_JUMP       ][1] = KEY_JOY1+5; // RB
-		gamecontroldefault[i][GC_PAUSE      ][1] = KEY_JOY1+6; // Back
-		gamecontroldefault[i][GC_SCREENSHOT ][1] = KEY_HAT1+1; // D-Pad Down
+		gamecontroldefault[i][GC_WEAPONPREV ][1] = KEY_JOY1+4; // LB
+		gamecontroldefault[i][GC_WEAPONNEXT ][1] = KEY_JOY1+5; // RB
+		gamecontroldefault[i][GC_SCREENSHOT ][1] = KEY_JOY1+6; // Back
 		gamecontroldefault[i][GC_SYSTEMMENU ][0] = KEY_JOY1+7; // Start
+		gamecontroldefault[i][GC_CAMTOGGLE  ][1] = KEY_HAT1+0; // D-Pad Up
+		gamecontroldefault[i][GC_VIEWPOINT  ][1] = KEY_HAT1+1; // D-Pad Down
+		gamecontroldefault[i][GC_TOSSFLAG   ][1] = KEY_HAT1+2; // D-Pad Left
+		gamecontroldefault[i][GC_SCORES     ][1] = KEY_HAT1+3; // D-Pad Right
 
 		// Second player controls only have joypad defaults
-		gamecontrolbisdefault[i][GC_WEAPONNEXT][0] = KEY_2JOY1+1; // B
-		gamecontrolbisdefault[i][GC_WEAPONPREV][0] = KEY_2JOY1+2; // X
-		gamecontrolbisdefault[i][GC_TOSSFLAG  ][0] = KEY_2JOY1+0; // A
-		gamecontrolbisdefault[i][GC_SPIN      ][0] = KEY_2JOY1+4; // LB
-		gamecontrolbisdefault[i][GC_CAMRESET  ][0] = KEY_2JOY1+3; // Y
-		gamecontrolbisdefault[i][GC_CENTERVIEW][0] = KEY_2JOY1+9; // Right Stick
-		gamecontrolbisdefault[i][GC_JUMP      ][0] = KEY_2JOY1+5; // RB
-		//gamecontrolbisdefault[i][GC_PAUSE     ][0] = KEY_2JOY1+6; // Back
-		//gamecontrolbisdefault[i][GC_SYSTEMMENU][0] = KEY_2JOY1+7; // Start
-		gamecontrolbisdefault[i][GC_CAMTOGGLE ][0] = KEY_2HAT1+0; // D-Pad Up
-		gamecontrolbisdefault[i][GC_SCREENSHOT][0] = KEY_2HAT1+1; // D-Pad Down
-		//gamecontrolbisdefault[i][GC_TALKKEY   ][0] = KEY_2HAT1+2; // D-Pad Left
-		//gamecontrolbisdefault[i][GC_SCORES    ][0] = KEY_2HAT1+3; // D-Pad Right
+		gamecontrolbisdefault[i][GC_JUMP       ][1] = KEY_2JOY1+0; // A
+		gamecontrolbisdefault[i][GC_SPIN       ][1] = KEY_2JOY1+2; // X
+		gamecontrolbisdefault[i][GC_CUSTOM1    ][1] = KEY_2JOY1+1; // B
+		gamecontrolbisdefault[i][GC_CUSTOM2    ][1] = KEY_2JOY1+3; // Y
+		gamecontrolbisdefault[i][GC_CUSTOM3    ][1] = KEY_2JOY1+8; // Left Stick
+		gamecontrolbisdefault[i][GC_CENTERVIEW ][1] = KEY_2JOY1+9; // Right Stick
+		gamecontrolbisdefault[i][GC_WEAPONPREV ][1] = KEY_2JOY1+4; // LB
+		gamecontrolbisdefault[i][GC_WEAPONNEXT ][1] = KEY_2JOY1+5; // RB
+		gamecontrolbisdefault[i][GC_SCREENSHOT ][1] = KEY_2JOY1+6; // Back
+		//gamecontrolbisdefault[i][GC_SYSTEMMENU ][0] = KEY_2JOY1+7; // Start
+		gamecontrolbisdefault[i][GC_CAMTOGGLE  ][1] = KEY_2HAT1+0; // D-Pad Up
+		gamecontrolbisdefault[i][GC_VIEWPOINT  ][1] = KEY_2HAT1+1; // D-Pad Down
+		gamecontrolbisdefault[i][GC_TOSSFLAG   ][1] = KEY_2HAT1+2; // D-Pad Left
+		//gamecontrolbisdefault[i][GC_SCORES     ][1] = KEY_2HAT1+3; // D-Pad Right
 	}
 }
 
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 8223705bd1afa4a30e6d1c1239693fcab98d8374..e02dbea5b6fbab58944c13cba4d232f95d5271ea 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -119,11 +119,6 @@ void HWR_DrawPatch(patch_t *gpatch, INT32 x, INT32 y, INT32 option)
 
 	flags = PF_Translucent|PF_NoDepthTest;
 
-	if (option & V_WRAPX)
-		flags |= PF_ForceWrapX;
-	if (option & V_WRAPY)
-		flags |= PF_ForceWrapY;
-
 	// clip it since it is used for bunny scroll in doom I
 	HWD.pfnDrawPolygon(NULL, v, 4, flags);
 }
@@ -135,6 +130,7 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p
 	float cx = FIXED_TO_FLOAT(x);
 	float cy = FIXED_TO_FLOAT(y);
 	UINT8 alphalevel = ((option & V_ALPHAMASK) >> V_ALPHASHIFT);
+	UINT8 blendmode = ((option & V_BLENDMASK) >> V_BLENDSHIFT);
 	GLPatch_t *hwrPatch;
 
 //  3--2
@@ -145,9 +141,6 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p
 
 	UINT8 perplayershuffle = 0;
 
-	if (alphalevel >= 10 && alphalevel < 13)
-		return;
-
 	// make patch ready in hardware cache
 	if (!colormap)
 		HWR_GetPatch(gpatch);
@@ -191,15 +184,9 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p
 			offsetx = (float)(gpatch->leftoffset) * fscalew;
 
 		// top offset
-		// TODO: make some kind of vertical version of V_FLIP, maybe by deprecating V_OFFSET in future?!?
+		// TODO: make some kind of vertical version of V_FLIP
 		offsety = (float)(gpatch->topoffset) * fscaleh;
 
-		if ((option & (V_NOSCALESTART|V_OFFSET)) == (V_NOSCALESTART|V_OFFSET)) // Multiply by dupx/dupy for crosshairs
-		{
-			offsetx *= dupx;
-			offsety *= dupy;
-		}
-
 		cx -= offsetx;
 		cy -= offsety;
 	}
@@ -359,21 +346,20 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p
 	v[0].t = v[1].t = 0.0f;
 	v[2].t = v[3].t = hwrPatch->max_t;
 
-	flags = PF_Translucent|PF_NoDepthTest;
-
-	if (option & V_WRAPX)
-		flags |= PF_ForceWrapX;
-	if (option & V_WRAPY)
-		flags |= PF_ForceWrapY;
-
 	// clip it since it is used for bunny scroll in doom I
+	if (blendmode)
+		flags = HWR_GetBlendModeFlag(blendmode+1)|PF_NoDepthTest;
+	else
+		flags = PF_Translucent|PF_NoDepthTest;
+
 	if (alphalevel)
 	{
 		FSurfaceInfo Surf;
 		Surf.PolyColor.s.red = Surf.PolyColor.s.green = Surf.PolyColor.s.blue = 0xff;
-		if (alphalevel == 13) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency];
-		else if (alphalevel == 14) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency];
-		else if (alphalevel == 15) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency];
+
+		if (alphalevel == 10) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency];
+		else if (alphalevel == 11) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency];
+		else if (alphalevel == 12) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency];
 		else Surf.PolyColor.s.alpha = softwaretranstogl[10-alphalevel];
 		flags |= PF_Modulated;
 		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
@@ -389,6 +375,7 @@ void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 	float cx = FIXED_TO_FLOAT(x);
 	float cy = FIXED_TO_FLOAT(y);
 	UINT8 alphalevel = ((option & V_ALPHAMASK) >> V_ALPHASHIFT);
+	UINT8 blendmode = ((option & V_BLENDMASK) >> V_BLENDSHIFT);
 	GLPatch_t *hwrPatch;
 
 //  3--2
@@ -399,9 +386,6 @@ void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 
 	UINT8 perplayershuffle = 0;
 
-	if (alphalevel >= 10 && alphalevel < 13)
-		return;
-
 	// make patch ready in hardware cache
 	if (!colormap)
 		HWR_GetPatch(gpatch);
@@ -589,13 +573,6 @@ void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 	else
 		v[2].t = v[3].t = (FIXED_TO_FLOAT(sy+h)/(float)(gpatch->height))*hwrPatch->max_t;
 
-	flags = PF_Translucent|PF_NoDepthTest;
-
-	if (option & V_WRAPX)
-		flags |= PF_ForceWrapX;
-	if (option & V_WRAPY)
-		flags |= PF_ForceWrapY;
-
 	// Auto-crop at splitscreen borders!
 	if (splitscreen && (option & V_PERPLAYER))
 	{
@@ -667,14 +644,21 @@ void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 	}
 
 	// clip it since it is used for bunny scroll in doom I
+	if (blendmode)
+		flags = HWR_GetBlendModeFlag(blendmode+1)|PF_NoDepthTest;
+	else
+		flags = PF_Translucent|PF_NoDepthTest;
+
 	if (alphalevel)
 	{
 		FSurfaceInfo Surf;
 		Surf.PolyColor.s.red = Surf.PolyColor.s.green = Surf.PolyColor.s.blue = 0xff;
-		if (alphalevel == 13) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency];
-		else if (alphalevel == 14) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency];
-		else if (alphalevel == 15) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency];
+
+		if (alphalevel == 10) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency];
+		else if (alphalevel == 11) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency];
+		else if (alphalevel == 12) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency];
 		else Surf.PolyColor.s.alpha = softwaretranstogl[10-alphalevel];
+
 		flags |= PF_Modulated;
 		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
 	}
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index b6c22004b6319cce2ebafe9e28cc4564e0541566..4d0f5182b7915807a812a2c07e63bf3dff8eabef 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -556,7 +556,7 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 
 	HWR_Lighting(&Surf, lightlevel, planecolormap);
 
-	if (PolyFlags & (PF_Translucent|PF_Fog))
+	if (PolyFlags & (PF_Translucent|PF_Fog|PF_Additive|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative|PF_Environment))
 	{
 		Surf.PolyColor.s.alpha = (UINT8)alpha;
 		PolyFlags |= PF_Modulated;
@@ -980,8 +980,8 @@ static void HWR_SplitWall(sector_t *sector, FOutVector *wallVerts, INT32 texnum,
 
 		if (cutflag & FF_FOG)
 			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|polyflags, false, lightnum, colormap);
+		else if (polyflags & (PF_Translucent|PF_Additive|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative|PF_Environment))
+			HWR_AddTransparentWall(wallVerts, Surf, texnum, polyflags, false, lightnum, colormap);
 		else
 			HWR_ProjectWall(wallVerts, Surf, PF_Masked|polyflags, lightnum, colormap);
 
@@ -1009,8 +1009,8 @@ static void HWR_SplitWall(sector_t *sector, FOutVector *wallVerts, INT32 texnum,
 
 	if (cutflag & FF_FOG)
 		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|polyflags, false, lightnum, colormap);
+	else if (polyflags & (PF_Translucent|PF_Additive|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative|PF_Environment))
+		HWR_AddTransparentWall(wallVerts, Surf, texnum, polyflags, false, lightnum, colormap);
 	else
 		HWR_ProjectWall(wallVerts, Surf, PF_Masked|polyflags, lightnum, colormap);
 }
@@ -1264,6 +1264,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			else
 				HWR_ProjectWall(wallVerts, &Surf, PF_Masked, lightnum, colormap);
 		}
+
 		gl_midtexture = R_GetTextureNum(gl_sidedef->midtexture);
 		if (gl_midtexture)
 		{
@@ -1434,7 +1435,14 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			// set alpha for transparent walls
 			// ooops ! this do not work at all because render order we should render it in backtofront order
-			if (gl_linedef->alpha >= 0 && gl_linedef->alpha < FRACUNIT)
+			if (gl_linedef->blendmode && gl_linedef->blendmode != AST_FOG)
+			{
+				if (gl_linedef->alpha >= 0 && gl_linedef->alpha < FRACUNIT)
+					blendmode = HWR_SurfaceBlend(gl_linedef->blendmode, R_GetLinedefTransTable(gl_linedef->alpha), &Surf);
+				else
+					blendmode = HWR_GetBlendModeFlag(gl_linedef->blendmode);
+			}
+			else if (gl_linedef->alpha >= 0 && gl_linedef->alpha < FRACUNIT)
 				blendmode = HWR_TranstableToAlpha(R_GetLinedefTransTable(gl_linedef->alpha), &Surf);
 			else
 				blendmode = PF_Masked;
@@ -1457,11 +1465,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			if (gl_frontsector->numlights)
 			{
 				if (!(blendmode & PF_Masked))
-					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_TRANSLUCENT, NULL, PF_Decal);
+					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_TRANSLUCENT, NULL, blendmode);
 				else
-				{
-					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_CUTLEVEL, NULL, PF_Decal);
-				}
+					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_CUTLEVEL, NULL, blendmode);
 			}
 			else if (!(blendmode & PF_Masked))
 				HWR_AddTransparentWall(wallVerts, &Surf, gl_midtexture, blendmode, false, lightnum, colormap);
@@ -1721,7 +1727,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, 0);
+						HWR_SplitWall(gl_frontsector, wallVerts, 0, &Surf, rover->flags, rover, blendmode);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -1729,14 +1735,14 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				{
 					FBITFIELD blendmode = PF_Masked;
 
-					if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
+					if ((rover->flags & FF_TRANSLUCENT && rover->alpha < 256) || rover->blend)
 					{
-						blendmode = PF_Translucent;
+						blendmode = rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent;
 						Surf.PolyColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
 					}
 
 					if (gl_frontsector->numlights)
-						HWR_SplitWall(gl_frontsector, wallVerts, texnum, &Surf, rover->flags, rover, 0);
+						HWR_SplitWall(gl_frontsector, wallVerts, texnum, &Surf, rover->flags, rover, blendmode);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -1844,7 +1850,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, 0);
+						HWR_SplitWall(gl_backsector, wallVerts, 0, &Surf, rover->flags, rover, blendmode);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -1852,14 +1858,14 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				{
 					FBITFIELD blendmode = PF_Masked;
 
-					if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
+					if ((rover->flags & FF_TRANSLUCENT && rover->alpha < 256) || rover->blend)
 					{
-						blendmode = PF_Translucent;
+						blendmode = rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent;
 						Surf.PolyColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
 					}
 
 					if (gl_backsector->numlights)
-						HWR_SplitWall(gl_backsector, wallVerts, texnum, &Surf, rover->flags, rover, 0);
+						HWR_SplitWall(gl_backsector, wallVerts, texnum, &Surf, rover->flags, rover, blendmode);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -2936,6 +2942,13 @@ static void HWR_AddPolyObjectPlanes(void)
 	}
 }
 
+static FBITFIELD HWR_RippleBlend(sector_t *sector, ffloor_t *rover, boolean ceiling)
+{
+	(void)sector;
+	(void)ceiling;
+	return /*R_IsRipplePlane(sector, rover, ceiling)*/ (rover->flags & FF_RIPPLE) ? PF_Ripple : 0;
+}
+
 // -----------------+
 // HWR_Subsector    : Determine floor/ceiling planes.
 //                  : Add sprites of things in sector.
@@ -3126,7 +3139,7 @@ static void HWR_Subsector(size_t num)
 					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, rover->master->frontsector->extra_colormap);
 				}
-				else if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256) // SoM: Flags are more efficient
+				else if ((rover->flags & FF_TRANSLUCENT && rover->alpha < 256) || rover->blend) // SoM: Flags are more efficient
 				{
 					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 
@@ -3135,14 +3148,15 @@ static void HWR_Subsector(size_t num)
 										   false,
 					                       *rover->bottomheight,
 					                       *gl_frontsector->lightlist[light].lightlevel,
-					                       rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Translucent,
+					                       rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector,
+					                       HWR_RippleBlend(gl_frontsector, rover, false) | (rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent),
 					                       false, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 				else
 				{
 					HWR_GetLevelFlat(&levelflats[*rover->bottompic]);
 					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(sub, &extrasubsectors[num], false, *rover->bottomheight, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic],
+					HWR_RenderPlane(sub, &extrasubsectors[num], false, *rover->bottomheight, HWR_RippleBlend(gl_frontsector, rover, false)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic],
 					                rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 			}
@@ -3171,7 +3185,7 @@ static void HWR_Subsector(size_t num)
 					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, rover->master->frontsector->extra_colormap);
 				}
-				else if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
+				else if ((rover->flags & FF_TRANSLUCENT && rover->alpha < 256) || rover->blend)
 				{
 					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 
@@ -3180,14 +3194,15 @@ static void HWR_Subsector(size_t num)
 											true,
 					                        *rover->topheight,
 					                        *gl_frontsector->lightlist[light].lightlevel,
-					                        rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Translucent,
+					                        rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector,
+ 					                        HWR_RippleBlend(gl_frontsector, rover, false) | (rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent),
 					                        false, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 				else
 				{
 					HWR_GetLevelFlat(&levelflats[*rover->toppic]);
 					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(sub, &extrasubsectors[num], true, *rover->topheight, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic],
+					HWR_RenderPlane(sub, &extrasubsectors[num], true, *rover->topheight, HWR_RippleBlend(gl_frontsector, rover, false)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic],
 					                  rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 			}
@@ -3829,6 +3844,12 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	else
 		occlusion = PF_Occlude;
 
+	INT32 blendmode;
+	if (spr->mobj->frame & FF_BLENDMASK)
+		blendmode = ((spr->mobj->frame & FF_BLENDMASK) >> FF_BLENDSHIFT) + 1;
+	else
+		blendmode = spr->mobj->blendmode;
+
 	if (!cv_translucency.value) // translucency disabled
 	{
 		Surf.PolyColor.s.alpha = 0xFF;
@@ -3838,12 +3859,12 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	else if (spr->mobj->flags2 & MF2_SHADOW)
 	{
 		Surf.PolyColor.s.alpha = 0x40;
-		blend = HWR_GetBlendModeFlag(spr->mobj->blendmode);
+		blend = HWR_GetBlendModeFlag(blendmode);
 	}
 	else if (spr->mobj->frame & FF_TRANSMASK)
 	{
 		INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
-		blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
+		blend = HWR_SurfaceBlend(blendmode, trans, &Surf);
 	}
 	else
 	{
@@ -3852,7 +3873,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 		// Hurdler: PF_Environement would be cool, but we need to fix
 		//          the issue with the fog before
 		Surf.PolyColor.s.alpha = 0xFF;
-		blend = HWR_GetBlendModeFlag(spr->mobj->blendmode)|occlusion;
+		blend = HWR_GetBlendModeFlag(blendmode)|occlusion;
 		if (!occlusion) use_linkdraw_hack = true;
 	}
 
@@ -3892,6 +3913,9 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 		}
 	}
 
+	if (R_ThingIsSemiBright(spr->mobj))
+		lightlevel = 128 + (lightlevel>>1);
+
 	for (i = 0; i < sector->numlights; i++)
 	{
 		if (endtop < endrealbot && top < realbot)
@@ -4245,6 +4269,9 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 		else if (!lightset)
 			lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel;
 
+		if (R_ThingIsSemiBright(spr->mobj))
+			lightlevel = 128 + (lightlevel>>1);
+
 		HWR_Lighting(&Surf, lightlevel, colormap);
 	}
 
@@ -4261,6 +4288,12 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 		else
 			occlusion = PF_Occlude;
 
+		INT32 blendmode;
+		if (spr->mobj->frame & FF_BLENDMASK)
+			blendmode = ((spr->mobj->frame & FF_BLENDMASK) >> FF_BLENDSHIFT) + 1;
+		else
+			blendmode = spr->mobj->blendmode;
+
 		if (!cv_translucency.value) // translucency disabled
 		{
 			Surf.PolyColor.s.alpha = 0xFF;
@@ -4270,12 +4303,12 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 		else if (spr->mobj->flags2 & MF2_SHADOW)
 		{
 			Surf.PolyColor.s.alpha = 0x40;
-			blend = HWR_GetBlendModeFlag(spr->mobj->blendmode);
+			blend = HWR_GetBlendModeFlag(blendmode);
 		}
 		else if (spr->mobj->frame & FF_TRANSMASK)
 		{
 			INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
-			blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
+			blend = HWR_SurfaceBlend(blendmode, trans, &Surf);
 		}
 		else
 		{
@@ -4284,7 +4317,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 			// Hurdler: PF_Environement would be cool, but we need to fix
 			//          the issue with the fog before
 			Surf.PolyColor.s.alpha = 0xFF;
-			blend = HWR_GetBlendModeFlag(spr->mobj->blendmode)|occlusion;
+			blend = HWR_GetBlendModeFlag(blendmode)|occlusion;
 			if (!occlusion) use_linkdraw_hack = true;
 		}
 
@@ -4979,10 +5012,16 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (thing->spritexscale < 1 || thing->spriteyscale < 1)
 		return;
 
+	INT32 blendmode;
+	if (thing->frame & FF_BLENDMASK)
+		blendmode = ((thing->frame & FF_BLENDMASK) >> FF_BLENDSHIFT) + 1;
+	else
+		blendmode = thing->blendmode;
+
 	// Visibility check by the blend mode.
 	if (thing->frame & FF_TRANSMASK)
 	{
-		if (!R_BlendLevelVisible(thing->blendmode, (thing->frame & FF_TRANSMASK)>>FF_TRANSSHIFT))
+		if (!R_BlendLevelVisible(blendmode, (thing->frame & FF_TRANSMASK)>>FF_TRANSSHIFT))
 			return;
 	}
 
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index f4c5e4c3b14cbef8dd9bc39b241b30358166bea9..cf7118fbe0694c206a2d26497196f2cc81d5787f 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1869,7 +1869,7 @@ static void HU_DrawChat_Old(void)
 
 static inline void HU_DrawCrosshair(void)
 {
-	INT32 i, y;
+	INT32 i, y, dupz;
 
 	i = cv_crosshair.value & 3;
 	if (!i)
@@ -1885,12 +1885,14 @@ static inline void HU_DrawCrosshair(void)
 #endif
 		y = viewwindowy + (viewheight>>1);
 
-	V_DrawScaledPatch(vid.width>>1, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
+	dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
+
+	V_DrawFixedPatch(vid.width<<(FRACBITS-1), y<<FRACBITS, FRACUNIT/dupz, V_TRANSLUCENT, crosshair[i - 1], NULL);
 }
 
 static inline void HU_DrawCrosshair2(void)
 {
-	INT32 i, y;
+	INT32 i, y, dupz;
 
 	i = cv_crosshair2.value & 3;
 	if (!i)
@@ -1906,17 +1908,19 @@ static inline void HU_DrawCrosshair2(void)
 #endif
 		y = viewwindowy + (viewheight>>1);
 
-	if (splitscreen)
-	{
-#ifdef HWRENDER
+	if (!splitscreen)
+		return;
+
+	#ifdef HWRENDER
 		if (rendermode != render_soft)
 			y += (INT32)gl_viewheight;
 		else
-#endif
+	#endif
 			y += viewheight;
 
-		V_DrawScaledPatch(vid.width>>1, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
-	}
+	dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
+
+	V_DrawFixedPatch(vid.width<<(FRACBITS-1), y<<FRACBITS, FRACUNIT/dupz, V_TRANSLUCENT, crosshair[i - 1], NULL);
 }
 
 static void HU_DrawCEcho(void)
@@ -2472,7 +2476,7 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		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)
+			             | (greycheck ? V_TRANSLUCENT : 0)
 			             | V_ALLOWLOWERCASE, name);
 
 		if (gametyperules & GTR_TEAMFLAGS)
@@ -2733,12 +2737,12 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 			if (circuitmap)
 			{
 				if (players[tab[i].num].exiting)
-					V_DrawRightAlignedThinString(x+146, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
+					V_DrawRightAlignedThinString(x+100, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
 				else
-					V_DrawRightAlignedThinString(x+146, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
+					V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 			}
 			else
-				V_DrawRightAlignedThinString(x+146, y, (greycheck ? V_TRANSLUCENT : 0), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
+				V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
 		}
 		else
 			V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
@@ -2786,7 +2790,7 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
 		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)
+			             | (greycheck ? V_TRANSLUCENT : 0)
 			             | V_ALLOWLOWERCASE, name);
 
 		if (G_GametypeUsesLives()) //show lives
@@ -2846,13 +2850,13 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
 				if (players[tab[i].num].exiting)
 					V_DrawRightAlignedThinString(x+128, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
 				else
-					V_DrawRightAlignedThinString(x+128, y, (greycheck ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+					V_DrawRightAlignedThinString(x+128, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 			}
 			else
-				V_DrawRightAlignedThinString(x+128, y, (greycheck ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
+				V_DrawRightAlignedThinString(x+128, y, (greycheck ? V_TRANSLUCENT : 0), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
 		}
 		else
-			V_DrawRightAlignedThinString(x+128, y, (greycheck ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+			V_DrawRightAlignedThinString(x+128, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 
 		y += 9;
 		if (i == 16)
@@ -3091,7 +3095,7 @@ static void HU_DrawRankings(void)
 		HU_DrawTeamTabRankings(tab, whiteplayer);
 	else if (scorelines <= 9 && !cv_compactscoreboard.value)
 		HU_DrawTabRankings(40, 32, tab, scorelines, whiteplayer);
-	else if (scorelines <= 20 && !cv_compactscoreboard.value)
+	else if (scorelines <= 18 && !cv_compactscoreboard.value)
 		HU_DrawDualTabRankings(32, 32, tab, scorelines, whiteplayer);
 	else
 		HU_Draw32TabRankings(14, 28, tab, scorelines, whiteplayer);
diff --git a/src/info.c b/src/info.c
index f56e5d78e3e786b33806a1a596f0051a182144fd..331e114b6381cf2161fb83d121ec2b77c9b7853b 100644
--- a/src/info.c
+++ b/src/info.c
@@ -5198,11 +5198,11 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // speed
 		24*FRACUNIT,    // radius
 		34*FRACUNIT,    // height
-		0,              // display offset
+		1,              // display offset
 		DMG_FIRE,       // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_FIRE|MF_PAIN, // flags
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIPHEIGHT|MF_FIRE|MF_PAIN, // flags
 		S_NULL          // raisestate
 	},
 
@@ -7977,7 +7977,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_SCENERY|MF_NOCLIPHEIGHT,  // flags
+		MF_SOLID|MF_SCENERY,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -8004,7 +8004,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		DMG_SPIKE,      // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SCENERY|MF_NOCLIPHEIGHT|MF_PAPERCOLLISION,  // flags
+		MF_SOLID|MF_NOGRAVITY|MF_SCENERY|MF_PAPERCOLLISION,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -8031,7 +8031,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPTHING,  // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SCENERY|MF_NOCLIP|MF_NOCLIPTHING,  // flags
 		S_NULL          // raisestate
 	},
 
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index a72b22b5a62b953bbccde13f271c76fb6e24cad1..287a185bc36b06b5d04cc18d5667b9e869047452 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -1117,7 +1117,7 @@ int LUA_HookMusicChange(const char *oldname, struct MusicChange *param)
 		lua_pushstring(gL, oldname);/* the only constant value */
 		lua_pushstring(gL, param->newname);/* semi constant */
 
-		for (k = 0; k <= map->numHooks; ++k)
+		for (k = 0; k < map->numHooks; ++k)
 		{
 			get_hook(&hook, map->ids, k);
 
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 0ee87d2d2e3e054d448e5fb355d6572f89cb3139..6444662f666ba0841998a7aa243d753f911978d3 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -204,6 +204,7 @@ enum ffloor_e {
 	ffloor_next,
 	ffloor_prev,
 	ffloor_alpha,
+	ffloor_blend,
 	ffloor_bustflags,
 	ffloor_busttype,
 	ffloor_busttag,
@@ -228,6 +229,7 @@ static const char *const ffloor_opt[] = {
 	"next",
 	"prev",
 	"alpha",
+	"blend",
 	"bustflags",
 	"busttype",
 	"busttag",
@@ -1869,6 +1871,9 @@ static int ffloor_get(lua_State *L)
 	case ffloor_bouncestrength:
 		lua_pushfixed(L, ffloor->bouncestrength);
 		return 1;
+	case ffloor_blend:
+		lua_pushinteger(L, ffloor->blend);
+		return 1;
 	}
 	return 0;
 }
@@ -1947,6 +1952,9 @@ static int ffloor_set(lua_State *L)
 	case ffloor_alpha:
 		ffloor->alpha = (INT32)luaL_checkinteger(L, 3);
 		break;
+	case ffloor_blend:
+		ffloor->blend = (INT32)luaL_checkinteger(L, 3);
+		break;
 	}
 	return 0;
 }
diff --git a/src/lua_mathlib.c b/src/lua_mathlib.c
index e6f8c98c1371e81295a64d8d14a4039239bc4522..bd9218a3d54bc076093c8596ae502c0075464602 100644
--- a/src/lua_mathlib.c
+++ b/src/lua_mathlib.c
@@ -88,6 +88,12 @@ static int lib_finetangent(lua_State *L)
 	return 1;
 }
 
+static int lib_fixedasin(lua_State *L)
+{
+	lua_pushangle(L, -FixedAcos(luaL_checkfixed(L, 1)) + ANGLE_90);
+	return 1;
+}
+
 static int lib_fixedacos(lua_State *L)
 {
 	lua_pushangle(L, FixedAcos(luaL_checkfixed(L, 1)));
@@ -199,6 +205,7 @@ static luaL_Reg lib_math[] = {
 	{"sin", lib_finesine},
 	{"cos", lib_finecosine},
 	{"tan", lib_finetangent},
+	{"asin", lib_fixedasin},
 	{"acos", lib_fixedacos},
 	{"FixedAngle", lib_fixedangle},
 	{"fixangle"  , lib_fixedangle},
diff --git a/src/m_menu.c b/src/m_menu.c
index fc1e33b67dc16c6e8898a1df754119ecf8cf86ed..3c1d8d7caa07ba20ec416afddf848a75324017ab 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4036,7 +4036,7 @@ static void M_DrawThermo(INT32 x, INT32 y, consvar_t *cv)
 	xx += p->width - p->leftoffset;
 	for (i = 0; i < 16; i++)
 	{
-		V_DrawScaledPatch(xx, y, V_WRAPX, W_CachePatchNum(centerlump[i & 1], PU_PATCH));
+		V_DrawScaledPatch(xx, y, 0, W_CachePatchNum(centerlump[i & 1], PU_PATCH));
 		xx += 8;
 	}
 	V_DrawScaledPatch(xx, y, 0, W_CachePatchNum(rightlump, PU_PATCH));
@@ -4134,7 +4134,7 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
 	p = W_CachePatchNum(viewborderlump[BRDR_L], PU_PATCH);
 	for (n = 0; n < boxlines; n++)
 	{
-		V_DrawScaledPatch(cx, cy, V_WRAPY, p);
+		V_DrawScaledPatch(cx, cy, 0, p);
 		cy += step;
 	}
 	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BL], PU_PATCH));
@@ -4146,8 +4146,8 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
 	cy = y;
 	while (width > 0)
 	{
-		V_DrawScaledPatch(cx, cy, V_WRAPX, W_CachePatchNum(viewborderlump[BRDR_T], PU_PATCH));
-		V_DrawScaledPatch(cx, y + boff + boxlines*step, V_WRAPX, W_CachePatchNum(viewborderlump[BRDR_B], PU_PATCH));
+		V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_T], PU_PATCH));
+		V_DrawScaledPatch(cx, y + boff + boxlines*step, 0, W_CachePatchNum(viewborderlump[BRDR_B], PU_PATCH));
 		width--;
 		cx += step;
 	}
@@ -4159,7 +4159,7 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
 	p = W_CachePatchNum(viewborderlump[BRDR_R], PU_PATCH);
 	for (n = 0; n < boxlines; n++)
 	{
-		V_DrawScaledPatch(cx, cy, V_WRAPY, p);
+		V_DrawScaledPatch(cx, cy, 0, p);
 		cy += step;
 	}
 	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BR], PU_PATCH));
@@ -4438,22 +4438,21 @@ static void M_DrawGenericMenu(void)
 	}
 }
 
-const char *PlaystyleNames[4] = {"Strafe", "Standard", "Simple", "Old Analog??"};
+const char *PlaystyleNames[4] = {"\x86Strafe\x80", "Manual", "Automatic", "Old Analog??"};
 const char *PlaystyleDesc[4] = {
-	// Legacy
-	"The play style used for\n"
-	"old-school SRB2.\n"
+	// Strafe (or Legacy)
+	"A play style resembling\n"
+	"old-school SRB2 gameplay.\n"
 	"\n"
 	"This play style is identical\n"
-	"to Standard, except that the\n"
+	"to Manual, except that the\n"
 	"player always looks in the\n"
 	"direction of the camera."
 	,
 
-	// Standard
-	"The default play style,\n"
-	"designed for full control\n"
-	"with a keyboard and mouse.\n"
+	// Manual (formerly Standard)
+	"A play style made for full control,\n"
+	"using a keyboard and mouse.\n"
 	"\n"
 	"The camera rotates only when\n"
 	"you tell it to. The player\n"
@@ -4465,8 +4464,8 @@ const char *PlaystyleDesc[4] = {
 	"open up the highest level of play!"
 	,
 
-	// Simple
-	"A play style designed for\n"
+	// Automatic (formerly Simple)
+	"The default play style, designed for\n"
 	"gamepads and hassle-free play.\n"
 	"\n"
 	"The camera rotates automatically\n"
@@ -4475,7 +4474,8 @@ const char *PlaystyleDesc[4] = {
 	"they're moving.\n"
 	"\n"
 	"Hold \x82" "Center View\x80 to lock the\n"
-	"camera behind the player!\n"
+	"camera behind the player, or target\n"
+	"enemies, bosses and monitors!\n"
 	,
 
 	// Old Analog
@@ -4486,7 +4486,7 @@ const char *PlaystyleDesc[4] = {
 	"your config file and brought it back.\n"
 	"\n"
 	"That's absolutely valid, but I implore\n"
-	"you to try the new Simple play style\n"
+	"you to try the new Automatic play style\n"
 	"instead!"
 };
 
@@ -9062,7 +9062,7 @@ static void M_LoadGame(INT32 choice)
 
 	if (tutorialmap && cv_tutorialprompt.value)
 	{
-		M_StartMessage("Do you want to \x82play a brief Tutorial\x80?\n\nWe highly recommend this because \nthe controls are slightly different \nfrom other games.\n\nPress 'Y' or 'Enter' to go\nPress 'N' or any key to skip\n",
+		M_StartMessage("Do you want to \x82play a brief Tutorial\x80?\n\nWe highly recommend this because \nthe controls are slightly different \nfrom other games.\n\nPress the\x82 Y\x80 key or the\x83 A button\x80 to go\nPress the\x82 N\x80 key or the\x83 Y button\x80 to skip\n",
 			M_FirstTimeResponse, MM_YESNO);
 		return;
 	}
@@ -11588,9 +11588,7 @@ 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[36].status = (netgame
-			? IT_GRAYEDOUT
-			: (IT_STRING | IT_CVAR | IT_CV_STRING));
+		OP_ServerOptionsMenu[36].status = IT_STRING | IT_CVAR | IT_CV_STRING;
 		OP_ServerOptionsMenu[37].status = IT_STRING | IT_CVAR;
 		OP_ServerOptionsMenu[38].status = IT_STRING | IT_CVAR;
 	}
@@ -13010,6 +13008,7 @@ static void M_DrawPlaystyleMenu(void)
 
 		if (i == playstyle_currentchoice)
 		{
+			V_DrawFill(20, 40, 280, 150, 159);
 			V_DrawScaledPatch((i+1)*BASEVIDWIDTH/4 - 8, 10, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
 			V_DrawString(30, 50, V_ALLOWLOWERCASE, PlaystyleDesc[i]);
 		}
diff --git a/src/p_local.h b/src/p_local.h
index bbcd86b39d238ea4a8baa98567ad422409f1b8bb..bab7270c670dd79befaced5645d26f90ea3028de 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -348,6 +348,7 @@ void P_FlashPal(player_t *pl, UINT16 type, UINT16 duration);
 #define PAL_MIXUP    2
 #define PAL_RECYCLE  3
 #define PAL_NUKE     4
+#define PAL_INVERT   5
 
 //
 // P_ENEMY
diff --git a/src/p_map.c b/src/p_map.c
index 836e75c4e8bb08937e79fadf5f572125c9841b90..329224d0bd353c8a345f5483ffd5142695b9f0e3 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1152,9 +1152,9 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			return true; // underneath
 
 		if (tmthing->eflags & MFE_VERTICALFLIP)
-			thing->z = tmthing->z - thing->height - FixedMul(FRACUNIT, tmthing->scale);
+			P_TeleportMove(thing, thing->x, thing->y, tmthing->z - thing->height - FixedMul(FRACUNIT, tmthing->scale));
 		else
-			thing->z = tmthing->z + tmthing->height + FixedMul(FRACUNIT, tmthing->scale);
+			P_TeleportMove(thing, thing->x, thing->y, tmthing->z + tmthing->height + FixedMul(FRACUNIT, tmthing->scale));
 		if (thing->flags & MF_SHOOTABLE)
 			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
 		return true;
@@ -1465,86 +1465,6 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		return true;
 	}
 
-	// Sprite Spikes!
-	// Do not return because solidity code comes below.
-	if (tmthing->type == MT_SPIKE && tmthing->flags & MF_SOLID && thing->player) // moving spike rams into player?!
-	{
-		if (tmthing->eflags & MFE_VERTICALFLIP)
-		{
-			if (thing->z + thing->height <= tmthing->z + FixedMul(FRACUNIT, tmthing->scale)
-			&& thing->z + thing->height + thing->momz  >= tmthing->z + FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
-			&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && thing->eflags & MFE_VERTICALFLIP))
-				P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
-		}
-		else if (thing->z >= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale)
-		&& thing->z + thing->momz <= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
-		&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && !(thing->eflags & MFE_VERTICALFLIP)))
-			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
-	}
-	else if (thing->type == MT_SPIKE && thing->flags & MF_SOLID && tmthing->player) // unfortunate player falls into spike?!
-	{
-		if (thing->eflags & MFE_VERTICALFLIP)
-		{
-			if (tmthing->z + tmthing->height <= thing->z - FixedMul(FRACUNIT, thing->scale)
-			&& tmthing->z + tmthing->height + tmthing->momz >= thing->z - FixedMul(FRACUNIT, thing->scale)
-			&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && tmthing->eflags & MFE_VERTICALFLIP))
-				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
-		}
-		else if (tmthing->z >= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
-		&& tmthing->z + tmthing->momz <= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
-		&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && !(tmthing->eflags & MFE_VERTICALFLIP)))
-			P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
-	}
-
-	if (tmthing->type == MT_WALLSPIKE && tmthing->flags & MF_SOLID && thing->player) // wall spike impales player
-	{
-		fixed_t bottomz, topz;
-		bottomz = tmthing->z;
-		topz = tmthing->z + tmthing->height;
-		if (tmthing->eflags & MFE_VERTICALFLIP)
-			bottomz -= FixedMul(FRACUNIT, tmthing->scale);
-		else
-			topz += FixedMul(FRACUNIT, tmthing->scale);
-
-		if (thing->z + thing->height > bottomz // above bottom
-		&&  thing->z < topz) // below top
-		// don't check angle, the player was clearly in the way in this case
-			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
-	}
-	else if (thing->type == MT_WALLSPIKE && thing->flags & MF_SOLID && tmthing->player)
-	{
-		fixed_t bottomz, topz;
-		angle_t touchangle = R_PointToAngle2(thing->tracer->x, thing->tracer->y, tmthing->x, tmthing->y);
-
-		if (P_PlayerInPain(tmthing->player) && (tmthing->momx || tmthing->momy))
-		{
-			angle_t playerangle = R_PointToAngle2(0, 0, tmthing->momx, tmthing->momy) - touchangle;
-			if (playerangle > ANGLE_180)
-				playerangle = InvAngle(playerangle);
-			if (playerangle < ANGLE_90)
-				return true; // Yes, this is intentionally outside the z-height check. No standing on spikes whilst moving away from them.
-		}
-
-		bottomz = thing->z;
-		topz = thing->z + thing->height;
-
-		if (thing->eflags & MFE_VERTICALFLIP)
-			bottomz -= FixedMul(FRACUNIT, thing->scale);
-		else
-			topz += FixedMul(FRACUNIT, thing->scale);
-
-		if (tmthing->z + tmthing->height > bottomz // above bottom
-		&&  tmthing->z < topz // below top
-		&& !P_MobjWasRemoved(thing->tracer)) // this probably wouldn't work if we didn't have a tracer
-		{ // use base as a reference point to determine what angle you touched the spike at
-			touchangle = thing->angle - touchangle;
-			if (touchangle > ANGLE_180)
-				touchangle = InvAngle(touchangle);
-			if (touchangle <= ANGLE_22h) // if you touched it at this close an angle, you get poked!
-				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
-		}
-	}
-
 	if (thing->flags & MF_PUSHABLE)
 	{
 		if (tmthing->type == MT_FAN || tmthing->type == MT_STEAM)
@@ -1623,6 +1543,22 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	if (thing->player)
 	{
+		if (tmthing->type == MT_WALLSPIKE && (tmthing->flags & MF_SOLID)) // wall spike impales player
+		{
+			fixed_t bottomz, topz;
+			bottomz = tmthing->z;
+			topz = tmthing->z + tmthing->height;
+			if (tmthing->eflags & MFE_VERTICALFLIP)
+				bottomz -= FixedMul(FRACUNIT, tmthing->scale);
+			else
+				topz += FixedMul(FRACUNIT, tmthing->scale);
+
+			if (thing->z + thing->height > bottomz // above bottom
+			&&  thing->z < topz) // below top
+			// don't check angle, the player was clearly in the way in this case
+				P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
+		}
+
 		// Doesn't matter what gravity player's following! Just do your stuff in YOUR direction only
 		if (tmthing->eflags & MFE_VERTICALFLIP
 		&& (tmthing->z + tmthing->height + tmthing->momz < thing->z
@@ -1657,6 +1593,55 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		if (!tmthing->health)
 			return true;
 
+		if (thing->type == MT_SPIKE && (thing->flags & MF_SOLID)) // unfortunate player falls into spike?!
+		{
+			if (thing->eflags & MFE_VERTICALFLIP)
+			{
+				if (tmthing->z + tmthing->height <= thing->z - FixedMul(FRACUNIT, thing->scale)
+				&& tmthing->z + tmthing->height + tmthing->momz >= thing->z - FixedMul(FRACUNIT, thing->scale)
+				&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && tmthing->eflags & MFE_VERTICALFLIP))
+					P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
+			}
+			else if (tmthing->z >= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
+			&& tmthing->z + tmthing->momz <= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
+			&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && !(tmthing->eflags & MFE_VERTICALFLIP)))
+				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
+		}
+
+		if (thing->type == MT_WALLSPIKE && (thing->flags & MF_SOLID))
+		{
+			fixed_t bottomz, topz;
+			angle_t touchangle = R_PointToAngle2(thing->tracer->x, thing->tracer->y, tmthing->x, tmthing->y);
+
+			if (P_PlayerInPain(tmthing->player) && (tmthing->momx || tmthing->momy))
+			{
+				angle_t playerangle = R_PointToAngle2(0, 0, tmthing->momx, tmthing->momy) - touchangle;
+				if (playerangle > ANGLE_180)
+					playerangle = InvAngle(playerangle);
+				if (playerangle < ANGLE_90)
+					return true; // Yes, this is intentionally outside the z-height check. No standing on spikes whilst moving away from them.
+			}
+
+			bottomz = thing->z;
+			topz = thing->z + thing->height;
+
+			if (thing->eflags & MFE_VERTICALFLIP)
+				bottomz -= FixedMul(FRACUNIT, thing->scale);
+			else
+				topz += FixedMul(FRACUNIT, thing->scale);
+
+			if (tmthing->z + tmthing->height > bottomz // above bottom
+			&&  tmthing->z < topz // below top
+			&& !P_MobjWasRemoved(thing->tracer)) // this probably wouldn't work if we didn't have a tracer
+			{ // use base as a reference point to determine what angle you touched the spike at
+				touchangle = thing->angle - touchangle;
+				if (touchangle > ANGLE_180)
+					touchangle = InvAngle(touchangle);
+				if (touchangle <= ANGLE_22h) // if you touched it at this close an angle, you get poked!
+					P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
+			}
+		}
+
 		if (thing->type == MT_FAN || thing->type == MT_STEAM)
 			P_DoFanAndGasJet(thing, tmthing);
 		else if (thing->flags & MF_SPRING && tmthing->player->powers[pw_carry] != CR_MINECART)
@@ -1721,8 +1706,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		}
 	}
 
-	if ((tmthing->flags & MF_SPRING || tmthing->type == MT_STEAM || tmthing->type == MT_SPIKE || tmthing->type == MT_WALLSPIKE) && (thing->player))
-		; // springs, gas jets and springs should never be able to step up onto a player
+	if ((thing->player) && (tmthing->flags & MF_SPRING || tmthing->type == MT_STEAM))
+		; // springs and gas jets should never be able to step up onto a player
 	// z checking at last
 	// Treat noclip things as non-solid!
 	else if ((thing->flags & (MF_SOLID|MF_NOCLIP)) == MF_SOLID
@@ -1730,6 +1715,9 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	{
 		fixed_t topz, tmtopz;
 
+		if (tmthing->type == MT_SPIKE || tmthing->type == MT_WALLSPIKE) // do not run height checks if you are a spike
+			return true;
+
 		if (tmthing->eflags & MFE_VERTICALFLIP)
 		{
 			// pass under
@@ -2727,6 +2715,16 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 				&& P_MobjFlip(thing)*thing->momz > FixedMul(FRACUNIT, thing->scale))
 					maxstep = 0;
 			}
+			else if (thing->flags & MF_PUSHABLE)
+			{
+				// If using type Section1:13, double the maxstep.
+				if (GETSECSPECIAL(R_PointInSubsector(x, y)->sector->special, 1) == 13)
+					maxstep <<= 1;
+
+				// If using type Section1:14, no maxstep.
+				if (GETSECSPECIAL(R_PointInSubsector(x, y)->sector->special, 1) == 14)
+					maxstep = 0;
+			}
 
 			if (thing->type == MT_SKIM)
 				maxstep = 0;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index d0af3112ebf5c69dfa7f3a9c4ee2d3205d3ad085..467fd446247a684c49a1632363c83f7c34a0e013 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -395,13 +395,13 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 			if (skin)
 			{
 				UINT16 stateframe = st->frame;
-				
+
 				// Add/Remove FF_SPR2SUPER based on certain conditions
 				if (player->charflags & SF_NOSUPERSPRITES)
 					stateframe = stateframe & ~FF_SPR2SUPER;
 				else if (player->powers[pw_super])
 					stateframe = stateframe | FF_SPR2SUPER;
-				
+
 				if (stateframe & FF_SPR2SUPER)
 				{
 					if (mobj->eflags & MFE_FORCENOSUPER)
@@ -409,11 +409,11 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 				}
 				else if (mobj->eflags & MFE_FORCESUPER)
 					stateframe = stateframe | FF_SPR2SUPER;
-					
+
 				// Get the sprite2 and frame number
 				spr2 = P_GetSkinSprite2(skin, (stateframe & FF_FRAMEMASK), mobj->player);
 				numframes = skin->sprites[spr2].numframes;
-				
+
 				if (state == S_PLAY_STND && (spr2 & FF_SPR2SUPER) && skin->sprites[SPR2_WAIT|FF_SPR2SUPER].numframes == 0)
 					mobj->tics = -1;	// If no super wait, don't wait at all
 			}
@@ -541,7 +541,7 @@ boolean P_SetMobjState(mobj_t *mobj, statenum_t state)
 			if (skin)
 			{
 				UINT16 stateframe = st->frame;
-				
+
 				// Add/Remove FF_SPR2SUPER based on certain conditions
 				if (stateframe & FF_SPR2SUPER)
 				{
@@ -550,11 +550,11 @@ boolean P_SetMobjState(mobj_t *mobj, statenum_t state)
 				}
 				else if (mobj->eflags & MFE_FORCESUPER)
 					stateframe = stateframe | FF_SPR2SUPER;
-					
+
 				// Get the sprite2 and frame number
 				spr2 = P_GetSkinSprite2(skin, (stateframe & FF_FRAMEMASK), NULL);
 				numframes = skin->sprites[spr2].numframes;
-				
+
 				if (state == S_PLAY_STND && (spr2 & FF_SPR2SUPER) && skin->sprites[SPR2_WAIT|FF_SPR2SUPER].numframes == 0)
 					mobj->tics = -1;	// If no super wait, don't wait at all
 			}
@@ -1877,7 +1877,7 @@ void P_XYMovement(mobj_t *mo)
 		// blocked move
 		moved = false;
 
-		if (player) 
+		if (player)
 			B_MoveBlocked(player);
 
 		if (LUA_HookMobjMoveBlocked(mo, tmhitthing, blockingline))
@@ -2527,7 +2527,7 @@ boolean P_ZMovement(mobj_t *mo)
 					{
 						P_KillMobj(mo, NULL, NULL, 0);
 					}
-					return false;
+					return !P_MobjWasRemoved(mo); // allows explosion states to run
 				}
 				else
 				{
@@ -3328,7 +3328,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 			{ // Water removes electric and non-water fire shields...
 			    if (electric)
 				    P_FlashPal(p, PAL_WHITE, 1);
-				
+
 				p->powers[pw_shield] = p->powers[pw_shield] & SH_STACK;
 			}
 		}
@@ -7064,6 +7064,8 @@ static void P_PyreFlyBurn(mobj_t *mobj, fixed_t hoffs, INT16 vrange, mobjtype_t
 	fixed_t zoffs = P_RandomRange(-vrange, vrange)*FRACUNIT;
 	mobj_t *particle = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, zoffs, mobjtype);
 	particle->momz = momz;
+	particle->flags2 |= MF2_LINKDRAW;
+	P_SetTarget(&particle->tracer, mobj);
 }
 
 static void P_MobjScaleThink(mobj_t *mobj)
@@ -7743,7 +7745,8 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 		break;
 	case MT_WATERDROP:
 		P_SceneryCheckWater(mobj);
-		if ((mobj->z <= mobj->floorz || mobj->z <= mobj->watertop)
+		if (((!(mobj->eflags & MFE_VERTICALFLIP) && (mobj->z <= mobj->floorz || mobj->z <= mobj->watertop))
+			|| (mobj->eflags & MFE_VERTICALFLIP && mobj->z + mobj->height >= mobj->ceilingz))
 			&& mobj->health > 0)
 		{
 			mobj->health = 0;
@@ -7847,6 +7850,48 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 		if (P_MobjFlip(mobj)*mobj->momz < mobj->info->speed)
 			mobj->momz = P_MobjFlip(mobj)*mobj->info->speed;
 		break;
+	case MT_SPIKE:
+		if (mobj->fuse)
+		{
+			mobj->fuse--;
+			break;
+		}
+		P_SetMobjState(mobj, mobj->state->nextstate);
+		mobj->fuse = mobj->info->speed;
+		if (mobj->spawnpoint)
+			mobj->fuse += mobj->spawnpoint->angle;
+		break;
+	case MT_WALLSPIKE:
+		if (mobj->fuse)
+		{
+			mobj->fuse--;
+			break;
+		}
+		P_SetMobjState(mobj, mobj->state->nextstate);
+		mobj->fuse = mobj->info->speed;
+		if (mobj->spawnpoint)
+			mobj->fuse += (mobj->spawnpoint->angle / 360);
+		break;
+	case MT_WALLSPIKEBASE:
+		if (!mobj->target)
+		{
+			P_RemoveMobj(mobj);
+			return;
+		}
+		mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
+#if 0
+		if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
+		{
+			mobj_t* target = mobj->target; // shortcut
+			const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
+			P_UnsetThingPosition(mobj);
+			mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
+			mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
+			P_SetThingPosition(mobj);
+			mobj->angle = target->angle + ANGLE_90;
+		}
+#endif
+		break;
 	case MT_ROCKCRUMBLE1:
 	case MT_ROCKCRUMBLE2:
 	case MT_ROCKCRUMBLE3:
@@ -9243,25 +9288,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 
 	switch (mobj->type)
 	{
-	case MT_WALLSPIKEBASE:
-		if (!mobj->target) {
-			P_RemoveMobj(mobj);
-			return false;
-		}
-		mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
-#if 0
-		if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
-		{
-			mobj_t* target = mobj->target; // shortcut
-			const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
-			P_UnsetThingPosition(mobj);
-			mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
-			mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
-			P_SetThingPosition(mobj);
-			mobj->angle = target->angle + ANGLE_90;
-		}
-#endif
-		break;
 	case MT_FALLINGROCK:
 		// Despawn rocks here in case zmovement code can't do so (blame slopes)
 		if (!mobj->momx && !mobj->momy && !mobj->momz
@@ -9943,18 +9969,6 @@ static boolean P_FuseThink(mobj_t *mobj)
 		break;
 	case MT_METALSONIC_BATTLE:
 		break; // don't remove
-	case MT_SPIKE:
-		P_SetMobjState(mobj, mobj->state->nextstate);
-		mobj->fuse = mobj->info->speed;
-		if (mobj->spawnpoint)
-			mobj->fuse += mobj->spawnpoint->angle;
-		break;
-	case MT_WALLSPIKE:
-		P_SetMobjState(mobj, mobj->state->nextstate);
-		mobj->fuse = mobj->info->speed;
-		if (mobj->spawnpoint)
-			mobj->fuse += (mobj->spawnpoint->angle / 360);
-		break;
 	case MT_NIGHTSCORE:
 		P_RemoveMobj(mobj);
 		return false;
@@ -10397,7 +10411,7 @@ static fixed_t P_DefaultMobjShadowScale (mobj_t *thing)
 
 		case MT_RING:
 		case MT_FLINGRING:
-		
+
 		case MT_COIN:
 		case MT_FLINGCOIN:
 
@@ -12931,17 +12945,18 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		// Pop up spikes!
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
-			mobj->flags &= ~MF_SCENERY;
 			mobj->fuse = (16 - mthing->extrainfo)*(mthing->angle + mobj->info->speed)/16;
 			if (mthing->options & MTF_EXTRA)
 				P_SetMobjState(mobj, mobj->info->meleestate);
 		}
-		// Use per-thing collision for spikes if the deaf flag isn't checked.
-		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
+		else
+			mobj->flags |= MF_NOTHINK;
+		// no collision for spikes if the ambush flag is checked
+		if ((mthing->options & MTF_AMBUSH) || metalrecording)
 		{
 			P_UnsetThingPosition(mobj);
-			mobj->flags &= ~(MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT);
-			mobj->flags |= MF_SOLID;
+			mobj->flags |= (MF_NOBLOCKMAP|MF_NOCLIPHEIGHT);
+			mobj->flags &= ~MF_SOLID;
 			P_SetThingPosition(mobj);
 		}
 		break;
@@ -12949,20 +12964,20 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		// Pop up spikes!
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
-			mobj->flags &= ~MF_SCENERY;
 			mobj->fuse = (16 - mthing->extrainfo)*((mthing->angle/360) + mobj->info->speed)/16;
 			if (mthing->options & MTF_EXTRA)
 				P_SetMobjState(mobj, mobj->info->meleestate);
 		}
-		// Use per-thing collision for spikes if the deaf flag isn't checked.
-		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
+		else
+			mobj->flags |= MF_NOTHINK;
+		// no collision for spikes if the ambush flag is checked
+		if ((mthing->options & MTF_AMBUSH) || metalrecording)
 		{
 			P_UnsetThingPosition(mobj);
-			mobj->flags &= ~(MF_NOBLOCKMAP | MF_NOCLIPHEIGHT);
-			mobj->flags |= MF_SOLID;
+			mobj->flags |= (MF_NOBLOCKMAP|MF_NOCLIPHEIGHT);
+			mobj->flags &= ~MF_SOLID;
 			P_SetThingPosition(mobj);
 		}
-
 		// spawn base
 		{
 			const angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS); // the mobj's own angle hasn't been set quite yet so...
@@ -12976,6 +12991,8 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 			P_SetScale(base, mobj->scale);
 			P_SetTarget(&base->target, mobj);
 			P_SetTarget(&mobj->tracer, base);
+			if (!(mthing->options & MTF_OBJECTSPECIAL))
+				base->flags |= MF_NOTHINK;
 		}
 		break;
 	case MT_RING_BOX:
diff --git a/src/p_pspr.h b/src/p_pspr.h
index 4525ba14cc9f4844573a22e5df2f36418799efa2..27002b7136f74747d73ef08c04da46b89c91a909 100644
--- a/src/p_pspr.h
+++ b/src/p_pspr.h
@@ -41,9 +41,20 @@
 /// \brief Frame flags - SPR2: Super sprite2
 #define FF_SPR2SUPER 0x80
 /// \brief Frame flags - SPR2: A change of state at the end of Sprite2 animation
-#define FF_SPR2ENDSTATE 0x1000
+#define FF_SPR2ENDSTATE 0x100
 /// \brief Frame flags - SPR2: 50% of starting in middle of Sprite2 animation
-#define FF_SPR2MIDSTART 0x2000
+#define FF_SPR2MIDSTART 0x200
+
+/// \brief Frame flags: blend types
+#define FF_BLENDMASK 0x7000
+/// \brief shift for FF_BLENDMASK
+#define FF_BLENDSHIFT 12
+/// \brief preshifted blend flags minus 1 as effects don't distinguish between AST_COPY and AST_TRANSLUCENT
+#define FF_ADD             ((AST_ADD-1)<<FF_BLENDSHIFT)
+#define FF_SUBTRACT        ((AST_SUBTRACT-1)<<FF_BLENDSHIFT)
+#define FF_REVERSESUBTRACT ((AST_REVERSESUBTRACT-1)<<FF_BLENDSHIFT)
+#define FF_MODULATE        ((AST_MODULATE-1)<<FF_BLENDSHIFT)
+#define FF_OVERLAY         ((AST_OVERLAY-1)<<FF_BLENDSHIFT)
 
 /// \brief Frame flags: 0 = no trans(opaque), 1-15 = transl. table
 #define FF_TRANSMASK 0xf0000
@@ -60,21 +71,31 @@
 #define FF_TRANS80 (tr_trans80<<FF_TRANSSHIFT)
 #define FF_TRANS90 (tr_trans90<<FF_TRANSSHIFT)
 
+/// \brief Frame flags: brightness mask
+#define FF_BRIGHTMASK	0x00300000
 /// \brief Frame flags: frame always appears full bright
-#define FF_FULLBRIGHT 0x00100000
+#define FF_FULLBRIGHT	0x00100000
+/// \brief Frame flags: frame always appears full darkness
+#define FF_FULLDARK		0x00200000
+/// \brief Frame flags: frame appears between sector bright and full bright
+#define FF_SEMIBRIGHT	(FF_FULLBRIGHT|FF_FULLDARK)
+
+/// \brief Frame flags: Thin, paper-like sprite (for collision equivalent, see MF_PAPERCOLLISION)
+#define FF_PAPERSPRITE 0x00400000
+/// \brief Frame flags: Splat!
+#define FF_FLOORSPRITE 0x00800000
+
 /// \brief Frame flags: Flip sprite vertically (relative to what it should be for its gravity)
-#define FF_VERTICALFLIP 0x00200000
+#define FF_VERTICALFLIP 0x01000000
 /// \brief Frame flags: Flip sprite horizontally
-#define FF_HORIZONTALFLIP 0x00400000
-/// \brief Frame flags: Thin, paper-like sprite (for collision equivalent, see MF_PAPERCOLLISION)
-#define FF_PAPERSPRITE 0x00800000
+#define FF_HORIZONTALFLIP 0x02000000
 
 /// \brief Frame flags - Animate: Simple stateless animation
-#define FF_ANIMATE 0x01000000
-/// \brief Frame flags - Animate: Start at a random place in the animation (mutually exclusive with below)
-#define FF_RANDOMANIM 0x02000000
-/// \brief Frame flags - Animate: Sync animation to global timer (mutually exclusive with above)
-#define FF_GLOBALANIM 0x04000000
+#define FF_ANIMATE 0x10000000
+/// \brief Frame flags - Animate: Sync animation to global timer (mutually exclusive with below, currently takes priority)
+#define FF_GLOBALANIM 0x20000000
+/// \brief Frame flags - Animate: Start at a random place in the animation (mutually exclusive with above)
+#define FF_RANDOMANIM 0x40000000
 
 /**	\brief translucency tables
 
diff --git a/src/p_saveg.c b/src/p_saveg.c
index bc9c31d64a66c41ccaf854d85bfda3c6e7145358..95229b3617d068d8e3f899d7bcbd598c7cba02fd 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1631,7 +1631,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	diff2 = 0;
 
 	// not the default but the most probable
-	if (mobj->momx != 0 || mobj->momy != 0 || mobj->momz != 0)
+	if (mobj->momx != 0 || mobj->momy != 0 || mobj->momz != 0 || mobj->pmomz !=0)
 		diff |= MD_MOM;
 	if (mobj->radius != mobj->info->radius)
 		diff |= MD_RADIUS;
@@ -1806,6 +1806,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEFIXED(save_p, mobj->momx);
 		WRITEFIXED(save_p, mobj->momy);
 		WRITEFIXED(save_p, mobj->momz);
+		WRITEFIXED(save_p, mobj->pmomz);
 	}
 	if (diff & MD_RADIUS)
 		WRITEFIXED(save_p, mobj->radius);
@@ -2817,6 +2818,7 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		mobj->momx = READFIXED(save_p);
 		mobj->momy = READFIXED(save_p);
 		mobj->momz = READFIXED(save_p);
+		mobj->pmomz = READFIXED(save_p);
 	} // otherwise they're zero, and the memset took care of it
 
 	if (diff & MD_RADIUS)
diff --git a/src/p_setup.c b/src/p_setup.c
index 02d7788c1c48530b662c7f41499bd3d510bee814..fc8cad41180d2ada1452af430de399bd94b08388 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1366,8 +1366,11 @@ static void P_LoadSidedefs(UINT8 *data)
 				if (msd->toptexture[0] == '#')
 				{
 					char *col = msd->toptexture;
-					sd->toptexture = sd->bottomtexture =
-						((col[1]-'0')*100 + (col[2]-'0')*10 + col[3]-'0') + 1;
+					sd->toptexture =
+						((col[1]-'0')*100 + (col[2]-'0')*10 + col[3]-'0')+1;
+					if (col[4]) // extra num for blendmode
+						sd->toptexture += (col[4]-'0')*1000;
+					sd->bottomtexture = sd->toptexture;
 					sd->midtexture = R_TextureNumForName(msd->midtexture);
 				}
 				else
@@ -1722,6 +1725,21 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 		lines[i].sidenum[1] = atol(val);
 	else if (fastcmp(param, "alpha"))
 		lines[i].alpha = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "blendmode") || fastcmp(param, "renderstyle"))
+	{
+		if (fastcmp(val, "translucent"))
+			lines[i].blendmode = AST_COPY;
+		else if (fastcmp(val, "add"))
+			lines[i].blendmode = AST_ADD;
+		else if (fastcmp(val, "subtract"))
+			lines[i].blendmode = AST_SUBTRACT;
+		else if (fastcmp(val, "reversesubtract"))
+			lines[i].blendmode = AST_REVERSESUBTRACT;
+		else if (fastcmp(val, "modulate"))
+			lines[i].blendmode = AST_MODULATE;
+		if (fastcmp(val, "fog"))
+			lines[i].blendmode = AST_FOG;
+	}
 	else if (fastcmp(param, "executordelay"))
 		lines[i].executordelay = atol(val);
 
@@ -3073,48 +3091,55 @@ static void P_AddBinaryMapTags(void)
 {
 	size_t i;
 
-	for (i = 0; i < numlines; i++)
-	{
-		// 96: Apply Tag to Tagged Sectors
+	for (i = 0; i < numlines; i++) {
 		// 97: Apply Tag to Front Sector
 		// 98: Apply Tag to Back Sector
 		// 99: Apply Tag to Front and Back Sectors
-		if (lines[i].special == 96) {
-			size_t j;
-			mtag_t tag = Tag_FGet(&lines[i].frontsector->tags);
-			mtag_t target_tag = Tag_FGet(&lines[i].tags);
-			mtag_t offset_tags[4];
-			memset(offset_tags, 0, sizeof(mtag_t)*4);
-			if (lines[i].flags & ML_EFFECT6) {
-				offset_tags[0] = (INT32)sides[lines[i].sidenum[0]].textureoffset / FRACUNIT;
-				offset_tags[1] = (INT32)sides[lines[i].sidenum[0]].rowoffset / FRACUNIT;
-			}
-			if (lines[i].flags & ML_TFERLINE) {
-				offset_tags[2] = (INT32)sides[lines[i].sidenum[1]].textureoffset / FRACUNIT;
-				offset_tags[3] = (INT32)sides[lines[i].sidenum[1]].rowoffset / FRACUNIT;
-			}
+		if (lines[i].special == 97 || lines[i].special == 99)
+			P_AddBinaryMapTagsFromLine(lines[i].frontsector, &lines[i]);
+		if (lines[i].special == 98 || lines[i].special == 99)
+			P_AddBinaryMapTagsFromLine(lines[i].backsector, &lines[i]);
+	}
 
-			for (j = 0; j < numsectors; j++) {
-				boolean matches_target_tag = target_tag && Tag_Find(&sectors[j].tags, target_tag);
-				size_t k; for (k = 0; k < 4; k++) {
-					if (lines[i].flags & ML_EFFECT5) {
-						if (matches_target_tag || (offset_tags[k] && Tag_Find(&sectors[j].tags, offset_tags[k]))) {
-							Tag_Add(&sectors[j].tags, tag);
-							break;
-						}
-					} else if (matches_target_tag) {
-						if (k == 0)
-							Tag_Add(&sectors[j].tags, tag);
-						if (offset_tags[k])
-							Tag_Add(&sectors[j].tags, offset_tags[k]);
+	// Run this loop after the 97-99 loop to ensure that 96 can search through all of the
+	// 97-99-applied tags.
+	for (i = 0; i < numlines; i++) {
+		size_t j;
+		mtag_t tag, target_tag;
+		mtag_t offset_tags[4];
+
+		// 96: Apply Tag to Tagged Sectors
+		if (lines[i].special != 96)
+			continue;
+
+		tag = Tag_FGet(&lines[i].frontsector->tags);
+		target_tag = Tag_FGet(&lines[i].tags);
+		memset(offset_tags, 0, sizeof(mtag_t)*4);
+		if (lines[i].flags & ML_EFFECT6) {
+			offset_tags[0] = (INT32)sides[lines[i].sidenum[0]].textureoffset / FRACUNIT;
+			offset_tags[1] = (INT32)sides[lines[i].sidenum[0]].rowoffset / FRACUNIT;
+		}
+		if (lines[i].flags & ML_TFERLINE) {
+			offset_tags[2] = (INT32)sides[lines[i].sidenum[1]].textureoffset / FRACUNIT;
+			offset_tags[3] = (INT32)sides[lines[i].sidenum[1]].rowoffset / FRACUNIT;
+		}
+
+		for (j = 0; j < numsectors; j++) {
+			boolean matches_target_tag = target_tag && Tag_Find(&sectors[j].tags, target_tag);
+			size_t k;
+			for (k = 0; k < 4; k++) {
+				if (lines[i].flags & ML_EFFECT5) {
+					if (matches_target_tag || (offset_tags[k] && Tag_Find(&sectors[j].tags, offset_tags[k]))) {
+						Tag_Add(&sectors[j].tags, tag);
+						break;
 					}
+				} else if (matches_target_tag) {
+					if (k == 0)
+						Tag_Add(&sectors[j].tags, tag);
+					if (offset_tags[k])
+						Tag_Add(&sectors[j].tags, offset_tags[k]);
 				}
 			}
-		} else {
-			if (lines[i].special == 97 || lines[i].special == 99)
-				P_AddBinaryMapTagsFromLine(lines[i].frontsector, &lines[i]);
-			if (lines[i].special == 98 || lines[i].special == 99)
-				P_AddBinaryMapTagsFromLine(lines[i].backsector, &lines[i]);
 		}
 	}
 
@@ -3138,6 +3163,24 @@ static void P_AddBinaryMapTags(void)
 	}
 }
 
+static void P_SetBinaryFOFAlpha(line_t *line)
+{
+	if (sides[line->sidenum[0]].toptexture > 0)
+	{
+		line->args[1] = sides[line->sidenum[0]].toptexture;
+		if (sides[line->sidenum[0]].toptexture >= 1001)
+		{
+			line->args[2] = (sides[line->sidenum[0]].toptexture/1000);
+			line->args[1] %= 1000;
+		}
+	}
+	else
+	{
+		line->args[1] = 128;
+		line->args[2] = TMB_TRANSLUCENT;
+	}
+}
+
 //For maps in binary format, converts setup of specials to UDMF format.
 static void P_ConvertBinaryMap(void)
 {
@@ -3297,36 +3340,33 @@ static void P_ConvertBinaryMap(void)
 			if (lines[i].special == 102)
 			{
 				if (lines[i].flags & ML_NOCLIMB)
-					lines[i].args[2] |= TMFA_INSIDES;
-				if (sides[lines[i].sidenum[0]].toptexture > 0)
-					lines[i].args[1] = sides[lines[i].sidenum[0]].toptexture;
-				else
-					lines[i].args[1] = 128;
+					lines[i].args[3] |= TMFA_INSIDES;
+				P_SetBinaryFOFAlpha(&lines[i]);
 
 				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
 				if (lines[i].args[1] == 256)
-					lines[i].args[2] |= TMFA_SPLAT;
+					lines[i].args[3] |= TMFA_SPLAT;
 			}
 			else
 				lines[i].args[1] = 255;
 
 			//Appearance
 			if (lines[i].special == 105)
-				lines[i].args[2] |= TMFA_NOPLANES|TMFA_NOSIDES;
+				lines[i].args[3] |= TMFA_NOPLANES|TMFA_NOSIDES;
 			else if (lines[i].special == 104)
-				lines[i].args[2] |= TMFA_NOSIDES;
+				lines[i].args[3] |= TMFA_NOSIDES;
 			else if (lines[i].special == 103)
-				lines[i].args[2] |= TMFA_NOPLANES;
+				lines[i].args[3] |= TMFA_NOPLANES;
 			if (lines[i].special != 100 && (lines[i].special != 104 || !(lines[i].flags & ML_NOCLIMB)))
-				lines[i].args[2] |= TMFA_NOSHADE;
+				lines[i].args[3] |= TMFA_NOSHADE;
 			if (lines[i].flags & ML_EFFECT6)
-				lines[i].args[2] |= TMFA_SPLAT;
+				lines[i].args[3] |= TMFA_SPLAT;
 
 			//Tangibility
 			if (lines[i].flags & ML_EFFECT1)
-				lines[i].args[3] |= TMFT_DONTBLOCKOTHERS;
+				lines[i].args[4] |= TMFT_DONTBLOCKOTHERS;
 			if (lines[i].flags & ML_EFFECT2)
-				lines[i].args[3] |= TMFT_DONTBLOCKPLAYER;
+				lines[i].args[4] |= TMFT_DONTBLOCKPLAYER;
 
 			lines[i].special = 100;
 			break;
@@ -3343,35 +3383,32 @@ static void P_ConvertBinaryMap(void)
 				lines[i].args[1] = 255;
 			else
 			{
-				if (sides[lines[i].sidenum[0]].toptexture > 0)
-					lines[i].args[1] = sides[lines[i].sidenum[0]].toptexture;
-				else
-					lines[i].args[1] = 128;
+				P_SetBinaryFOFAlpha(&lines[i]);
 
 				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
 				if (lines[i].args[1] == 256)
-					lines[i].args[2] |= TMFW_SPLAT;
+					lines[i].args[3] |= TMFW_SPLAT;
 			}
 
 			//No sides?
 			if (lines[i].special == 122 || lines[i].special == 123 || lines[i].special == 125)
-				lines[i].args[2] |= TMFW_NOSIDES;
+				lines[i].args[3] |= TMFW_NOSIDES;
 
 			//Flags
 			if (lines[i].flags & ML_NOCLIMB)
-				lines[i].args[2] |= TMFW_DOUBLESHADOW;
+				lines[i].args[3] |= TMFW_DOUBLESHADOW;
 			if (lines[i].flags & ML_EFFECT4)
-				lines[i].args[2] |= TMFW_COLORMAPONLY;
+				lines[i].args[3] |= TMFW_COLORMAPONLY;
 			if (!(lines[i].flags & ML_EFFECT5))
-				lines[i].args[2] |= TMFW_NORIPPLE;
+				lines[i].args[3] |= TMFW_NORIPPLE;
 
 			//Goo?
 			if (lines[i].special >= 124)
-				lines[i].args[2] |= TMFW_GOOWATER;
+				lines[i].args[3] |= TMFW_GOOWATER;
 
 			//Splat rendering?
 			if (lines[i].flags & ML_EFFECT6)
-				lines[i].args[2] |= TMFW_SPLAT;
+				lines[i].args[3] |= TMFW_SPLAT;
 
 			lines[i].special = 120;
 			break;
@@ -3388,41 +3425,38 @@ static void P_ConvertBinaryMap(void)
 			if (lines[i].special == 141 || lines[i].special == 142 || lines[i].special == 144 || lines[i].special == 145)
 			{
 				if (lines[i].flags & ML_NOCLIMB)
-					lines[i].args[2] |= TMFA_INSIDES;
-				if (sides[lines[i].sidenum[0]].toptexture > 0)
-					lines[i].args[1] = sides[lines[i].sidenum[0]].toptexture;
-				else
-					lines[i].args[1] = 128;
+					lines[i].args[3] |= TMFA_INSIDES;
+				P_SetBinaryFOFAlpha(&lines[i]);
 
 				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
 				if (lines[i].args[1] == 256)
-					lines[i].args[2] |= TMFA_SPLAT;
+					lines[i].args[3] |= TMFA_SPLAT;
 			}
 			else
 				lines[i].args[1] = 255;
 
 			//Appearance
 			if (lines[i].special == 142 || lines[i].special == 145)
-				lines[i].args[2] |= TMFA_NOSIDES;
+				lines[i].args[3] |= TMFA_NOSIDES;
 			else if (lines[i].special == 146)
-				lines[i].args[2] |= TMFA_NOPLANES;
+				lines[i].args[3] |= TMFA_NOPLANES;
 			if (lines[i].special != 146 && (lines[i].flags & ML_NOCLIMB))
-				lines[i].args[2] |= TMFA_NOSHADE;
+				lines[i].args[3] |= TMFA_NOSHADE;
 			if (lines[i].flags & ML_EFFECT6)
-				lines[i].args[2] |= TMFA_SPLAT;
+				lines[i].args[3] |= TMFA_SPLAT;
 
 			//Tangibility
 			if (lines[i].special <= 142)
-				lines[i].args[3] |= TMFT_INTANGIBLEBOTTOM;
+				lines[i].args[4] |= TMFT_INTANGIBLEBOTTOM;
 			else if (lines[i].special <= 145)
-				lines[i].args[3] |= TMFT_INTANGIBLETOP;
+				lines[i].args[4] |= TMFT_INTANGIBLETOP;
 			else
-				lines[i].args[3] |= TMFT_INTANGIBLEBOTTOM|TMFT_INTANGIBLETOP;
+				lines[i].args[4] |= TMFT_INTANGIBLEBOTTOM|TMFT_INTANGIBLETOP;
 
 			if (lines[i].flags & ML_EFFECT1)
-				lines[i].args[3] |= TMFT_DONTBLOCKOTHERS;
+				lines[i].args[4] |= TMFT_DONTBLOCKOTHERS;
 			if (lines[i].flags & ML_EFFECT2)
-				lines[i].args[3] |= TMFT_DONTBLOCKPLAYER;
+				lines[i].args[4] |= TMFT_DONTBLOCKPLAYER;
 
 			lines[i].special = 100;
 			break;
@@ -3462,38 +3496,35 @@ static void P_ConvertBinaryMap(void)
 			//Alpha
 			if (lines[i].special >= 174 && lines[i].special <= 175)
 			{
-				if (sides[lines[i].sidenum[0]].toptexture > 0)
-					lines[i].args[1] = sides[lines[i].sidenum[0]].toptexture;
-				else
-					lines[i].args[1] = 128;
+				P_SetBinaryFOFAlpha(&lines[i]);
 
 				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
 				if (lines[i].args[1] == 256)
-					lines[i].args[3] |= TMFC_SPLAT;
+					lines[i].args[4] |= TMFC_SPLAT;
 			}
 			else
 				lines[i].args[1] = 255;
 
 			if (lines[i].special >= 172 && lines[i].special <= 175)
 			{
-				lines[i].args[2] |= TMFT_INTANGIBLEBOTTOM;
+				lines[i].args[3] |= TMFT_INTANGIBLEBOTTOM;
 				if (lines[i].flags & ML_NOCLIMB)
-					lines[i].args[3] |= TMFC_NOSHADE;
+					lines[i].args[4] |= TMFC_NOSHADE;
 			}
 
 			if (lines[i].special % 2 == 1)
-				lines[i].args[3] |= TMFC_NORETURN;
+				lines[i].args[4] |= TMFC_NORETURN;
 			if (lines[i].special == 176 || lines[i].special == 177 || lines[i].special == 180)
-				lines[i].args[3] |= TMFC_AIRBOB;
+				lines[i].args[4] |= TMFC_AIRBOB;
 			if (lines[i].special >= 176 && lines[i].special <= 179)
-				lines[i].args[3] |= TMFC_FLOATBOB;
+				lines[i].args[4] |= TMFC_FLOATBOB;
 			if (lines[i].flags & ML_EFFECT6)
-				lines[i].args[3] |= TMFC_SPLAT;
+				lines[i].args[4] |= TMFC_SPLAT;
 
 			if (lines[i].flags & ML_EFFECT1)
-				lines[i].args[2] |= TMFT_DONTBLOCKOTHERS;
+				lines[i].args[3] |= TMFT_DONTBLOCKOTHERS;
 			if (lines[i].flags & ML_EFFECT2)
-				lines[i].args[2] |= TMFT_DONTBLOCKPLAYER;
+				lines[i].args[3] |= TMFT_DONTBLOCKPLAYER;
 
 			lines[i].special = 170;
 			break;
@@ -3508,44 +3539,41 @@ static void P_ConvertBinaryMap(void)
 			//Translucency
 			if (lines[i].special == 192 || lines[i].special == 195)
 			{
-				if (sides[lines[i].sidenum[0]].toptexture > 0)
-					lines[i].args[1] = sides[lines[i].sidenum[0]].toptexture;
-				else
-					lines[i].args[1] = 128;
+				P_SetBinaryFOFAlpha(&lines[i]);
 
 				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
 				if (lines[i].args[1] == 256)
-					lines[i].args[2] |= TMFA_SPLAT;
+					lines[i].args[3] |= TMFA_SPLAT;
 			}
 			else
 				lines[i].args[1] = 255;
 
 			//Appearance
 			if (lines[i].special == 193)
-				lines[i].args[2] |= TMFA_NOPLANES|TMFA_NOSIDES;
+				lines[i].args[3] |= TMFA_NOPLANES|TMFA_NOSIDES;
 			if (lines[i].special >= 194)
-				lines[i].args[2] |= TMFA_INSIDES;
+				lines[i].args[3] |= TMFA_INSIDES;
 			if (lines[i].special != 190 && (lines[i].special <= 193 || lines[i].flags & ML_NOCLIMB))
-				lines[i].args[2] |= TMFA_NOSHADE;
+				lines[i].args[3] |= TMFA_NOSHADE;
 			if (lines[i].flags & ML_EFFECT6)
-				lines[i].args[2] |= TMFA_SPLAT;
+				lines[i].args[3] |= TMFA_SPLAT;
 
 			//Tangibility
 			if (lines[i].flags & ML_EFFECT1)
-				lines[i].args[3] |= TMFT_DONTBLOCKOTHERS;
+				lines[i].args[4] |= TMFT_DONTBLOCKOTHERS;
 			if (lines[i].flags & ML_EFFECT2)
-				lines[i].args[3] |= TMFT_DONTBLOCKPLAYER;
+				lines[i].args[4] |= TMFT_DONTBLOCKPLAYER;
 			if (lines[i].special >= 194)
-				lines[i].args[3] |= TMFT_INTANGIBLEBOTTOM;
+				lines[i].args[4] |= TMFT_INTANGIBLEBOTTOM;
 
 			//Speed
-			lines[i].args[4] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+			lines[i].args[5] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
 
 			//Flags
 			if (lines[i].flags & ML_BLOCKMONSTERS)
-				lines[i].args[5] |= TMFR_REVERSE;
+				lines[i].args[6] |= TMFR_REVERSE;
 			if (lines[i].flags & ML_BLOCKMONSTERS)
-				lines[i].args[5] |= TMFR_SPINDASH;
+				lines[i].args[6] |= TMFR_SPINDASH;
 
 			lines[i].special = 190;
 			break;
@@ -3568,27 +3596,24 @@ static void P_ConvertBinaryMap(void)
 			//Alpha
 			if (lines[i].special == 221)
 			{
-				if (sides[lines[i].sidenum[0]].toptexture > 0)
-					lines[i].args[1] = sides[lines[i].sidenum[0]].toptexture;
-				else
-					lines[i].args[1] = 128;
+				P_SetBinaryFOFAlpha(&lines[i]);
 
 				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
 				if (lines[i].args[1] == 256)
-					lines[i].args[2] |= TMFA_SPLAT;
+					lines[i].args[3] |= TMFA_SPLAT;
 			}
 			else
 				lines[i].args[1] = 255;
 
 			//Appearance
 			if (lines[i].special == 222)
-				lines[i].args[2] |= TMFA_NOPLANES;
+				lines[i].args[3] |= TMFA_NOPLANES;
 			if (lines[i].special == 221)
-				lines[i].args[2] |= TMFA_INSIDES;
+				lines[i].args[3] |= TMFA_INSIDES;
 			if (lines[i].special != 220 && !(lines[i].flags & ML_NOCLIMB))
-				lines[i].args[2] |= TMFA_NOSHADE;
+				lines[i].args[3] |= TMFA_NOSHADE;
 			if (lines[i].flags & ML_EFFECT6)
-				lines[i].args[2] |= TMFA_SPLAT;
+				lines[i].args[3] |= TMFA_SPLAT;
 
 			lines[i].special = 220;
             break;
@@ -3629,40 +3654,37 @@ static void P_ConvertBinaryMap(void)
 			//Alpha
 			if (lines[i].special == 253 || lines[i].special == 256)
 			{
-				if (sides[lines[i].sidenum[0]].toptexture > 0)
-					lines[i].args[1] = sides[lines[i].sidenum[0]].toptexture;
-				else
-					lines[i].args[1] = 128;
+				P_SetBinaryFOFAlpha(&lines[i]);
 
 				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
 				if (lines[i].args[1] == 256)
-					lines[i].args[3] |= TMFB_SPLAT;
+					lines[i].args[4] |= TMFB_SPLAT;
 			}
 			else
 				lines[i].args[1] = 255;
 
 			//Bustable type
 			if (lines[i].special <= 253)
-				lines[i].args[2] = TMFB_TOUCH;
+				lines[i].args[3] = TMFB_TOUCH;
 			else if (lines[i].special >= 255)
-				lines[i].args[2] = TMFB_SPIN;
+				lines[i].args[3] = TMFB_SPIN;
 			else if (lines[i].flags & ML_NOCLIMB)
-				lines[i].args[2] = TMFB_STRONG;
+				lines[i].args[3] = TMFB_STRONG;
 			else
-				lines[i].args[2] = TMFB_REGULAR;
+				lines[i].args[3] = TMFB_REGULAR;
 
 			//Flags
 			if (lines[i].flags & ML_EFFECT4)
-				lines[i].args[3] |= TMFB_PUSHABLES;
+				lines[i].args[4] |= TMFB_PUSHABLES;
 			if (lines[i].flags & ML_EFFECT5)
 			{
-				lines[i].args[3] |= TMFB_EXECUTOR;
-				lines[i].args[4] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
+				lines[i].args[4] |= TMFB_EXECUTOR;
+				lines[i].args[5] = P_AproxDistance(lines[i].dx, lines[i].dy) >> FRACBITS;
 			}
 			if (lines[i].special == 252 && lines[i].flags & ML_NOCLIMB)
-				lines[i].args[3] |= TMFB_ONLYBOTTOM;
+				lines[i].args[4] |= TMFB_ONLYBOTTOM;
 			if (lines[i].flags & ML_EFFECT6)
-				lines[i].args[3] |= TMFB_SPLAT;
+				lines[i].args[4] |= TMFB_SPLAT;
 
 			lines[i].special = 254;
 			break;
@@ -3677,17 +3699,14 @@ static void P_ConvertBinaryMap(void)
 			lines[i].args[0] = tag;
 
 			//Alpha
-			if (sides[lines[i].sidenum[0]].toptexture > 0)
-				lines[i].args[1] = sides[lines[i].sidenum[0]].toptexture;
-			else
-				lines[i].args[1] = 128;
+			P_SetBinaryFOFAlpha(&lines[i]);
 
 			//Flags
 			if (lines[i].flags & ML_EFFECT1)
-				lines[i].args[2] |= TMFL_NOBOSSES;
+				lines[i].args[3] |= TMFL_NOBOSSES;
 			//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
 			if (lines[i].flags & ML_EFFECT6 || lines[i].args[1] == 256)
-				lines[i].args[2] |= TMFL_SPLAT;
+				lines[i].args[3] |= TMFL_SPLAT;
 
 			break;
 		case 259: //Custom FOF
@@ -3695,20 +3714,17 @@ static void P_ConvertBinaryMap(void)
 				I_Error("Custom FOF (tag %d) found without a linedef back side!", tag);
 
 			lines[i].args[0] = tag;
-			lines[i].args[2] = sides[lines[i].sidenum[1]].toptexture;
+			lines[i].args[3] = sides[lines[i].sidenum[1]].toptexture;
 			if (lines[i].flags & ML_EFFECT6)
-				lines[i].args[2] |= FF_SPLAT;
-			lines[i].args[3] = sides[lines[i].sidenum[1]].midtexture;
-			if (lines[i].args[2] & FF_TRANSLUCENT)
+				lines[i].args[3] |= FF_SPLAT;
+			lines[i].args[4] = sides[lines[i].sidenum[1]].midtexture;
+			if (lines[i].args[3] & FF_TRANSLUCENT)
 			{
-				if (sides[lines[i].sidenum[0]].toptexture > 0)
-					lines[i].args[1] = sides[lines[i].sidenum[0]].toptexture;
-				else
-					lines[i].args[1] = 128;
+				P_SetBinaryFOFAlpha(&lines[i]);
 
 				//Replicate old hack: Translucent FOFs set to full opacity cut cyan pixels
 				if (lines[i].args[1] == 256)
-					lines[i].args[2] |= FF_SPLAT;
+					lines[i].args[3] |= FF_SPLAT;
 			}
 			else
 				lines[i].args[1] = 255;
@@ -4828,20 +4844,31 @@ static void P_ConvertBinaryMap(void)
 				lines[i].args[4] |= TMSC_BACKTOFRONTCEILING;
 			lines[i].special = 720;
 			break;
-
-		case 900: //Translucent wall (10%)
-		case 901: //Translucent wall (20%)
-		case 902: //Translucent wall (30%)
-		case 903: //Translucent wall (40%)
-		case 904: //Translucent wall (50%)
-		case 905: //Translucent wall (60%)
-		case 906: //Translucent wall (70%)
-		case 907: //Translucent wall (80%)
-		case 908: //Translucent wall (90%)
-			lines[i].alpha = ((909 - lines[i].special) << FRACBITS)/10;
+		case 909: //Fog wall
+			lines[i].blendmode = AST_FOG;
 			break;
 		}
 
+		// Set alpha for translucent walls
+		if (lines[i].special >= 900 && lines[i].special < 909)
+			lines[i].alpha = ((909 - lines[i].special) << FRACBITS)/10;
+
+		// Set alpha for additive/subtractive/reverse subtractive walls
+		if (lines[i].special >= 910 && lines[i].special <= 939)
+			lines[i].alpha = ((10 - lines[i].special % 10) << FRACBITS)/10;
+
+		if (lines[i].special >= 910 && lines[i].special <= 919) // additive
+			lines[i].blendmode = AST_ADD;
+
+		if (lines[i].special >= 920 && lines[i].special <= 929) // subtractive
+			lines[i].blendmode = AST_SUBTRACT;
+
+		if (lines[i].special >= 930 && lines[i].special <= 939) // reverse subtractive
+			lines[i].blendmode = AST_REVERSESUBTRACT;
+
+		if (lines[i].special == 940) // modulate
+			lines[i].blendmode = AST_MODULATE;
+
 		//Linedef executor delay
 		if (lines[i].special >= 400 && lines[i].special < 500)
 		{
diff --git a/src/p_spec.c b/src/p_spec.c
index c548a32cac145da04da05ad483dce85679e1f1ba..597b5370f22a84d3d4fd244a0c7804feeac8bfd3 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -116,7 +116,7 @@ static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, ext
 static void P_AddBlockThinker(sector_t *sec, line_t *sourceline);
 static void P_AddFloatThinker(sector_t *sec, UINT16 tag, line_t *sourceline);
 //static void P_AddBridgeThinker(line_t *sourceline, sector_t *sec);
-static void P_AddFakeFloorsByLine(size_t line, INT32 alpha, ffloortype_e ffloorflags, thinkerlist_t *secthinkers);
+static void P_AddFakeFloorsByLine(size_t line, INT32 alpha, UINT8 blendmode, ffloortype_e ffloorflags, thinkerlist_t *secthinkers);
 static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec);
 static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer);
 static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee, UINT8 reverse);
@@ -5320,12 +5320,13 @@ static inline void P_AddFFloorToList(sector_t *sec, ffloor_t *fflr)
   * \param sec2        Control sector.
   * \param master      Control linedef.
   * \param alpha       Alpha value (0-255).
+  * \param blendmode   Blending mode.
   * \param flags       Options affecting this 3Dfloor.
   * \param secthinkers List of relevant thinkers sorted by sector. May be NULL.
   * \return Pointer to the new 3Dfloor.
   * \sa P_AddFFloor, P_AddFakeFloorsByLine, P_SpawnSpecials
   */
-static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, INT32 alpha, ffloortype_e flags, thinkerlist_t *secthinkers)
+static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, INT32 alpha, UINT8 blendmode, ffloortype_e flags, thinkerlist_t *secthinkers)
 {
 	ffloor_t *fflr;
 	thinker_t *th;
@@ -5462,6 +5463,26 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, I
 	}
 	fflr->spawnalpha = fflr->alpha; // save for netgames
 
+	switch (blendmode)
+	{
+		case TMB_TRANSLUCENT:
+		default:
+			fflr->blend = AST_COPY;
+			break;
+		case TMB_ADD:
+			fflr->blend = AST_ADD;
+			break;
+		case TMB_SUBTRACT:
+			fflr->blend = AST_SUBTRACT;
+			break;
+		case TMB_REVERSESUBTRACT:
+			fflr->blend = AST_REVERSESUBTRACT;
+			break;
+		case TMB_MODULATE:
+			fflr->blend = AST_MODULATE;
+			break;
+	}
+
 	if (flags & FF_QUICKSAND)
 		CheckForQuicksand = true;
 
@@ -6315,109 +6336,109 @@ void P_SpawnSpecials(boolean fromnetsave)
 				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL;
 
 				//Appearance settings
-				if (lines[i].args[2] & TMFA_NOPLANES)
+				if (lines[i].args[3] & TMFA_NOPLANES)
 					ffloorflags &= ~FF_RENDERPLANES;
-				if (lines[i].args[2] & TMFA_NOSIDES)
+				if (lines[i].args[3] & TMFA_NOSIDES)
 					ffloorflags &= ~FF_RENDERSIDES;
-				if (lines[i].args[2] & TMFA_INSIDES)
+				if (lines[i].args[3] & TMFA_INSIDES)
 				{
 					if (ffloorflags & FF_RENDERPLANES)
 						ffloorflags |= FF_BOTHPLANES;
 					if (ffloorflags & FF_RENDERSIDES)
 						ffloorflags |= FF_ALLSIDES;
 				}
-				if (lines[i].args[2] & TMFA_ONLYINSIDES)
+				if (lines[i].args[3] & TMFA_ONLYINSIDES)
 				{
 					if (ffloorflags & FF_RENDERPLANES)
 						ffloorflags |= FF_INVERTPLANES;
 					if (ffloorflags & FF_RENDERSIDES)
 						ffloorflags |= FF_INVERTSIDES;
 				}
-				if (lines[i].args[2] & TMFA_NOSHADE)
+				if (lines[i].args[3] & TMFA_NOSHADE)
 					ffloorflags |= FF_NOSHADE;
-				if (lines[i].args[2] & TMFA_SPLAT)
+				if (lines[i].args[3] & TMFA_SPLAT)
 					ffloorflags |= FF_SPLAT;
 
 				//Tangibility settings
-				if (lines[i].args[3] & TMFT_INTANGIBLETOP)
+				if (lines[i].args[4] & TMFT_INTANGIBLETOP)
 					ffloorflags |= FF_REVERSEPLATFORM;
-				if (lines[i].args[3] & TMFT_INTANGIBLEBOTTOM)
+				if (lines[i].args[4] & TMFT_INTANGIBLEBOTTOM)
 					ffloorflags |= FF_PLATFORM;
-				if (lines[i].args[3] & TMFT_DONTBLOCKPLAYER)
+				if (lines[i].args[4] & TMFT_DONTBLOCKPLAYER)
 					ffloorflags &= ~FF_BLOCKPLAYER;
-				if (lines[i].args[3] & TMFT_DONTBLOCKOTHERS)
+				if (lines[i].args[4] & TMFT_DONTBLOCKOTHERS)
 					ffloorflags &= ~FF_BLOCKOTHERS;
 
 				//Cutting options
 				if (ffloorflags & FF_RENDERALL)
 				{
 					//If translucent or player can enter it, cut inner walls
-					if ((lines[i].args[1] < 255) || (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE))
+					if ((lines[i].args[1] < 255) || (lines[i].args[4] & TMFT_VISIBLEFROMINSIDE))
 						ffloorflags |= FF_CUTEXTRA|FF_EXTRA;
 					else
 						ffloorflags |= FF_CUTLEVEL;
 				}
 
-				P_AddFakeFloorsByLine(i, lines[i].args[1], ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 				break;
 
 			case 120: // FOF (water)
 				ffloorflags = FF_EXISTS|FF_RENDERPLANES|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
-				if (!(lines[i].args[2] & TMFW_NOSIDES))
+				if (!(lines[i].args[3] & TMFW_NOSIDES))
 					ffloorflags |= FF_RENDERSIDES|FF_ALLSIDES;
-				if (lines[i].args[2] & TMFW_DOUBLESHADOW)
+				if (lines[i].args[3] & TMFW_DOUBLESHADOW)
 					ffloorflags |= FF_DOUBLESHADOW;
-				if (lines[i].args[2] & TMFW_COLORMAPONLY)
+				if (lines[i].args[3] & TMFW_COLORMAPONLY)
 					ffloorflags |= FF_COLORMAPONLY;
-				if (!(lines[i].args[2] & TMFW_NORIPPLE))
+				if (!(lines[i].args[3] & TMFW_NORIPPLE))
 					ffloorflags |= FF_RIPPLE;
-				if (lines[i].args[2] & TMFW_GOOWATER)
+				if (lines[i].args[3] & TMFW_GOOWATER)
 					ffloorflags |= FF_GOOWATER;
-				if (lines[i].args[2] & TMFW_SPLAT)
+				if (lines[i].args[3] & TMFW_SPLAT)
 					ffloorflags |= FF_SPLAT;
-				P_AddFakeFloorsByLine(i, lines[i].args[1], ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 				break;
 
 			case 150: // FOF (Air bobbing)
-				P_AddFakeFloorsByLine(i, 0xff, FF_EXISTS|FF_SOLID|FF_RENDERALL, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, FF_EXISTS|FF_SOLID|FF_RENDERALL, secthinkers);
 				P_AddAirbob(lines[i].frontsector, lines[i].args[0], lines[i].args[1] << FRACBITS, !!(lines[i].args[2] & TMFB_REVERSE), !!(lines[i].args[2] & TMFB_SPINDASH), !!(lines[i].args[2] & TMFB_DYNAMIC));
 				break;
 
 			case 160: // FOF (Water bobbing)
-				P_AddFakeFloorsByLine(i, 0xff, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB, secthinkers);
 				break;
 
 			case 170: // FOF (Crumbling)
 				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CRUMBLE;
 
 				//Tangibility settings
-				if (lines[i].args[2] & TMFT_INTANGIBLETOP)
+				if (lines[i].args[3] & TMFT_INTANGIBLETOP)
 					ffloorflags |= FF_REVERSEPLATFORM;
-				if (lines[i].args[2] & TMFT_INTANGIBLEBOTTOM)
+				if (lines[i].args[3] & TMFT_INTANGIBLEBOTTOM)
 					ffloorflags |= FF_PLATFORM;
-				if (lines[i].args[2] & TMFT_DONTBLOCKPLAYER)
+				if (lines[i].args[3] & TMFT_DONTBLOCKPLAYER)
 					ffloorflags &= ~FF_BLOCKPLAYER;
-				if (lines[i].args[2] & TMFT_DONTBLOCKOTHERS)
+				if (lines[i].args[3] & TMFT_DONTBLOCKOTHERS)
 					ffloorflags &= ~FF_BLOCKOTHERS;
 
 				//Flags
-				if (lines[i].args[3] & TMFC_NOSHADE)
+				if (lines[i].args[4] & TMFC_NOSHADE)
 					ffloorflags |= FF_NOSHADE;
-				if (lines[i].args[3] & TMFC_NORETURN)
+				if (lines[i].args[4] & TMFC_NORETURN)
 					ffloorflags |= FF_NORETURN;
-				if (lines[i].args[3] & TMFC_FLOATBOB)
+				if (lines[i].args[4] & TMFC_FLOATBOB)
 					ffloorflags |= FF_FLOATBOB;
-				if (lines[i].args[3] & TMFC_SPLAT)
+				if (lines[i].args[4] & TMFC_SPLAT)
 					ffloorflags |= FF_SPLAT;
 
 				//If translucent or player can enter it, cut inner walls
-				if (lines[i].args[1] < 0xff || (lines[i].args[2] & TMFT_VISIBLEFROMINSIDE))
+				if (lines[i].args[1] < 0xff || (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE))
 					ffloorflags |= FF_CUTEXTRA|FF_EXTRA;
 				else
 					ffloorflags |= FF_CUTLEVEL;
 
 				//If player can enter it, render insides
-				if (lines[i].args[2] & TMFT_VISIBLEFROMINSIDE)
+				if (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE)
 				{
 					if (ffloorflags & FF_RENDERPLANES)
 						ffloorflags |= FF_BOTHPLANES;
@@ -6425,8 +6446,8 @@ void P_SpawnSpecials(boolean fromnetsave)
 						ffloorflags |= FF_ALLSIDES;
 				}
 
-				P_AddFakeFloorsByLine(i, lines[i].args[1], ffloorflags, secthinkers);
-				if (lines[i].args[3] & TMFC_AIRBOB)
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
+				if (lines[i].args[4] & TMFC_AIRBOB)
 					P_AddAirbob(lines[i].frontsector, lines[i].args[0], 16*FRACUNIT, false, false, false);
 				break;
 
@@ -6438,58 +6459,58 @@ void P_SpawnSpecials(boolean fromnetsave)
 				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL;
 
 				//Appearance settings
-				if (lines[i].args[2] & TMFA_NOPLANES)
+				if (lines[i].args[3] & TMFA_NOPLANES)
 					ffloorflags &= ~FF_RENDERPLANES;
-				if (lines[i].args[2] & TMFA_NOSIDES)
+				if (lines[i].args[3] & TMFA_NOSIDES)
 					ffloorflags &= ~FF_RENDERSIDES;
-				if (lines[i].args[2] & TMFA_INSIDES)
+				if (lines[i].args[3] & TMFA_INSIDES)
 				{
 					if (ffloorflags & FF_RENDERPLANES)
 						ffloorflags |= FF_BOTHPLANES;
 					if (ffloorflags & FF_RENDERSIDES)
 						ffloorflags |= FF_ALLSIDES;
 				}
-				if (lines[i].args[2] & TMFA_ONLYINSIDES)
+				if (lines[i].args[3] & TMFA_ONLYINSIDES)
 				{
 					if (ffloorflags & FF_RENDERPLANES)
 						ffloorflags |= FF_INVERTPLANES;
 					if (ffloorflags & FF_RENDERSIDES)
 						ffloorflags |= FF_INVERTSIDES;
 				}
-				if (lines[i].args[2] & TMFA_NOSHADE)
+				if (lines[i].args[3] & TMFA_NOSHADE)
 					ffloorflags |= FF_NOSHADE;
-				if (lines[i].args[2] & TMFA_SPLAT)
+				if (lines[i].args[3] & TMFA_SPLAT)
 					ffloorflags |= FF_SPLAT;
 
 				//Tangibility settings
-				if (lines[i].args[3] & TMFT_INTANGIBLETOP)
+				if (lines[i].args[4] & TMFT_INTANGIBLETOP)
 					ffloorflags |= FF_REVERSEPLATFORM;
-				if (lines[i].args[3] & TMFT_INTANGIBLEBOTTOM)
+				if (lines[i].args[4] & TMFT_INTANGIBLEBOTTOM)
 					ffloorflags |= FF_PLATFORM;
-				if (lines[i].args[3] & TMFT_DONTBLOCKPLAYER)
+				if (lines[i].args[4] & TMFT_DONTBLOCKPLAYER)
 					ffloorflags &= ~FF_BLOCKPLAYER;
-				if (lines[i].args[3] & TMFT_DONTBLOCKOTHERS)
+				if (lines[i].args[4] & TMFT_DONTBLOCKOTHERS)
 					ffloorflags &= ~FF_BLOCKOTHERS;
 
 				//Cutting options
 				if (ffloorflags & FF_RENDERALL)
 				{
 					//If translucent or player can enter it, cut inner walls
-					if ((lines[i].args[1] < 255) || (lines[i].args[3] & TMFT_VISIBLEFROMINSIDE))
+					if ((lines[i].args[1] < 255) || (lines[i].args[4] & TMFT_VISIBLEFROMINSIDE))
 						ffloorflags |= FF_CUTEXTRA|FF_EXTRA;
 					else
 						ffloorflags |= FF_CUTLEVEL;
 				}
 
-				P_AddFakeFloorsByLine(i, lines[i].args[1], ffloorflags, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, lines[i].args[0], lines[i].args[4] << FRACBITS, ceilingtop, ceilingbottom, !!(lines[i].args[5] & TMFR_REVERSE), !!(lines[i].args[5] & TMFR_SPINDASH));
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
+				P_AddRaiseThinker(lines[i].frontsector, lines[i].args[0], lines[i].args[5] << FRACBITS, ceilingtop, ceilingbottom, !!(lines[i].args[6] & TMFR_REVERSE), !!(lines[i].args[6] & TMFR_SPINDASH));
 				break;
 			}
 			case 200: // Light block
 				ffloorflags = FF_EXISTS|FF_CUTSPRITES;
 				if (!lines[i].args[1])
 					ffloorflags |= FF_DOUBLESHADOW;
-				P_AddFakeFloorsByLine(i, 0xff, ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, ffloorflags, secthinkers);
 				break;
 
 			case 202: // Fog
@@ -6498,41 +6519,41 @@ void P_SpawnSpecials(boolean fromnetsave)
 				// SoM: Because it's fog, check for an extra colormap and set the fog flag...
 				if (sectors[sec].extra_colormap)
 					sectors[sec].extra_colormap->flags = CMF_FOG;
-				P_AddFakeFloorsByLine(i, 0xff, ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, ffloorflags, secthinkers);
 				break;
 
 			case 220: //Intangible
 				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES;
 
 				//Appearance settings
-				if (lines[i].args[2] & TMFA_NOPLANES)
+				if (lines[i].args[3] & TMFA_NOPLANES)
 					ffloorflags &= ~FF_RENDERPLANES;
-				if (lines[i].args[2] & TMFA_NOSIDES)
+				if (lines[i].args[3] & TMFA_NOSIDES)
 					ffloorflags &= ~FF_RENDERSIDES;
-				if (!(lines[i].args[2] & TMFA_INSIDES))
+				if (!(lines[i].args[3] & TMFA_INSIDES))
 				{
 					if (ffloorflags & FF_RENDERPLANES)
 						ffloorflags |= FF_BOTHPLANES;
 					if (ffloorflags & FF_RENDERSIDES)
 						ffloorflags |= FF_ALLSIDES;
 				}
-				if (lines[i].args[2] & TMFA_ONLYINSIDES)
+				if (lines[i].args[3] & TMFA_ONLYINSIDES)
 				{
 					if (ffloorflags & FF_RENDERPLANES)
 						ffloorflags |= FF_INVERTPLANES;
 					if (ffloorflags & FF_RENDERSIDES)
 						ffloorflags |= FF_INVERTSIDES;
 				}
-				if (lines[i].args[2] & TMFA_NOSHADE)
+				if (lines[i].args[3] & TMFA_NOSHADE)
 					ffloorflags |= FF_NOSHADE;
-				if (lines[i].args[2] & TMFA_SPLAT)
+				if (lines[i].args[3] & TMFA_SPLAT)
 					ffloorflags |= FF_SPLAT;
 
-				P_AddFakeFloorsByLine(i, lines[i].args[1], ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 				break;
 
 			case 223: // FOF (intangible, invisible) - for combining specials in a sector
-				P_AddFakeFloorsByLine(i, 0xff, FF_EXISTS|FF_NOSHADE, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, FF_EXISTS|FF_NOSHADE, secthinkers);
 				break;
 
 			case 250: // Mario Block
@@ -6542,14 +6563,14 @@ void P_SpawnSpecials(boolean fromnetsave)
 				if (lines[i].args[1] & TMFM_INVISIBLE)
 					ffloorflags &= ~(FF_SOLID|FF_RENDERALL|FF_CUTLEVEL);
 
-				P_AddFakeFloorsByLine(i, 0xff, ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, ffloorflags, secthinkers);
 				break;
 
 			case 251: // A THWOMP!
 			{
 				UINT16 sound = (lines[i].stringargs[0]) ? get_number(lines[i].stringargs[0]) : sfx_thwomp;
 				P_AddThwompThinker(lines[i].frontsector, &lines[i], lines[i].args[1] << (FRACBITS - 3), lines[i].args[2] << (FRACBITS - 3), sound);
-				P_AddFakeFloorsByLine(i, 0xff, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
+				P_AddFakeFloorsByLine(i, 0xff, TMB_TRANSLUCENT, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
 				break;
 			}
 
@@ -6561,7 +6582,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				ffloorflags = FF_EXISTS|FF_BLOCKOTHERS|FF_RENDERALL|FF_BUSTUP;
 
 				//Bustable type
-				switch (lines[i].args[2])
+				switch (lines[i].args[3])
 				{
 					case TMFB_TOUCH:
 						busttype = BT_TOUCH;
@@ -6578,13 +6599,13 @@ void P_SpawnSpecials(boolean fromnetsave)
 				}
 
 				//Flags
-				if (lines[i].args[3] & TMFB_PUSHABLES)
+				if (lines[i].args[4] & TMFB_PUSHABLES)
 					bustflags |= FB_PUSHABLES;
-				if (lines[i].args[3] & TMFB_EXECUTOR)
+				if (lines[i].args[4] & TMFB_EXECUTOR)
 					bustflags |= FB_EXECUTOR;
-				if (lines[i].args[3] & TMFB_ONLYBOTTOM)
+				if (lines[i].args[4] & TMFB_ONLYBOTTOM)
 					bustflags |= FB_ONLYBOTTOM;
-				if (lines[i].args[3] & TMFB_SPLAT)
+				if (lines[i].args[4] & TMFB_SPLAT)
 					ffloorflags |= FF_SPLAT;
 
 				if (busttype != BT_TOUCH || bustflags & FB_ONLYBOTTOM)
@@ -6592,12 +6613,12 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 				TAG_ITER_SECTORS(lines[i].args[0], s)
 				{
-					ffloor_t *fflr = P_AddFakeFloor(&sectors[s], lines[i].frontsector, lines + i, lines[i].args[1], ffloorflags, secthinkers);
+					ffloor_t *fflr = P_AddFakeFloor(&sectors[s], lines[i].frontsector, lines + i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 					if (!fflr)
 						continue;
 					fflr->bustflags = bustflags;
 					fflr->busttype = busttype;
-					fflr->busttag = lines[i].args[4];
+					fflr->busttag = lines[i].args[5];
 				}
 				break;
 			}
@@ -6608,7 +6629,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 				TAG_ITER_SECTORS(lines[i].args[0], s)
 				{
-					ffloor_t *fflr = P_AddFakeFloor(&sectors[s], lines[i].frontsector, lines + i, 0xff, ffloorflags, secthinkers);
+					ffloor_t *fflr = P_AddFakeFloor(&sectors[s], lines[i].frontsector, lines + i, 0xff, TMB_TRANSLUCENT, ffloorflags, secthinkers);
 					if (!fflr)
 						continue;
 					fflr->sinkspeed = abs(lines[i].args[2]) << (FRACBITS - 1);
@@ -6618,28 +6639,28 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 258: // Laser block
 				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA|FF_TRANSLUCENT;
-				P_AddLaserThinker(lines[i].args[0], lines + i, !!(lines[i].args[2] & TMFL_NOBOSSES));
-				if (lines[i].args[2] & TMFL_SPLAT)
+				P_AddLaserThinker(lines[i].args[0], lines + i, !!(lines[i].args[3] & TMFL_NOBOSSES));
+				if (lines[i].args[3] & TMFL_SPLAT)
 					ffloorflags |= FF_SPLAT;
-				P_AddFakeFloorsByLine(i, lines[i].args[1], ffloorflags, secthinkers);
+				P_AddFakeFloorsByLine(i, lines[i].args[1], lines[i].args[2], ffloorflags, secthinkers);
 				break;
 
 			case 259: // Custom FOF
 				TAG_ITER_SECTORS(lines[i].args[0], s)
 				{
-					ffloor_t *fflr = P_AddFakeFloor(&sectors[s], lines[i].frontsector, lines + i, lines[i].args[1], lines[i].args[2], secthinkers);
+					ffloor_t *fflr = P_AddFakeFloor(&sectors[s], lines[i].frontsector, lines + i, lines[i].args[1], lines[i].args[2], lines[i].args[3], secthinkers);
 					if (!fflr)
 						continue;
 					if (!udmf) // Ugly backwards compatibility stuff
 					{
-						if (lines[i].args[2] & FF_QUICKSAND)
+						if (lines[i].args[3] & FF_QUICKSAND)
 						{
 							fflr->sinkspeed = abs(lines[i].dx) >> 1;
 							fflr->friction = abs(lines[i].dy) >> 6;
 						}
-						if (lines[i].args[2] & FF_BUSTUP)
+						if (lines[i].args[3] & FF_BUSTUP)
 						{
-							switch (lines[i].args[3] % TMFB_ONLYBOTTOM)
+							switch (lines[i].args[4] % TMFB_ONLYBOTTOM)
 							{
 								case TMFB_TOUCH:
 									fflr->busttype = BT_TOUCH;
@@ -6655,7 +6676,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 									break;
 							}
 
-							if (lines[i].args[3] & TMFB_ONLYBOTTOM)
+							if (lines[i].args[4] & TMFB_ONLYBOTTOM)
 								fflr->bustflags |= FB_ONLYBOTTOM;
 							if (lines[i].flags & ML_EFFECT4)
 								fflr->bustflags |= FB_PUSHABLES;
@@ -6730,7 +6751,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 						}
 					}
 
-					P_AddFakeFloorsByLine(i, dopacity, ffloorflags, secthinkers);
+					P_AddFakeFloorsByLine(i, dopacity, TMB_TRANSLUCENT, ffloorflags, secthinkers);
 				}
 				break;
 
@@ -7102,12 +7123,13 @@ void P_SpawnSpecials(boolean fromnetsave)
   *
   * \param line        Control linedef to use.
   * \param alpha       Alpha value (0-255).
+  * \param blendmode   Blending mode.
   * \param ffloorflags 3Dfloor flags to use.
   * \param secthkiners Lists of thinkers sorted by sector. May be NULL.
   * \sa P_SpawnSpecials, P_AddFakeFloor
   * \author Graue <graue@oceanbase.org>
   */
-static void P_AddFakeFloorsByLine(size_t line, INT32 alpha, ffloortype_e ffloorflags, thinkerlist_t *secthinkers)
+static void P_AddFakeFloorsByLine(size_t line, INT32 alpha, UINT8 blendmode, ffloortype_e ffloorflags, thinkerlist_t *secthinkers)
 {
 	INT32 s;
 	mtag_t tag = lines[line].args[0];
@@ -7115,7 +7137,7 @@ static void P_AddFakeFloorsByLine(size_t line, INT32 alpha, ffloortype_e ffloorf
 
 	line_t* li = lines + line;
 	TAG_ITER_SECTORS(tag, s)
-		P_AddFakeFloor(&sectors[s], &sectors[sec], li, alpha, ffloorflags, secthinkers);
+		P_AddFakeFloor(&sectors[s], &sectors[sec], li, alpha, blendmode, ffloorflags, secthinkers);
 }
 
 /*
diff --git a/src/p_spec.h b/src/p_spec.h
index 054f2daf5094a015008b4490ec05be1b20d486d8..7ee803dafad5b98116bf869731eee19156e7d41b 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -331,6 +331,15 @@ typedef enum
 	TMPF_NONEXCLUSIVE = 1<<1,
 } textmappusherflags_t;
 
+typedef enum
+{
+	TMB_TRANSLUCENT     = 0,
+	TMB_ADD             = 1,
+	TMB_SUBTRACT        = 2,
+	TMB_REVERSESUBTRACT = 3,
+	TMB_MODULATE        = 4,
+} textmapblendmodes_t;
+
 // GETSECSPECIAL (specialval, section)
 //
 // Pulls out the special # from a particular section.
diff --git a/src/p_user.c b/src/p_user.c
index 4858c6ddd7989fcd866b553cb2285367217de6ca..f6bbbf0ac6b5a22eabc67b0ace3fd58748711214 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1189,8 +1189,8 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
 	if (!player)
 		return;
 
-	if (player->bot)
-		player = &players[consoleplayer];
+	if ((player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN) && player->botleader)
+		player = player->botleader;
 
 	if (!player->mo)
 		return;
@@ -9545,7 +9545,7 @@ consvar_t cv_cam_still = CVAR_INIT ("cam_still", "Off", 0, CV_OnOff, NULL);
 consvar_t cv_cam_speed = CVAR_INIT ("cam_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL);
 consvar_t cv_cam_rotate = CVAR_INIT ("cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange);
 consvar_t cv_cam_rotspeed = CVAR_INIT ("cam_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL);
-consvar_t cv_cam_turnmultiplier = CVAR_INIT ("cam_turnmultiplier", "1.0", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL);
+consvar_t cv_cam_turnmultiplier = CVAR_INIT ("cam_turnmultiplier", "0.75", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL);
 consvar_t cv_cam_orbit = CVAR_INIT ("cam_orbit", "Off", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_cam_adjust = CVAR_INIT ("cam_adjust", "On", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_cam2_dist = CVAR_INIT ("cam2_curdist", "160", CV_FLOAT, NULL, NULL);
@@ -9554,30 +9554,30 @@ consvar_t cv_cam2_still = CVAR_INIT ("cam2_still", "Off", 0, CV_OnOff, NULL);
 consvar_t cv_cam2_speed = CVAR_INIT ("cam2_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL);
 consvar_t cv_cam2_rotate = CVAR_INIT ("cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange);
 consvar_t cv_cam2_rotspeed = CVAR_INIT ("cam2_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL);
-consvar_t cv_cam2_turnmultiplier = CVAR_INIT ("cam2_turnmultiplier", "1.0", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL);
+consvar_t cv_cam2_turnmultiplier = CVAR_INIT ("cam2_turnmultiplier", "0.75", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL);
 consvar_t cv_cam2_orbit = CVAR_INIT ("cam2_orbit", "Off", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_cam2_adjust = CVAR_INIT ("cam2_adjust", "On", CV_SAVE, CV_OnOff, NULL);
 
 // [standard vs simple][p1 or p2]
 consvar_t cv_cam_savedist[2][2] = {
 	{ // standard
-		CVAR_INIT ("cam_dist", "160", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
-		CVAR_INIT ("cam2_dist", "160", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
+		CVAR_INIT ("cam_dist", "192", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
+		CVAR_INIT ("cam2_dist", "192", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
 	},
 	{ // simple
-		CVAR_INIT ("cam_simpledist", "224", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
-		CVAR_INIT ("cam2_simpledist", "224", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
+		CVAR_INIT ("cam_simpledist", "256", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
+		CVAR_INIT ("cam2_simpledist", "256", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
 
 	}
 };
 consvar_t cv_cam_saveheight[2][2] = {
 	{ // standard
-		CVAR_INIT ("cam_height", "25", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
-		CVAR_INIT ("cam2_height", "25", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
+		CVAR_INIT ("cam_height", "40", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
+		CVAR_INIT ("cam2_height", "40", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
 	},
 	{ // simple
-		CVAR_INIT ("cam_simpleheight", "48", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
-		CVAR_INIT ("cam2_simpleheight", "48", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
+		CVAR_INIT ("cam_simpleheight", "60", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
+		CVAR_INIT ("cam2_simpleheight", "60", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
 
 	}
 };
@@ -9759,17 +9759,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	if (P_CameraThinker(player, thiscam, resetcalled))
 		return true;
 
-	if (tutorialmode)
-	{
-		// force defaults because we have a camera look section
-		camspeed = (INT32)(atof(cv_cam_speed.defaultvalue) * FRACUNIT);
-		camstill = (!stricmp(cv_cam_still.defaultvalue, "off")) ? false : true;
-		camorbit = (!stricmp(cv_cam_orbit.defaultvalue, "off")) ? false : true;
-		camrotate = atoi(cv_cam_rotate.defaultvalue);
-		camdist = FixedMul((INT32)(atof(cv_cam_dist.defaultvalue) * FRACUNIT), mo->scale);
-		camheight = FixedMul((INT32)(atof(cv_cam_height.defaultvalue) * FRACUNIT), mo->scale);
-	}
-	else if (thiscam == &camera)
+	if (thiscam == &camera)
 	{
 		camspeed = cv_cam_speed.value;
 		camstill = cv_cam_still.value;
@@ -11509,7 +11499,7 @@ void P_PlayerThink(player_t *player)
 
 			for (i = 0; i < MAXPLAYERS; i++)
 			{
-				if (!playeringame[i] || players[i].spectator || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN)
+				if (!playeringame[i] || players[i].spectator || players[i].bot)
 					continue;
 				if (players[i].lives <= 0)
 					continue;
@@ -11540,8 +11530,8 @@ void P_PlayerThink(player_t *player)
 			INT32 i, total = 0, exiting = 0;
 
 			for (i = 0; i < MAXPLAYERS; i++)
-			{ 
-				if (!playeringame[i] || players[i].spectator || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN)
+			{
+				if (!playeringame[i] || players[i].spectator || players[i].bot)
 					continue;
 				if (players[i].quittime > 30 * TICRATE)
 					continue;
@@ -12127,7 +12117,7 @@ void P_PlayerThink(player_t *player)
 		player->losstime--;
 
 	// Flash player after being hit.
-	if (player->powers[pw_flashing] > 0 && player->powers[pw_flashing] < flashingtics && (leveltime & 1))
+	if (player->powers[pw_flashing] > 0 && player->powers[pw_flashing] < flashingtics && (leveltime & 1) && player->playerstate == PST_LIVE)
 		player->mo->flags2 |= MF2_DONTDRAW;
 	else
 		player->mo->flags2 &= ~MF2_DONTDRAW;
@@ -12481,7 +12471,7 @@ void P_PlayerAfterThink(player_t *player)
 					player->mo->momy = tails->momy;
 					player->mo->momz = tails->momz;
 				}
-				
+
 				if (G_CoopGametype() && tails->player && tails->player->bot != BOT_2PAI)
 				{
 					player->mo->angle = tails->angle;
@@ -12665,12 +12655,12 @@ void P_PlayerAfterThink(player_t *player)
 				if (!ptera->movefactor)
 					goto dropoff;
 
-				if (ptera->cusval >= 50)
+				if (ptera->cusval >= 30)
 				{
 					player->powers[pw_carry] = CR_NONE;
 					P_SetTarget(&player->mo->tracer, NULL);
 					P_KillMobj(ptera, player->mo, player->mo, 0);
-					player->mo->momz = 9*FRACUNIT;
+					P_SetObjectMomZ(player->mo, 12*FRACUNIT, false);
 					player->pflags |= PF_APPLYAUTOBRAKE|PF_JUMPED|PF_THOKKED;
 					P_SetMobjState(player->mo, S_PLAY_ROLL);
 					break;
diff --git a/src/r_data.h b/src/r_data.h
index 571fdc54f0a20e795f97704e6a2ef173bb6fdb82..7580a94ea0eeab4d446b5b671e9dc760ff69a52b 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -30,9 +30,6 @@ typedef struct
 	size_t numlumps;
 } lumplist_t;
 
-// Possible alpha types for a patch.
-enum patchalphastyle {AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY};
-
 UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha);
 UINT32 ASTBlendTexturePixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha);
 UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT8 alpha);
diff --git a/src/r_defs.h b/src/r_defs.h
index 8e17addf5abbb7e16a7c94ba0a6e5640e2dca7cc..ed6e2bdb5ba3fed3ed30b8717e405598f849abb5 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -197,6 +197,7 @@ typedef struct ffloor_s
 
 	INT32 lastlight;
 	INT32 alpha;
+	UINT8 blend; // blendmode
 	tic_t norender; // for culling
 
 	// Only relevant for FF_BUSTUP
@@ -405,7 +406,7 @@ typedef enum
 
 #define HORIZONSPECIAL 41
 
-#define NUMLINEARGS 8
+#define NUMLINEARGS 10
 #define NUMLINESTRINGARGS 2
 
 typedef struct line_s
@@ -426,6 +427,7 @@ typedef struct line_s
 	// Visual appearance: sidedefs.
 	UINT16 sidenum[2]; // sidenum[1] will be 0xffff if one-sided
 	fixed_t alpha; // translucency
+	UINT8 blendmode; // blendmode
 	INT32 executordelay;
 
 	fixed_t bbox[4]; // bounding box for the extent of the linedef
@@ -742,6 +744,9 @@ typedef struct
 #pragma pack()
 #endif
 
+// Possible alpha types for a patch.
+enum patchalphastyle {AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY, AST_FOG};
+
 typedef enum
 {
 	RF_HORIZONTALFLIP   = 0x0001,   // Flip sprite horizontally
@@ -755,17 +760,19 @@ typedef enum
 	RF_NOSPLATBILLBOARD = 0x0040,   // Don't billboard floor sprites (faces forward from the view angle)
 	RF_NOSPLATROLLANGLE = 0x0080,   // Don't rotate floor sprites by the object's rollangle (uses rotated patches instead)
 
-	RF_BLENDMASK        = 0x0F00,   // --Blending modes
-	RF_FULLBRIGHT       = 0x0100,   // Sprite is drawn at full brightness
-	RF_FULLDARK         = 0x0200,   // Sprite is drawn completely dark
-	RF_NOCOLORMAPS      = 0x0400,   // Sprite is not drawn with colormaps
+	RF_BRIGHTMASK       = 0x00000300,   // --Bright modes
+	RF_FULLBRIGHT       = 0x00000100,   // Sprite is drawn at full brightness
+	RF_FULLDARK         = 0x00000200,   // Sprite is drawn completely dark
+	RF_SEMIBRIGHT       = (RF_FULLBRIGHT | RF_FULLDARK), // between sector bright and full bright
+
+	RF_NOCOLORMAPS      = 0x00000400,   // Sprite is not drawn with colormaps
 
-	RF_SPRITETYPEMASK   = 0x7000,   // ---Different sprite types
-	RF_PAPERSPRITE      = 0x1000,   // Paper sprite
-	RF_FLOORSPRITE      = 0x2000,   // Floor sprite
+	RF_SPRITETYPEMASK   = 0x00003000,   // --Different sprite types
+	RF_PAPERSPRITE      = 0x00001000,   // Paper sprite
+	RF_FLOORSPRITE      = 0x00002000,   // Floor sprite
 
-	RF_SHADOWDRAW       = 0x10000,  // Stretches and skews the sprite like a shadow.
-	RF_SHADOWEFFECTS    = 0x20000,  // Scales and becomes transparent like a shadow.
+	RF_SHADOWDRAW       = 0x00004000,  // Stretches and skews the sprite like a shadow.
+	RF_SHADOWEFFECTS    = 0x00008000,  // Scales and becomes transparent like a shadow.
 	RF_DROPSHADOW       = (RF_SHADOWDRAW | RF_SHADOWEFFECTS | RF_FULLDARK),
 } renderflags_t;
 
diff --git a/src/r_draw.c b/src/r_draw.c
index f0a19a462848d02c54b07a8a481f11e0969ebef0..65bb87bfb54de7a8002717c94c868972803409ee 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -248,6 +248,9 @@ static void BlendTab_Subtractive(UINT8 *table, int style, UINT8 blendamt)
 			result.s.green = max(0, result.s.green - blendamt);
 			result.s.blue = max(0, result.s.blue - blendamt);
 
+			//probably incorrect, but does look better at lower opacity...
+			//result.rgba = ASTBlendPixel(result, frontrgba, AST_TRANSLUCENT, blendamt);
+
 			table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut, result.s.red, result.s.green, result.s.blue);
 		}
 	}
diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c
index dae3a7b53a6cab88c151e7d1605d99fc5972c321..5dbc30286630c4f51eee55bb7fe2a8d57f4a10e4 100644
--- a/src/r_patchrotation.c
+++ b/src/r_patchrotation.c
@@ -227,10 +227,10 @@ void RotatedPatch_DoRotation(rotsprite_t *rotsprite, patch_t *patch, INT32 angle
 
 	ox = (newwidth / 2) + (leftoffset - xpivot);
 	oy = (newheight / 2) + (patch->topoffset - ypivot);
-	width = (maxx+1 - minx);
-	height = (maxy+1 - miny);
+	width = (maxx - minx);
+	height = (maxy - miny);
 
-	if ((unsigned)(width * height) != size)
+	if ((unsigned)(width * height) > size)
 	{
 		UINT16 *src, *dest;
 
diff --git a/src/r_plane.c b/src/r_plane.c
index 88abed44a48c53a34fa1e42bfdfc44628f7d846b..34073e0e7e7ad16265d737aac0dfa7324552dadc 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -855,28 +855,16 @@ void R_DrawSinglePlane(visplane_t *pl)
 				spanfunctype = (pl->ffloor->flags & FF_SPLAT) ? SPANDRAWFUNC_TRANSSPLAT : SPANDRAWFUNC_TRANS;
 
 				// Hacked up support for alpha value in software mode Tails 09-24-2002
-				if (pl->ffloor->alpha < 12)
-					return; // Don't even draw it
-				else if (pl->ffloor->alpha < 38)
-					ds_transmap = R_GetTranslucencyTable(tr_trans90);
-				else if (pl->ffloor->alpha < 64)
-					ds_transmap = R_GetTranslucencyTable(tr_trans80);
-				else if (pl->ffloor->alpha < 89)
-					ds_transmap = R_GetTranslucencyTable(tr_trans70);
-				else if (pl->ffloor->alpha < 115)
-					ds_transmap = R_GetTranslucencyTable(tr_trans60);
-				else if (pl->ffloor->alpha < 140)
-					ds_transmap = R_GetTranslucencyTable(tr_trans50);
-				else if (pl->ffloor->alpha < 166)
-					ds_transmap = R_GetTranslucencyTable(tr_trans40);
-				else if (pl->ffloor->alpha < 192)
-					ds_transmap = R_GetTranslucencyTable(tr_trans30);
-				else if (pl->ffloor->alpha < 217)
-					ds_transmap = R_GetTranslucencyTable(tr_trans20);
-				else if (pl->ffloor->alpha < 243)
-					ds_transmap = R_GetTranslucencyTable(tr_trans10);
-				else // Opaque, but allow transparent flat pixels
-					spanfunctype = SPANDRAWFUNC_SPLAT;
+				// ...unhacked by toaster 04-01-2021, re-hacked a little by sphere 19-11-2021
+				{
+					INT32 trans = (10*((256+12) - pl->ffloor->alpha))/255;
+					if (trans >= 10)
+						return; // Don't even draw it
+					if (pl->ffloor->blend) // additive, (reverse) subtractive, modulative
+						ds_transmap = R_GetBlendTable(pl->ffloor->blend, trans);
+					else if (!(ds_transmap = R_GetTranslucencyTable(trans)) || trans == 0)
+						spanfunctype = SPANDRAWFUNC_SPLAT; // Opaque, but allow transparent flat pixels
+				}
 
 				if ((spanfunctype == SPANDRAWFUNC_SPLAT) || (pl->extra_colormap && (pl->extra_colormap->flags & CMF_FOG)))
 					light = (pl->lightlevel >> LIGHTSEGSHIFT);
diff --git a/src/r_segs.c b/src/r_segs.c
index 68fb2419526b37aaafbdb669e8ea35a2e53562b7..48d071b296cae26683be5b69c9a2b713547a3745 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -155,18 +155,25 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	if (!ldef->alpha)
 		return;
 
-	if (ldef->alpha > 0 && ldef->alpha < FRACUNIT)
-	{
-		dc_transmap = R_GetTranslucencyTable(R_GetLinedefTransTable(ldef->alpha));
-		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
-
-	}
-	else if (ldef->special == 909)
+	if (ldef->blendmode == AST_FOG)
 	{
 		colfunc = colfuncs[COLDRAWFUNC_FOG];
 		windowtop = frontsector->ceilingheight;
 		windowbottom = frontsector->floorheight;
 	}
+	else if (ldef->blendmode)
+	{
+		if (ldef->alpha == NUMTRANSMAPS || ldef->blendmode == AST_MODULATE)
+			dc_transmap = R_GetBlendTable(ldef->blendmode, 0);
+		else
+			dc_transmap = R_GetBlendTable(ldef->blendmode, R_GetLinedefTransTable(ldef->alpha));
+		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
+	}
+	else if (ldef->alpha > 0 && ldef->alpha < FRACUNIT)
+	{
+		dc_transmap = R_GetTranslucencyTable(R_GetLinedefTransTable(ldef->alpha));
+		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
+	}
 	else
 		colfunc = colfuncs[BASEDRAWFUNC];
 
@@ -600,28 +607,16 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 		boolean fuzzy = true;
 
 		// Hacked up support for alpha value in software mode Tails 09-24-2002
-		if (pfloor->alpha < 12)
-			return; // Don't even draw it
-		else if (pfloor->alpha < 38)
-			dc_transmap = R_GetTranslucencyTable(tr_trans90);
-		else if (pfloor->alpha < 64)
-			dc_transmap = R_GetTranslucencyTable(tr_trans80);
-		else if (pfloor->alpha < 89)
-			dc_transmap = R_GetTranslucencyTable(tr_trans70);
-		else if (pfloor->alpha < 115)
-			dc_transmap = R_GetTranslucencyTable(tr_trans60);
-		else if (pfloor->alpha < 140)
-			dc_transmap = R_GetTranslucencyTable(tr_trans50);
-		else if (pfloor->alpha < 166)
-			dc_transmap = R_GetTranslucencyTable(tr_trans40);
-		else if (pfloor->alpha < 192)
-			dc_transmap = R_GetTranslucencyTable(tr_trans30);
-		else if (pfloor->alpha < 217)
-			dc_transmap = R_GetTranslucencyTable(tr_trans20);
-		else if (pfloor->alpha < 243)
-			dc_transmap = R_GetTranslucencyTable(tr_trans10);
-		else
-			fuzzy = false; // Opaque
+		// ...unhacked by toaster 04-01-2021, re-hacked a little by sphere 19-11-2021
+		{
+			INT32 trans = (10*((256+12) - pfloor->alpha))/255;
+			if (trans >= 10)
+				return; // Don't even draw it
+			if (pfloor->blend) // additive, (reverse) subtractive, modulative
+				dc_transmap = R_GetBlendTable(pfloor->blend, trans);
+			else if (!(dc_transmap = R_GetTranslucencyTable(trans)) || trans == 0)
+				fuzzy = false; // Opaque
+		}
 
 		if (fuzzy)
 			colfunc = colfuncs[COLDRAWFUNC_FUZZY];
diff --git a/src/r_things.c b/src/r_things.c
index bed71a6d791f1c5dbc41a0f517560dc5387c1b08..accd1e2b3cd16795a27700d23f128d25e3c99fbb 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1110,6 +1110,10 @@ static void R_SplitSprite(vissprite_t *sprite)
 
 				if (lindex >= MAXLIGHTSCALE)
 					lindex = MAXLIGHTSCALE-1;
+
+				if (newsprite->cut & SC_SEMIBRIGHT)
+					lindex = (MAXLIGHTSCALE/2) + (lindex >>1);
+
 				newsprite->colormap = spritelights[lindex];
 			}
 		}
@@ -1798,13 +1802,19 @@ static void R_ProjectSprite(mobj_t *thing)
 			return;
 	}
 
+	INT32 blendmode;
+	if (oldthing->frame & FF_BLENDMASK)
+		blendmode = ((oldthing->frame & FF_BLENDMASK) >> FF_BLENDSHIFT) + 1;
+	else
+		blendmode = oldthing->blendmode;
+
 	// Determine the translucency value.
 	if (oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW) // actually only the player should use this (temporary invisibility)
 		trans = tr_trans80; // because now the translucency is set through FF_TRANSMASK
 	else if (oldthing->frame & FF_TRANSMASK)
 	{
 		trans = (oldthing->frame & FF_TRANSMASK) >> FF_TRANSSHIFT;
-		if (!R_BlendLevelVisible(oldthing->blendmode, trans))
+		if (!R_BlendLevelVisible(blendmode, trans))
 			return;
 	}
 	else
@@ -2016,13 +2026,15 @@ static void R_ProjectSprite(mobj_t *thing)
 		vis->scale += FixedMul(scalestep, spriteyscale) * (vis->x1 - x1);
 	}
 
-	if ((oldthing->blendmode != AST_COPY) && cv_translucency.value)
-		vis->transmap = R_GetBlendTable(oldthing->blendmode, trans);
+	if ((blendmode != AST_COPY) && cv_translucency.value)
+		vis->transmap = R_GetBlendTable(blendmode, trans);
 	else
 		vis->transmap = NULL;
 
 	if (R_ThingIsFullBright(oldthing) || oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW)
 		vis->cut |= SC_FULLBRIGHT;
+	else if (R_ThingIsSemiBright(oldthing))
+		vis->cut |= SC_SEMIBRIGHT;
 	else if (R_ThingIsFullDark(oldthing))
 		vis->cut |= SC_FULLDARK;
 
@@ -2045,6 +2057,9 @@ static void R_ProjectSprite(mobj_t *thing)
 		if (lindex >= MAXLIGHTSCALE)
 			lindex = MAXLIGHTSCALE-1;
 
+		if (vis->cut & SC_SEMIBRIGHT)
+			lindex = (MAXLIGHTSCALE/2) + (lindex >> 1);
+
 		vis->colormap = spritelights[lindex];
 	}
 
@@ -3073,17 +3088,22 @@ boolean R_ThingIsPaperSprite(mobj_t *thing)
 
 boolean R_ThingIsFloorSprite(mobj_t *thing)
 {
-	return (thing->flags2 & MF2_SPLAT || thing->renderflags & RF_FLOORSPRITE);
+	return (thing->flags2 & MF2_SPLAT || thing->frame & FF_FLOORSPRITE || thing->renderflags & RF_FLOORSPRITE);
 }
 
 boolean R_ThingIsFullBright(mobj_t *thing)
 {
-	return (thing->frame & FF_FULLBRIGHT || thing->renderflags & RF_FULLBRIGHT);
+	return ((thing->frame & FF_BRIGHTMASK) == FF_FULLBRIGHT || (thing->renderflags & RF_BRIGHTMASK) == RF_FULLBRIGHT);
+}
+
+boolean R_ThingIsSemiBright(mobj_t *thing)
+{
+	return ((thing->frame & FF_BRIGHTMASK) == FF_SEMIBRIGHT || (thing->renderflags & RF_BRIGHTMASK) == RF_SEMIBRIGHT);
 }
 
 boolean R_ThingIsFullDark(mobj_t *thing)
 {
-	return (thing->renderflags & RF_FULLDARK);
+	return ((thing->frame & FF_BRIGHTMASK) == FF_FULLDARK || (thing->renderflags & RF_BRIGHTMASK) == RF_FULLDARK);
 }
 
 //
diff --git a/src/r_things.h b/src/r_things.h
index 79dc80d94a2a15691056e34716f038a491cf54ba..b1ff32b1ee4a3e723bb020f27a397c71e3f91297 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -82,6 +82,7 @@ boolean R_ThingIsPaperSprite (mobj_t *thing);
 boolean R_ThingIsFloorSprite (mobj_t *thing);
 
 boolean R_ThingIsFullBright (mobj_t *thing);
+boolean R_ThingIsSemiBright (mobj_t *thing);
 boolean R_ThingIsFullDark (mobj_t *thing);
 
 // --------------
@@ -123,13 +124,14 @@ typedef enum
 	SC_PRECIP     = 1<<2,
 	SC_LINKDRAW   = 1<<3,
 	SC_FULLBRIGHT = 1<<4,
-	SC_FULLDARK   = 1<<5,
-	SC_VFLIP      = 1<<6,
-	SC_ISSCALED   = 1<<7,
-	SC_ISROTATED  = 1<<8,
-	SC_SHADOW     = 1<<9,
-	SC_SHEAR      = 1<<10,
-	SC_SPLAT      = 1<<11,
+	SC_SEMIBRIGHT = 1<<5,
+	SC_FULLDARK   = 1<<6,
+	SC_VFLIP      = 1<<7,
+	SC_ISSCALED   = 1<<8,
+	SC_ISROTATED  = 1<<9,
+	SC_SHADOW     = 1<<10,
+	SC_SHEAR      = 1<<11,
+	SC_SPLAT      = 1<<12,
 	// masks
 	SC_CUTMASK    = SC_TOP|SC_BOTTOM,
 	SC_FLAGMASK   = ~SC_CUTMASK
diff --git a/src/s_sound.c b/src/s_sound.c
index 30f24236923a45200f40ccddd5eff680f4e98c99..76f0d67c16e490777c3e9c6e59bdf625e221945c 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -74,9 +74,9 @@ consvar_t stereoreverse = CVAR_INIT ("stereoreverse", "Off", CV_SAVE, CV_OnOff,
 static consvar_t precachesound = CVAR_INIT ("precachesound", "Off", CV_SAVE, CV_OnOff, NULL);
 
 // actual general (maximum) sound & music volume, saved into the config
-consvar_t cv_soundvolume = CVAR_INIT ("soundvolume", "18", CV_SAVE, soundvolume_cons_t, NULL);
-consvar_t cv_digmusicvolume = CVAR_INIT ("digmusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL);
-consvar_t cv_midimusicvolume = CVAR_INIT ("midimusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL);
+consvar_t cv_soundvolume = CVAR_INIT ("soundvolume", "16", CV_SAVE, soundvolume_cons_t, NULL);
+consvar_t cv_digmusicvolume = CVAR_INIT ("digmusicvolume", "16", CV_SAVE, soundvolume_cons_t, NULL);
+consvar_t cv_midimusicvolume = CVAR_INIT ("midimusicvolume", "16", CV_SAVE, soundvolume_cons_t, NULL);
 
 static void Captioning_OnChange(void)
 {
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index 105e1def868b96e66c05302674a9a93e4f83e159..d79dde7662142d132271ed676bf5d1f2c5423f09 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -245,6 +245,7 @@
     <ClInclude Include="..\i_sound.h" />
     <ClInclude Include="..\i_system.h" />
     <ClInclude Include="..\i_tcp.h" />
+    <ClInclude Include="..\i_threads.h" />
     <ClInclude Include="..\i_video.h" />
     <ClInclude Include="..\keys.h" />
     <ClInclude Include="..\libdivide.h" />
@@ -304,6 +305,7 @@
     <ClInclude Include="..\st_stuff.h" />
     <ClInclude Include="..\s_sound.h" />
     <ClInclude Include="..\tables.h" />
+    <ClInclude Include="..\taglist.h" />
     <ClInclude Include="..\v_video.h" />
     <ClInclude Include="..\w_wad.h" />
     <ClInclude Include="..\y_inter.h" />
@@ -415,6 +417,7 @@
     <ClCompile Include="..\lua_polyobjlib.c" />
     <ClCompile Include="..\lua_script.c" />
     <ClCompile Include="..\lua_skinlib.c" />
+    <ClCompile Include="..\lua_taglib.c" />
     <ClCompile Include="..\lua_thinkerlib.c" />
     <ClCompile Include="..\lzf.c" />
     <ClCompile Include="..\md5.c" />
@@ -475,10 +478,12 @@
     <ClCompile Include="..\r_things.c" />
     <ClCompile Include="..\screen.c" />
     <ClCompile Include="..\sounds.c" />
+    <ClCompile Include="..\strcasestr.c" />
     <ClCompile Include="..\string.c" />
     <ClCompile Include="..\st_stuff.c" />
     <ClCompile Include="..\s_sound.c" />
     <ClCompile Include="..\tables.c" />
+    <ClCompile Include="..\taglist.c" />
     <ClCompile Include="..\t_facon.c">
       <ExcludedFromBuild>true</ExcludedFromBuild>
     </ClCompile>
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index 4048903976b57317d6be3ac77ff0f87a38b17f8c..4d2532ca4ef61adf61711cfc3affbc6b86e73f14 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -135,6 +135,16 @@
     <ClInclude Include="..\dehacked.h">
       <Filter>D_Doom</Filter>
     </ClInclude>
+    <ClInclude Include="..\deh_lua.h">
+      <Filter>D_Doom</Filter>
+    </ClInclude>
+    <ClInclude Include="..\deh_soc.h">
+      <Filter>D_Doom</Filter>
+    </ClInclude>
+    <ClInclude Include="..\deh_tables.h">
+      <Filter>D_Doom</Filter>
+    </ClInclude>
+
     <ClInclude Include="..\doomdata.h">
       <Filter>D_Doom</Filter>
     </ClInclude>
@@ -288,6 +298,9 @@
     <ClInclude Include="..\i_tcp.h">
       <Filter>I_Interface</Filter>
     </ClInclude>
+    <ClInclude Include="..\i_threads.h">
+      <Filter>I_Interface</Filter>
+    </ClInclude>
     <ClInclude Include="..\i_video.h">
       <Filter>I_Interface</Filter>
     </ClInclude>
@@ -402,6 +415,9 @@
     <ClInclude Include="..\tables.h">
       <Filter>P_Play</Filter>
     </ClInclude>
+    <ClInclude Include="..\taglist.h">
+      <Filter>P_Play</Filter>
+    </ClInclude>
     <ClInclude Include="..\libdivide.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
@@ -600,6 +616,16 @@
     <ClCompile Include="..\dehacked.c">
       <Filter>D_Doom</Filter>
     </ClCompile>
+    <ClCompile Include="..\deh_lua.c">
+      <Filter>D_Doom</Filter>
+    </ClCompile>
+    <ClCompile Include="..\deh_soc.c">
+      <Filter>D_Doom</Filter>
+    </ClCompile>
+    <ClCompile Include="..\deh_tables.c">
+      <Filter>D_Doom</Filter>
+    </ClCompile>
+
     <ClCompile Include="..\d_clisrv.c">
       <Filter>D_Doom</Filter>
     </ClCompile>
@@ -747,6 +773,9 @@
     <ClCompile Include="..\lua_skinlib.c">
       <Filter>LUA</Filter>
     </ClCompile>
+    <ClCompile Include="..\lua_taglib.c">
+      <Filter>LUA</Filter>
+    </ClCompile>
     <ClCompile Include="..\lua_thinkerlib.c">
       <Filter>LUA</Filter>
     </ClCompile>
@@ -792,6 +821,9 @@
     <ClCompile Include="..\string.c">
       <Filter>M_Misc</Filter>
     </ClCompile>
+    <ClCompile Include="..\strcasestr.c">
+      <Filter>M_Misc</Filter>
+    </ClCompile>
     <ClCompile Include="..\comptime.c">
       <Filter>O_Other</Filter>
     </ClCompile>
@@ -852,6 +884,9 @@
     <ClCompile Include="..\tables.c">
       <Filter>P_Play</Filter>
     </ClCompile>
+    <ClCompile Include="..\taglist.c">
+      <Filter>P_Play</Filter>
+    </ClCompile>
     <ClCompile Include="..\t_facon.c">
       <Filter>P_Play</Filter>
     </ClCompile>
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index c426e6792f6c8116c52615a27f997e63e9f2b275..bdc693ca5306fee0a6ea20c13dc12f4a190b0821 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -177,7 +177,9 @@ boolean OglSdlSurface(INT32 w, INT32 h)
 			// Also set the renderer variable back to software so the next launch won't
 			// repeat this error.
 			CV_StealthSet(&cv_renderer, "Software");
-			I_Error("OpenGL Error: Failed to access the GPU. There may be an issue with your graphics drivers.");
+			I_Error("OpenGL Error: Failed to access the GPU. Possible reasons include:\n"
+					"- GPU vendor has dropped OpenGL support on your GPU and OS. (Old GPU?)\n"
+					"- GPU drivers are missing or broken. You may need to update your drivers.");
 		}
 	}
 	first_init = true;
diff --git a/src/st_stuff.c b/src/st_stuff.c
index a328d669e51169ba18d8ae8d046774d3b298eed3..f17b58fa62d0161d859c5371beb3d5c0a161c6da 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1176,7 +1176,17 @@ static void ST_drawInput(void)
 			break;
 
 		case CS_SIMPLE:
-			V_DrawThinString(x, y, hudinfo[HUD_LIVES].f, "SIMPLE");
+			V_DrawThinString(x, y, hudinfo[HUD_LIVES].f, "AUTOMATIC");
+			y -= 8;
+			break;
+
+		case CS_STANDARD:
+			V_DrawThinString(x, y, hudinfo[HUD_LIVES].f, "MANUAL");
+			y -= 8;
+			break;
+
+		case CS_LEGACY:
+			V_DrawThinString(x, y, hudinfo[HUD_LIVES].f, "STRAFE");
 			y -= 8;
 			break;
 
@@ -2291,7 +2301,7 @@ static void ST_drawTextHUD(void)
 
 			for (i = 0; i < MAXPLAYERS; i++)
 			{
-				if (!playeringame[i] || players[i].spectator)
+				if (!playeringame[i] || players[i].spectator || players[i].bot)
 					continue;
 				if (players[i].lives <= 0)
 					continue;
diff --git a/src/taglist.c b/src/taglist.c
index f78f0c82b8b2845ebe0df0ec1699a0dab3ca36d6..a67c7f1c2b7d3652d69de654db0a0a1df469310c 100644
--- a/src/taglist.c
+++ b/src/taglist.c
@@ -210,6 +210,38 @@ void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id)
 	group->elements[i] = id;
 }
 
+static void Taggroup_Add_Init(taggroup_t *garray[], const mtag_t tag, size_t id)
+{
+	taggroup_t *group;
+
+	if (tag == MTAG_GLOBAL)
+		return;
+
+	group = garray[(UINT16)tag];
+
+	if (! in_bit_array(tags_available, tag))
+	{
+		num_tags++;
+		set_bit_array(tags_available, tag);
+	}
+
+	// Create group if empty.
+	if (!group)
+		group = garray[(UINT16)tag] = Z_Calloc(sizeof(taggroup_t), PU_LEVEL, NULL);
+	else if (group->elements[group->count - 1] == id)
+		return; // Don't add duplicates
+
+	group->count++;
+
+	if (group->count > group->capacity)
+	{
+		group->capacity = 2 * group->count;
+		group->elements = Z_Realloc(group->elements, group->capacity * sizeof(size_t), PU_LEVEL, NULL);
+	}
+
+	group->elements[group->count - 1] = id;
+}
+
 static size_t total_elements_with_tag (const mtag_t tag)
 {
 	return
@@ -269,17 +301,17 @@ void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id)
 
 static void Taglist_AddToSectors (const mtag_t tag, const size_t itemid)
 {
-	Taggroup_Add(tags_sectors, tag, itemid);
+	Taggroup_Add_Init(tags_sectors, tag, itemid);
 }
 
 static void Taglist_AddToLines (const mtag_t tag, const size_t itemid)
 {
-	Taggroup_Add(tags_lines, tag, itemid);
+	Taggroup_Add_Init(tags_lines, tag, itemid);
 }
 
 static void Taglist_AddToMapthings (const mtag_t tag, const size_t itemid)
 {
-	Taggroup_Add(tags_mapthings, tag, itemid);
+	Taggroup_Add_Init(tags_mapthings, tag, itemid);
 }
 
 /// After all taglists have been built for each element (sectors, lines, things),
diff --git a/src/taglist.h b/src/taglist.h
index f15423d7ac56ed7e01dd180c71172a3abaf23485..39f71ad431b4df88c57c504d3e39f0f6626e4115 100644
--- a/src/taglist.h
+++ b/src/taglist.h
@@ -44,6 +44,7 @@ typedef struct
 {
 	size_t *elements;
 	size_t count;
+	size_t capacity;
 } taggroup_t;
 
 extern bitarray_t tags_available[];
diff --git a/src/v_video.c b/src/v_video.c
index c3993854403fe87db28c06e18705a69f01932a84..12588f9c2fc2bebda04d6292e137778dff504d63 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -511,7 +511,8 @@ static inline UINT8 transmappedpdraw(const UINT8 *dest, const UINT8 *source, fix
 void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap)
 {
 	UINT8 (*patchdrawfunc)(const UINT8*, const UINT8*, fixed_t);
-	UINT32 alphalevel = 0;
+	UINT32 alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT);
+	UINT32 blendmode = ((scrn & V_BLENDMASK) >> V_BLENDSHIFT);
 
 	fixed_t col, ofs, colfrac, rowfrac, fdup, vdup;
 	INT32 dupx, dupy;
@@ -538,13 +539,13 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 	patchdrawfunc = standardpdraw;
 
 	v_translevel = NULL;
-	if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT)))
+	if (alphalevel)
 	{
-		if (alphalevel == 13)
+		if (alphalevel == 10)
 			alphalevel = hudminusalpha[st_translucency];
-		else if (alphalevel == 14)
+		else if (alphalevel == 11)
 			alphalevel = 10 - st_translucency;
-		else if (alphalevel == 15)
+		else if (alphalevel == 12)
 			alphalevel = hudplusalpha[st_translucency];
 
 		if (alphalevel >= 10)
@@ -552,7 +553,11 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 
 		if (alphalevel)
 		{
-			v_translevel = R_GetTranslucencyTable(alphalevel);
+			if (blendmode)
+				v_translevel = R_GetBlendTable(blendmode+1, alphalevel);
+			else
+				v_translevel = R_GetTranslucencyTable(alphalevel);
+
 			patchdrawfunc = translucentpdraw;
 		}
 	}
@@ -591,10 +596,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 	colfrac = FixedDiv(FRACUNIT, fdup);
 	rowfrac = FixedDiv(FRACUNIT, vdup);
 
-	// So it turns out offsets aren't scaled in V_NOSCALESTART unless V_OFFSET is applied ...poo, that's terrible
-	// For now let's just at least give V_OFFSET the ability to support V_FLIP
-	// I'll probably make a better fix for 2.2 where I don't have to worry about breaking existing support for stuff
-	// -- Monster Iestyn 29/10/18
 	{
 		fixed_t offsetx = 0, offsety = 0;
 
@@ -605,15 +606,8 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 			offsetx = FixedMul(patch->leftoffset<<FRACBITS, pscale);
 
 		// top offset
-		// TODO: make some kind of vertical version of V_FLIP, maybe by deprecating V_OFFSET in future?!?
 		offsety = FixedMul(patch->topoffset<<FRACBITS, vscale);
 
-		if ((scrn & (V_NOSCALESTART|V_OFFSET)) == (V_NOSCALESTART|V_OFFSET)) // Multiply by dupx/dupy for crosshairs
-		{
-			offsetx = FixedMul(offsetx, dupx<<FRACBITS);
-			offsety = FixedMul(offsety, dupy<<FRACBITS);
-		}
-
 		// Subtract the offsets from x/y positions
 		x -= offsetx;
 		y -= offsety;
@@ -812,7 +806,8 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
 {
 	UINT8 (*patchdrawfunc)(const UINT8*, const UINT8*, fixed_t);
-	UINT32 alphalevel = 0;
+	UINT32 alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT);
+	UINT32 blendmode = ((scrn & V_BLENDMASK) >> V_BLENDSHIFT);
 	// boolean flip = false;
 
 	fixed_t col, ofs, colfrac, rowfrac, fdup, vdup;
@@ -838,13 +833,13 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, IN
 	patchdrawfunc = standardpdraw;
 
 	v_translevel = NULL;
-	if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT)))
+	if (alphalevel)
 	{
-		if (alphalevel == 13)
+		if (alphalevel == 10)
 			alphalevel = hudminusalpha[st_translucency];
-		else if (alphalevel == 14)
+		else if (alphalevel == 11)
 			alphalevel = 10 - st_translucency;
-		else if (alphalevel == 15)
+		else if (alphalevel == 12)
 			alphalevel = hudplusalpha[st_translucency];
 
 		if (alphalevel >= 10)
@@ -852,7 +847,11 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, IN
 
 		if (alphalevel)
 		{
-			v_translevel = R_GetTranslucencyTable(alphalevel);
+			if (blendmode)
+				v_translevel = R_GetBlendTable(blendmode+1, alphalevel);
+			else
+				v_translevel = R_GetTranslucencyTable(alphalevel);
+
 			patchdrawfunc = translucentpdraw;
 		}
 	}
@@ -1411,11 +1410,11 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 
 	if ((alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT)))
 	{
-		if (alphalevel == 13)
+		if (alphalevel == 10)
 			alphalevel = hudminusalpha[st_translucency];
-		else if (alphalevel == 14)
+		else if (alphalevel == 11)
 			alphalevel = 10 - st_translucency;
-		else if (alphalevel == 15)
+		else if (alphalevel == 12)
 			alphalevel = hudplusalpha[st_translucency];
 
 		if (alphalevel >= 10)
diff --git a/src/v_video.h b/src/v_video.h
index c10ab22cea8f56497f998aa449fb672b59f62f84..bcb39706ef26f6903a33d488242d4d23dce6f3ee 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -122,17 +122,23 @@ void V_CubeApply(UINT8 *red, UINT8 *green, UINT8 *blue);
 #define V_70TRANS            0x00070000
 #define V_80TRANS            0x00080000 // used to be V_8020TRANS
 #define V_90TRANS            0x00090000
-#define V_HUDTRANSHALF       0x000D0000
-#define V_HUDTRANS           0x000E0000 // draw the hud translucent
-#define V_HUDTRANSDOUBLE     0x000F0000
+#define V_HUDTRANSHALF       0x000A0000
+#define V_HUDTRANS           0x000B0000 // draw the hud translucent
+#define V_HUDTRANSDOUBLE     0x000C0000
 // Macros follow
 #define V_USERHUDTRANSHALF   ((10-(cv_translucenthud.value/2))<<V_ALPHASHIFT)
 #define V_USERHUDTRANS       ((10-cv_translucenthud.value)<<V_ALPHASHIFT)
 #define V_USERHUDTRANSDOUBLE ((10-min(cv_translucenthud.value*2, 10))<<V_ALPHASHIFT)
 
-#define V_AUTOFADEOUT        0x00100000 // used by CECHOs, automatic fade out when almost over
-#define V_RETURN8            0x00200000 // 8 pixel return instead of 12
-#define V_OFFSET             0x00400000 // account for offsets in patches
+// use bits 21-23 for blendmodes
+#define V_BLENDSHIFT         20
+#define V_BLENDMASK          0x00700000
+// preshifted blend flags minus 1 as effects don't distinguish between AST_COPY and AST_TRANSLUCENT
+#define V_ADD                ((AST_ADD-1)<<V_BLENDSHIFT) // Additive
+#define V_SUBTRACT           ((AST_SUBTRACT-1)<<V_BLENDSHIFT) // Subtractive
+#define V_REVERSESUBTRACT    ((AST_REVERSESUBTRACT-1)<<V_BLENDSHIFT) // Reverse subtractive
+#define V_MODULATE           ((AST_MODULATE-1)<<V_BLENDSHIFT) // Modulate
+
 #define V_ALLOWLOWERCASE     0x00800000 // (strings only) allow fonts that have lowercase letters to use them
 #define V_FLIP               0x00800000 // (patches only) Horizontal flip
 #define V_CENTERNAMETAG      0x00800000 // (nametag only) center nametag lines
@@ -142,8 +148,8 @@ void V_CubeApply(UINT8 *red, UINT8 *green, UINT8 *blue);
 #define V_SNAPTOLEFT         0x04000000 // for centering
 #define V_SNAPTORIGHT        0x08000000 // for centering
 
-#define V_WRAPX              0x10000000 // Don't clamp texture on X (for HW mode)
-#define V_WRAPY              0x20000000 // Don't clamp texture on Y (for HW mode)
+#define V_AUTOFADEOUT        0x10000000 // used by CECHOs, automatic fade out when almost over
+#define V_RETURN8            0x20000000 // 8 pixel return instead of 12
 
 #define V_NOSCALESTART       0x40000000 // don't scale x, y, start coords
 #define V_PERPLAYER          0x80000000 // automatically adjust coordinates/scaling for splitscreen mode
diff --git a/src/y_inter.c b/src/y_inter.c
index f24436d4082e645fbbd9fe103a7ed33e741a18b7..288a821e6f33209408ce708b1666bc93aabee93d 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -2023,7 +2023,7 @@ static void Y_AwardCoopBonuses(void)
 
 	for (i = 0; i < MAXPLAYERS; ++i)
 	{
-		if (!playeringame[i] || players[i].lives < 1) // not active or game over
+		if (!playeringame[i] || players[i].lives < 1 || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN) // not active, game over or tails bot
 			bonusnum = 0; // all null
 		else
 			bonusnum = mapheaderinfo[prevmap]->bonustype + 1; // -1 is none
@@ -2073,7 +2073,7 @@ static void Y_AwardSpecialStageBonus(void)
 	{
 		oldscore = players[i].score;
 
-		if (!playeringame[i] || players[i].lives < 1) // not active or game over
+		if (!playeringame[i] || players[i].lives < 1 || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN) // not active, game over or tails bot
 		{
 			Y_SetNullBonus(&players[i], &localbonuses[0]);
 			Y_SetNullBonus(&players[i], &localbonuses[1]);