diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cb794f3573276dbc2cbec4935dc8dd6bd08ce727..016ac951b7dad13cc49bb29a188c326528c2576f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -94,7 +94,7 @@ default:
     - - |
           # apt_common
           echo -e "\e[0Ksection_start:`date +%s`:apt_common[collapsed=true]\r\e[0KInstalling common packages"
-      - apt-get install make git ccache nasm
+      - apt-get install make git ccache nasm cmake ca-certificates
       - |
           # apt_common
           echo -e "\e[0Ksection_end:`date +%s`:apt_common\r\e[0K"
@@ -526,21 +526,22 @@ Windows x64:
 Debian stable Clang:
   stage: build
 
-  when: manual
+  when: on_success
 
-  allow_failure: true
+  allow_failure: false
 
   artifacts:
     paths:
-      - "bin/"
-      - "src/comptime.h"
+      - "build.clang/bin/"
+      - "build.clang/src/comptime.h"
     expose_as: "clang"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang"
 
   variables:
     CC: clang
-    WFLAGS: -Wno-cast-align
-    CFLAGS: -Wno-cast-align
+    CXX: clang
+    WFLAGS: -Wno-cast-align -Wno-implicit-const-int-float-conversion -Werror
+    CFLAGS: -Wno-cast-align -Wno-implicit-const-int-float-conversion -Werror
     LDFLAGS: -Wl,-fuse-ld=gold
 
   script:
@@ -560,10 +561,18 @@ Debian stable Clang:
           # apt_development
           echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K"
 
+    - - |
+          # cmake
+          echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
+      - cmake -B build.clang -D CPM_USE_LOCAL_PACKAGES:BOOL=ON -D SRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -D SRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -G "Unix Makefiles"
+      - |
+          # cmake
+          echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
+
     - - |
           # make
           echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2"
-      - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1
+      - make --directory=build.clang --keep-going || make --directory=src --keep-going
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
@@ -573,19 +582,22 @@ Debian testing Clang:
 
   when: manual
 
+  allow_failure: true
+
   image: debian:testing-slim
 
   artifacts:
     paths:
-      - "bin/"
-      - "src/comptime.h"
+      - "build.clang/bin/"
+      - "build.clang/src/comptime.h"
     expose_as: "testing-clang"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-clang"
 
   variables:
     CC: clang
-    WFLAGS: -Wno-cast-align -Wno-deprecated-non-prototype -Wno-single-bit-bitfield-constant-conversion
-    CFLAGS: -Wno-cast-align -Wno-deprecated-non-prototype -Wno-single-bit-bitfield-constant-conversion
+    CXX: clang
+    WFLAGS: -Wno-cast-align -Wno-implicit-const-int-float-conversion -Werror -Wno-deprecated-non-prototype -Wno-single-bit-bitfield-constant-conversion
+    CFLAGS: -Wno-cast-align -Wno-implicit-const-int-float-conversion -Werror -Wno-deprecated-non-prototype -Wno-single-bit-bitfield-constant-conversion
     LDFLAGS: -Wl,-fuse-ld=gold
 
 Alpine 3 GCC:
diff --git a/doc/Doublescan.txt b/doc/Doublescan.txt
deleted file mode 100644
index 5e492ec89c56e550d1a653be5d695d78d798c9ca..0000000000000000000000000000000000000000
--- a/doc/Doublescan.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-   ================================================================
-   How to add Low-res modes to your XF86Config under Linux MANUALLY
-   ================================================================
-
-   I TAKE NO RESPONSIBILITY FOR ANY DAMAGE DONE TO YOUR EQUIPMENT!!!
-
-   This document explains how to add low-res modes like 320x200 to your
-   X-Server configuration, because some new setup tools for the X-Server
-   do not support this. ONLY RECOMMENDED FOR USERS WHO KNOW WHAT THEY DO!
-
-   I do not take any responsibility for damage done to your monitor, your
-   videocard, your harddisk, your cat, your dog or anything else!!!
-   IMPORTANT IS, THAT YOUR "HorizSync" AND "VertRefresh" VALUES REALLY
-   MATCH YOUR MONITOR! OTHERWISE YOUR MONITOR CAN BLOW UP!!!
-
-   OK, if you have read up to here, you either know what you do or really
-   die-hard want those low-res modes. Here is what to do:
-   Look up your XF86Config. Is is either in /etc or in /etc/X11. Here is
-   what you have to add to the definition of your modeslines:
-
-# Low-res Doublescan modes
-# If your chipset does not support doublescan, you get a 'squashed'
-# resolution like 320x400.
-
-# 320x200 @ 70 Hz, 31.5 kHz hsync, 8:5 aspect ratio
-Modeline "320x200"     12.588 320  336  384  400   200  204  205  225 Doublescan
-# 320x240 @ 60 Hz, 31.5 kHz hsync, 4:3 aspect ratio
-Modeline "320x240"     12.588 320  336  384  400   240  245  246  262 Doublescan
-# 320x240 @ 72 Hz, 36.5 kHz hsync
-Modeline "320x240"     15.750 320  336  384  400   240  244  246  262 Doublescan
-# 400x300 @ 56 Hz, 35.2 kHz hsync, 4:3 aspect ratio
-ModeLine "400x300"     18     400  416  448  512   300  301  302  312 Doublescan
-# 400x300 @ 60 Hz, 37.8 kHz hsync
-Modeline "400x300"     20     400  416  480  528   300  301  303  314 Doublescan
-# 400x300 @ 72 Hz, 48.0 kHz hsync
-Modeline "400x300"     25     400  424  488  520   300  319  322  333 Doublescan
-
-   If your video card only supports a specific set of discrete dotclocks
-   (RAMDAC) you may have to replace the dotclocks given here by one of the
-   specified (e.g in the first modeline the dotclock is 12.588 MHz). I believe
-   that nowadays all cards and monitors should work with these settings, but
-   if you have outdated hardware you better check the frequencies yourself. If
-   there is any uncertainty, please check the "XFree86 Video Timings HOWTO".
-
-
-   Then have a look at the section "Screen" with the appropriate driver
-   (usually either "svga" or "accel"). Under Subsection "Display" there
-   are modes for the given color depth. Add the desired modes. As an
-   example I give you my screens definition here with low-res modes in
-   16 bit color depth:
-
-Section "Screen"
-    Driver      "accel"
-    Device      "3D Charger"
-    Monitor     "Iiyama Pro 450"
-    DefaultColorDepth 16
-
-    Subsection "Display"
-        Depth       8
-        Modes       "1280x1024" "1024x768" "800x600" "640x480"
-        ViewPort    0 0
-        Virtual     1280 1024
-    EndSubsection
-    Subsection "Display"
-        Depth       16
-        Modes       "1152x864" "1024x768" "800x600" "640x480" "400x300" "320x200" <- THIS IS ACTUALLY WHAT YOU WANT!!!
-        ViewPort    0 0                                       ^^^^^^^^^^^^^^^^^^^
-        Virtual     1152 864
-    EndSubsection
-    Subsection "Display"
-        Depth       24
-        Modes       "800x600" "640x480"
-        ViewPort    0 0
-        Virtual     800 600
-    EndSubsection
-    Subsection "Display"
-        Depth       32
-        Modes       "800x600" "640x480"
-        ViewPort    0 0
-        Virtual     800 600
-    EndSubsection
-EndSection
-
-   Once again: important is, that you edit the correct Screen section.
-   If you use the SVGA Server and edit the ACCEL Server, you might
-   wonder where your new modes have gone.
-
-   If everything went fine and you want to say thank you, just write
-   to "metzgermeister@users.sourceforge.net". If your monitor blew
-   up and you want to kill me, find me playing Legacy or Q3A on the net
-   and frag me (with your second monitor, hehe).
-
-   - metzgermeister
diff --git a/doc/Item Ranges.txt b/doc/Item Ranges.txt
deleted file mode 100644
index 60251e5479c75e9b74a5b04c7ab7c9c9b5788a5b..0000000000000000000000000000000000000000
--- a/doc/Item Ranges.txt	
+++ /dev/null
@@ -1,212 +0,0 @@
-1-99 : Player Starts
-	1 - Player 1 Start		1
-	2 - Player 2 Start		2
-	3 - Player 3 Start		3
-	4 - Player 4 Start		4
-	5 - Player 5 Start 		4001
-	6 - Player 6 Start 		4002
-	7 - Player 7 Start 		4003
-	8 - Player 8 Start 		4004
-	9 - Player 9 Start 		4005
-	10 - Player 10 Start 		4006
-	11 - Player 11 Start 		4007
-	12 - Player 12 Start 		4008
-	13 - Player 13 Start 		4009
-	14 - Player 14 Start 		4010
-	15 - Player 15 Start 		4011
-	16 - Player 16 Start 		4012
-	17 - Player 17 Start 		4013
-	18 - Player 18 Start 		4014
-	19 - Player 19 Start 		4015
-	20 - Player 20 Start 		4016
-	21 - Player 21 Start 		4017
-	22 - Player 22 Start 		4018
-	23 - Player 23 Start 		4019
-	24 - Player 24 Start 		4020
-	25 - Player 25 Start 		4021
-	26 - Player 26 Start 		4022
-	27 - Player 27 Start 		4023
-	28 - Player 28 Start 		4024
-	29 - Player 29 Start 		4025
-	30 - Player 30 Start 		4026
-	31 - Player 31 Start 		4027
-	32 - Player 32 Start 		4028
-	33 - Player Match Start 	11
-	34 - Red Team Start 		87
-	35 - Blue Team Start 		89
-	36 - Tag start			New
-
-100 - 199 : Enemies
-	100 - Blue Crawla		3004
-	101 - Red Crawla		9
-	102 - GFZ Fish			58
-	103 - Gold Buzz			5005
-	104 - Red Buzz			5006
-	105 - Jetty-Syn Bomber		3005
-	106 - Jetty-Syn Gunner		22
-	107 - Crawla Commander		21
-	108 - Deton			71
-	109 - Skim			56
-	110 - THZ Turret		2004
-	111 - Pop-up Turret		42
-
-200 - 299 : Bosses and their associated items (if any)
-	200 - Boss 1			16
-	201 - Boss 2			2008
-	290 - Boss Fly Point		17
-	291 - EggTrap Center		2049
-
-300 - 399 : Collectibles
-	300 - Ring			2014
-	301 - Homing Ring		69
-	302 - Rail Ring			3003
-	303 - Infinity Ring		80
-	304 - Automatic Ring		26
-	305 - Explosion Ring		54
-	306 - Red CTF Flag		31
-	307 - Blue CTF Flag		34
-	308 - Special Stage Token	2013
-	309 - Emerald 1			420
-	310 - Emerald 2			421
-	311 - Emerald 3			422
-	312 - Emerald 4			423
-	313 - Emerald 5			424
-	314 - Emerald 6			425
-	315 - Emerald 7			426
-	316 - Hunting Emerald 1		64
-	317 - Hunting Emerald 2		3002
-	318 - Hunting Emerald 3		3001
-
-400 - 499 : Boxes
-	400 - Super Ring Box		2011
-	401 - Grey Ring Box		2012
-	402 - Ring Shield Box		48
-	403 - Fire Shield Box		2002
-	404 - Bomb Shield Box		2018
-	405 - Jump Shield Box		35
-	406 - Water Shield Box		2028
-	407 - Sneaker Box		25
-	408 - Invincibility Box		2022
-	409 - 1-Up Box			41
-	410 - Eggman Box		2005
-	411 - Mixup Box			78
-	412 - Question Box		3000
-
-500 - 599 : Interactive Objects (friendly or otherwise - includes springs)
-	500 - Bubble Patch		33
-	501 - Level End Sign		86
-	502 - Starpost			3006
-	520 - Spike Ball		-1
-	521 - Special Stage Spike Ball	23
-	522 - Ceiling Spike		67
-	523 - Floor Spike		68
-	540 - Fan			32
-	541 - Steam Riser		30
-	550 - Yellow Spring		28
-	551 - Red Spring		79
-	552 - Blue Spring		5004
-	553 - Yellow Spring Down	65
-	554 - Red Spring Down		66
-	555 - Yellow Diagonal Spring	2015
-	556 - Red Diagonal Spring	38
-	557 - Yellow Diag Spring Down	20
-	558 - Red Diag Spring Down	39
-
-600 - 699 : Special placement patterns
-	600 - Vertical Rigns - Stack of 5 (suitable for Yellow Spring)	84
-	601 - Vertical Rings - Stack of 5 (suitable for Red Spring)	44
-	602 - Diagonal rings (5)					76
-	603 - Diagonal rings (10)					77
-	604 - A ring of rings						47
-	605 - A BIGGER ring of rings					2007
-	606 - A ring of wing items					2048
-	607 - A BIGGER ring of wing items				2010
-	608 - A ring of rings and wings (alternating)			2046
-	609 - A BIGGER ring of rings and wings (alternating)		2047
-
-700 - 799 : Powerup indicators/environmental effects/miscellany
-	700 - Ambient Water 1a (S)	2026
-	701 - Ambient Water 1b (S)	2024
-	702 - Ambient Water 2a (M)	2023
-	703 - Ambient Water 2b (M)	2045
-	704 - Ambient Water 3a (L)	83
-	705 - Ambient Water 3b (L)	2019
-	706 - Ambient Water 4a (XL)	2025
-	707 - Ambient Water 4b (XL)	27
-	708 - Random Ambient 1		14
-	709 - Random Ambient 2		43
-	750 - Chaos Spawner		8
-	751 - Teleport Point		5003
-	752 - Alternate View Point	5007
-	753 - Zoom Tube Waypoint	18
-	754 - Pusher			5001
-	755 - Puller			5002
-	756 - Street Light		2003
-	
-800 - 899 : Greenflower Scenery
-	800 - Flower 1			36
-	801 - Flower 2			70
-	802 - Flower 3			73
-	804 - Berry Bush		74
-	805 - Bush			75
-
-900 - 999 : Techno Hill Scenery
-	900 - THZ Plant			2035
-	901 - Alarm			2006
-
-1000 - 1099 : Deep Sea Scenery
-	1000 - Gargoyle			81
-
-1100 - 1199 : Castle Eggman Scenery
-	1100 - Ceiling Chain		49
-	1101 - Torch Flame		24
-	1102 - Eggman Statue		52
-	1103 - CEZ Flower		2001
-
-1200 - 1299 : Arid Canyon Scenery
-1300 - 1399 : Red Volcano Scenery
-1400 - 1499 : Dark City Scenery
-1500 - 1599 : Doom Ship Scenery
-1600 - 1699 : Egg Rock/Final Fight Scenery
-1700 - 1799 : NiGHTS Items
-	1700 - Axis			72
-	1701 - Axis Transfer (Normal)	61
-	1702 - Axis Transfer (Line)	46
-	1703 - Nights Drone		60
-	1704 - Nights Bumper		82
-	1705 - Hoop			57
-	1706 - Nights Wing		37
-	1707 - Super Loop Powerup	3007
-	1708 - Drill Refill Powerup	3008
-	1709 - Helper Powerup		3009
-	1710 - Egg Capsule		40
-
-1800 - 1849 : Mario Items
-	1800 - Coin			10005
-	1801 - Goomba			10000
-	1802 - Blue Goomba		10001
-	1803 - FireFlower		50
-	1804 - Shell			10
-	1805 - Puma			29
-	1806 - Koopa			19
-	1807 - Axe			12
-	1808 - Mario Bush 1		10002
-	1809 - Mario Bush 2		10003
-	1810 - Toad			10004
-
-1850 - 1899 : Christmas Items
-	1850 - Xmas Pole		5
-	1851 - Candy Cane		13
-	1852 - Snowman			6
-
-1900 - 1999 : Misc Scenery
-	1900 - Stalagmite 0
-	1901 - Stalagmite 1
-	1902 - Stalagmite 2
-	1903 - Stalagmite 3
-	1904 - Stalagmite 4
-	1905 - Stalagmite 5
-	1906 - Stalagmite 6
-	1907 - Stalagmite 7
-	1908 - Stalagmite 8
-	1909 - Stalagmite 9
diff --git a/doc/Linedef Ranges.txt b/doc/Linedef Ranges.txt
deleted file mode 100644
index 81fa695a0dcb8c0b3592225edbd82a7885c683e6..0000000000000000000000000000000000000000
--- a/doc/Linedef Ranges.txt	
+++ /dev/null
@@ -1,223 +0,0 @@
-	Description	OldNum	NewNum	Description
-	Old Water 	14	Removed	
-
-	Level Parameters/Misc:			
-	Per-Sector Gravity 	64	1	
-	Custom Exit 	71	2	
-	Zoom Tube Parameters 	18	3	
-	Speed Pad 	65	4	
-	Camera Scanner 	63	5	
-	Disable Linedef 	73	6	
-	Flat Alignment 	66	7	
-	Sector Special Parameters	New	8	
-	Mace Parameters	New	9	
-	Sprite Cull Height	New	10	
-	Rope Hang Parameters	New	11
-	Rock Spawner Parameters	New	12
-
-	PolyObjects
-	Marks first line in PolyObject	New	20
-	Explicitly includes a PolyObject line	New	21
-	PolyObject: Parameters	New	22
-	PolyObject: Waving Flag	New	31
-
-	Level-Load Effects:		
-	Instant Floor Lower 	26	50
-	Instant Ceiling Raise 	24	51
-	Continuously Falling Sector 	88	52
-	Continuous Floor/Ceiling Mover 	2	53
-	Continuous Floor Mover 	3	54
-	Continuous Ceiling Mover 	4	55
-	Continuous Two-Speed Floor/Ceiling Mover 	6	56
-	Continuous Two-Speed Floor Mover 	7	57
-	Continuous Two-Speed Ceiling Mover 	8	58
-	Activate Floating Platform 	232	59
-	Activate Floating Platform (Adjustable Speed) 	233	60
-	Crusher 1 (Ceiling to Floor) 	43	61
-	Crusher 2 (Floor to Ceiling) 	50	62
-	Fake Floor/Ceiling	242	63
-	Appearing/Disappearing FOF	New	64
-	Bridge Thinker	New	65
-
-	Floor Over Floors:		
-	"Floor Over Floor: Solid, Opaque, Shadowcasting "	25	100
-	"Floor Over Floor: Solid, Opaque, Non-Shadowcasting "	33	101
-	"Floor Over Floor: Solid, Translucent "	44	102
-	"Floor Over Floor: Solid, Sides Only "	69	103
-	"Floor Over Floor: Solid, No Sides "	51	104
-	"Floor Over Floor: Solid, Invisible "	57	105
-
-	"Floor Over Floor: Water, Opaque "	48	120
-	"Floor Over Floor: Water, Translucent "	45	121
-	"Floor Over Floor: Water, Opaque, No Sides "	75	122
-	"Floor Over Floor: Water, Translucent, No Sides "	74	123
-
-	"Floor Over Floor: Platform, Opaque "	59	140
-	"Floor Over Floor: Platform, Translucent "	81	141
-	"Floor Over Floor: Platform, Translucent, No Sides "	77	142
-
-	Floor Over Floor: Bobbing (Air) 	38	150
-	Floor Over Floor: Adjustable Bobbing (Air) 	68	151
-	Floor Over Floor: Reverse Adjustable Bobbing (Air) 	72	152
-
-	"Floor Over Floor: Floating, Bobbing "	34	160
-
-	Floor Over Floor: Crumbling (Respawn) 	36	170
-	Floor Over Floor: Crumbling (No Respawn) 	35	171
-	"Floor Over Floor: Crumbling (Respawn), Platform  "	79	172
-	"Floor Over Floor: Crumbling (No Respawn), Platform  "	80	173
-	"Floor Over Floor: Crumbling (Respawn), Platform, Translucent "	82	174
-	"Floor Over Floor: Crumbling (No Respawn), Platform, Translucent "	83	175
-	"Floor Over Floor: Crumbling (Respawn), Floating, Bobbing "	39	176
-	"Floor Over Floor: Crumbling (No Respawn), Floating, Bobbing "	1	177
-	"Floor Over Floor: Crumbling (Respawn), Floating "	37	178
-	"Floor Over Floor: Crumbling (No Respawn), Floating "	42	179
-	"Floor Over Floor: Crumbling (Respawn), Bobbing (Air) "	40	180
-
-	"Floor Over Floor: Rising Platform, Solid, Opaque, Shadowcasting "	89	190
-	"Floor Over Floor: Rising Platform, Solid, Opaque, Non-Shadowcasting "	90	191
-	"Floor Over Floor: Rising Platform, Solid, Translucent "	91	192
-	"Floor Over Floor: Rising Platform, Solid, Invisible "	94	193
-	"Floor Over Floor: Rising Platform, Platform, Opaque "	92	194
-	"Floor Over Floor: Rising Platform, Platform, Translucent "	93	195
-
-	Floor Over Floor: Light Block 	49	200
-	Floor Over Floor: Half Light Block 	47	201
-	Floor Over Floor: Fog Block 	46	202
-
-	"Floor Over Floor: Intangible, Opaque "	62	220
-	"Floor Over Floor: Intangible, Translucent "	52	221
-	"Floor Over Floor: Intangible, Sides Only "	67	222
-	"Floor Over Floor: Intangible, Invisible "	58	223
-
-	Floor Over Floor: Mario Block 	41	250
-	Floor Over Floor: Thwomp Block 	54	251
-	Floor Over Floor: Shatter Block 	76	252
-	"Floor Over Floor: Shatter Block, Translucent "	86	253
-	Floor Over Floor: Bustable Block 	55	254
-	Floor Over Floor: Spin Bust Block 	78	255
-	"Floor Over Floor: Spin Bust Block, Translucent "	84	256
-	Floor Over Floor: Quicksand Block 	56	257
-	Floor Over Floor: Laser Block 	53	258
-	Floor Over Floor: Custom 	87	259
-
-	Linedef Executor Triggers:		
-	Trigger Linedef Executor (Continuous) 	96	300
-	Trigger Linedef Executor (Each Time) 	97	301
-	Trigger Linedef Executor (Once) 	98	302
-	Trigger Linedef Executor (Ring Count - Continuous) 	95	303
-	Trigger Linedef Executor (Ring Count - Once) 	99	304
-	Trigger Linedef Executor (Character Ability - Continuous) 	19	305
-	Trigger Linedef Executor (Character Ability - Each Time) 	20	306
-	Trigger Linedef Executor (Character Ability - Once) 	21	307
-	"Trigger Linedef Executor (Race Only, Once) "	9	308
-	Trigger Linedef Executor (CTF Red Team - Continuous) 	10	309
-	Trigger Linedef Executor (CTF Red Team - Each Time) 	11	310
-	Trigger Linedef Executor (CTF Blue Team - Continuous) 	12	311
-	Trigger Linedef Executor (CTF Blue Team - Each Time) 	13	312
-	Trigger Linedef Executor (No More Enemies - Once) 	15	313
-	Trigger Linedef Executor (# of Pushables - Continuous)	New	314
-	Trigger Linedef Executor (# of Pushables - Once)	New	315
-	Trigger Linedef Executors (PolyObject - Land On)	New	316
-	Trigger Linedef Executor (Level Load)	New	399
-
-	Linedef Executor Options:		
-	Linedef Executor: Set Tagged Sector's Floor Height/Pic 	101	400
-	Linedef Executor: Set Tagged Sector's Ceiling Height/Pic 	102	401
-	Linedef Executor: Set Tagged Sector's Light Level 	103	402
-	Linedef Executor: Move Tagged Sector's Floor 	106	403
-	Linedef Executor: Move Tagged Sector's Ceiling 	107	404
-	Linedef Executor: Lower Floor by Line 	108	405
-	Linedef Executor: Raise Floor by Line 	109	406
-	Linedef Executor: Lower Ceiling by Line 	110	407
-	Linedef Executor: Raise Ceiling by Line 	111	408
-	Linedef Executor: Change Calling Sector's Tag 	112	409
-	Linedef Executor: Change Front Sector's Tag 	114	410
-	Linedef Executor: Stop Plane Movement 	116	411
-	Linedef Executor: Teleport Player to Tagged Sector 	104	412
-	Linedef Executor: Change Music 	105	413
-	Linedef Executor: Play SFX 	115	414
-	Linedef Executor: Run Script 	113	415
-	Linedef Executor: Start Adjustable Fire Flicker 	119	416
-	Linedef Executor: Start Adjustable Glowing Light 	120	417
-	Linedef Executor: Start Adjustable Strobe Flash (unsynchronized)	New	418
-	Linedef Executor: Start Adjustable Strobe Flash (synchronized)	New	419
-	Linedef Executor: Fade Light Level 	117	420
-	Linedef Executor: Stop Lighting Effect 	118	421
-	Linedef Executor: Cut-Away View 	121	422
-	Linedef Executor: Change Sky 	123	423
-	Linedef Executor: Change Weather 	124	424
-	Linedef Executor: Change Object State 	125	425
-	Linedef Executor: Stop Object 	122	426
-	Linedef Executor: Award Score 	126	427
-	Linedef Executor: Start Platform Movement 	127	428
-	Linedef Executor: Crush Ceiling Once	New	429
-	Linedef Executor: Crush Floor Once	New	430
-	Linedef Executor: Crush Floor & Ceiling Once	New	431
-	Linedef Executor: Enable 2D Mode	New	432
-	Linedef Executor: Disable 2D Mode	New	433
-	Linedef Executor: Award Custom Power	New	434
-	Linedef Executor: Stop Conveyor	New	435
-	Linedef Executor: Start Conveyor	New	436
-	Linedef Executor: Disable Player Movement	New	437
-
-	Linedef Executor: Execute Linedef Executor	New	450
-
-	Linedef Executor: PolyObject: Door Slide	New	480
-	Linedef Executor: PolyObject: Door Swing	New	481
-	Linedef Executor: PolyObject: Move XY	New	482
-	Linedef Executor: PolyObject: Move XY w/ override	New	483
-	Linedef Executor: PolyObject: Rotate Right	New	484
-	Linedef Executor: PolyObject: Rotate Right w/ override	New	485
-	Linedef Executor: PolyObject: Rotate Left	New	486
-	Linedef Executor: PolyObject: Rotate Left w/ override	New	487
-	Linedef Executor: PolyObject: Start waypoint movement	New	488
-	Linedef Executor: PolyObject: Make Invisible	New	489
-	Linedef Executor: PolyObject: Make Visible	New	490
-
-	Scrollers/Pushers:		
-	Scroll Wall First Side Left 	100	500
-	Scroll Wall First Side Opposite Direction 	85	501
-	Scroll Wall According to Linedef 	254	502
-	Acc Scroll Wall According to Linedef 	218	503
-	Disp Scroll Wall According to Linedef 	249	504
-	Scroll Texture by Offsets 	255	505
-
-	Scroll Floor Texture 	251	510
-	Acc Scroll Floor Texture 	215	511
-	Disp Scroll Floor Texture 	246	512
-	Scroll Ceiling Texture 	250	513
-	Acc Scroll Ceiling Texture 	214	514
-	Disp Scroll Ceiling Texture 	245	515
-
-	Carry Objects on Floor (no scroll)	252	520
-	Acc Carry Objects on Floor 	216	521
-	Disp Carry Objects on Floor 	247	522
-	Carry Objects on Ceiling 	203	523
-	Acc Carry Objects on Ceiling 	205	524
-	Disp Carry Objects on Ceiling 	201	525
-
-	Scroll Floor Texture and Carry Objects 	253	530
-	Acc Scroll Floor Texture and Carry Objects 	217	531
-	Disp Scroll Floor Texture and Carry Objects 	248	532
-	Scroll Ceiling Texture and Carry Objects 	202	533
-	Acc Scroll Ceiling Texture and Carry Objects 	204	534
-	Disp Scroll Ceiling Texture and Carry Objects 	200	535
-
-	Friction 	223	540
-	Horizontal Wind 	224	541
-	Upwards Wind 	229	542
-	Downwards Wind 	230	543
-	Horizontal Current 	225	544
-	Upwards Current 	227	545
-	Downwards Current 	228	546
-	Boom Push/Pull Thing 	226	547
-
-	Lighting:		
-	Floor Lighting 	213	600
-	Ceiling Lighting 	5	601
-	Adjustable Pulsating Light 	60	602
-	Adjustable Flickering Light 	61	603
-	Adjustable Blinking Light (unsynchronized)	New	604
-	Adjustable Blinking Light (synchronized)	New	605
-	Colormap 	16	606
diff --git a/doc/SSN-Todo.xls b/doc/SSN-Todo.xls
deleted file mode 100644
index c468b34764f0b1a7ebcb3f552aeb8fd4cd56fb76..0000000000000000000000000000000000000000
Binary files a/doc/SSN-Todo.xls and /dev/null differ
diff --git a/doc/Sector Ranges.txt b/doc/Sector Ranges.txt
deleted file mode 100644
index 42760e1334a90a7fbf25028fcdaf62d0feb3156c..0000000000000000000000000000000000000000
--- a/doc/Sector Ranges.txt	
+++ /dev/null
@@ -1,78 +0,0 @@
-Removed:
-	- Buttons 1-20					690-709
-	- Button 21 (THZ2 A/740 B/741 D/742 E/745	710
-	- Close Door Blazing (Tag 743)			711
-	- Raise Ceiling to Highest (Tag 744)		981
-	- THZ2 Slime Raise (B/712 W713 P714 D715 S716)	986
-
-Stuff to Remove/Change:
-	- Light Blinks On Every 0.5 Seconds		2 Add Linedef Combine
-	- Light Blinks On Every 1 Second		3 Add Linedef Combine
-	- Light Pulses Smoothly			8 Remove
-	- Light Blinks On Every 0.5 Seconds (Sync)	12 Add Linedef Combine
-	- Lights Blinks On Every 1 Second (Sync)	13 Add Linedef Combine
-	- Light Flickers Like Fire			17 Remove
-	? - Damage (Fire) and Current			519 Remove (convert to combination)
-	? - Damage (Water) and Current			984 Remove (convert to combination)
-
-Section 1:
-	1 - Damage (Generic)				11
-	2 - Damage (Water)				983
-	3 - Damage (Fire)				7
-	4 - Damage (Electrical)				18
-	5 - Spikes					4
-	6 - Death Pit (Camera Mod)			16
-	7 - Death Pit (No Camera Mod)			5
-	8 - Instant Kill				10
-	9 - Ring Drainer (Floor Touch)			978
-	10 - Ring Drainer (No Floor Touch)		980
-	11 - Special Stage Damage			9
-	12 - Space Countdown				6
-	13 - Ramp Sector (Increase step-up)		992
-	14 - Non-Ramp Sector (Don't step-down)		996
-	15 - Bouncy Sector (FOF Control Only)		14
-
-Section 2:		<< 4
-	1 - Trigger Linedef Exec (Pushable Objects)	971
-	2 - Trigger LD Exec (Anywhere in Sec/All Pls)	972
-	3 - Trigger Linedef Exec (Floor Touch/All Pls)	973
-	4 - Trigger Linedef Exec (Anywhere in Sec)	974
-	5 - Trigger Linedef Exec (Floor Touch)		975
-	6 - Trigger Linedef Exec (Emerald Check)	967
-	7 - Trigger Linedef Exec (NiGHTS Mare)		968
-	8 - Check for linedef executor on FOFs (ANY)	970
-	9 - Egg Trap Capsule				666
-	10 - Special Stage Time/Rings, Par		990
-	11 - Custom Global Gravity			991
-
-Section 3:		<< 8
-	1 - Ice/Sludge (required?!)			256
-	2 - Wind/Current (required?!)			512
-	3 - Ice/Sludge and Wind/Current			768
-	4 - Conveyor Belt				985
-	5 - Speed Pad (No Spin)				976
-	6 - Speed Pad (Spin)				977
-	7 - Bustable Block Sprite Parameter		1500-1515
-	8 - "
-	9 - "
-	10 - "
-	11 - "
-	12 - "
-	13 - "
-	14 - "
-	15 - "
-
-Section 4:		<< 12
-	1 - Starpost Activator				993
-	2 - Special Stage Goal	Combine			33
-	2 - Exit Sector		Combine			982
-	2 - No Tag Zone		Combine			987
-	2 - CTF: Flag Return	Combine			995
-	3 - CTF: Red Team Base				988
-	4 - CTF: Blue Team Base				989
-	5 - Fan Sector					997
-	6 - Super Sonic Transform			969
-	7 - Spinner					979
-	8 - Zoom Tube Start				998
-	9 - Zoom Tube End				999
-	10 - Finish Line				994
\ No newline at end of file
diff --git a/doc/faq.txt b/doc/faq.txt
deleted file mode 100644
index 26c75bbadd5581c36c835ae1a11f405f634a2834..0000000000000000000000000000000000000000
--- a/doc/faq.txt
+++ /dev/null
@@ -1,307 +0,0 @@
- SRB2
- Release v1.09, ? 2005.
-
- Last Updated: June 2005
-
- Original game & sources by: Id Software.
- Additions: (c)1998 by: Fabrice Denis & Boris Pereira
-            (c)1999 by: Fabrice Denis, Boris Pereira & Thierry Van Elsuwe
-            (c)2000 by: Boris Pereira & Thierry Van Elsuwe
-            (c)2004 By: AJ, Graue, Alam Arias, Logan Arias & Andrew Clunis
-
- Special thanks to Steven McGranahan,  Lee Killough, Robert B�uml and Bell Kin for
- their large contribution and to other DooM LEGACY & SRB2 Team members.
-
- Web site: http://www.SRB2.org/
-   e-mail: none@none.com
-
- OpenGL specific:
- Web site: http://legacy.newdoom.com/gl
-
-
- -----------------------------------------------------------------------
- F.A.Q.
- -----------------------------------------------------------------------
-
-
- If you have any trouble with SRB2, you might find a solution
- here.
-
- If you find a solution to a problem that was not listed here,
- please tell us so that we can update the FAQ and help other people!
-
- Mail your hardware/software problems to:
-
- None@none.com subject: FAQ
-
-
- --------
- CONTENTS
- --------
-
- [0] Miscellaneous
- [1] Mouse/Joystick/Keyboard
- [2] Video
- [3] Sound
- [4] Network
- [5] Troubleshooting
-
-
- -----------------
- [0] MISCELLANEOUS
- -----------------
-
- * under win95 or OS/2, I don't have enough memory. How can i handle with ?
-
-   Tell win95 to put more dpmi memory for your dos box.
-   Or use the -mb option.
-
-
-
- ---------------------------
- [1] MOUSE/JOYSTICK/KEYBOARD
- ---------------------------
-
- * My mouse/joystick does not work in SRB2.
-
-   First, check that the mouse/joystick is activated : go at the
-   console and type either 'use_mouse' (or use the respective
-   menuitem) or 'use_joystick'.
-
-   If it tells '0' or off than the mouse/joystick is not used,
-   set the variable to 1. eg: 'use_mouse 1'.
-
-   For the joystick, different values will support different
-   types of joystick, check the console documentation for the
-   command 'use_joystick' for more.
-
-   Even if the mouse or joystick is activated, you have to
-   set up the contols into the Setup Controls menu. That is:
-   tell what use you will make of the mouse/joystick buttons.
-
-
- ---------
- [2] VIDEO
- ---------
-
-
- * Where are the other video modes ? I have only '320x200' in the
-   Video Modes menu.
-
-   DOS
-   ---
-
-   SRB2 adds new video modes only if a VESA2 (or better) driver
-   is present. The VESA2 driver is a standard of 'talking' between a
-   program and the huge amount of different graphics cards
-   available today.
-
-   If you don't have a VESA2 driver, you can download UNIVBE, or
-   SMART DISPLAY DOCTOR from
-
-   http://www.scitechsoft.com/products/ent/free_titles.html
-
-   or if you have an S3 based card, you can download the free
-   software called 'S3VBE'.
-
-   ftp://ftp.externet.hu/pub/mirror/sac/graph/s3vbe318.zip
-   ftp://ftp.digsys.bg/pub/simtelnet/msdos/graphics/s3vbe318.zip
-   http://www.filesearching.com/cgi-bin/s?q=s3vbe318.zip
-   http://www.google.com/search?q=s3vbe318.zip
-
- * The game doesn't restore the video mode I have chosen the last time
-   I played SRB2.
-
-   The current video mode has to be made the 'default' so that it is
-   saved to the config : press the key 'D' on the Video Options menu
-   to set the current video mode the default.
-
-  * I have some problems with OpenGL mode
-
-   Have a look at the FAQ for OpenGL on the glLegacy web site:
-
-   http://www.doomnation.com/gllegacy/faqe.htm
-
- # Linux: I only have a 1024x768 (or 800x600, 1280x1024, ...) resolution
-   in fullscreen mode under X and SRB2 is really really slow. Can I
-   have lower resolutions like 320x200 in fullscreen mode as well?
-
-   Probably yes. SRB2 can only use the resolutions offered by the
-   X-Server. So if all fullscreen modes have a very high resolution you
-   have to modify /etc/XF86Config (or /etc/X11/XF86Config). Use XF86Setup
-   (or the appropriate tool coming with your distribution - sax,
-   xf86config, ...) to do this.
-   If you do not succeed there, you can enter them manually into your
-   XF86Config file. ONLY RECOMMENDED FOR USERS WHO KNOW WHAT THEY DO!
-   For a short guide on how to do this, have a look at the file
-   "Doublescan.txt".
-   In case of doubt consult the XFree86-HOWTO (or ask your system
-   administrator :).
-
- # Linux: I cannot have any fullscreen modes at all!
-
-   You have only modes above 1024x768 in your XF86Config. Proceed as
-   described above.
-
- # Linux: After a certain idle time my screensaver jams the display of
-   SRB2. I can still operate SRB2, but I do not see what's happening
-   and the screensaver won't go away.
-
-   You probably have KDE. The KDE screensaver does not obey the screensaver
-   rules (at least mine, version 1.1). The solution is to deactivate the
-   KDE screensaver and use another screensaver (like the xscreensaver,
-   e.g.). But the hell, when you started SRB2 you should have played it
-   as well and not left it alone!!!
-
- ---------
- [3] SOUND
- ---------
-
- + DOS:I can't have CD audio music, why ?
-
-   Make sure that the MSCDEX driver version 2.0 or later is loaded.
-   If it says 'MSCDEX version xxx' at game startup, and you still
-   don't hear the cd music, then probably your card doesn't respond
-   when SRB2 tries to set the cd volume. If so, make sure your sound
-   card's mixer have the cd volume set up so that you can hear something.
-
- + When the CD plays, the game is very 'jerky'. It doesn't do that when
-   I type 'cd off' in the console.
-
-   You have an old/bad cd driver, that can take up to a second to
-   respond to cd driver commands. Either get the latest version of
-   your driver, or turn cd update off. Check 'cd_udpate' in the
-   console documentation for more.
-
- * DOS:How can I *ALWAYS* disable the sounds or music of the game ?
-
-   Edit the allegro.cfg file and set digicard/midicard to 0 (none)
-
- * DOS:My sterero sound is reversed, how can I set it the right way ?
-
-   Change the console variable 'stereoreverse' to either 1 or 0.
-   Or, you can edit the allegro.cfg file, and set the 'flip_pan' variable.
-
-
- * DOS:The sounds are too 'slow', or 'low-pitched'
-
-   It seems to be a problem of the auto-detection of some 8bit sound
-   cards. You will have to set manually the 'sb_freq' value in the
-   allegro.cfg file to a lower value : 11906, 16129.
-
- * DOS:SRB2 doesn't play any sound/music, but I have a sound
-   blaster genuine/compatible card.
-
-   If you have a genuine or compatible SoundBlaster card, it is very
-   important that you set the BLASTER environment variable.
-
-   If you are playing under DOS, and never installed your sound card
-   under DOS, run the setup of your sound card for DOS.
-
-   Check if the BLASTER variable was set: type 'SET' under dos
-   (or DOSbox)
-
-   Do you see something like 'BLASTER=A220 I5 D1 ...' ?
-
-   Yes? If you don't hear sounds/music, then tweak the settings in the
-   allegro.cfg file until you get something, first try changing the
-   type of the sound card, it is not always properly detected.
-
-   No? You have to set this variable in order that your sound card is
-   detected. Run the setup that was shipped with your sound card, and
-   make sure you run the setup for DOS too, it will usually add a
-   line of the type 'SET BLASTER=... ...' in the autoexec.bat file.
-
-
- * DOS:How can I have better midi music on my 8bit sound card ?
-
-   Use the DIGMID driver, it is supported in SRB2.
-
-   What the hell is this? Well, the Gravis Ultrasound uses digital
-   samples to play midi music. On a simple 8bit card, you can use digital
-   samples too, which will sound usually better than what is output
-   by the poor fm synthesis chip of 8bit cards.
-
-   You will need to get a Gravis Ultrasound patch set, you can find
-   several ones for free on internet, it consists of a bunch of '.pat'
-   files which are the digital samples to play the midi instruments
-   (eg: piano, conga, guitar, ect.).
-
-   Check the Allegro homepage for some links to GUS patches:
-   http://alleg.sourceforge.net/digmid.html
-   http://alleg.sourceforge.net/
-   http://www.talula.demon.co.uk/allegro/digmid.html
-   http://www.talula.demon.co.uk/allegro/
-
-   Now to activate the DIGMID driver:
-
-   Set the 'midi_card' value to 8 (DIGMID) in the allegro.cfg file.
-   Make sure you leave the 'digi_voices' blank, or set it to a low
-   value, because the midi music will use digital voices.
-   At the end of the allegro.cfg file, set the 'patches' value
-   to the path, where you have installed a Gravis Ultrasound midi
-   patch set. eg: patches = d:\music\midipat\
-
- # Linux: CD music does not work or only works when run as root.
-
-   We do not encourage you to run SRB2 as root (you never know
-   what SRB2 can do to your system - it's a mighty piece of code :).
-   There is a common problem with ATAPI CD-rom drives, which are
-   treated as harddisks. Usually there is a link /dev/cdrom pointing to
-   device hd[b,c,d]. As harddisks are not supposed to be read directly
-   via this device (especially not by a common user), there are no read
-   permissions for "all". For CD-roms you can savely set read permissions
-   unless you are very paranoid. Assuming your CD-rom drive is /dev/hdc,
-   set permissions with "chmod +r /dev/hdc" (as root). SCSI CD-rom drives
-   should not have this problem. But if they do, proceed as described
-   with ATAPI drives.
-
- # Linux: The CD music volume is not set properly.
-
-   Go to the console and type "jigglecdvolume 1".
-
- -----------
- [4] NETWORK
- -----------
-
- * Where can I find Internet servers ?
-
-   For the moment there is one public server.
-   http://srb2.servegame.org/ Master server web page
-   srb2.servegame.org:28910 current Master Server
-
- * When I start SRB2 with -server or -connect it say :
-    "BinToPort: Address already in use (EADDRINUSE)"
-
-   It appears only when SRB2 crashes or when you leave with ctrl-break.
-   use -udpport 12345 (or any other free slot) on both sides (client and
-   server).
-
-   This can also happens when there is already a SRB2 running on your
-   computer if you whant to try two SRB2 running on the same computer
-   use -clientport 12345 (or any other free slot). Then the second will
-   connect to the first one.
-
- * Do you use the tcp protocol ?
-
-   No, we use the udp protocol which is faster, but don't worry udp is a
-   part of the internet protocol.
-
-
- -------------------
- [5] Troubleshooting
- -------------------
-
- # Linux: SRB2 is hung in fullscreen mode and won�t let me leave.
-   What shall I do?
-
-   Some people press the reset button, but hey, we are not in the
-   stoneage of operating systems! There are two "proper" ways to
-   get out: kill your X-Server. You can usually do this by pressing
-   "CTRL-ALT-BACKSPACE". But if you have other open applications with
-   important data (probably hacked away on your diploma thesis for 3
-   weeks without saving once) you can also kill SRB2 directly. Press
-   "CTRL-ALT-F2" and you will get to a console. Log in, type
-   "killall llxSRB2" and switch back to the X-Server with "CTRL-ALT-F7".
-   Some X-Server crash on this procedure - blame the X-Server for the
-   loss of 3 weeks work on your diploma thesis :)
diff --git a/doc/manual/manual.htm b/doc/manual/manual.htm
deleted file mode 100644
index 3fea1b66f18b694468c9103064e8c6fd1f33efd3..0000000000000000000000000000000000000000
--- a/doc/manual/manual.htm
+++ /dev/null
@@ -1,68 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-<html>
-<head>
-	<title>
-		Sonic Robo Blast 2 Manual
-	</title>
-	<link rel="stylesheet" type="text/css" href="srb2manstyle.css">
-	<!-- Borrowed some javascript code so the height of the iframe is equal to the size of the document - Sonict -->
-	<script type="text/javascript">
-	/* free code from dyn-web.com */
-
-	function getDocHeight(doc) {
-		doc = doc || document;
-		// from http://stackoverflow.com/questions/1145850/get-height-of-entire-document-with-javascript
-		var body = doc.body, html = doc.documentElement;
-		var height = Math.max( body.scrollHeight, body.offsetHeight,
-			html.clientHeight, html.scrollHeight, html.offsetHeight );
-		return height;
-}
-
-	function setIframeHeight(id) {
-		var ifrm = document.getElementById(id);
-		var doc = ifrm.contentDocument? ifrm.contentDocument: ifrm.contentWindow.document;
-		ifrm.style.visibility = 'hidden';
-		ifrm.style.height = "10px"; // reset to minimal height in case going from longer to shorter doc
-		ifrm.style.height = getDocHeight( doc ) + "px";
-		ifrm.style.visibility = 'visible';
-	}
-	</script>
-	<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
-</head>
-<body>
-	<p class="c1">
-		<img src="manual_img/sonicname2.png" alt="SONIC" width="136" height="36">
-		<br>
-		<img src="manual_img/srb2banner.png" alt="ROBO BLAST 2" width="224" height="43">
-	</p>
-	<p class="c1">
-		<big><big><strong>Manual</strong></big></big>
-	</p>
-	<table class="cf" align="center">
-		<tr><td class="c">
-		<ul class="hmenu">
-			<li class="hmenu"><a class="hmenu" href="intro.htm" target="ifrm">Main</a></li>
-			<li class="hmenu"><a class="hmenu" href="items.htm" target="ifrm">Items</a></li>
-			<li class="hmenu"><a class="hmenu" href="playerabilities.htm" target="ifrm">Player Abilities</a></li>
-			<li class="hmenu"><a class="hmenu" href="basicplay.htm" target="ifrm">Gameplay</a></li>
-			<li class="hmenu"><a class="hmenu" href="surroundings.htm" target="ifrm">Surroundings</a></li>
-		</ul>
-		</td></tr>
-		<tr><td class="c">
-		<ul class="hmenu">
-			<li class="hmenu"><a class="hmenu" href="multiplayer.htm" target="ifrm">Multiplayer</a></li>
-			<li class="hmenu"><a class="hmenu" href="zones.htm" target="ifrm">Zones</a></li>
-			<li class="hmenu"><a class="hmenu" href="controls.htm" target="ifrm">Controls</a></li>
-			<li class="hmenu"><a class="hmenu" href="consolecommands.htm" target="ifrm">Console Commands</a></li>
-			<li class="hmenu"><a class="hmenu" href="misc.htm" target="ifrm">Misc</a></li>
-		</ul>
-		</td></tr>
-	</table>
-	<hr>
-	<p class="c1">
-		<!-- The "onload" property of the iframe makes an error when validated through the W3C validation checker. -->
-		<!-- This will not be fixed as it isn't worth the time to fix it up properly. - Sonict -->
-		<iframe id="ifrm" name="ifrm" src="intro.htm" onload="setIframeHeight(this.id)"> </iframe>
-	</p>
-</body>
-</html>
diff --git a/doc/rules.txt b/doc/rules.txt
deleted file mode 100644
index 4bbb8cdc1f786022bc0f090154de4e7733080d32..0000000000000000000000000000000000000000
--- a/doc/rules.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-SVN-RULES
-
-- As you can see, there is sub-directory in the repository, one for eatch
-  platform (djgpp (dos),win32,SDL) the root directory is for all platform,
-  so take care of the order we have put in.
-- do not commit/upload tests of bugged code, try to fix a maximum of know
-  bugs and update know bugs list in source.txt. If you must commit your source
-  make your code in #ifdef so we can disable it
-- SRB2 is a modification of doom/Doom Legacy source. We allow additionnal feature
-  and visual addition.
-- Maximize communications between members, do not impose your changes, if your
-  are not sure about a feature/change, talk about it in irc://irc.esper.net/srb2 chat room.
-
-CODE-RULES
-
-- We use no tab, 4 space indent, and tab size 8 (in case some tab have filtred
-  and for makefile)
-- Self documented code, variable and function must have a name that help
-  understand the code, so do not call variable and function a,b, a2, ...
-- the usage of extern in a c file is prohibited, except for declaration of a
-  function with body (so it is like public keyword in c++)
-  Also function protos haren't allowed for external function, put it un the
-  corresponding h file.
-- Try to minimize #ifdef usage for :
-  - code readability
-  - the main code is for all port so if something is good for a platform all
-    platform can benefit by this feature
-- Take care of platform dependent code, we would like to have code that work
-  on Dos, Win32, SDL, ... little and big endian, software/Glide/OpenGl.
-
-GOOD PRACTICE
-
-- Try to put as mush static variable and function on module so it help to
-  understand the role of the varaible/function in the module also this
-  help the compiler to optimize
-- minimise global variable
-- make a log of your work, so you don't need to put a lot of comment in
-  the code, this will also help us to update the what's new section of doc
-  when doing final release
diff --git a/doc/source.txt b/doc/source.txt
deleted file mode 100644
index 5926d95fb94d9a31edccac1266cbfdb9661bda07..0000000000000000000000000000000000000000
--- a/doc/source.txt
+++ /dev/null
@@ -1,240 +0,0 @@
-
-1. Compile SRB2
-2. Explanation of the code
- 2.1 The memory model
- 2.2 Hardware Texture model
-
-1. Compile SRB2
-=================
-
-DOS
----
-
-need:
-- djgpp 2.03     (http://www.delorie.com/djgpp/)
-- allegro 3.12   (http://alleg.sourceforge.net/index.html)
-(
-- libsocket 0.7.4 (beta 4) or better
-   for use with Winsock 1.1 (example Windows 3.1)
-  (http://www.phekda.freeserve.co.uk/richdawe/lsck/lsck.htm)
-   OR
-- Wattcp-32 v2.2 dev.rel 6 or better
-   For use with a packet driver
-  (http://www.bgnett.no/~giva/)
-  (http://groups.yahoo.com/group/watt-32/)
-  (http://groups.yahoo.com/group/watt-32/files/v2.2/)
-)
-- bcd 1.03 (inlcude in this package)
-- gcc 2.95.2 is recommended
-- nasm 0.98 (or better)     (http://nasm.sourceforge.net/)
-
-compile:
-make
-make WATTCP=1 (to make a Wattcp-32 version)
-
-debug:
-when craching SRB2 will return eip
-type make asm, then you will have a 8 megs srb2.s (a assembler file)
-the you can find the faulting instruction and function
-
-------------------------------------------------------------------------
-
-Linux/SDL
------
-
-need:
-- tested with gcc 2.95 and 3.X.
-- SDL 1.2
-- SDL_mixer 1.2
-- ibogg and libvorbis (http://Xiph.org/)
-- nasm 0.98 (or better)(http://nasm.sourceforge.net/) only with 2.95, else add CC30=1
-
-compile
-make LINUX=1
-
-debug:
-gdb ?
-
-------------------------------------------------------------------------
-
-Win32
------
-
-need :
-- glide 3.x sdk (optional)  (http://www.3dfx.com)
-- directx6 sdk (or higher)  (http://www.micosoft.com/directx)
-- nasm 0.98 (or better)     (http://nasm.sourceforge.net/)
-- use src\win32\wLegacy.dsp
-- VC6 should also work with VC5, and VS.NET 200X
-
-debug:
-press on "step over" with the release version (there is some debug info
-on it). Then change the eip in the regster window when you will type
-enter the edi will go to the faulting instruction. don't forget that
-you can follow the stack for calls.
-You can also use trace with the debug version but add -win and -nomouse.
-
-------------------------------------------------------------------------
-
-Win32/minGW/SDL
------
-
-need:
-- tested with gcc 2.95 and 3.X.
-- can also use Dev-C++ 5.0 beta 9 (4.9.9.0) from http://www.bloodshed.net/dev/devcpp.html
-- SDL 1.2
-- SDL_mixer 1.2
-
-compile
-make minGW=1 SDL=1
-or use src\SDL\Win32SDL.dev with Dev-C++ 4.9.9.0 or later
-
-debug:
-gdb ?
-
-------------------------------------------------------------------------
-
-WinCE/SDL WIP
------
-
-need:
-- ActiveSync 3.8
- http://www.microsoft.com/windowsmobile/downloads/activesync38.mspx
-
-- ActiveSync 3.7.1, if 3.8 isn't available for your language
- http://www.microsoft.com/windowsmobile/downloads/activesync37.mspx
-
-- eMbedded Visual Tools 3.0 - 2002 Edition
- http://www.microsoft.com/downloads/details.aspx?FamilyID=f663bf48-31ee-4cbe-aac5-0affd5fb27dd
-
-- Pocket PC 2000 SDK
- http://www.microsoft.com/downloads/details.aspx?FamilyID=bb3f4d7b-de2a-4e1a-a175-26a68c301ac4
-
-- Pocket PC 2002 SDK (eMVT 3.0 2002 Ed. comes with this)
- http://www.microsoft.com/downloads/details.aspx?FamilyID=2dbee84a-bd94-4167-b817-2b2e548b2e92
-
-- Pocket PC 2002 SDK Emulator Images (eMVT 3.0 2002 Ed. comes with this)
- http://www.microsoft.com/downloads/details.aspx?FamilyID=25f4de97-ae80-477a-9df1-496b85b3d3e3
-
-- eMbedded Visual C++ 4.0
- http://www.microsoft.com/downloads/details.aspx?familyid=1DACDB3D-50D1-41B2-A107-FA75AE960856
-
-- eMbedded Visual C++ 4.0 SP3 (Win CE 4.0-4.2)
- http://www.microsoft.com/downloads/details.aspx?FamilyID=5bb36f3e-5b3d-419a-9610-2fe53815ae3b
-
-  OR
-
-- eMbedded Visual C++ 4.0 SP4 (No SH3 support,Win CE 4.0-5.0 support)
- http://www.microsoft.com/downloads/details.aspx?FamilyID=4a4ed1f4-91d3-4dbe-986e-a812984318e5
-
-- eMbedded Visual C++ 4.0 Update 5625 (SP4 only)
- http://www.microsoft.com/downloads/details.aspx?FamilyID=aa282a6d-6f57-436d-8c10-0ec02d94f5b1
-
-- Windows CE: Standard Software Development Kit
- http://www.microsoft.com/downloads/details.aspx?familyid=a08f6991-16b0-4019-a174-0c40e6d25fe7
-
-- SDK for Windows Mobile 2003-based Pocket PCs
- http://www.microsoft.com/downloads/details.aspx?FamilyId=9996B314-0364-4623-9EDE-0B5FBB133652
-
-- Emulator Images for Windows Mobile 2003 Second Edition software for Pocket PC
- http://www.microsoft.com/downloads/details.aspx?familyid=5C53E3B5-F2A2-47D7-A41D-825FD68EBB6C
-
-- Microsoft Device Emulator 1.0 Community Preview
- http://beta.microsoft.com Use Guest ID "MSDEVICE" to access the Community Preview website
-
-- Windows CE Utilities for Visual Studio .NET 2003 Add-on Pack 1.1
-  (if you also have VS 2003 installed, you need this to install Win CE 5.0 SDK, else no need)
- http://www.microsoft.com/downloads/details.aspx?FamilyId=7EC99CA6-2095-4086-B0CC-7C6C39B28762
-
-- Windows CE 5.0: Standard Software Development Kit (eMC++ 4 SP4 only)
- http://www.microsoft.com/downloads/details.aspx?FamilyID=fa1a3d66-3f61-4ddc-9510-ae450e2318c3
-
-- SDL 1.27 (use patch and zip in tools\SDL1.2.7_CE)
-
-compile
-use src\SDL\WinCE\SRB2CE.vcw
-
-debug:
-?
-
-
-2. Explanation of the code
-==========================
-
- 2.1 The memory model (original) (z_zone.c) (by BP)
- --------------------
-
- SRB2 allocate a heap of 6/32/48 megs at begining and provide a Z_Malloc function
- to allocate in this heap.
-
-   Z_Malloc( int size,int tag,void* user )
-
-    size is the size in byte
-    tag can be : PU_STATIC   allocated static (like malloc do)
-                             call Z_Free to free it
-                 PU_LEVEL    same as static but the zone is "tagged" with the
-                             tag PU_LEVEL, when calling
-                             Z_FreeTag (PU_LEVEL, PU_LEVEL) all zone tagged
-                             with PU_LEVEL are freed (at the end of the level)
-                 PU_CACHE    this one _can_ be freed automatiquely by one other
-                             call to Z_Malloc. the *user point to a pointer
-                             to this zone so when freed automatiquely the
-                             pointer is set to NULL so eatch time you use it
-                             you must check if the pointer is not NULL and
-                             reload it.
-
- (...)
-
- 2.2 Hardware Texture model (by BP)
- --------------------------
-
- Eatch texture/patch/flats/pic in SRB2 are converted to hardware texture at
- runtime (the GlideMipmap_s structure (hw_data.h)). I will call hardware
- texture a gr_texture so there is no confusion.
-
- To remind you :
-  - Texture are set of patch and are associate to linedefs (walls) can be
-    upper, lower or middle texture. It can have hole on it.
-  - patch are sprites (the doom patch are run of vertical lines)
-  - flats are used for floors and ceiling of sectors and have size of 64x64
-    it can't have hole on it
-  - pic are new legacy format for picture, it can only handle plain texture
-    like flats it is now used for hud in full screen for the main picture
-    of legacy and for coronas (the format was extended to handle 32 bit color
-    or intensity + alpha, not all are implemented at this time)
-
- Since patch, flat and pic are basic structure represented by only one lump in
- the wad, the wad loader allocate for eatch lump a GlideMipmap_s (cache3Dfx)
- and init data field to NULL. Since the data structure is allocated in
- PU_3DFXCACHE (like PU_CACHE) the data will be initilised when needed
- (hw_cache.c).
-
- The GlideMipmap_s structures for textures are initialized on
- HWR_PrepLevelCache (hw_cache.c) it is called in P_SetupLevel (load level)
- the number of textures is computed with TEXTURE1, TEXTURE2 lumps since this
- can be changed in runtime in SRB2 (load a wad while runing) it must be
- reallocated. Well, at this time it is realloceted at eatch level start. We
- can do better, since numtextures change only when a wad is loaded.
-
- The 3dfx driver use glide3, it load gr_texture in gr_texture memory of the
- card in fifo order when there is no more place it remove the first gr_texture,
- the downloaded field of GlideMipmap_s go to false and when needed it is
- reloaded in gr_texture memory. In OpenGl, since OpenGl keep texture in there
- own memory and handle gr_texture memory of the card we no more need to
- redownload it but if we not free time to time gr_texture memory in opengl,
- it will get alot of memory, so the gr_texture memory is cleared at eatch
- new level (same time of texture reallocation). Opengl and 3dfx link the
- loaded gr_texture with the nextmipmap field of GlideMipmap_s so before clear
- textures of the heap we MUST free gr_texture memory of OpenGl or 3dfx !
-
- SRB2 can also draw patch with a differant colormap (thanks to Hurdler).
- When needed it create the same gr_texture but just with a differant colormap.
- This one is linked with the original in the GlideMipmap_s with the
- nextcolormap field.
-
- So when a polygone with a gr_texture must be drawn, first we check if the
- gr_textures is not allready loaded in hadware memory (downloaded field) if
- not then we check if gr_texture data is there (not grabbed by z_malloc
- function) if not we must recompute it eatch type of gr_texture (texture,
- patch, flat, pic have there own methode) the we can send the gr_texture
- to 3dfx or OpenGl.
diff --git a/doc/specials.html b/doc/specials.html
deleted file mode 100644
index 2cc840632e4a32264dc0b089ab76fb8647683c38..0000000000000000000000000000000000000000
--- a/doc/specials.html
+++ /dev/null
@@ -1,1992 +0,0 @@
-<html>
-
-<head>
-<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-<title>Sonic Robo Blast II - Specials Reference Document</title>
-</head>
-
-<body bgcolor="white" text="black" link="blue" vlink="blue" alink="blue">
-
-<h1><big>SRB2 Specials Reference Document</big></h1>
-
-<p><i>Last updated May 27, 2008</i></p>
-
-<p><i>For v1.1 Private Beta</i></p>
-
-<p>This is the SRB2 Specials Reference Document. It is designed to be the ultimate
-reference for effects used in SRB2. As such, it is rather technical in areas and quite
-concise, and is not something a beginner with level design should be dealing with.</p>
-
-<h1><a name="things"></a>Thing Types</h1>
-
-<p>In general, thing bitsets have 4 flags and 3 digits for their height. The bitset is
-0xAAAB, where AAA is the object's height above ground, and B are the Easy, Normal, Hard,
-and Deaf flags. To get the bitset on a normal object, multiply the height desired by 16,
-and then add the existant B. Some objects use 32 as this number, and they will be noted.
-Objects that multiply by 16 can be placed up to 4095 units in the air, while objects that
-multiply by 32 can be placed up to 2047 units in the air. 
-
-<ul>
-  <li><u><big><big>Player Starts</big></big></u><ol>
-      <h3><a name="t1"></a>1 - Player 01 Start</h3>
-      <p>This is the start for the first player in single player mode, cooperative mode, or race
-      mode. This start must be placed on every map, as it is what the game defaults to if the
-      start it is attempting to find isn't there. If there is no Player 1 Start on the map, and
-      the game is confused over where to spawn the player, the game will crash outright.</p>
-      <p>The Deaf tag will make the player spawn from the ceiling, and the object needs to be
-      multiplied by 32 to give height, not 16.</p>
-      <h3><a name="t2"></a>2 - Player 02 Start</h3>
-      <p>This is the start for the second player in cooperative and race mode.</p>
-      <p>The Deaf tag will make the player spawn from the ceiling, and the object needs to be
-      multiplied by 32 to give height, not 16.</p>
-      <h3><a name="t3"></a>3 - Player 03 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t4"></a>4 - Player 04 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t5"></a>5 - Player 05 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t6"></a>6 - Player 06 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t7"></a>7 - Player 07 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t8"></a>8 - Player 08 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t9"></a>9 - Player 09 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t10"></a>10 - Player 10 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t11"></a>11 - Player 11 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t12"></a>12 - Player 12 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t13"></a>13 - Player 13 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t14"></a>14 - Player 14 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t15"></a>15 - Player 15 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t16"></a>16 - Player 16 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t17"></a>17 - Player 17 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t18"></a>18 - Player 18 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t19"></a>19 - Player 19 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t20"></a>20 - Player 20 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t21"></a>21 - Player 21 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t22"></a>22 - Player 22 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t23"></a>23 - Player 23 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t24"></a>24 - Player 24 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t25"></a>25 - Player 25 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t26"></a>26 - Player 26 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t27"></a>27 - Player 27 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t28"></a>28 - Player 28 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t29"></a>29 - Player 29 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t30"></a>30 - Player 30 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t31"></a>31 - Player 31 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t32"></a>32 - Player 32 Start</h3>
-      <p>See Thing <a href="#t2">2</a> for more information.</p>
-      <h3><a name="t33"></a>33 - Player Match Start</h3>
-      <p>This is the start for players in Match and Chaos modes. They should also be placed in
-      Capture the Flag maps as well. There should be 32 of these in a map to assure proper
-      randomization. While it's unelegant, they can be stacked on top of each other without
-      negative effect.</p>
-      <p>The Deaf tag will make the player spawn from the ceiling, and the object needs to be
-      multiplied by 32 to give height, not 16.</p>
-      <h3><a name="t34"></a>34 - CTF Team Start (Red)</h3>
-      <p>This is the start for players on the red team in Capture the Flag mode. There should be
-      32 of these in a map to assure proper randomization. While it's unelegant, they can be
-      stacked on top of each other without negative effect.</p>
-      <p>The Deaf tag will make the player spawn from the ceiling, and the object needs to be
-      multiplied by 32 to give height, not 16.</p>
-      <h3><a name="t35"></a>35 - CTF Team Start (Blue)</h3>
-      <p>This is the start for players on the blue team in Capture the Flag mode. There should
-      be 32 of these in a map to assure proper randomization. While it's unelegant, they can be
-      stacked on top of each other without negative effect.</p>
-      <p>The Deaf tag will make the player spawn from the ceiling, and the object needs to be
-      multiplied by 32 to give height, not 16.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Enemies</big></big></u><ol>
-      <h3><a name="t100"></a>100 - Crawla (Blue)</h3>
-      <p>These are the blue ground enemies found in the one player stages. They can't move off
-      of cliffs and are exceedingly slow.</p>
-      <h3><a name="t101"></a>101 - Crawla (Red)</h3>
-      <p>These are the red ground enemies found in the one player stages. They can't move off
-      cliffs and are relatively slow.</p>
-      <h3><a name="t102"></a>102 - Stupid Dumb Unnamed RoboFish (tm)</h3>
-      <p>This is the little fish in Greenflower Zone. The angle determines the jump height, with
-      0 being the old jump style. Note that the jump height is based on force, not units, so
-      experimentation will be necessary to get the correct height.</p>
-      <h3><a name="t103"></a>103 - Yellow Buzz</h3>
-      <p>This enemy flies at a moderate speed directly at the player.</p>
-      <h3><a name="t104"></a>104 - Red Buzz</h3>
-      <p>This enemy flies at a relatively high speed directly at the player.</p>
-      <h3><a name="t105"></a>105 - Jetty-Syn Bomber</h3>
-      <p>This is a highly mobile flying enemy with a bomb that it drops on the player from
-      directly above. It is considered highly difficult to kill, and should only be used in
-      situations where the stage is supposed to be difficult.</p>
-      <h3><a name="t106"></a>106 - Jetty-Syn Gunner</h3>
-      <p>This is a highly mobile flying enemy with a gun that it fires at the player with high
-      accuracy. It is considered highly difficult to kill, and should only be used in situations
-      where the stage is supposed to be difficult.</p>
-      <h3><a name="t107"></a>107 - Crawla Commander</h3>
-      <p>This is the grey floating enemy in the opening room of Techno Hill Zone Act 2. It is
-      quite fast and will start bouncing after taking the first hit. It is significantly
-      challenging, although a spindash will kill it given time.</p>
-      <h3><a name="t108"></a>108 - Deton</h3>
-      <p>This is the red spherical enemy in Techno Hill Zone Act 2. Upon seeing the player, it
-      makes a mad dash straight for them. With the exception of the <a href="#t404">Armageddon
-      Shield</a>, Detons are invincible, and must be avoided by running behind a wall or another
-      enemy.</p>
-      <h3><a name="t109"></a>109 - Skim</h3>
-      <p>This is an enemy that floats on the surface of the water, dropping bombs into the water
-      below. It is not currently used in any of the Single Player stages, but it is fully
-      operational. The designer does not have to put them on the surface of the water, they know
-      where it is.</p>
-      <h3><a name="t110"></a>110 - THZ Turret</h3>
-      <p>This is the turret from Techno Hill Zone Act 2. It fires large bursts of laser fire at
-      the player with high accuracy. It is invincible unless it is somehow dipped into water.</p>
-      <h3><a name="t111"></a>111 - Popup Turret</h3>
-      <p>This is a small turret that pops up now and then and shoots. The object's angle is a
-      value defining the delay between shooting.</p>
-      <h3><a name="t112"></a>112 - Sharp</h3>
-      <p>This is a blue enemy with spikes on top if it. It starts off by slowly chasing the player,
-      then it fades to red and runs after the player, and is invincible until it fades back to red.</p>
-      <h3><a name="t113"></a>113 � Jet Jaw</h3>
-      <p>This is an underwater enemy that tries to bite at the player, which can be found in
-      Deep Sea Zone.</p>
-      <h3><a name="t114"></a>114 � Snailer</h3>
-      <p>This is an incomplete enemy.</p>
-      <h3><a name="t115"></a>115 � Bird Aircraft Strike Hazard (B.A.S.H.)</h3>
-      <p>This is the red vulture-like enemy in Arid Canyon Zone. If it sees a player, it lifts off and
-      charges at him. Collision with a wall will send it plummeting.</p>
-      <h3><a name="t116"></a>116 � Pointy</h3>
-      <p>This is the orbinaut enemy that has spikes circling around it. None of the single player stages
-      currently use him. You have to place him in a map by using a WAD editor, he can't be placed using
-      objectplace.</p>
-      <h3><a name="t117"></a>117 � Robo-Hood</h3>
-      <p>This is the green enemy from Castle Eggman Zone, which shoots arrows at the player.</p>
-      <h3><a name="t118"></a>118 � CastleBot FaceStabber</h3>
-      <p>This is the large grey enemy from Castle Eggman Zone. It slowly trudges towards the player,
-      and if the player in range, lunges at them with his sword.</p>
-      <h3><a name="t119"></a>119 � Egg Guard</h3>
-      <p>This is the enemy from Castle Eggman Zone that wields a protective shield.</p>
-      <h3><a name="t120"></a>120 � Green Snapper</h3>
-      <p>This is the green turtle enemy from Arid Canyon Zone. This enemy behaves exactly like a blue
-      crawla does. The circumference of its shell is covered with spikes, so the only way to destroy it
-      is by jumping on top of it.</p>
-      <h3><a name="t121"></a>121 � Minus</h3>
-      <p>This is the digging enemy from Arid Canyon Zone. It burrows underground towards the player, and
-      once it's directly underneath, it bursts out from under the ground, jumping up and hurting the player.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Bosses and their associated items (if any)</big></big></u><ol>
-      <h3><a name="t200"></a>200 - Egg Mobile (Boss 1)</h3>
-      <p>The boss of Greenflower Zone and Castle Eggman Zone. He moves around firing at the
-      player, and after taking six hits, he dashes at the player.</p>
-      <p>Giving the boss the Deaf flag will make him have spikeballs, like CEZ3, and giving him
-      the Multi flag will make the level end when he is dead. To place him above ground,
-      multiply by 32 to give height, not 16.</p>
-      <h3><a name="t201"></a>201 - Egg Slimer (Boss 2)</h3>
-      <p>This is the boss of Techno Hill Zone. It requires an axis point at the center to
-      function, and it goes in a circle around the axis point dropping slime. After 6 hits, he
-      stops going in a circle, and bouncing at the player, spewing a lot more slime.</p>
-      <p>The Multi flag will make the level end when he is dead.</p>
-      <h3><a name="t201"></a>202 - Sea Egg (Boss 3)</h3>
-      <p>This is the boss of Deep Sea Zone. More information will be supplied later.</p>
-      <p>The Multi flag will make the level end when he is dead.</p>
-      <h3>203 - Eggscalibur (Boss 4)</h3>
-      <p>This is the boss of Castle Eggman Zone. More information will be supplied later.</p>
-      <p>The Multi flag will make the level end when he is dead.</p>
-      <h3><a name="t290"></a>290 - Boss Flypoint</h3>
-      <p>This is the location the boss will fly to after being killed.</p>
-      <h3><a name="t291"></a>291 - Egg Capsule Center</h3>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Collectibles</big></big></u><ol>
-      <h3><a name="t300"></a>300 - Ring</h3>
-      <p>This is a normal ring. Pick this up to get one ring.</p>
-      <p>Giving the deaf tag to a ring will cause it to float 31 units above the ground. This
-      does stack with bitsets, allowing rings to be a total of 4127 units above the ground at
-      maximum.</p>
-      <h3><a name="t301"></a>301 - Bounce Ring</h3>
-      <p>Picking this up gives you more ammo for this particular ring weapon.  You cannot fire
-      the weapon, however, if you do not have the associated panel.
-      <h3><a name="t302"></a>302 - Rail Ring</h3>
-      <p>See thing <a href="#t301">#301</a>.</p>
-      <h3><a name="t304"></a>304 - Automatic Ring</h3>
-      <p>See thing <a href="#t301">#301</a>.</p>
-      <h3><a name="t305"></a>305 - Explosion Ring</h3>
-      <p>See thing <a href="#t301">#301</a>.</p>
-      <h3><a name="t305"></a>306 - Scatter Ring</h3>
-      <p>See thing <a href="#t301">#301</a>.</p>
-      <h3><a name="t305"></a>307 - Grenade Ring</h3>
-      <p>See thing <a href="#t301">#301</a>.</p>
-      <h3>310 - CTF Flag (Red)</h3>
-      <p>This is the red team's flag in capture the flag mode. If the blue team takes this to
-      their team base (sector type <a href="#s16384">16384</a>), they score a point.</p>
-      <h3><a name="t307"></a>311 - CTF Flag (Blue)</h3>
-      <p>This is the blue team's flag in capture the flag mode. If the red team takes this to
-      their team base (sector type <a href="#s12288">12288</a>), they score a point.</p>
-      <h3><a name="t308"></a>312 - Special Stage Token</h3>
-      <p>This token gives the player a chance at the special stage after the current stage has
-      ended. If more than one token is collected, the player gets that many chances at the
-      special stages, continuing until they run out of tokens or have all the emeralds.</p>
-      <h3><a name="t309"></a>313 - Emerald 1 (Green)</h3>
-      <p>This object gives the player the first emerald as a pickup object, instead of by
-      completing a special stage.</p>
-      <h3><a name="t310"></a>314 - Emerald 2 (Orange)</h3>
-      <p>This object gives the player the second emerald as a pickup object, instead of by
-      completing a special stage.</p>
-      <h3><a name="t311"></a>315 - Emerald 3 (Pink)</h3>
-      <p>This object gives the player the third emerald as a pickup object, instead of by
-      completing a special stage.</p>
-      <h3><a name="t312"></a>316 - Emerald 4 (Blue)</h3>
-      <p>This object gives the player the fourth emerald as a pickup object, instead of by
-      completing a special stage.</p>
-      <h3><a name="t313"></a>317 - Emerald 5 (Red)</h3>
-      <p>This object gives the player the fifth emerald as a pickup object, instead of by
-      completing a special stage.</p>
-      <h3><a name="t314"></a>318 - Emerald 6 (Light Blue)</h3>
-      <p>This object gives the player the sixth emerald as a pickup object, instead of by
-      completing a special stage.</p>
-      <h3><a name="t315"></a>319 - Emerald 7 (Grey)</h3>
-      <p>This object gives the player the seventh emerald as a pickup object, instead of by
-      completing a special stage.</p>
-      <h3><a name="t316"></a>320 - Emerald Hunt Location</h3>
-      <p>This is one of the three emeralds to be used in Hunting mode.</p>
-      <h3>323 - Emerald Spawn</h3>
-      <p>Spawn location for emeralds in Match mode.</p>
-      <h3><a name="t330"></a>330 - Bounce Ring Panel</h3>
-      <p>This is a match weapon panel. The Bounce Ring throws a slow ring that will bounce when
-      it hits walls.</p>
-      <p>Giving the deaf tag to a panel will cause it to float 31 units above the ground. This
-      does stack with bitsets, allowing panels to be a total of 4127 units above the ground at
-      maximum.</p>
-      <h3><a name="t331"></a>331 - Rail Ring Panel</h3>
-      <p>This is a match weapon panel. The Rail Ring gives the player an instantaneous shot, that
-      strikes its target the instant its fired, however there is a long downtime between shots.
-      Being shot by a rail ring causes more kickback than normal.</p>
-      <p>Giving the deaf tag to a panel will cause it to float 31 units above the ground. This
-      does stack with bitsets, allowing panels to be a total of 4127 units above the ground at
-      maximum.</p>
-      <h3><a name="t332"></a>332 - Automatic Ring Panel</h3>
-      <p>This is a match weapon panel. The Automatic Ring gives the player a fire rate of 17.5
-      rings per second.</p>
-      <p>Giving the deaf tag to a panel will cause it to float 31 units above the ground. This
-      does stack with bitsets, allowing panels to be a total of 4127 units above the ground at
-      maximum.</p>
-      <h3><a name="t333"></a>333 - Explosion Ring Panel</h3>
-      <p>This is a match weapon panel. The Explosion Ring throws a slow ring that explodes into
-      many fragments upon striking a wall or another player. Being struck directly by the
-      Explosion Ring causes more kickback than usual.</p>
-      <p>Giving the deaf tag to a panel will cause it to float 31 units above the ground. This
-      does stack with bitsets, allowing panels to be a total of 4127 units above the ground at
-      maximum.</p>
-      <h3><a name="t334"></a>334 - Scatter Ring Panel</h3>
-      <p>This is a match weapon panel. The Scatter Ring throws 5 rings in a plus-shape.</p>
-      <p>Giving the deaf tag to a panel will cause it to float 31 units above the ground. This
-      does stack with bitsets, allowing panels to be a total of 4127 units above the ground at
-      maximum.</p>
-      <h3><a name="t335"></a>335 - Grenade Ring Panel</h3>
-      <p>This is a match weapon panel. The Grenade Ring throws a grenade that will explode
-      if an opposing player gets too close to it.  It will also explode automatically after a
-      while if left untouched.</p>
-      <p>Giving the deaf tag to a panel will cause it to float 31 units above the ground. This
-      does stack with bitsets, allowing panels to be a total of 4127 units above the ground at
-      maximum.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Boxes</big></big></u><ol>
-      <h3><a name="t400"></a>400 - Super Ring (10 Rings)</h3>
-      <p>This monitor gives the player ten rings.</p>
-      <p>If monitors are given the Deaf tag, they will respawn as a random monitor type (not a ?
-      monitor) from the weighted table in modes that support respawn. Elsewise, they will
-      respawn as the same monitor.</p>
-      <h3><a name="t402"></a>402 - Attraction Shield</h3>
-      <p>Also known as the yellow shield and god shield, this shield protects the player from a
-      single hit, then disappears. It also attracts all normal rings, spilled or on the map to
-      the player with the shield. It also protects the player from electric damage.</p>
-      <p>If monitors are given the Deaf tag, they will respawn as a random monitor type (not a ?
-      monitor) from the weighted table in modes that support respawn. Elsewise, they will
-      respawn as the same monitor.</p>
-      <h3><a name="t403"></a>403 - Force Shield</h3>
-      <p>Also known as the blue shield, this shield protects the player from two hits, then
-      disappears. If the spin&nbsp; button is pressed while jumping, it is also possible to
-      reflect many projectiles.</p>
-      <p>If monitors are given the Deaf tag, they will respawn as a random monitor type (not a ?
-      monitor) from the weighted table in modes that support respawn. Elsewise, they will
-      respawn as the same monitor.</p>
-      <h3><a name="t404"></a>404 - Armageddon Shield</h3>
-      <p>Also known as the black shield, this shield protects the player from a single hit,
-      triggering upon a hit. The shield can also be triggered by jumping, and then hitting the
-      spin key in midair. When the shield is triggered, a flash of light damages everything
-      within a large radius, destroying the shield in the process.</p>
-      <p>If monitors are given the Deaf tag, they will respawn as a random monitor type (not a ?
-      monitor) from the weighted table in modes that support respawn. Elsewise, they will
-      respawn as the same monitor.</p>
-      <h3><a name="t405"></a>405 - Whirlwind Shield</h3>
-      <p>This shield protects the player from a single hit, then disappears. If the player does
-      a jump-spin, they will do a second jump in midair, making the maximum height that the
-      player can jump with the shield 224.</p>
-      <p>Versions of SRB2 previous to 1.09 had the Basic Shield in this object number, so make
-      sure to note that if the player loads the map in an older version, that is what they will
-      see.</p>
-      <p>If monitors are given the Deaf tag, they will respawn as a random monitor type (not a ?
-      monitor) from the weighted table in modes that support respawn. Elsewise, they will
-      respawn as the same monitor.</p>
-      <h3><a name="t406"></a>406 - Elemental Shield</h3>
-      <p>Also known as the green&nbsp; shield, this shield protects the player from a single
-      hit, then disappears. While this shield is active, the player cannot drown. It also
-      protects the player from water, fire, and other damage. When the player with this shield
-      spin-dashes, it leaves a trail of fire, which deals fire damage to any enemy that touches
-      it.</p>
-      <p>If monitors are given the Deaf tag, they will respawn as a random monitor type (not a ?
-      monitor) from the weighted table in modes that support respawn. Elsewise, they will
-      respawn as the same monitor.</p>
-      <h3><a name="t407"></a>407 - Super Sneakers</h3>
-      <p>This is a monitor powerup that gives the player about 2x running speed for 20 seconds.</p>
-      <h3><a name="t408"></a>408 - Invincibility</h3>
-      <p>This is a monitor powerup that prevents all damage to the player for 20 seconds.</p>
-      <h3><a name="t409"></a>409 - Extra Life</h3>
-      <p>This powerup monitor features the player's face, and provides an extra life when
-      struck.</p>
-      <p>If monitors are given the Deaf tag, they will respawn as a random monitor type (not a ?
-      monitor) from the weighted table in modes that support respawn. Elsewise, they will
-      respawn as the same monitor.</p>
-      <h3><a name="t410"></a>410 - Eggman</h3>
-      <p>This monitor damages the player if they strike it.</p>
-      <p>If monitors are given the Deaf tag, they will respawn as a random monitor type (not a ?
-      monitor) from the weighted table in modes that support respawn. Elsewise, they will
-      respawn as the same monitor.</p>
-      <h3><a name="t411"></a>411 - Teleporter</h3>
-      <p>This monitor mixes up all locations of players, teleporting them to the location of a
-      random other player. It has no effect in Single Player or in multiplayer modes while only
-      one player is in the game.</p>
-      <p>If monitors are given the Deaf tag, they will respawn as a random monitor type (not a ?
-      monitor) from the weighted table in modes that support respawn. Elsewise, they will
-      respawn as the same monitor.</p>
-      <h3><a name="t412"></a>412 - Random Box</h3>
-      <p>Destroy this monitor and you will get a random powerup, like the boxes in Sonic 2 race
-      mode.</p>
-      <h3><a name="t412"></a>413 - Gravity Boots Box</h3>
-      <p>Destroy this monitor and the gravity will be flipped for a short time.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Interactive Objects (friendly or otherwise)</big></big></u><ol>
-      <h3><a name="t500"></a>500 - Air Bubble Patch</h3>
-      <p>This is the air bubble patch used underwater to give players air. It spawns big bubbles
-      randomly which replenish the player's air.</p>
-      <h3><a name="t501"></a>501 - End Level Sign</h3>
-      <p>This is the sign at the end of the stage. When the player enters the <a href="#s8192">Exit
-      Sector</a>, this sign will start to spin, and end on the face of the player. This sign
-      does not make the stage end, it's just a visual effect for it.</p>
-      <h3><a name="t502"></a>502 - Star Post</h3>
-      <p>Star Posts allow the player to respawn after dying at a point other than the beginning
-      of the stage. There can be up to 32 Star Posts in a map, and they work with the bitsets.</p>
-      <p>Instead of controlling the difficulty and deaf flags, the final digit of the bitset
-      determines the number of the Star Post. 0x0000 is the first one and 0x000f is the
-      sixteenth one. Note that since this overwrites all of the difficulty flags, they will
-      appear in all difficulties, even though 0x0000 would normally mean it wouldn't appear in
-      any difficulty level.</p>
-      <h3><a name="t520"></a>520 - Spikeball</h3>
-      <p>Just like thing <a href="#t521">521</a>, except they do normal damage to the player on
-      contact. </p>
-      <h3><a name="t521"></a>521 - Spikeball (Special Stage)</h3>
-      <p>These are the spikeballs used in the special stages. They harm the player for damage on
-      contact, but only if they are carrying rings.</p>
-      <h3><a name="t522"></a>522 - Ceiling Spike</h3>
-      <p>This is a downward pointing spike for use on the ceiling. Touching the pointy end of
-      the spike deals damage to the player.</p>
-      <p>By default, it attaches itself to the ceiling, and the height part of the bitset
-      measures how far down from the ceiling, instead of up from the floor.</p>
-      <h3><a name="t523"></a>523 - Floor Spike</h3>
-      <p>This is a upward pointing spike for use on the floor. Touching the pointy end of the
-      spike deals damage to the player.</p>
-      <h3><a name="t524"></a>524 - Big Floating Mine</h3>
-      <p>When you get close, this mine will start to follow you. Touches you, and it explodes.</p>
-      <h3><a name="t540"></a>540 - THZ Fan</h3>
-      <p>This is the fan used inside the secret passage in Techno Hill Zone Act 1. It pushes the
-      player slowly up until it reaches the maximum height it can. The maximum height is
-      determined by the angle, measured in normal fracunits (It can go above 360 just fine).</p>
-      <h3><a name="t541"></a>541 - THZ Gas Jet</h3>
-      <p>This is the gas jet used at the end of Techno Hill Zone Act 1. It launches the player
-      straight up on regular intervals about the same height as a yellow spring pointing up.</p>
-      <h3><a name="t550"></a>550 - Yellow Spring (Up)</h3>
-      <p>This is a yellow spring pointing straight up. It has a medium amount of force behind
-      it.</p>
-      <h3><a name="t551"></a>551 - Red Spring (Up)</h3>
-      <p>This is a red spring pointing straight up. It has a large amount of force behind it.</p>
-      <h3><a name="t552"></a>552 - Blue Spring</h3>
-      <p>This is a blue spring pointing straight up. It has a small amount of force behind it.
-      The intent is for this spring to be used underwater. It has about the same effect
-      underwater as a yellow spring does above water.</p>
-      <h3><a name="t553"></a>553 - Yellow Spring (Down)</h3>
-      <p>This is a yellow spring pointing straight down. It has a medium amount of force behind
-      it.</p>
-      <h3><a name="t554"></a>554 - Red Spring (Down)</h3>
-      <p>This is a red spring pointing straight down. It has a large amount of force behind it.</p>
-      <h3><a name="t555"></a>555 - Yellow Spring (Diagonal Up)</h3>
-      <p>This is a yellow spring pointing upwards and in the direction the thing is facing. It
-      has a medium amount of force behind it. When the player touches this spring, he will
-      automatically turn to face the direction the spring is launching the player.</p>
-      <h3><a name="t556"></a>556 - Red Spring (Diagonal Up)</h3>
-      <p>This is a red spring pointing upwards and in the direction the thing is facing. It has
-      a large amount of force behind it. When the player touches this spring, he will
-      automatically turn to face the direction the spring is launching the player.</p>
-      <h3><a name="t557"></a>557 - Yellow Spring (Diagonal Down)</h3>
-      <p>This is a yellow spring pointing downwards and in the direction the thing is facing. It
-      has a medium amount of force behind it. When the player touches this spring, he will
-      automatically turn to face the direction the spring is launching the player.</p>
-      <h3><a name="t558"></a>558 - Red Spring (Diagonal Down)</h3>
-      <p>This is a red spring pointing downward and in the direction the thing is facing. It has
-      a large amount of force behind it. When the player touches this spring, he will
-      automatically turn to face the direction the spring is launching the player.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Special placement patterns</big></big></u><ol>
-      <h3><a name="t600"></a>600 - 5 Vertical Rings (Yellow Spring)</h3>
-      <p>This is a chain of five rings intended to be used with thing <a href="#t550">550</a>.
-      Do not use ring chain objects in any mode where items respawn, because ring chains do not
-      respawn. Use bitsets to create chains in any mode with item respawn.</p>
-      <h3><a name="t601"></a>601 - 5 Vertical Rings (Red Spring)</h3>
-      <p>This is a chain of five rings intended to be used with thing <a href="#t551">551</a>.
-      Do not use ring chain objects in any mode where items respawn, because ring chains do not
-      respawn. Use bitsets to create chains in any mode with item respawn.</p>
-      <h3><a name="t602"></a>602 - 5 Diagonal Rings (Yellow Spring)</h3>
-      <p>This is a chain of five rings intended to be used with thing <a href="#t555">555</a>.
-      Do not use ring chain objects in any mode where items respawn, because ring chains do not
-      respawn. Use bitsets to create chains in any mode with item respawn.</p>
-      <h3><a name="t603"></a>603 - 10 Diagonal Rings (Red Spring)</h3>
-      <p>This is a chain of ten rings intended to be used with thing <a href="#t556">556</a>. Do
-      not use ring chain objects in any mode where items respawn, because ring chains do not
-      respawn. Use bitsets to create chains in any mode with item respawn.</p>
-      <h3><a name="t604"></a>604 - Nights: Circle of Rings</h3>
-      <h3><a name="t605"></a>605 - Nights: Circle of Rings (Big)</h3>
-      <h3><a name="t606"></a>606 - Nights: Circle of Wing Logos</h3>
-      <h3><a name="t607"></a>607 - Nights: Circle of Wing Logos (Big)</h3>
-      <h3><a name="t608"></a>608 - Nights: Circle of Rings and Wings</h3>
-      <h3><a name="t609"></a>609 - Nights: Circle of Rings and Wings (Big)</h3>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Powerup indicators/environmental effects/miscellany</big></big></u><ol>
-      <h3><a name="t700"></a>700 - Ambient Water SFX 1A (Small)</h3>
-      <h3><a name="t701"></a>701 - Ambient Water SFX 1B (Small)</h3>
-      <h3><a name="t702"></a>702 - Ambient Water SFX 2A (Medium)</h3>
-      <h3><a name="t703"></a>703 - Ambient Water SFX 2B (Medium)</h3>
-      <h3><a name="t704"></a>704 - Ambient Water SFX 3A (Large)</h3>
-      <h3><a name="t705"></a>705 - Ambient Water SFX 3B (Large)</h3>
-      <h3><a name="t706"></a>706 - Ambient Water SFX 4A (Extra Large)</h3>
-      <h3><a name="t707"></a>707 - Ambient Water SFX 4B (Extra Large)</h3>
-      <h3><a name="t708"></a>708 - Random Ambience 1</h3>
-      <h3><a name="t709"></a>709 - Random Ambience 2</h3>
-      <h3><a name="t750"></a>750 - Chaos Mode Enemy Spawn</h3>
-      <p>This is where the enemies spawn from in Chaos mode. There should be around 12 of these
-      points on a map with Chaos support.</p>
-      <h3><a name="t751"></a>751 - Teleport Destination</h3>
-      <p>This is the thing to be used with linedef type <a href="#l412">412</a>, the linedef
-      executor that teleports a player. This thing is where the player will spawn in the tagged
-      sector.</p>
-      <h3><a name="t752"></a>752 - Alternate View Point</h3>
-      <p>This is the thing to be used with linedef type <a href="#l422">422</a>, the linedef
-      executor that changes the camera view. This thing is where the camera will be moved to in
-      the tagged sector.</p>
-      <h3><a name="t753"></a>753 - Zoom Tube Waypoint</h3>
-      <p>Waypoints for zoom tubes. Think of Sonic 2's Metropolis Zone, Sonic 3 &amp; Knuckles's
-      Death Egg Zone, and Lava Reef Zone. The lower byte of the ANGLE field specifies the
-      waypoint's number in the sequence, and the upper byte specifies the sequence that the
-      waypoint belongs to. These are used in conjunction with sector type <a href="#s32768">32768</a>
-      and <a href="#s36864">36864</a>.</p>
-      <h3><a name="t754"></a>754 - Push</h3>
-      <h3><a name="t755"></a>755 - Pull</h3>
-      <h3><a name="t756"></a>756 - Street Light Source</h3>
-      <p>This produces a light in OpenGL. It is used in Starlit Warehouse Zone, one of the match
-      stages, as the street lights.</p>
-      <h3><a name="t760"></a>760 - PolyObject Anchor</h3>
-      <p>This is the first of the two points used to set up 'how much to move' a polyobject by
-      when creating it. Angle is the PolyObject ID#.</p>
-      <h3><a name="t761"></a>761 - PolyObject SpawnPoint</h3>
-      <p>This is the second of the two points used to set up 'how much to move' a polyobject by
-      when creating it. Angle is the PolyObject ID#.</p>
-      <h3><a name="t762"></a>762 - PolyObject SpawnPoint Crush</h3>
-      <p>This is the second of the two points used to set up 'how much to move' a polyobject by
-      when creating it. Angle is the PolyObject ID#. This item tells the PolyObject that it
-      should hurt the player.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Greenflower Scenery</big></big></u><ol>
-      <h3><a name="t800"></a>800 - GFZ Flower (Normal)</h3>
-      <p>This is a scenery object from Greenflower Zone. It is the orange flower seen all
-      throughout GFZ and most GFZ-themed custom maps.</p>
-      <h3><a name="t801"></a>801 - GFZ Sunflower</h3>
-      <p>This is a scenery object from Greenflower Zone. It is the large blue sunflower seen all
-      throughout GFZ and most GFZ-themed custom maps.</p>
-      <h3><a name="t802"></a>802 - GFZ Budding Flower</h3>
-      <p>This is a scenery object from Greenflower Zone. It is the small purple flower seen all
-      throughout GFZ and most GFZ-themed custom maps.</p>
-      <h3><a name="t804"></a>804 - Berry Bush</h3>
-      <p>This is a scenery object from Greenflower Zone. It is the green bush with red berries
-      seen all throughout GFZ and most GFZ-themed custom maps.</p>
-      <h3><a name="t805"></a>805 - Bush</h3>
-      <p>This is a scenery object from Greenflower Zone. It is the green bush without the
-      berries seen all throughout GFZ and most GFZ-themed custom maps.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Techno Hill Scenery</big></big></u><ol>
-      <h3><a name="t900"></a>900 - THZ Flower</h3>
-      <p>This is a scenery object from Techno Hill Zone Act 1. It is the metallic white flower.</p>
-      <h3><a name="t901"></a>901 - THZ Alarm</h3>
-      <p>This is a scenery object from Techno Hill Zone Act 2. It is the little alarm in the
-      passage with the first Star Post. It creates noise, but the red visual effect in THZ2 was
-      done with a colormap, not this object.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Deep Sea Scenery</big></big></u><ol>
-      <h3><a name="t1000"></a>1000 - Gargoyle</h3>
-      <p>Pushable gargoyle. Can be stood on top of as well.</p>
-      <p>Giving this the Deaf tag will prevent it from being pushable.</p>
-      <h3><a name="t1001"></a>1001 - Seaweed</h3>
-      <p>Animated seaweed. Intangible scenery.</p>
-      <h3><a name="t1002"></a>1002 - Dripping Water</h3>
-      <p>Water dripping from the ceiling. ANGLE value specifies start delay.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Castle Eggman Scenery</big></big></u><ol>
-      <h3><a name="t1100"></a>1100 - Hanging Chain</h3>
-      <p>This is a scenery object from Castle Eggman, a dungeon chain hanging from the ceiling.</p>
-      <p>By default, it attaches itself to the ceiling, and the height part of the bitset
-      measures how far down from the ceiling, instead of up from the floor.</p>
-      <h3><a name="t1101"></a>1101 - CEZ Torch</h3>
-      <p>This is the torch used in Castle Eggman Zone. It produces light in OpenGL, and it harms
-      the player for fire damage on contact.</p>
-      <h3><a name="t1102"></a>1102 - Eggman Statue </h3>
-      <p>This is the large Eggman statue in Castle Eggman Zone.</p>
-      <h3><a name="t1103"></a>1103 - CEZ Flower</h3>
-      <p>This is a scenery object from Castle Eggman Zone. It is the decaying flower.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Arid Canyon Scenery</big></big></u><ol>
-      <h3><a name="t1200"></a>1200 � Big Tumbleweed</h3>
-      <p>A large moveable tumbleweed that rolls along the floor.</p>
-      <h3><a name="t1201"></a>1201 � Little Tumbleweed</h3>
-      <p>A small movable tumbleweed that rolls along the floor.</p>
-      <h3><a name="t1202"></a>1202 � Rock Spawner</h3>
-      <p>An object which randomly spawns falling rocks, which damage the player on impact.<br>
-      Description on how to use goes here.</p>
-    </ol>
-  </li>
-  <li><u><big><big>Red Volcano Scenery</big></big></u><ol>
-      <h3><a name="t1300"></a>1300 � Horizontal Flame Jet</h3>
-      <p>A stready stream of flames comes out horizontally.</p>
-      <h3><a name="t1301"></a>1301 � Vertial Flame Jet</h3>
-      <p>A stready stream of flames comes out vertially.</p>
-    </ol>
-  </li>
-  <li><u><big><big>Dark City Scenery</big></big></u><ol>
-    </ol>
-  </li>
-  <li><u><big><big>Doom Ship Scenery</big></big></u><ol>
-    </ol>
-  </li>
-  <li><u><big><big>Egg Rock / Final Fight Scenery</big></big></u><ol>
-    </ol>
-  </li>
-  <li><u><big><big>NiGHTS Items</big></big></u><ol>
-      <h3><a name="t1700"></a>1700 - Nights: Axis</h3>
-      <p>Lower 10 bits: Axis number in the mare (0-based) Upper 6 bits: Mare that axis belongs
-      to (0-based). ANGLE value determines the size of the axis to rotate around. If 16384 is
-      added to the ANGLE value, the axis will be inverted.</p>
-      <h3><a name="t1701"></a>1701 - Nights: Axis Transfer (Normal)</h3>
-      <h3><a name="t1702"></a>1702 - Nights: Axis Transfer Line</h3>
-      <h3><a name="t1703"></a>1703 - Nights: Ideya Drone</h3>
-      <p>Angle value sets the NiGHTS timer, in seconds. </p>
-      <h3><a name="t1704"></a>1704 - Nights: Bumper</h3>
-      <p>Lower 4 bits of the flags specify the angle of the bumper in 30 degree increments.</p>
-      <h3><a name="t1705"></a>1705 - Nights: Hoop</h3>
-      <h3><a name="t1706"></a>1706 - Nights: Wing Logo</h3>
-      <h3><a name="t1707"></a>1707 - Nights: Super Loop</h3>
-      <h3><a name="t1708"></a>1708 - Nights: Drill Refill</h3>
-      <h3><a name="t1709"></a>1709 - Nights: Helper</h3>
-      <h3><a name="t1710"></a>1710 - Nights: Egg Capsule</h3>
-      <p>The capsule you need to collect rings to break in NiGHTS. The value of its ANGLE field
-      determines how many rings you need to break it. Just like the axis points, the upper bits
-      (value &gt;&gt; 10) determine the mare it belongs to. For example, an angle value of 1024
-      means it belongs to mare 1 (2nd mare, it's zero based), and requires 0 rings to break.
-      1030 would be mare 1, and 6 rings to break. 2048 would be mare 2, no rings.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Mario Items</big></big></u><ol>
-      <h3><a name="t1800"></a>1800 - Coin</h3>
-      <p>This is a coin, which is essentially a ring with Mario graphics and sound effects.</p>
-      <h3><a name="t1801"></a>1801 - Overworld Goomba</h3>
-      <p>These are the enemies in Mario Koopa Blast 1, and are essentially Crawlas with a Mario
-      graphic.</p>
-      <h3><a name="t1802"></a>1802 - Underworld Goomba</h3>
-      <p>These are the enemies in Mario Koopa Blast 2, and are essentially Crawlas with a Mario
-      graphic.</p>
-      <h3><a name="t1803"></a>1803 - Fire Flower</h3>
-      <p>This is the powerup from the Mario Koopa Blast stages. It changes the player to a white
-      palette, and allows the player to throw fireballs with the fire button. The fireballs fly
-      in a Mario-style bounce trajectory until they hit an enemy or a wall.</p>
-      <h3><a name="t1804"></a>1804 - Koopa Shell</h3>
-      <p>This is the Koopa Shell in Mario Koopa Blast 1. It will bounce around, striking enemies
-      and players.</p>
-      <h3><a name="t1805"></a>1805 - Puma (Mario Jumping Fireball)</h3>
-      <p>This is the fireball used in Mario Koopa Blast 3. The angle determines the jump height,
-      with 0 being the old jump style. Note that the jump height is based on force, not units,
-      so experimentation will be necessary to get the correct height.</p>
-      <h3><a name="t1806"></a>1806 - King Bowser</h3>
-      <h3><a name="t1807"></a>1807 - Axe</h3>
-      <p>The axe used to defeat Bowser in the third Mario level.</p>
-      <h3><a name="t1808"></a>1808 - Bush (Short)</h3>
-      <p>This is a scenery object from Mario Koopa Blast</p>
-      <h3><a name="t1809"></a>1809 - Bush (Tall)</h3>
-      <p>This is a scenery object from Mario Koopa Blast</p>
-      <h3><a name="t1810"></a>1810 - Toad</h3>
-      <p>This is Toad at the end of Mario Koopa Blast 3.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Xmas Items</big></big></u><ol>
-      <h3><a name="t1850"></a>1850 - Xmas Pole</h3>
-      <p>X-Mas scenery object. Looks like a little barber shop pole.</p>
-      <h3><a name="t1851"></a>1851 - Candy Cane</h3>
-      <p>X-Mas scenery object. Looks like a candy cane.</p>
-      <p>Note that Mystic Realm 4 replaces this object with the Sonic 1 palm tree, so any maps
-      loaded while Mystic Realm 4 is loaded will overwrite the image, making any candy canes
-      look like palm trees, which can look kinda stupid.</p>
-      <h3><a name="t1852"></a>1852 - Snowman</h3>
-      <p>X-Mas scenery object. Pushable snowman with a happy face. Can be stood on top of as
-      well. Acts the same as thing <a href="#t1000">1000</a>.</p>
-      <p>Giving this the Deaf tag will prevent it from being pushable.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <h1><a name="linetypes"></a>Linedef Types</h1>
-  <p>Lines may have flags applied to them. The following is a reference of their values.
-  Unless specified otherwise in a line type, the flags behave as follows:</p>
-  <div align="left"><table border="1" width="83%">
-    <tr>
-      <td width="33%">NAME</td>
-      <td width="10%">VALUE</td>
-      <td width="57%">DESCRIPTION</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#EFFECT6"></a>EFFECT6</td>
-      <td width="10%">1</td>
-      <td width="57%">Special use flag #6.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#BLOCKMONSTERS"></a>BLOCKMONSTERS</td>
-      <td width="10%">2</td>
-      <td width="57%">Prevents an enemy from crossing the line. May not work for especially
-      speedy enemies.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#TWOSIDED"></a>TWOSIDED</td>
-      <td width="10%">4</td>
-      <td width="57%">Flag used to indicate if a line is two sided. Do not modify.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#DONTPEGTOP"></a>DONTPEGTOP</td>
-      <td width="10%">8</td>
-      <td width="57%">Unpeg upper texture. Good for moving floors.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#DONTPEGBOTTOM"></a>DONTPEGBOTTOM</td>
-      <td width="10%">16</td>
-      <td width="57%">Unpeg bottom texture. Good for moving ceilings.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#EFFECT1"></a>EFFECT1</td>
-      <td width="10%">32</td>
-      <td width="57%">Special use flag #1.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#NOCLIMB"></a>NOCLIMB</td>
-      <td width="10%">64</td>
-      <td width="57%">Don't allow Knuckles to climb on this wall.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#EFFECT2"></a>EFFECT2</td>
-      <td width="10%">128</td>
-      <td width="57%">Special use flag #2.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#EFFECT3"></a>EFFECT3</td>
-      <td width="10%">256</td>
-      <td width="57%">Special use flag #3.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#EFFECT4"></a>EFFECT4</td>
-      <td width="10%">512</td>
-      <td width="57%">Special use flag #4.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#EFFECT5"></a>EFFECT5</td>
-      <td width="10%">1024</td>
-      <td width="57%">Special use flag #5.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#NOSONIC"></a>NOSONIC</td>
-      <td width="10%">2048</td>
-      <td width="57%">Disable line special if playing as Sonic (Single Player Only).</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#NOTAILS"></a>NOTAILS</td>
-      <td width="10%">4096</td>
-      <td width="57%">Disable line special if playing as Tails (Single Player Only).</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#NOKNUX"></a>NOKNUX</td>
-      <td width="10%">8192</td>
-      <td width="57%">Disable line special if playing as Knuckles (Single Player Only).</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#BOUNCY"></a>BOUNCY</td>
-      <td width="10%">16384</td>
-      <td width="57%">Bounce the player off this line.</td>
-    </tr>
-    <tr>
-      <td width="33%"><a name="#TFERLINE"></a>TFERLINE</td>
-      <td width="10%">32768</td>
-      <td width="57%">Use this on a FOF line special to define the texture &amp; offsets for
-      each side of the FOF. The control sector must have at LEAST the same # of sides as the
-      target sector(s).</td>
-    </tr>
-  </table>
-  </div><hr>
-  <li><u><big><big>Level Parameters / Miscellany</big></big></u><ol>
-      <h3><a name="l1"></a>1 - Per-Sector Gravity</h3>
-      <p>Sets the gravity of the tagged sector or sectors, as a percentage of global gravity
-      (which can be set separately using sector type <a href="#s176">176</a>). The floor height
-      of the control sector is used. If it is 1000, then the target sector will have 100%
-      gravity. If it is 500, the target sector will have 50% of the global gravity. Negative
-      values work as well, but players can't jump down; they'll get stuck to the ceiling, unless
-      the <a href="#NOCLIMB">NOCLIMB</a> flag is checked.</p>
-      <p>You can apply this special to the control sector of an intangible FOF to change the
-      gravity only inside that FOF.</p>
-      <h3><a name="l2"></a>2 - Custom Exit</h3>
-      <p>Tag this to an Exit Sector (type <a href="#s8192">8192</a>) to exit to a custom level,
-      overriding the one set in the map header. The map number you go to is indicated by the
-      front sector's floor. Additionally, if the control linedef's bitset is set to disallow
-      climbing (with the <a href="#NOCLIMB">NOCLIMB</a> attribute, whose value is 64), skip the
-      score tally screen when switching to the new map.</p>
-      <p>If the control linedef has the <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a> flag set,
-      this effect does something super complicated and fun, going to a different level depending
-      on whether the player has all emeralds or not. If the player has seven emeralds, the
-      linedef's front sector's ceiling height will be used. Otherwise, go to the map number
-      indicated by the linedef's front sector's floor. That's <i>only</i> if you set the <a
-      href="#BLOCKMONSTERS">BLOCKMONSTERS</a> flag.</p>
-      <p>If the <a href="#EFFECT4">EFFECT4</a> flag is set, the linedef's front side x offset
-      will be used as the new gametype after the map change, providing it is in range (from 0 to
-      4, inclusive).</p>
-      <h3><a name="l3"></a>3 - Zoom Tube Parameters</h3>
-      <p>X length = speed. Y length = waypoint sequence #. See sector type <a href="#s32768">32768</a>
-      for more information.</p>
-      <h3><a name="l4"></a>4 - Speed Pad</h3>
-      <p>Creates a speed pad. The linedef direction and indicates the direction of the pad. The
-      target sector must have type <a href="#s1280">1280</a> or <a href="#s1536">1536</a> for
-      this to work.</p>
-      <p>If the <a href="#EFFECT4">EFFECT4</a> flag is set, you will not be teleported to the
-      center of the sector when the speed pad is activated.</p>
-      <h3><a name="l5"></a>5 - Camera Scanner</h3>
-      <p>Modifies camera position while the player is in the target sector. The floor and
-      ceiling of the control sector and the angle of the control linedef are the values for
-      CAM_HEIGHT, CAM_DIST, and CAM_ROTATE, respectively. Camera position is reset when the
-      player steps outside the sector.</p>
-      <h3><a name="l6"></a>6 - Disable Linedef</h3>
-      <p>Disables any linedef specials that share the same tag. Will be used in the future to
-      check if a particular level has been previously cleared or not.</p>
-      <h3><a name="l7"></a>7 - Flat Alignment</h3>
-      <p>Aligns floor and/or ceiling flats. The x alignment is specified by the control
-      linedef's x distance (the difference between the x values of its two vertices), and the y
-      alignment is specified by the control linedef's y distance.</p>
-      <p>By default, works on both the floor and ceiling (however, note that skies cannot be
-      &quot;aligned&quot; ;). Adding the <a href="#NOCLIMB">NOCLIMB</a> flag to the linedef will
-      align the floor only, while the <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a> flag will make
-      it align the ceiling only.</p>
-      <h3><a name="l8"></a>8 - Sector Special Parameters</h3>
-      <p>Sets special behavior of a sector's type depending on the flag(s) checked:</p>
-      <p><a href="#NOCLIMB">NOCLIMB</a> - Special only operates when touching ceiling</p>
-      <p><a href="#EFFECT4">EFFECT4</a> - Special operates when touching either the floor or the
-      ceiling</p>
-      <p><a href="#EFFECT3">EFFECT3</a> - Special operates by just touching the sector, rather
-      than having to be inside of it.</p>
-      <h3><a name="l9"></a>9 - Chain Parameters</h3>
-      <p>Sets special behavior of a moving chain as such:</p>
-      <p>x length - # of links on the chain</p>
-      <p>y length - Overall speed (0-15)</p>
-      <p>X offset - Rotation speed on the X axis (0-15)</p>
-      <p>Y offset - Rotation speed on the Z axis (0-15)</p>
-      <p>floorheight - angle to start at (0-15)</p>
-      <p>ceilingheight - maximum rotation speed</p>
-      <h3><a name="l10"></a>10 - Culling Plane</h3>
-      <p>Set like <a href="#l1">line 1</a>, this creates an invisible plane in the sector. If
-      your view is above this plane, lots of things drawn below the height of this plane will be
-      discarded, and if your view is below the plane, a lot of things drawn above the height of
-      the plane will be discarded. This is to tell the game to not draw stuff that you aren't
-      going to see anyway. Do note that the view doesn't have to be in the current sector, you
-      can also be viewing from the side, which may be undesirable. To prevent this problem, you
-      can check the <a href="#NOCLIMB">NOCLIMB</a> flag, which will allow you to 'group' a set
-      of sectors to one control sector in which the culling will only take effect. For example,
-      you have a control sector set up for culling, and have two culling lines in the control
-      sector, tagged to sectors 'A' and 'B'. If the player is in sector 'A' or 'B', the culling
-      will occur, but if the player is in sector 'C', it will not.</p>
-      <h3>11 - Rope Hang Parameters</h3>
-      <p>X length = speed. Y length = waypoint sequence #.</p>
-      <p>EFFECT1 - Don't wrap movement</p>
-      <p>See sector type 45056 for more information.</p>
-      <h3>12 - Rock Spawn Parameters</h3>
-      <p>Sets special behavior of a rock spawner (#1202) as such:</p>
-      <p>length - momentum strength</p>
-      <p>line angle - momentum angle</p>
-      <p>X offset - # of tics to wait until another is spawned</p>
-      <p>Y offset - Rock crumble sprite to use (0-15)</p>
-      <p>NOCLIMB - add some randomization to the momentum</p>
-      <p>&nbsp;</p>
-      <h3>13 - Heat Wave</h3>
-      <p>Applies a heat effect to the screen. Tag this to a sector, or to the control sector of a FOF.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Level Parameters / Miscellany</big></big></u><ol>
-      <h3><a name="l20"></a>20 - Marks first line in PolyObject</h3>
-      <p>Explain here.</p>
-      <h3><a name="l21"></a>21 - Explicitly include a PolyObject line</h3>
-      <p>Explain here.</p>
-      <h3><a name="l30"></a>30 - PolyObject Parameters</h3>
-      <p>Explain here.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Level-Load Effects</big></big></u><ol>
-      <h3><a name="l50"></a>50 - Instant Floor Lower</h3>
-      <p>Makes the floor instantly lower on level load to be at the same height as the lowest
-      floor of any bordering sector.</p>
-      <h3><a name="l51"></a>51 - Instant Ceiling Raise</h3>
-      <p>Makes the ceiling instantly rise on level load to be the same height as the highest
-      ceiling of any bordering sector.</p>
-      <h3><a name="l52"></a>52 - Continuously Falling Sector</h3>
-      <p>Requires two control sectors. Sector continuously falls until its ceiling reaches the
-      floor of the line's back sector, then returns to its original position and keeps falling.
-      Linedef length determines speed. Good for things like intermittently falling lava. If the <a
-      href="#NOCLIMB">NOCLIMB</a> flag is set, it falls upwards, instead of downwards.</p>
-      <h3><a name="l53"></a>53 - Continuous Floor/Ceiling Mover</h3>
-      <p>Must be a two-sided linedef, tagged to another sector on the map. The tagged sector's
-      floor and ceiling will move, first so that they're equal to the floor and ceiling of the
-      linedef's front sector, then so they're equal to the floor and ceiling of the linedef's
-      back sector, then the front sector again, and so on.</p>
-      <p>The speed of the movement is determined by the linedef's length and uses the same units
-      as linetype <a href="#l60">60</a>.</p>
-      <h3><a name="l54"></a>54 - Continuous Floor Mover</h3>
-      <p>Like linetype <a href="#l53">53</a>, but only moves the floor, not the ceiling. Can be
-      used to replace floating platforms in some cases, where only the floor was desired to
-      move.</p>
-      <h3><a name="l55"></a>55 - Continuous Ceiling Mover</h3>
-      <p>Like linetype <a href="#l53">53</a>, but only moves the ceiling, not the floor.</p>
-      <h3><a name="l56"></a>56 - Continuous Two-Speed Floor/Ceiling Mover</h3>
-      <p>Must be a two-sided linedef, tagged to another sector on the map. The tagged sector's
-      floor and ceiling will move, first so that they're equal to the floor and ceiling of the
-      linedef's front sector, then so they're equal to the floor and ceiling of the linedef's
-      back sector, then the front sector again, and so on.</p>
-      <p>The speed of the movement is determined by the linedef's x distance (the first way,
-      towards the front sector) and y distance (the second way, towards the back sector), using
-      the same units as linetype <a href="#l60">60</a>.</p>
-      <p>Unlike linetype <a href="#l53">53</a>, this effect does not slow down when it reaches
-      the end of its movement. Instead, it changes instantly from going in one direction to
-      going in the other. It's designed for making more sophisticated crushers than the crusher
-      type allows (i.e. crushers with varying rise/crush speeds, FOF crushers, crushers with
-      different start points).</p>
-      <h3><a name="l57"></a>57 - Continuous Two-Speed Floor Mover</h3>
-      <p>Like linetype <a href="#l56">56</a>, but only moves the floor, not the ceiling.</p>
-      <h3><a name="l58"></a>58 - Continuous Two-Speed Ceiling Mover</h3>
-      <p>Like linetype <a href="#l56">56</a>, but only moves the ceiling, not the floor.</p>
-      <h3><a name="l59"></a>59 - Activate Floating Platform</h3>
-      <p>This is used to make floating platforms (that move up and down) as well as moving
-      water. In fact, you can use this to make any type of block move vertically. The way it
-      works is somewhat confusing - You use three control sectors, all connected by at least one
-      linedef. Easiest thing to do is make three square sectors together in a row. One of the
-      linedefs on the middle sector should contain the Floor Over Floor line special that you
-      want. This will be the Floor Over Floor control sector. The other two sectors represent
-      the bottommost position you want the Floor Over Floor to reach, and the topmost position
-      you want the Floor Over Floor to reach. The 59 line can be on any of these sectors, as
-      long as you tag it to the middle one. If you still don't understand, look at Greenflower
-      Zone Act 2. If the <a href="#NOCLIMB">NOCLIMB</a> flag is set, the platform will begin
-      moving upwards, rather than downwards.</p>
-      <h3><a name="l60"></a>60 - Activate Floating Platform (Adjustable Speed)</h3>
-      <p>Speed is indicated by linedef length; one unit of speed here is 0.25 fracunits per tic.
-      (Floating platforms made with type <a href="#l59">59</a> move at 2 fracunits per tic.)
-      Aside from the linedef length controlling speed, works exactly like linedef type <a
-      href="#l59">59</a>.</p>
-      <h3><a name="l61"></a>61 - Crusher 1 (Ceiling to Floor)</h3>
-      <p>The crush motion is from the ceiling to the floor. Linedef length indicates crusher
-      speed. See also linetype <a href="#l62">62</a>, Crusher 2.</p>
-      <h3><a name="l62"></a>62 - Crusher 2 (Floor to Ceiling)</h3>
-      <p>Like linetype <a href="#l61">61</a>, Crusher 1, except that it starts in a different
-      place, not synchronised with any crushers that use the Crusher 1 type. The highest ceiling
-      this crusher reaches will be the highest ceiling height of any bordering sector.</p>
-      <h3><a name="l63"></a>63 - Fake Floor</h3>
-      <p>Creates two fake planes, fake floor and fake ceiling. Main textures are not affected,
-      but as far as above/below textures and floor/ceiling flats are concerned, the floor and
-      ceiling height are those of the control sector. As far as collisions, walking, etc. are
-      concerned, the floor and ceiling flats are whatever they normally would be.</p>
-      <p>Fake floor is useful for railings (THZRAIL and WOODRAIL; see THZ2 for examples) and
-      snow effects (making a fake floor of snow just a few units above normal floor, so it looks
-      like the player's feet are buried in the snow).</p>
-      <h3><a name="l64"></a>64 - Appearing/Disappearing FOF</h3>
-      <p>Tag this to any FOF <i>line</i> and this will cause it to appear and disappear
-      intermittently. The line's X length is the amount of time (in tics) that the FOF will
-      appear, and Y length is the amount of time (in tics) that the FOF will disappear. The
-      control sector's floor height allows you to specify an offset (in tics) of how much time
-      will pass before the appearing/disappearing kicks in.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Floor-Over-Floors (FOFs)</big></big></u><ol>
-      <h3><a name="l100"></a>100 - Floor Over Floor: Solid, Opaque, Shadowcasting</h3>
-      <p>This is just a regular old FOF. As with any block, the ceiling of the control sector is
-      the top of the block, and the floor of the control sector is the bottom.
-      &quot;Shadowcasting&quot; means that the light value used in the control sector is used
-      for the area below where the actual FOF appears, as opposed to above it.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL-->
-      <h3><a name="l101"></a>101 - Floor Over Floor: Solid, Opaque, Non-Shadowcasting</h3>
-      <p>See notes for <a href="#l100">100</a>. &quot;Non-shadowcasting&quot; means that the
-      light value you set in the control sector will be used for the area above the FOF, instead
-      of below it.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_CUTLEVEL-->
-      <h3><a name="l102"></a>102 - Floor Over Floor: Solid, Translucent</h3>
-      <p>Useful for windows. The GLASSTEX texture is good for this purpose. You can change the
-      alpha value of the translucency by setting the control linedef's Above texture to a #
-      followed by a three-digit decimal number, 000 to 255. #000 is most transparent, #255 is
-      most opaque. Note that in software mode, there are actually only ten different values that
-      serve as a 'best guess'.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA-->
-      <h3><a name="l103"></a>103 - Floor Over Floor: Solid, Sides Only</h3>
-      <p>A solid FOF that renders sides only, not planes (floor and ceiling). You were supposed
-      to be able to use it to place railings (THZRAIL, WOODRAIL, etc.) on FOFs. It doesn't work
-      for that, because the railings use a different kind of transparency and software mode
-      won't draw them on FOFs. So this one is going on the list of useless effects, right next
-      to linetype <a href="#l104">104</a>.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERSIDES|FF_NOSHADE|FF_CUTLEVEL-->
-      <h3><a name="l104"></a>104 - Floor Over Floor: Solid, No Sides</h3>
-      <p>Like a 3D floor of type <a href="#l101">101</a>, except that sides are not drawn.
-      Supposedly a little bit faster than a normal 3D floor. You can use it when the sides
-      wouldn't be visible anyway.</p>
-      <p>This type of 3D floor will have shadows if and only if you set the control linedef's <a
-      href="#NOCLIMB">NOCLIMB</a> flag.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERPLANES|FF_CUTLEVEL
-If the <a href="#NOCLIMB">NOCLIMB</a> flag is disabled, it also adds FF_NOSHADE-->
-      <h3><a name="l105"></a>105 - Floor Over Floor: Solid, Invisible</h3>
-      <p>For making invisible walls and other strange effects.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_NOSHADE-->
-      <hr>
-      <h3><a name="l120"></a>120 - Floor Over Floor: Water, Opaque</h3>
-      <p>This one looks exactly like linetype <a href="#l100">100</a> ingame, but is a block of
-      water instead of solid.</p>
-      <p>The block will have the attribute of linetype <a href="#l200">200</a> if the <a
-      href="#NOCLIMB">NOCLIMB</a> flag is set.</p>
-      <p>To use the light level of the target sector, utilize the <a href="#EFFECT4">EFFECT4</a>
-      flag.</p>
-      <p>If this is used as lava (Fire Damage), and you set the <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a>
-      flag, you can still pass through the lava.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_RENDERALL|FF_SWIMMABLE|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES-->
-      <h3><a name="l121"></a>121 - Floor Over Floor: Water, Translucent</h3>
-      <p>This one looks exactly like linetype <a href="#l102">102</a> ingame, but is a block of
-      water instead of solid.</p>
-      <p>The block will have the attribute of linetype <a href="#l200">200</a> if the <a
-      href="#NOCLIMB">NOCLIMB</a> flag is set.</p>
-      <p>To use the light level of the target sector, utilize the <a href="#EFFECT4">EFFECT4</a>
-      flag.</p>
-      <p>If this is used as lava (Fire Damage), and you set the <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a>
-      flag, you can still pass through the lava.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_SWIMMABLE|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|
-FF_CUTSPRITES-->
-      <h3><a name="l122"></a>122 - Floor Over Floor: Water, Opaque, No Sides</h3>
-      <p>Like linetype <a href="#l120">120</a>, but doesn't render sides.</p>
-      <p>The block will have the attribute of linetype <a href="#l200">200</a> if the <a
-      href="#NOCLIMB">NOCLIMB</a> flag is set.</p>
-      <p>To use the light level of the target sector, utilize the <a href="#EFFECT4">EFFECT4</a>
-      flag.</p>
-      <p>If this is used as lava (Fire Damage), and you set the <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a>
-      flag, you can still pass through the lava.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_RENDERPLANES|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES-->
-      <h3><a name="l123"></a>123 - Floor Over Floor: Water, Translucent, No Sides</h3>
-      <p>Like linetype <a href="#l121">121</a>, but doesn't render sides. Most of the time this
-      won't make a difference. It can be useful, however, for windows that have water on one
-      side and not on the other.</p>
-      <p>The block will have the attribute of linetype <a href="#l200">200</a> if the <a
-      href="#NOCLIMB">NOCLIMB</a> flag is set.</p>
-      <p>To use the light level of the target sector, utilize the <a href="#EFFECT4">EFFECT4</a>
-      flag.</p>
-      <p>If this is used as lava (Fire Damage), and you set the <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a>
-      flag, you can still pass through the lava.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_RENDERPLANES|FF_TRANSLUCENT|FF_SWIMMABLE|FF_BOTHPLANES|FF_CUTEXTRA|FF_EXTRA|
-FF_CUTSPRITES-->
-      <hr>
-      <h3><a name="l140"></a>140 - Floor Over Floor: Intangible from Bottom, Opaque </h3>
-      <p>This sector type is solid from the top and walls, but is not solid from the bottom.
-      This allows the designer to create one-way passages as well as simulate 2D design by
-      having platforms that players can jump up to from below.</p>
-      <p>This type of 3D floor will have shadows unless you set the control linedef's <a
-      href="#NOCLIMB">NOCLIMB</a> flag.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_BOTHPLANES|FF_ALLSIDES
-If the <a href="#NOCLIMB">NOCLIMB</a> flag is enabled, it also adds FF_NOSHADE-->
-      <h3><a name="l141"></a>141 - Floor Over Floor: Intangible from Bottom, Translucent</h3>
-      <p>A copy of linetype <a href="#l140">140</a> that is also translucent.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES
-If the <a href="#NOCLIMB">NOCLIMB</a> flag is enabled, it also adds FF_NOSHADE-->
-      <h3><a name="l142"></a>142 - Floor Over Floor: Intangible from Bottom, Translucent, No
-      Sides</h3>
-      <p>A platform you can jump up through, like linetype <a href="#l140">140</a> (and decides
-      the same way whether to have shadows or not), with translucency and that doesn't render
-      sides. Alpha value supported the same way as linetype <a href="#l102">102</a>.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_CUTLEVEL|FF_RENDERPLANES|FF_TRANSLUCENT|FF_PLATFORM|FF_BOTHPLANES
-If the <a href="#NOCLIMB">NOCLIMB</a> flag is enabled, it also adds FF_NOSHADE-->
-      <hr>
-      <h3><a name="l150"></a>150 - Floor Over Floor: Bobbing (Air)</h3>
-      <p>FOF that moves down 16 units when you step on, then returns to its former position when
-      you step off. The control sector must be connected to another sector with the same floor
-      and ceiling height. This seemingly redundant sector is used for resetting the heights. (If
-      you forget to put it in, the bobbing floor when stepped on will go down, keep going down,
-      and never stop or come back up.) See also linetypes <a href="#l151">151</a> and <a
-      href="#l152">152</a>. This linedef is obsolete. Please use linetype 190-195.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_AIRBOB-->
-      <h3><a name="l151"></a>151 - Floor Over Floor: Adjustable Bobbing (Air)</h3>
-      <p>Like linetype <a href="#l150">150</a>, except that instead of the floor moving down 16
-      units when you step on it, it moves down the number of units of the control linedef's
-      length. This linedef is obsolete. Please use linetype 190-195.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_AIRBOB-->
-      <h3><a name="l152"></a>152 - Floor Over Floor: Reverse Bobbing (Air)</h3>
-      <p>Like linetype <a href="#l151">151</a>, except in reverse. The platform goes <i>up</i>
-      when you step on it and back <i>down</i> when you step off. This linedef is obsolete.
-      Please use linetype 190-195.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_AIRBOB-->
-      <hr>
-      <h3><a name="l160"></a>160 - Floor Over Floor: Floating, Bobbing</h3>
-      <p>Bobs and floats in water. The floating part means that if the water moves or rises,
-      this platform will rise with it.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB-->
-      <hr>
-      <h3><a name="l170"></a>170 - Floor Over Floor: Crumbling (Respawn)</h3>
-      <p>Crumbles and falls away, then reappears 15 seconds later.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE-->
-      <h3><a name="l171"></a>171 - Floor Over Floor: Crumbling (No Respawn)</h3>
-      <p>Crumbles and falls away and never comes back.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE|FF_NORETURN-->
-      <h3><a name="l172"></a>172 - Floor Over Floor: Crumbling (Respawn)</h3>
-      <p>A copy of linetype <a href="#l140">140</a> that also crumbles when stood on and
-      reappears after 15 seconds.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_BOTHPLANES|FF_ALLSIDES
-If the <a href="#NOCLIMB">NOCLIMB</a> flag is enabled, it also adds FF_NOSHADE-->
-      <h3><a name="l173"></a>173 - Floor Over Floor: Crumbling (No Respawn)</h3>
-      <p>A copy of linetype <a href="#l172">172</a> that stays gone forever after crumbling.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_NORETURN|FF_BOTHPLANES|
-FF_ALLSIDES
-If the <a href="#NOCLIMB">NOCLIMB</a> flag is enabled, it also adds FF_NOSHADE-->
-      <h3><a name="l174"></a>174 - Floor Over Floor: Intangible from Bottom, Crumbling
-      (Respawn), Translucent</h3>
-      <p>A copy of linetype <a href="#l141">141</a> that also crumbles when stood on and
-      reappears after 15 seconds.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_TRANSLUCENT|FF_BOTHPLANES|
-FF_ALLSIDES
-If the <a href="#NOCLIMB">NOCLIMB</a> flag is enabled, it also adds FF_NOSHADE-->
-      <h3><a name="l175"></a>175 - Floor Over Floor: Intangible from Bottom, Crumbling (No
-      Respawn), Translucent</h3>
-      <p>A copy of linetype <a href="#l174">174</a> that stays gone forever after crumbling.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_PLATFORM|FF_CRUMBLE|FF_NORETURN|FF_TRANSLUCENT|
-FF_BOTHPLANES|FF_ALLSIDES
-If the <a href="#NOCLIMB">NOCLIMB</a> flag is enabled, it also adds FF_NOSHADE-->
-      <h3><a name="l176"></a>176 - Floor Over Floor: Crumbling (Respawn), Floating, Bobbing</h3>
-      <p>Crumbles and falls, then floats on water, then bobs when you step on it.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_AIRBOB|FF_CRUMBLE-->
-      <h3><a name="l177"></a>177 - Floor Over Floor: Crumbling (No Respawn), Floating, Bobbing</h3>
-      <p>Crumbles and falls, then floats on water, then bobs when you step on it. Unlike
-      linetype <a href="#l176">176</a>, does not return to its former position.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_AIRBOB|FF_CRUMBLE|FF_NORETURN-->
-      <h3><a name="l178"></a>178 - Floor Over Floor: Crumbling (Respawn), Floating</h3>
-      <p>Crumbles and falls, then floats on water, then reappears up in the air 15 seconds
-      later.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE|FF_FLOATBOB-->
-      <h3><a name="l179"></a>179 - Floor Over Floor: Crumbling (No Respawn), Floating</h3>
-      <p>Crumbles and falls, then spends the rest of its days floating on water, never to
-      reappear up in the air.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE|FF_FLOATBOB|FF_NORETURN-->
-      <h3><a name="l180"></a>180 - Floor Over Floor: Crumbling (Respawn), Bobbing (Air)</h3>
-      <p>Bobs, crumbles, and falls when stepped on.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_AIRBOB|FF_CRUMBLE-->
-      <hr>
-      <h3><a name="l190"></a>190 - Floor Over Floor: Rising Platform, Solid, Opaque,
-      Shadowcasting</h3>
-      <p>Just like <a href="#l100">100</a>, except when a player steps on it, it will rise up to
-      the control sector's highest adjacent sector. You set the control sectors for this up like
-      special <a href="#l59">59</a>. Linedef length controls speed like <a href="#l60">60</a>.
-      If the <a href="#NOCLIMB">NOCLIMB</a> flag is set, it will require the player to spindash
-      to raise the platform.</p>
-      <h3><a name="l191"></a>191 - Floor Over Floor: Rising Platform, Solid, Opaque,
-      Non-Shadowcasting</h3>
-      <p>Just like <a href="#l101">101</a>, except when a player steps on it, it will rise up to
-      the control sector's highest adjacent sector. You set the control sectors for this up like
-      special <a href="#l59">59</a>. Linedef length controls speed like <a href="#l60">60</a>.
-      If the <a href="#NOCLIMB">NOCLIMB</a> flag is set, it will require the player to spindash
-      to raise the platform.</p>
-      <h3><a name="l192"></a>192 - Floor Over Floor: Rising Platform, Solid, Translucent</h3>
-      <p>Just like <a href="#l102">102</a>, except when a player steps on it, it will rise up to
-      the control sector's highest adjacent sector. You set the control sectors for this up like
-      special <a href="#l59">59</a>. Linedef length controls speed like <a href="#l60">60</a>.
-      If the <a href="#NOCLIMB">NOCLIMB</a> flag is set, it will require the player to spindash
-      to raise the platform.</p>
-      <h3><a name="l193"></a>193 - Floor Over Floor: Rising Platform, Solid, Invisible</h3>
-      <p>Just like <a href="#l105">105</a>, except when a player steps on it, it will rise up to
-      the control sector's highest adjacent sector. You set the control sectors for this up like
-      special <a href="#l59">59</a>. Linedef length controls speed like <a href="#l60">60</a>.
-      If the <a href="#NOCLIMB">NOCLIMB</a> flag is set, it will require the player to spindash
-      to raise the platform.</p>
-      <h3><a name="l194"></a>194 - Floor Over Floor: Rising Platform, Intangible from Bottom,
-      Opaque</h3>
-      <p>Just like <a href="#l140">140</a>, except when a player steps on it, it will rise up to
-      the control sector's highest adjacent sector. You set the control sectors for this up like
-      special <a href="#l59">59</a>. Linedef length controls speed like <a href="#l60">60</a>.
-      If the <a href="#NOCLIMB">NOCLIMB</a> flag is set, it will require the player to spindash
-      to raise the platform.</p>
-      <h3><a name="l195"></a>195 - Floor Over Floor: Rising Platform, Intangible from Bottom,
-      Translucent</h3>
-      <p>Just like <a href="#l141">141</a>, except when a player steps on it, it will rise up to
-      the control sector's highest adjacent sector. You set the control sectors for this up like
-      special <a href="#l59">59</a>. Linedef length controls speed like <a href="#l60">60</a>.
-      If the <a href="#NOCLIMB">NOCLIMB</a> flag is set, it will require the player to spindash
-      to raise the platform.</p>
-      <hr>
-      <h3><a name="l200"></a>200 - Floor Over Floor: Light Block</h3>
-      <p>Like a half light block, but it's really an actual block. That is, the light only comes
-      down to the control sector's floor, not to the bottom of the level (as with linetype <a
-      href="#l201">201</a>.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_CUTSPRITES|FF_DOUBLESHADOW-->
-      <h3><a name="l201"></a>201 - Floor Over Floor: Half Light Block</h3>
-      <p>Light blocks can be used to set color maps and light values. The light value of the
-      control sector will be used and any colormap attached to it will be used also. Note that
-      only the ceiling of the control sector is used; the light goes all the way down to the
-      bottom of the level. If this isn't what you want, consider using linedef type <a
-      href="#l200">200</a> instead.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_CUTSPRITES
-Note: Although it's not a true FOF, it does still have the same kind of definition, so 
-the FOF flags are included despite not being a real block.-->
-      <h3><a name="l202"></a>202 - Floor Over Floor: Fog Block</h3>
-      <p>Creates a block of colored fog. Attach a colormap (linetype <a href="#l606">606</a>) to
-      the control sector for the fog block; otherwise you won't see anything out of the
-      ordinary.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_RENDERALL|FF_FOG|FF_BOTHPLANES|FF_INVERTPLANES|FF_ALLSIDES|FF_INVERTSIDES|FF_CUTEXTRA|
-FF_EXTRA|FF_DOUBLESHADOW|FF_CUTSPRITES-->
-      <hr>
-      <h3><a name="l220"></a>220 - Floor Over Floor: Intangible, Opaque </h3>
-      <p>Like <a href="#l120">opaque water</a>, but not swimmable. Good for a snow effect on
-      FOFs. Can also be used to make hidden rooms, like you would normally do by setting a Main
-      texture.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_RENDERALL|FF_BOTHPLANES|FF_ALLSIDES|FF_CUTEXTRA|FF_EXTRA|FF_CUTSPRITES-->
-      <h3><a name="l221"></a>221 - Floor Over Floor: Intangible, Translucent</h3>
-      <p>See linedef type <a href="#l102">102</a> for how to adjust the translucency, making the
-      3D floor more transparent or more opaque.</p>
-      <p>This type of 3D floor will have shadows if and only if you set the control linedef's <a
-      href="#NOCLIMB">NOCLIMB</a> flag.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_RENDERALL|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA
-If the <a href="#NOCLIMB">NOCLIMB</a> flag is disabled, it also adds FF_NOSHADE-->
-      <h3><a name="l222"></a>222 - Floor Over Floor: Intangible, Sides Only</h3>
-      <p>An intangible FOF that renders sides only, not planes (floor and ceiling). It renders
-      both inside sides and outside sides. You can use it to place sector borders (GFZGRASS,
-      etc.) on FOFs.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_RENDERSIDES|FF_NOSHADE|FF_ALLSIDES-->
-      <h3><a name="l223"></a>223 - Floor Over Floor: Intangible, Invisible</h3>
-      <p>Useful for setting effects, such as wind and gravity.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_NOSHADE-->
-      <hr>
-      <h3><a name="l250"></a>250 - Floor Over Floor: Mario Block</h3>
-      <p>Like a normal FOF, except that the control linedef's Above texture is used after the
-      block has been hit (the Main texture is used before this). Any things in the control
-      sector will pop out the top of the block in the order in which they were placed. Rings
-      will be obtained and the effects of monitors will begin as soon as the block has been hit.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_MARIO-->
-      <h3><a name="l251"></a>251 - Floor Over Floor: Thwomp Block</h3>
-      <p>The thwomps are the crazy platforms with faces in Mario Koopa Blast 3. They can crush
-      you, but you can also ride on them.</p>
-      <p>Control sector is set up like a <a href="#l100">normal FOF</a>. When a player steps
-      underneath the thwomp, it will crush down to the floor. You don't need to tell it where
-      the floor is. It knows.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL-->
-      <h3><a name="l252"></a>252 - Floor Over Floor: Shatter Block</h3>
-      <p>Like the bustable block, linetype <a href="#l254">254</a>, except that it shatters on
-      any sort of contact, whether it's a spindash or not (and whether you're Knuckles or not).</p>
-      <p>If the <a href="#NOCLIMB">NOCLIMB</a> flag is set, the block is only shatterable from
-      the bottom, like some things you spring up and break in Launch Base Zone from Sonic 3.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SHATTER-->
-      <h3><a name="l253"></a>253 - Floor Over Floor: Shatter Block, Translucent</h3>
-      <p>Translucent version of <a href="#l252">252</a> supporting alpha values.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SPINBUST|FF_TRANSLUCENT-->
-      <h3><a name="l254"></a>254 - Floor Over Floor: Bustable Block</h3>
-      <p>Bustable blocks can be destroyed by spindashing. Additionally, Knuckles can destroy
-      them by walking or jumping into them, since he is very strong. If the <a href="#NOCLIMB">NOCLIMB</a>
-      flag is set, only Knuckles can break the block.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP (|FF_ONLYKNUX if <a href="#NOCLIMB">NOCLIMB</a>)-->
-      <h3><a name="l255"></a>255 - Floor Over Floor: Spin Bust Block</h3>
-      <p>Like the bustable block, linetype <a href="#l254">254</a>, set off in a different way.
-      To break, jump onto it or fall down onto it while spinning. Similar to blocks found in
-      Marble Zone, as well as the ice cubes that would encase buttons and monitors in parts of
-      Ice Cap Zone.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP|FF_SPINBUST-->
-      <h3><a name="l256"></a>256 - Floor Over Floor: Spin Bust Block, Translucent</h3>
-      <p>Translucent version of <a href="#l255">255</a> supporting alpha values.</p>
-      <h3><a name="l257"></a>257 - Floor Over Floor: Quicksand Block</h3>
-      <p>It's set up like any block. You can, of course, sink and die in it. X length of the
-      linedef determines sink speed, Y length determines how &quot;sludgy&quot; movement in the
-      quicksand is.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_QUICKSAND|FF_RENDERALL|FF_ALLSIDES|FF_CUTSPRITES-->
-      <h3><a name="l258"></a>258 - Floor Over Floor: Laser Block</h3>
-      <p>Creates a blinking FOF that zaps you if you touch it. You can set the flats and texture
-      to whatever you want. For a red laser like in THZ2, use REDFLR for the flats and REDWALL
-      for the texture.</p>
-      <p>You can also make other colors using BLUEFLR/BLUWALL (blue laser), GREENFLR/GRNWALL
-      (green laser), and YELFLR/YELWALL (yellow laser). Of course, those colors of lasers are
-      very expensive, so Eggman doesn't have nearly as many of them. He usually goes with the
-      cheap red lasers.</p>
-<!--Exact FOF flags:
-FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA-->
-      <h3><a name="l259"></a>259 - Floor Over Floor: Custom</h3>
-      <p>Place the appropriate flag values in hex (do not include the 0x in front of it) in the
-      line's UPPER TEXTURE field on the 2nd side of the linedef.</p>
-    </ol>
-  </li>
-  <li><u><big><big>Linedef Executor Triggers</big></big></u><ol>
-      <h3><a name="l300"></a>300 - Trigger Linedef Executor (Continuous)</h3>
-      <p>Triggers linedef executor in the control sector when a player touches the tagged
-      sector's floor or steps in the sector (depending on the sector special used). The linedef
-      executor will keep being triggered over and over again as long as a player is there, hence
-      the word &quot;continuous&quot; in this linetype's name. Tagged sector must have one of
-      the <a href="#sCat2">Trigger Linedef Executor</a> types for this to work.</p>
-      <h3><a name="l301"></a>301 - Trigger Linedef Executor (Each Time)</h3>
-      <p>Like <a href="#l300">300</a>, except that it only gets triggered once for each time you
-      fall or jump onto the floor. Tagged sector must have one of the <a href="#sCat2">Trigger
-      Linedef Executor</a> types for this to work.</p>
-      <h3><a name="l302"></a>302 - Trigger Linedef Executor (Once)</h3>
-      <p>Like <a href="#l300">300</a>, except that after that linedef executor executes its
-      linedefs, it's done. It's over. The linedefs will never be executed again.</p>
-      <h3><a name="l303"></a>303 - Trigger Linedef Executor (Ring Count - Continuous)</h3>
-      <p>Triggers linedef executor in the control sector when a player touches the tagged
-      sector's floor or steps in the sector (depending on the sector special used). The linedef
-      executor will keep being triggered over and over again as long as a player is there, hence
-      the word &quot;continuous&quot; in this linetype's name. Tagged sector must have one of
-      the <a href="#sCat2">Trigger Linedef Executor</a> types for this to work. Executor will be
-      triggered depending on how many rings the player has:</p>
-      <p>No flags -&gt; Runs if (rings = line length)</p>
-      <p><a href="#NOCLIMB">NOCLIMB</a> -&gt; Runs if (rings &lt;= line length)</p>
-      <p><a href="#BLOCKMONSTERS">BLOCKMONSTERS</a> -&gt; Runs if (rings &gt;= line length)</p>
-      <p><a href="#EFFECT4">EFFECT4</a> -&gt; Takes the rings of ALL players into account.</p>
-      <h3><a name="l304"></a>304 - Trigger Linedef Executor (Ring Count - Once)</h3>
-      <p>Like <a href="#l303">303</a>, except that after that linedef executor executes its
-      linedefs, it's done. It's over. The linedefs will never be executed again.</p>
-      <h3><a name="l305"></a>305 - Trigger Linedef Executor (Character Ability - Once)</h3>
-      <p>Like linetype <a href="#l302">302</a>, but is only activated when the character's
-      ability number matches the linedef length by multiples of 10. For example:</p>
-      <p>0-9 = Charability 0</p>
-      <p>10-19 = Charability 1</p>
-      <p>20-29 = Charability 2</p>
-      <p>etc...</p>
-      <h3><a name="l306"></a>306 - Trigger Linedef Executor (Character Ability - Continuous)</h3>
-      <p>Like <a href="#l300">300</a>, but only triggers when the character's ability number
-      matches the linedef length by multiples of 10. See linetype <a href="#l305">305</a> for a
-      futher description.</p>
-      <h3><a name="l307"></a>307 - Trigger Linedef Executor (Character Ability - Each Time)</h3>
-      <p>Like <a href="#l301">301</a>, but only triggers when the character's ability number
-      matches the linedef length by multiples of 10. See linetype <a href="#l305">305</a> for a
-      futher description.</p>
-      <h3><a name="l308"></a>308 - Trigger Linedef Executor (Race Only, Once)</h3>
-      <p>Like linetype <a href="#l302">302</a>, but is only activated when the gametype is Race.
-      Useful for doing things like opening doors, pre-solving puzzles, etc. to make race
-      smoother.</p>
-      <h3><a name="l309"></a>309 - Trigger Linedef Executor (CTF Red Team - Continuous)</h3>
-      <p>Like <a href="#l300">300</a>, but only triggers if you are in CTF and on the red team.</p>
-      <h3><a name="l310"></a>310 - Trigger Linedef Executor (CTF Red Team - Each Time)</h3>
-      <p>Like <a href="#l301">301</a>, but only triggers if you are in CTF and on the red team.</p>
-      <h3><a name="l311"></a>311 - Trigger Linedef Executor (CTF Blue Team - Continuous)</h3>
-      <p>Like <a href="#l300">300</a>, but only triggers if you are in CTF and on the blue team.</p>
-      <h3><a name="l312"></a>312 - Trigger Linedef Executor (CTF Blue Team - Each Time)</h3>
-      <p>Like <a href="#l301">301</a>, but only triggers if you are in CTF and on the blue team.</p>
-      <h3><a name="l313"></a>313 - Trigger Linedef Executor (No More Enemies - Once)</h3>
-      <p>Like linetype <a href="#l302">302</a>, but is only activated when no more objects of
-      type MF_ENEMY exist in its tagged area. Think &quot;destroy all enemies in this room for
-      the door to open to go to the next room&quot;. Tag this to a control sector. It will go
-      through the lines of the control sector, checking for any lines of type <a href="#l223">223</a>
-      and checking inside the area occupied by the invisible intangible FOF to see if any
-      enemies exist. If no alive enemies are in all of the type <a href="#l223">223</a> FOFs,
-      the linedef executor is run once. The line length is the tag number of the linedef
-      executor trigger to run.</p>
-      <h3><a name="l314"></a>314 - Trigger Linedef Executor (# of Pushables - Continuous)</h3>
-      <p>Like <a href="#l300">300</a>, but only triggers if the number of pushable objects in
-      the sector compared to the line length is:</p>
-      <p>No flags -&gt; Runs if (# pushables = line length)</p>
-      <p><a href="#NOCLIMB">NOCLIMB</a> -&gt; Runs if (# pushables &gt;= line length)</p>
-      <p><a href="#EFFECT4">EFFECT4</a> -&gt; Runs if (# pushables &lt; line length)</p>
-      <h3><a name="l315"></a>315 - Trigger Linedef Executor (# of Pushables - Once)</h3>
-      <p>Like <a href="#l314">314</a>, but only triggers once.</p>
-      <h3><a name="l316"></a>316 - Trigger Linedef Executor (PolyObject - Land On)</h3>
-      <p>This will trigger every time you land on the polyobject. Line's tag # is 32000 + the
-      PolyObject ID #. You must also flag the PolyObject Start (#20) line with <a
-      href="#NOCLIMB">NOCLIMB</a> to tell the game it has a linedef executor associated with it.</p>
-      <p>So if you had a PolyObject with an ID of 1, this line would have a tag of 32001.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Linedef Executor Options</big></big></u><ol>
-      <h3><a name="l400"></a>400 - Linedef Executor: Set Tagged Sector's Floor Height/Pic</h3>
-      <p>When executed, instantly changes the tagged sector's floor height and flat to the floor
-      height and flat of the linedef's front sector.</p>
-      <h3><a name="l401"></a>401 - Linedef Executor: Set Tagged Sector's Ceiling Height/Pic</h3>
-      <p>When executed, instantly changes the tagged sector's ceiling height and flat to the
-      ceiling height and flat of the linedef's front sector.</p>
-      <h3><a name="l402"></a>402 - Linedef Executor: Set Tagged Sector's Light Level</h3>
-      <p>When executed, instantly changes the tagged sector's light level to that of the
-      linedef's front sector. Floor and ceiling light settings done with linetypes <a
-      href="#l601">601</a> and <a href="#l600">600</a> are transferred as well; colormaps are
-      not.</p>
-      <p>If there is a lighting effect active in the target sector or sectors at the time (glow,
-      fade, strobe, flicker), it will be stopped.</p>
-      <h3><a name="l403"></a>403 - Linedef Executor: Move Tagged Sector's Floor</h3>
-      <p>When executed, starts moving the tagged sector's floor until it is at the same height
-      as the linedef's front sector's floor. Speed is indicated in the same units used by
-      linetype <a href="#l60">60</a>.</p>
-      <p>If the line used has <a href="#NOCLIMB">NOCLIMB</a> flag, the floor flat will change
-      after the move, to that on the front sector's floor. This is like what linetype <a
-      href="#l400">400</a> does.</p>
-      <p>If the line used has <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a> flag, another linedef
-      executor will be run when the floor movement is finished. (If multiple sectors finish at
-      different times, it goes by the lowest numbered sector, but you should probably try to
-      avoid this scenario.) The tag of the new linedef executor to run is specified by the X
-      alignment on the front side of the line. The tag number you use must be positive, and this
-      functionality cannot be combined with changing the floor flat using the <a href="#NOCLIMB">NOCLIMB</a>
-      flag. Running a linedef executor will take precedence over changing the floor flat.</p>
-      <h3><a name="l404"></a>404 - Linedef Executor: Move Tagged Sector's Ceiling</h3>
-      <p>When executed, starts moving the tagged sector's ceiling until it is at the same height
-      as the linedef's front sector's ceiling. Speed is indicated in the same units used by
-      linetype <a href="#l60">60</a>.</p>
-      <p>If the line used has <a href="#NOCLIMB">NOCLIMB</a> flag, the ceiling flat will change
-      after the move, to that on the front sector's ceiling. This is like what linetype <a
-      href="#l401">401</a> does.</p>
-      <p>If the line used has <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a> flag, another linedef
-      executor will be run when the ceiling movement is finished. (If multiple sectors finish at
-      different times, it goes by the lowest numbered sector, but you should probably try to
-      avoid this scenario.) The tag of the new linedef executor to run is specified by the X
-      alignment on the front side of the line. The tag number you use must be positive, and this
-      functionality cannot be combined with changing the ceiling flat using the <a
-      href="#NOCLIMB">NOCLIMB</a> flag. Running a linedef executor will take precedence over
-      changing the ceiling flat.</p>
-      <h3><a name="l405"></a>405 - Linedef Executor: Lower Floor by Line</h3>
-      <p>Speed is indicated by x distance; amount to lower is indicated by y distance.</p>
-      <h3><a name="l406"></a>406 - Linedef Executor: Raise Floor by Line</h3>
-      <p>Speed is indicated by x distance; amount to raise is indicated by y distance.</p>
-      <h3><a name="l407"></a>407 - Linedef Executor: Lower Ceiling by Line</h3>
-      <p>Speed is indicated by x distance; amount to lower is indicated by y distance.</p>
-      <h3><a name="l408"></a>408 - Linedef Executor: Raise Ceiling by Line</h3>
-      <p>Speed is indicated by x distance; amount to raise is indicated by y distance.</p>
-      <h3><a name="l409"></a>409 - Linedef Executor: Change Calling Sector's Tag</h3>
-      <p>Changes the tag of the calling sector; that is, the sector on the map that activated
-      this linedef executor. The new tag is the linedef's length.</p>
-      <h3><a name="l410"></a>410 - Linedef Executor: Change Front Sector's Tag</h3>
-      <p>Changes the tag of the linedef's front sector. The new tag is the linedef's length.</p>
-      <h3><a name="l411"></a>411 - Linedef Executor: Stop Plane Movement</h3>
-      <p>Stops any and all floor, ceiling, or elevator movement in the tagged sector or sectors.</p>
-      <h2><a name="l412"></a>412 - Linedef Executor: Teleport Player to Tagged Sector</h2>
-      <p>The player who triggered the linedef executor will be teleported to the tagged sector.
-      The player's exact X, Y, Z, and angle are determined by a teleport destination thing, type
-      <a href="#t751">751</a>, somewhere in the tagged sector.</p>
-      <p>If the <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a> flag is used, it won't flash and make
-      the teleport sound effects. If the <a href="#NOCLIMB">NOCLIMB</a> flag is used, it won't
-      reset the angle to the angle of the teleport destination thing, and if the <a
-      href="#EFFECT4">EFFECT4</a> flag is used, it will not kill your acceleration/speed upon
-      teleport.</p>
-      <h2><a name="l413"></a>413 - Linedef Executor: Change Music</h2>
-      <p>Linedef length indicates the music slot to use. If the linedef's <a href="#NOCLIMB">NOCLIMB</a>
-      flag is set, play the music once, otherwise loop it.</p>
-      <p>If the player dies and goes back to a starpost, the beginning of the level, or the
-      appropriate multiplayer start, the map music from before will be restored. The linedef
-      flag <a href="#BLOCKMONSTERS">BLOCKMONSTERS</a> can be set to change this behavior, and
-      retain the new music even after dying.</p>
-      <p>If the linedef length isn't a valid music slot, the music is stopped.</p>
-      <h3><a name="l414"></a>414 - Linedef Executor: Play SFX</h3>
-      <p>Plays a sound effect. The line length is the sound number to use. The list of sound
-      effects can be found in sounds.h. The origin of the sound depends on which linedef flags
-      are set: <ul>
-        <li><a href="#NOCLIMB">NOCLIMB</a> : The sound is played from nowhere, but only for the
-          player who triggered it.</li>
-        <li><a href="#EFFECT4">EFFECT4</a>: The sound is played from nowhere for everyone.</li>
-        <li><a href="#BLOCKMONSTERS">BLOCKMONSTERS</a>: The sound is played from the center of the
-          sector that triggered the linedef executor.</li>
-      </ul>
-      <p>Otherwise, the sound is played from the location of the player or thing who triggered
-      it.</p>
-      <h3><a name="l415"></a>415 - Linedef Executor: Run Script</h3>
-      <p>Runs a script, the same kind of script you can run on level load with the level header
-      scriptname attribute. The script that will be run should have a lumpname of the form SCR<i>xxyyy</i>,
-      where <i>xx</i> is the two-digit map number and <i>yyy</i> is the linedef's sector's floor
-      height in decimal, with leading zeroes as necessary (or 000 if the floor height exceeds
-      999 fracunits). For instance, if the linedef is in MAP31 and the floor of its sector is
-      337 fracunits, the script named SCR31337 will be run.</p>
-      <h3><a name="l416"></a>416 - Linedef Executor: Start Adjustable Fire Flicker</h3>
-      <p>Essentially a copy of linetype <a href="#l603">603</a> that waits to activate until the
-      linedef executor is triggered. It does have an extra feature, though. If you use a
-      two-sided linedef with the <a href="#NOCLIMB">NOCLIMB</a> flag, the linedef's back sector
-      will be used as the maximum light level, allowing you to set the target sector (or
-      sectors) at a different starting light level entirely.</p>
-      <h3><a name="l417"></a>417 - Linedef Executor: Start Adjustable Glowing Light</h3>
-      <p>Essentially a copy of linetype <a href="#l602">602</a> that waits to activate until the
-      linedef executor is triggered. It does have an extra feature, though. If you use a
-      two-sided linedef with the <a href="#NOCLIMB">NOCLIMB</a> flag, the linedef's back sector
-      will be used as the maximum light level, allowing you to set the target sector (or
-      sectors) at a different starting light level entirely.</p>
-      <h3><a name="l418"></a>418 - Linedef Executor: Start Adjustable Blinking Light
-      (unsynchronized)</h3>
-      <p>Essentially a copy of linetype <a href="#l604">604</a> that waits to activate until the
-      linedef executor is triggered. It does have an extra feature, though. If you use a
-      two-sided linedef with the <a href="#NOCLIMB">NOCLIMB</a> flag, the linedef's back sector
-      will be used as the maximum light level, allowing you to set the target sector (or
-      sectors) at a different starting light level entirely.</p>
-      <h3><a name="l419"></a>419 - Linedef Executor: Start Adjustable Blinking Light
-      (synchronized)</h3>
-      <p>Essentially a copy of linetype <a href="#l605">605</a> that waits to activate until the
-      linedef executor is triggered. It does have an extra feature, though. If you use a
-      two-sided linedef with the <a href="#NOCLIMB">NOCLIMB</a> flag, the linedef's back sector
-      will be used as the maximum light level, allowing you to set the target sector (or
-      sectors) at a different starting light level entirely.</p>
-      <h3><a name="l420"></a>420 - Linedef Executor: Fade Light Level</h3>
-      <p>When executed, gradually fades the tagged sector's light level to that of the linedef's
-      front sector. Floor and ceiling light settings done with linetypes <a href="#l601">601</a>
-      and <a href="#l600">600</a> are not affected or used.</p>
-      <p>If there is a lighting effect already active in the target sector or sectors at the
-      time (glow, other fade, strobe, flicker), it will be halted in favor of this one.</p>
-      <p>Linedef length in fracunits indicates speed. Fading from 224 to 64 with a linedef
-      length of 4 will take 40 fracunits (224 - 64 = 160, 160 / 4 = 40). There are 35 fracunits
-      in a second.</p>
-      <h3><a name="l421"></a>421 - Linedef Executor: Stop Lighting Effect</h3>
-      <p>Stops any lighting effects active in the tagged sector or sectors: glow, fade, strobe,
-      flicker, etc. The light level, whatever it is at the moment this script line is run, will
-      be preserved until a new lighting effect or light level change is used.</p>
-      <p>Note that the lighting effects will all stop other lighting effects when activated. In
-      other words, you only need to use this when you really want the lighting effect to stop,
-      not when you want one effect to stop and another to start.</p>
-      <h3><a name="l422"></a>422 - Linedef Executor: Cut-Away View</h3>
-      <p>Cuts away to a view from a different place for a moment. Only works for linedef
-      executors triggered by a player. Tag the line to a sector with an alt view thing (map
-      thing type 5007) in it at the proper location with the proper Z and angle. The line length
-      indicates how long to stay in this view, in tics.</p>
-      <p>By giving the linedef a <a href="#NOCLIMB">NOCLIMB</a> flag, you can adjust the
-      vertical viewing angle from the cut-away view. Set the x offset on the linedef's front
-      side to an integer -90 to 90. In software mode the range of viewing angles is actually
-      about -68 to 68. This is in degrees.</p>
-      <h3><a name="l423"></a>423 - Linedef Executor: Change Sky</h3>
-      <p>Changes sky to the # of the control sector's floorheight. This only affects the player
-      who activates it. If you'd like it to affect all players, make sure you check the <a
-      href="#NOCLIMB">NOCLIMB</a> flag.</p>
-      <h3><a name="l424"></a>424 - Linedef Executor: Change Weather</h3>
-      <p>Changes weather to the control sector's floorheight in powers of 10.</p>
-      <p>Example:</p>
-      <div align="left"><table border="1" width="28%">
-        <tr>
-          <td width="50%">Linedef Length</td>
-          <td width="50%">Weather Type</td>
-        </tr>
-        <tr>
-          <td width="50%">10</td>
-          <td width="50%">None</td>
-        </tr>
-        <tr>
-          <td width="50%">20</td>
-          <td width="50%">Snow</td>
-        </tr>
-        <tr>
-          <td width="50%">30</td>
-          <td width="50%">Rain</td>
-        </tr>
-        <tr>
-          <td width="50%">40</td>
-          <td width="50%">Storm</td>
-        </tr>
-      </table>
-      </div><p>(higher numbers reserved for future use)</p>
-      <p>This only affects the player who activates it. If you'd like it to affect all players,
-      make sure you check the <a href="#NOCLIMB">NOCLIMB</a> flag.</p>
-      <h3><a name="l425"></a>425 - Linedef Executor: Change Object State</h3>
-      <p>Changes the animation frame of the activating object to the state # indicated by the
-      length of the control linedef. Be careful how you use this.</p>
-      <h3><a name="l426"></a>426 - Linedef Executor: Stop Object</h3>
-      <p>Makes the object that triggered the linedef executor stop moving, after being sent to
-      the center of the sector it's in (only if <a href="#NOCLIMB">NOCLIMB</a> flag is set), on
-      the floor. Although it comes to a complete stop, the object can begin moving right away
-      again. If the object is a player, the player will stop jumping, spinning, or anything
-      else.</p>
-      <h3><a name="l427"></a>427 - Linedef Executor: Award Score</h3>
-      <p>Adds to the score of the player who activated it. Control sector's floorheight = points
-      to award. This even works with negative values.</p>
-      <h3><a name="l428"></a>428 - Linedef Executor: Start Platform Movement</h3>
-      <p>Starts a moving platform in the nature of linetype <a href="#l59">59</a> or <a
-      href="#l60">60</a>. If the <a href="#NOCLIMB">NOCLIMB</a> flag is set, the platform will
-      begin moving upwards. Otherwise, it will start moving downwards. Speed of movement is set
-      just like with linetype <a href="#l60">60</a>.</p>
-      <h3><a name="l429"></a>429 - Linedef Executor: Crush Ceiling Once</h3>
-      <p>Ceiling moves down to the floor, then back up. Speed is determined by line length -
-      every 16 units equals 1 FRACUNIT/tic.</p>
-      <h3><a name="l430"></a>430 - Linedef Executor: Crush Floor Once</h3>
-      <p>Floor moves up to the ceiling, then back down. Speed is determined by line length -
-      every 16 units equals 1 FRACUNIT/tic.</p>
-      <h3><a name="l431"></a>431 - Linedef Executor: Crush Floor And Ceiling Once</h3>
-      <p>Floor and ceiling meet in the middle and then return, sandwiching anything that's
-      inbetween. Speed is determined by line length - every 16 units equals 1 FRACUNIT/tic.</p>
-      <h3><a name="l432"></a>432 - Linedef Executor: Enable 2D Mode</h3>
-      <p>Turns on 2D mode within the level. You'll probably only want to use this with a zoom
-      tube or teleport to guarantee that the player is in the correct position when it switches.</p>
-      <h3><a name="l433"></a>433 - Linedef Executor: Disable 2D Mode</h3>
-      <p>Turns off 2D mode within the level.</p>
-      <h3><a name="l434"></a>434 - Linedef Executor: Award Custom Power</h3>
-      <p>Awards (or removes!) a power to the calling player.</p>
-      <p>X length: Power Index + 1</p>
-      <p>Y length: Power Duration (in 35ths of a second)</p>
-      <h3><a name="l435"></a>435 - Linedef Executor: Change Scroller Direction</h3>
-      <p>Changes direction of a scroller (conveyor, texture). Also changes speed if this is not
-      an accelerative/displacement scroller.</p>
-      <h3><a name="l436"></a>436 - Linedef Executor: Shatter Block</h3>
-      <p>Shatters a FOF - of any type. Parameters are as follows:</p>
-      <p>Texture X Offset: Tag # of FOF target sector</p>
-      <p>Texture Y Offset: Tag # of FOF control sector</p>
-      <p>Note that the FOF should only have one target sector.</p>
-      <h3><a name="l437"></a>437 - Linedef Executor: Disable Player Control</h3>
-      <p>Disables the controls of the player that triggered the linedef executor. If the
-      <a href="#NOCLIMB">NOCLIMB</a> flag is set, they will be able to jump, however.</p>
-      <p>Giving the front texture of the linedef an X offset will make the effect last that amount
-      of time, in tics.  Otherwise, it ends immediately, so you would need to use a continuous
-      trigger.</p>
-      <h3><a name="l438"></a>438 - Linedef Executor: Set Object's Scale</h3>
-      <p>Length of this line determines the scale size of an object, in percentage. Note that there is a max of 400%.</p>
-      <h3><a name="l450"></a>450 - Linedef Executor: Execute Linedef Executor</h3>
-      <p>Just what it says. Can be used for recursion. Be careful, because you CAN make a loop
-      out of this that will freeze the game.</p>
-      <p>Tag is the linedef executor trigger tag to run.</p>
-      <h3><a name="l488"></a>488 - Linedef Executor: PolyObject - Move by Waypoints</h3>
-      <p>Moves a polyobject along a sequence of Zoom Tube waypoints.</p>
-      <p>Texture X offset: Speed (8 = 1 FRACUNIT).</p>
-      <p>Texture Y offset: Sequence # of Zoom Tube waypoints.</p>
-      <p>The movement also depends on which linedef flags are set: <ul>
-        <li><a href="#EFFECT1">EFFECT1</a> : Moves from highest waypoint # to lowest waypoint #.</li>
-        <li><a href="#EFFECT2">EFFECT2</a> : Comes back the way it came when the end is reached.</li>
-        <li><a href="#EFFECT3">EFFECT3</a> : Wrap around the waypoints.</li>
-        <li><a href="#EFFECT4">EFFECT4</a>:&nbsp; Continuously move (used with EFFECT2 or EFFECT3).</li>
-      </ul>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Scrollers / Pushers</big></big></u><ol>
-      <h3><a name="l500"></a>500 - Scroll Wall First Side Left</h3>
-      <h3><a name="l501"></a>501 - Scroll Wall First Side Opposite Direction</h3>
-      <h3><a name="l502"></a>502 - Scroll Wall According to Linedef</h3>
-      <h3><a name="l503"></a>503 - Acc Scroll Wall According to Linedef</h3>
-      <p>Accelerative scrolling version of <a href="#l502">502</a>.</p>
-      <h3><a name="l504"></a>504 - Disp Scroll Wall According to Linedef</h3>
-      <p>Displacement scrolling version of <a href="#l502">502</a>.</p>
-      <h3><a name="l505"></a>505 - Scroll Texture by Offsets</h3>
-      <hr>
-      <h3><a name="l510"></a>510 - Scroll Floor Texture</h3>
-      <p>Linedef length and direction indicate speed and direction.</p>
-      <h3><a name="l511"></a>511 - Acc Scroll Floor Texture</h3>
-      <p>Accelerative scrolling version of <a href="#l510">510</a>.</p>
-      <h3><a name="l512"></a>512 - Disp Scroll Floor Texture</h3>
-      <p>Displacement scrolling version of <a href="#l510">510</a>.</p>
-      <h3><a name="l513"></a>513 - Scroll Ceiling Texture</h3>
-      <p>Linedef length and direction indicate speed and direction.</p>
-      <h3><a name="l514"></a>514 - Acc Scroll Ceiling Texture</h3>
-      <p>Accelerative scrolling version of <a href="#l513">513</a>.</p>
-      <h3><a name="l515"></a>515 - Disp Scroll Ceiling Texture</h3>
-      <p>Displacement scrolling version of <a href="#l513">513</a>.</p>
-      <hr>
-      <h3><a name="l520"></a>520 - Carry Objects on Floor</h3>
-      <p>Like linedef type <a href="#l530">530</a>, without scrolling the floor texture, so the
-      floor doesn't look like it's moving.</p>
-      <h3><a name="l521"></a>521 - Acc Carry Objects on Floor</h3>
-      <p>Accelerative scrolling version of <a href="#l520">520</a>.</p>
-      <h3><a name="l522"></a>522 - Disp Carry Objects on Floor</h3>
-      <p>Displacement scrolling version of <a href="#l520">520</a>.</p>
-      <h3><a name="l523"></a>523 - Carry Objects on Ceiling</h3>
-      <p>For FOF conveyor belts. Like <a href="#l533">533</a>, except without the scrolling to
-      accompany it.</p>
-      <h3><a name="l524"></a>524 - Acc Carry Objects on Ceiling</h3>
-      <p>Accelerative scrolling version of <a href="#l523">523</a>. Untested.</p>
-      <h3><a name="l525"></a>525 - Disp Carry Objects on Ceiling</h3>
-      <p>Displacement scrolling version of <a href="#l523">523</a>. Untested.</p>
-      <hr>
-      <h3><a name="l530"></a>530 - Scroll Floor Texture and Carry Objects</h3>
-      <p>Used for conveyor belts, in conjunction with sector type <a href="#s1024">1024</a>
-      (Conveyor Belt). Linedef length and direction indicate conveyor speed and direction,
-      respectively. This can also be used to convey items on the underneath of a FOF. See Egg
-      Rock Zone for an example.</p>
-      <h3><a name="l531"></a>531 - Acc Scroll Floor Texture and Carry Objects</h3>
-      <p>Accelerative scrolling version of <a href="#l530">530</a>.</p>
-      <h3><a name="l532"></a>532 - Disp Scroll Floor Texture and Carry Objects</h3>
-      <p>Displacement scrolling version of <a href="#l530">530</a>.</p>
-      <h3><a name="l533"></a>533 - Scroll Ceiling Texture and Carry Objects</h3>
-      <p>For conveyor belts on the top of FOFs, or for conveying items on a ceiling. Tag this to
-      the FOF control sector and give the FOF control sector a type of <a href="#s1024">1024</a>,
-      Conveyor Belt. For realism you might also want to scroll the control sector's floor
-      texture in the opposite direction (see linetype <a href="#l510">510</a>).</p>
-      <h3><a name="l534"></a>534 - Acc Scroll Ceiling Texture and Carry Objects</h3>
-      <p>Accelerative scrolling version of <a href="#l533">533</a>. Untested.</p>
-      <h3><a name="l535"></a>535 - Disp Scroll Ceiling Texture and Carry Objects</h3>
-      <p>Displacement scrolling version of <a href="#l533">533</a>. Untested.</p>
-      <hr>
-      <h3><a name="l540"></a>540 - Friction</h3>
-      <p>Linedef lengths greater than 100 indicate slippery ice, while linedef lengths less than
-      100 can be used for sludge, with extra friction.</p>
-      <p>If you want friction on a FOF, tag this line to the control sector of the FOF.
-      Otherwise, tag it to the sector of desired destination.</p>
-      <h3><a name="l541"></a>541 - Wind</h3>
-      <p>Speed and direction are indicated by linedef length and direction. The target sector
-      should be of type <a href="#s512">512</a>, Wind/Current. If being used in a 3D Floor, put
-      the 512/768 sector type in the control sector, not the target sector. Also tag the line to
-      the control sector, and not the target sector.</p>
-      <p>Special flags:</p>
-      <p><a href="#NOCLIMB">NOCLIMB</a> -&gt; Only this pusher will affect the object - the
-      object can't have multiple 'pushings' due to being on the edge of a sector, etc.</p>
-      <p><a href="#EFFECT4">EFFECT4</a> -&gt; Player will go into slide with limited control
-      (similar to the water and oil slides in Labyrinth and Oil Ocean).</p>
-      <h3><a name="l542"></a>542 - Upwards Wind</h3>
-      <p>The length of the linedef is the wind speed. The target sector will need type <a
-      href="#s512">512</a> or <a href="#s768">768</a>. If being used in a 3D Floor, put the
-      512/768 sector type in the control sector, not the target sector. Also tag the line to the
-      control sector, and not the target sector.</p>
-      <p>NOCLIMB/EFFECT4 flags operate the same as for line <a href="#l541">541</a>.</p>
-      <h3><a name="l543"></a>543 - Downwards Wind</h3>
-      <p>Wind speed is determined by the linedef's length. Type <a href="#s512">512</a> or <a
-      href="#s768">768</a> must be applied to the target sector. If being used in a 3D Floor,
-      put the 512/768 sector type in the control sector, not the target sector. Also tag the
-      line to the control sector, and not the target sector.</p>
-      <p>NOCLIMB/EFFECT4 flags operate the same as for line <a href="#l541">541</a>.</p>
-      <h3><a name="l544"></a>544 - Current</h3>
-      <p>Speed and direction are indicated by linedef length and direction. The target sector
-      should have type <a href="#s512">512</a>, Wind/Current. If being used in a 3D Floor, put
-      the 512/768 sector type in the control sector, not the target sector. Also tag the line to
-      the control sector, and not the target sector.</p>
-      <p>NOCLIMB/EFFECT4 flags operate the same as for line <a href="#l541">541</a>.</p>
-      <h3><a name="l545"></a>545 - Upwards Current</h3>
-      <p>Linedef length indicates speed. Target sector needs sector type <a href="#s512">512</a>
-      or <a href="#s768">768</a>. If being used in a 3D Floor, put the 512/768 sector type in
-      the control sector, not the target sector. Also tag the line to the control sector, and
-      not the target sector.</p>
-      <p>NOCLIMB/EFFECT4 flags operate the same as for line <a href="#l541">541</a>.</p>
-      <h3><a name="l546"></a>546 - Downwards Current</h3>
-      <p>Speed is indicated by linedef length. Assign a type of <a href="#s512">512</a> or <a
-      href="#s768">768</a> to the target sector. If being used in a 3D Floor, put the 512/768
-      sector type in the control sector, not the target sector. Also tag the line to the control
-      sector, and not the target sector.</p>
-      <p>NOCLIMB/EFFECT4 flags operate the same as for line <a href="#l541">541</a>.</p>
-      <h3><a name="l547"></a>547 - Boom Push/Pull Thing</h3>
-      <p>Creates a &quot;point pusher,&quot; or a point that pushes you away or pulls you toward
-      it if you get close enough. Tag the linedef to a sector with type <a href="#s512">512</a>,
-      Wind/Current, and with a thing on it of type 5001 (push) or 5002 (pull). The control
-      linedef's length indicates pushing/pulling strength; if length is L, the effect fades away
-      to nothing when you are 2L away from the point.</p>
-      <p>If you want to create multiple point pushers/pullers, you'll need to have them in
-      different target sectors, but they can share the same tag.</p>
-      <p>NOCLIMB/EFFECT4 flags operate the same as for line <a href="#l541">541</a>.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Lighting</big></big></u><ol>
-      <h3><a name="l600"></a>600 - Floor Lighting</h3>
-      <p>Sets the lighting for the floor only. The control sector's light value will be used for
-      the target sector's floor. Also see type <a href="#l601">601</a>.</p>
-      <h3><a name="l601"></a>601 - Ceiling Lighting</h3>
-      <p>Sets the lighting of the ceiling only. The light value of the control sector will be
-      used for the target sector's ceiling. Also see type <a href="#l600">600</a>.</p>
-      <h3><a name="l602"></a>602 - Adjustable Pulsating Light</h3>
-      <p>Linedef length indicates glow speed. The normal speed would be a linedef 32 units long.</p>
-      <p>The control sector (the linedef's front sector) is used to get what will be the minimum
-      light level for this effect, while the target sector's light level ends up being the
-      maximum.</p>
-      <h3><a name="l603"></a>603 - Adjustable Flickering Light</h3>
-      <p>Linedef length indicates flicker speed. Normal speed would be a 16 fracunit long
-      linedef. A longer linedef means more time in between flickers.</p>
-      <p>The control sector (the linedef's front sector) is used to get what will be the minimum
-      light level for this effect, while the target sector's light level ends up being the
-      maximum.</p>
-      <h3><a name="l604"></a>604 - Adjustable Blinking Light (unsynchronized)</h3>
-      <p>Line's X length is time for the light to be off, and Y length is the time for the light
-      to be on.</p>
-      <p>The control sector (the linedef's front sector) is used to get what will be the minimum
-      light level for this effect, while the target sector's light level ends up being the
-      maximum.</p>
-      <h3><a name="l605"></a>605 - Adjustable Blinking Light (synchronized)</h3>
-      <p>Line's X length is time for the light to be off, and Y length is the time for the light
-      to be on.</p>
-      <p>The control sector (the linedef's front sector) is used to get what will be the minimum
-      light level for this effect, while the target sector's light level ends up being the
-      maximum.</p>
-      <h3><a name="l606"></a>606 - Colormap</h3>
-      <p>Sets a colormap. Tag the linedef to the sector or sectors affected by the colormap. The
-      control linedef's front Above texture is used to determine the colormap. The format is
-      #rrggbba, where rr, gg, and bb are two hexadecimal digits for determining each color: red,
-      blue, and green. The a stands for alpha, and is a number or letter indicating the
-      translucency; from A-Z and 0-9, with A being most transparent and 9 being most opaque.</p>
-      <p>It does not generally matter what sector the colormap linedef belongs to. However, it
-      should not belong to the same sector as another colormap, as this can cause problems.</p>
-    </ol>
-  </li>
-  <h1><a name="sectortypes"></a>Sector Types</h1>
-  <p>You can apply up to four different types to one sector, provided that you only choose
-  ONE from EACH category. Add the numbers together to obtain the final value to use in level
-  editors.</p>
-  <li><u><big><big>Section 1</big></big></u><ol>
-      <h3><a name="s1"></a>1 - Damage (Generic)</h3>
-      <p>This special hurts, period. It doesn't matter whether you have a liquid shield, fire
-      shield, attraction shield, or whatever else; step on one of these and suffer.</p>
-      <h3><a name="s2"></a>2 - Damage (Water)</h3>
-      <p>Also known as Slime Hurt. Stepping here will be painful, as in shield/ring/life loss
-      (depending on how you are equipped), unless you happen to have the liquid shield.</p>
-      <h3><a name="s3"></a>3 - Damage (Fire)</h3>
-      <p>Stepping here will hurt, unless you happen to have a fire shield.</p>
-      <h3><a name="s4"></a>4 - Damage (Electrical)</h3>
-      <p>Hurts players whenever they're in the sector, unless they have the attraction shield.</p>
-      <p>Usage tip: Give the sector a floor flat that looks electrical and looks like it could
-      hurt you. </p>
-      <h3><a name="s5"></a>5 - Spikes</h3>
-      <p>Making spikes using sectors is rather tedious and difficult. You can use things instead
-      (<a href="#t523">Floor Spike</a> and <a href="#t522">Ceiling Spike</a>). But the sector
-      version DOES look cooler. ;)</p>
-      <h3><a name="s6"></a>6 - Death Pit (Camera Modifications)</h3>
-      <p>Used for bottomless pits. You'll probably want the sector's floor flat to be either
-      F_SKY1 (falling from the sky) or PIT (falling into a pit of complete blackness). The
-      camera modifications keep the camera from following you all the way down, for a Sonic
-      Adventure-style pit death. If you don't like the camera modifications, use sector type <a
-      href="#s7">5</a>.</p>
-      <h3><a name="s7"></a>7 - Death Pit (No Camera Modifications)</h3>
-      <p>For bottomless pits. Use if the camera modifications of sector type <a href="#s6">6</a>
-      are not to your taste.</p>
-      <h3><a name="s8"></a>8 - Instant Kill</h3>
-      <p>Die right away if you even step into this sector. No need to touch the floor as with
-      those sissy death pits.</p>
-      <h3><a name="s9"></a>9 - Ring Drainer (Floor Touch)</h3>
-      <p>Lose one ring per 15 tics while touching the floor.</p>
-      <h3><a name="s10"></a>10 - Ring Drainer (No Floor Touch)</h3>
-      <p>Like sector type <a href="#s9">9</a>, but doesn't require touching floor.</p>
-      <h3><a name="s11"></a>11 - Special Stage Damage</h3>
-      <p>If you have rings and no shield, and you step on it, you only lose 10 rings, maximum.
-      It's just like the special stages!</p>
-      <h3><a name="s12"></a>12 - Space Countdown</h3>
-      <p>In space, you have no chance to survive make your time, ha ha ha. Starts an immediate
-      five-second countdown, like when you drown.</p>
-      <h3><a name="s13"></a>13 - Ramp Sector</h3>
-      <p>Doubles the step-up height of the player. Default step-up height is 24 fracunits, but
-      with this, it becomes 48. Useful for steps and other things if your players seem to be
-      getting 'stopped' by the stairs while moving quickly.</p>
-      <h3><a name="s14"></a>14 - Non-Ramp Sector (Don't step down)</h3>
-      <p>Removes the 'step-down' that a player will normally do when moving to a nearby sector.</p>
-      <h3><a name="s15"></a>15 - Bouncy Sector (FOF Control Only)</h3>
-      <p>Use this on a 3D floor's control sector to make it bouncy. Players will bounce off the
-      top of it. If the 3D floor's control line has the BOUNCY flag set, the linedef length sets
-      the minimum bounce force. Otherwise, you will slowly come to a stop.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big><a name="sCat2"></a>Section 2</big></big></u><ol>
-      <h3><a name="s16"></a>16 - Trigger Linedef Executor (Pushable Objects)</h3>
-      <p>Works like <a href="#s80">80</a> but with a pushable object (gargoyle or snowman)
-      touching the floor rather than a player.</p>
-      <h3><a name="s32"></a>32 - Trigger Linedef Executor (Anywhere in Sector) (All Players)</h3>
-      <p>Sector type <a href="#s64">64</a> with the added requirement that all players who don't
-      have a game over need to be in the sector, not just one player. Currently does not work in
-      FOFs.</p>
-      <h3><a name="s48"></a>48 - Trigger Linedef Executor (Floor Touch) (All Players)</h3>
-      <p>Sector type <a href="#s80">80</a> with the added requirement that all players who don't
-      have a game over need to be in the sector, not just one player.</p>
-      <h3><a name="s64"></a>64 - Trigger Linedef Executor (Anywhere in Sector)</h3>
-      <p>Like sector type <a href="#s80">80</a>, but you don't have to be touching the floor to
-      do the triggering. You could be flying high in the air. You should also use this one for
-      linedef executors triggered by FOFs.</p>
-      <h3><a name="s80"></a>80 - Trigger Linedef Executor (Floor Touch)</h3>
-      <p>Required for any of the <a href="#ltriggers">Linedef Executor Triggers</a> to work in
-      the sector.</p>
-      <h3><a name="s96"></a>96 - Trigger Linedef Executor (Emerald Check)</h3>
-      <p>Sector type <a href="#s64">64</a> which will only execute if you have all 7 chaos
-      emeralds.</p>
-      <h3><a name="s112"></a>112 - Trigger Linedef Executor (NiGHTS Mare)</h3>
-      <p>Like sector type <a href="#s64">64</a>, but this is only triggered if you are in a
-      NiGHTS map, and checks what mare you're on using the following format, depending on what
-      flags you have set for this line:</p>
-      <p>No flags -&gt; Runs if (current mare = line length)</p>
-      <p><a href="#NOCLIMB">NOCLIMB</a> -&gt; Runs if (current mare &lt;= line length)</p>
-      <p><a href="#BLOCKMONSTERS">BLOCKMONSTERS</a> -&gt; Runs if (current mare &gt;= line
-      length)</p>
-      <h3><a name="s128"></a>128 - Check for linedef executor on 3D Floors (ANY object)</h3>
-      <p>For any item to detect sector type <a href="#l16">16</a> on a 3D floor, the target
-      sector on the map must have this type. This allows you to have any kind of object trigger
-      a linedef executor.</p>
-      <h3><a name="s144"></a>144 - Egg Trap Capsule</h3>
-      <h3><a name="s160"></a>160 - Special Stage Time/Rings, Par</h3>
-      <p>For special stages, floor height is time limit in seconds, and ceiling height is rings
-      required in seconds. If the ceiling height is 0, there is no ring requirement, only a time
-      limit to find an exit.</p>
-      <h3><a name="s176"></a>176 - Custom Global Gravity</h3>
-      <p>Floor height sets global gravity. 500 is normal. 1000 is twice the normal gravity, 250
-      is half. You can also set per-sector gravity with linetype <a href="#l1">1</a>. This can
-      also be adjusted in realtime, for some really cool effects.</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Section 3</big></big></u><ol>
-      <h3><a name="s256"></a>256 - Ice/Sludge</h3>
-      <p>See linedef type <a href="#l540">540</a>.</p>
-      <h3><a name="s512"></a>512 - Wind/Current</h3>
-      <p>See linedef types <a href="#l541">541</a> and <a href="#l544">544</a>.</p>
-      <h3><a name="s768"></a>768 - Ice/Sludge and Wind/Current</h3>
-      <p>Combination of sector specials 256 and 512.</p>
-      <h3><a name="s1024"></a>1024 - Conveyor Belt</h3>
-      <p>See linedef type <a href="#l520">520</a>.</p>
-      <h3><a name="s1280"></a>1280 - Speed Pad (No Spin)</h3>
-      <p>See linedef type <a href="#l4">4</a>.</p>
-      <h3><a name="s1536"></a>1536 - Speed Pad (Spin)</h3>
-      <p>See linedef type <a href="#l4">4</a>. This type of speed pad forces you into a spin.</p>
-      <h3><a name="s1792"></a>1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, 3840 - Bustable
-      Block Sprite Parameter</h3>
-      <p>Used in a control sector of a bustable block. Chooses which debris sprite to spawn.</p>
-      <p>1792 = ROIA</p>
-      <p>2048 = ROIB</p>
-      <p>2304 = ROIC</p>
-      <p>2560 = ROID</p>
-      <p>2816 = ROIE</p>
-      <p>3072 = ROIF</p>
-      <p>3328 = ROIG</p>
-      <p>3584 = ROIH</p>
-      <p>3840 = ROII</p>
-      <p>&nbsp;</p>
-    </ol>
-  </li>
-  <li><u><big><big>Section 4</big></big></u><ol>
-      <h3><a name="s4096"></a>4096 - Starpost Activator</h3>
-      <p>Whenever a player steps in the sector, a starpost in that sector will be searched for
-      and, if found, activated.</p>
-      <h3><a name="s8192"></a>8192 - Special Stage Goal</h3>
-      <p>This is like the &quot;GOAL&quot; buttons in Sonic 1's special stages. Ends the special
-      stage when stepped on.</p>
-      <h3><a name="s8192a"></a>8192 - Exit Sector</h3>
-      <p>In single-player, cooperative, or race mode, being in this sector ends the level. You
-      don't necessarily have to be touching the floor. (If you want the player to have to be
-      touching the floor, you can use linedef type <a href="#l223">223</a>, an invisible,
-      intangible FOF, to do the trick. Give the FOF control sector a type of <a href="#s8192">8192</a>.)</p>
-      <p>See linedef type <a href="#l2">2</a> for a way to exit to any map, not just the one
-      whose number is specified in the map header. Linedef 2 also allows you to skip the score
-      tally screen.</p>
-      <h3><a name="s8192b"></a>8192 - No Tag Zone</h3>
-      <p>In games of tag, this sector is a safe spot. You cannot be tagged while in it.</p>
-      <h3><a name="s8192c"></a>8192 - CTF: Flag Return</h3>
-      <p>In CTF, if the red or blue flag enters this sector, it will automatically return to
-      base, much like how it behaves when it falls in a pit. This can also be set as a special
-      on a 3D floor.</p>
-      <h3><a name="s12288"></a>12288 - CTF: Red Team Base</h3>
-      <p>The red team has to bring the <a href="#t307">blue flag</a> onto this sector to score.
-      It's generally a good idea to have the <a href="#t306">red flag</a> here and the <a
-      href="#t34">red team player starts</a> somewhere close by.</p>
-      <h3><a name="s16384"></a>16384 - CTF: Blue Team Base</h3>
-      <p>The blue team has to bring the <a href="#t306">red flag</a> onto this sector to score.
-      It's generally a good idea to have the <a href="#t307">blue flag</a> here and the <a
-      href="#t35">blue team player starts</a> somewhere close by.</p>
-      <h3><a name="s20480"></a>20480 - Fan Sector</h3>
-      <p>Acts like a fan, pushing the player up at constant speed and activating the proper
-      animation. Can be used on intangible FOFs.</p>
-      <h3><a name="s24576"></a>24576 - Super Sonic Transform</h3>
-      <p>Transforms you into Super Sonic and gives you 50 rings, providing you have all of the
-      chaos emeralds.</p>
-      <h3><a name="s28672"></a>28672 - Spinner</h3>
-      <p>Forces the player into a spin.</p>
-      <h3><a name="s32768"></a>32768 - Zoom Tube Start</h3>
-      <p>When the player touches this sector, a line type <a href="#l3">3</a> with the same tag
-      as the sector is searched for, and if found, the line's X length determines the speed at
-      which the tube operates, while its Y length determines which zoom tube sequence to use.
-      Then the player is immediately put into a spin, loses control, and gravitates toward the
-      first Zoom Tube Waypoint (thing type <a href="#t753">753</a>), which does not have to be
-      in the same sector. Once they reach the first waypoint, they begin traveling to the 2nd,
-      3rd, and so on, until the last waypoint is reached.</p>
-      <p>This can be used with Floor-Over-Floors, just use these specials in the control sector
-      instead. </p>
-      <h3><a name="s36864"></a>36864 - Zoom Tube End</h3>
-      <p>Just like sector type <a href="#s32768">32768</a>, but starts from the last waypoint
-      and goes to the first.</p>
-      <h3><a name="s40960"></a>40960 - Finish Line</h3>
-      <p>The finish line for a race circuit. This increments a lap when you pass it, after
-      hitting all the star posts in the stage in sequential order. Once the number of laps
-      specified by the server is reached, the level is completed.</p>
-    </ol>
-  </li>
-</ul>
-</body>
-</html>
diff --git a/doc/specs/udmf_srb2.txt b/doc/specs/udmf_srb2.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c758d7e40f13d466afcfc689c679845fec71b906
--- /dev/null
+++ b/doc/specs/udmf_srb2.txt
@@ -0,0 +1,311 @@
+===============================================================================
+Universal Doom Map Format Sonic Robo Blast 2 extensions v1.0 19.02.2024
+
+    Copyright (c) 2024 Sonic Team Junior
+       uses Universal Doom Map Format Specification v1.1 as a template,
+       original document Copyright (c) 2009 James Haley.
+    Permission is granted to copy, distribute and/or modify this document
+    under the terms of the GNU Free Documentation License, Version 1.2
+    or any later version published by the Free Software Foundation;
+    with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+
+===============================================================================
+
+This document discusses the UDMF implementation found in Sonic Robo Blast 2's engine.
+
+=======================================
+I. Grammar / Syntax
+=======================================
+
+    No changes.
+
+=======================================
+II. Implementation Semantics
+=======================================
+
+------------------------------------
+II.A : Storage and Retrieval of Data
+------------------------------------
+
+    No changes.
+
+-----------------------------------
+II.B : Storage Within Archive Files
+-----------------------------------
+
+    No changes.
+
+--------------------------------
+II.C : Implementation Dependence
+--------------------------------
+
+The SRB2 engine only supports the following namespace:
+    "srb2"
+
+The engine is allowed to refuse maps with an unsupported namespace,
+or emit a warning.
+
+=======================================
+III. Standardized Fields
+=======================================
+
+The SRB2 engine ignores any user-defined fields.
+All boolean fields default to false unless mentioned otherwise.
+
+Sonic Robo Blast 2 defines the following standardized fields:
+
+   vertex
+   {
+      x = <float>;        // X coordinate. No valid default.
+      y = <float>;        // Y coordinate. No valid default.
+      zfloor = <float>;   // Floor height at this vertex. Only applies to triangular sectors
+      zceiling = <float>; // Ceiling height at this vertex. Only applies to triangular sectors
+   }
+
+   linedef
+   {
+      id      = <integer>;        // ID of line. Interpreted as tag.
+      moreids = <string>;         // Additional line IDs, specified as a space separated list of numbers (e.g. "2 666 1003 4505")
+
+      v1 = <integer>;             // Index of first vertex. No valid default.
+      v2 = <integer>;             // Index of second vertex. No valid default.
+
+      blocking      = <bool>;     // Line blocks things.
+      blockmonsters = <bool>;     // Line blocks enemies.
+      twosided      = <bool>;     // Line is 2S.
+      dontpegtop    = <bool>;     // Upper texture unpegged.
+      dontpegbottom = <bool>;     // Lower texture unpegged.
+      skewtd        = <bool>;     // Upper and lower textures are skewed.
+      noclimb       = <bool>;     // Line is not climbable.
+      noskew        = <bool>;     // Middle texture is not skewed.
+      midpeg        = <bool>;     // Middle texture is pegged.
+      midsolid      = <bool>;     // Middle texture is solid.
+      wrapmidtex    = <bool>;     // Line's mid textures are wrapped.
+      nonet         = <bool>;     // Special only takes effect in singleplayer games.
+      netonly       = <bool>;     // Special only takes effect in multiplayer games.
+      bouncy        = <bool>;     // Line is bouncy.
+      transfer      = <bool>;     // In 3D floor sides, uses the sidedef properties of the control sector.
+
+      alpha       = <float>;      // Translucency of this line. Default is 1.0
+      renderstyle = <string>;     // Render style. Can be:
+                                  // - "translucent"
+                                  // - "add"
+                                  // - "subtract"
+                                  // - "reversesubtract"
+                                  // - "modulate"
+                                  // - "fog"
+                                  // Default is "translucent".
+
+      special    = <integer>; // Linedef action. Default = 0.
+      arg0       = <integer>; // Argument 0. Default = 0.
+      arg1       = <integer>; // Argument 1. Default = 0.
+      arg2       = <integer>; // Argument 2. Default = 0.
+      arg3       = <integer>; // Argument 3. Default = 0.
+      arg4       = <integer>; // Argument 4. Default = 0.
+      arg5       = <integer>; // Argument 5. Default = 0.
+      arg6       = <integer>; // Argument 6. Default = 0.
+      arg7       = <integer>; // Argument 7. Default = 0.
+      arg8       = <integer>; // Argument 8. Default = 0.
+      arg9       = <integer>; // Argument 9. Default = 0.
+      stringarg0 = <string>;  // String argument 0.
+      stringarg1 = <string>;  // String argument 1.
+
+      sidefront = <integer>; // Sidedef 1 index. No valid default.
+      sideback  = <integer>; // Sidedef 2 index. Default = -1.
+
+      comment = <string>; // A comment. Implementors should attach no special
+                          // semantic meaning to this field.
+   }
+
+   sidedef
+   {
+      offsetx = <integer>; // X offset. Default = 0.
+      offsety = <integer>; // Y offset. Default = 0.
+
+      texturetop    = <string>; // Upper texture. Default = "-".
+      texturebottom = <string>; // Lower texture. Default = "-".
+      texturemiddle = <string>; // Middle texture. Default = "-".
+
+      repeatcnt = <string>; // Number of middle texture repetitions. Default = 0
+
+      sector = <integer>; // Sector index. No valid default.
+
+      scalex_top = <float>;            // X scale for upper texture. Default = 1.0.
+      scaley_top = <float>;            // Y scale for upper texture. Default = 1.0.
+      scalex_mid = <float>;            // X scale for mid texture. Default = 1.0.
+      scaley_mid = <float>;            // Y scale for mid texture. Default = 1.0.
+      scalex_bottom = <float>;         // X scale for lower texture. Default = 1.0.
+      scaley_bottom = <float>;         // Y scale for lower texture. Default = 1.0.
+      offsetx_top = <float>;           // X offset for upper texture. Default = 0.0.
+      offsety_top = <float>;           // Y offset for upper texture. Default = 0.0.
+      offsetx_mid = <float>;           // X offset for mid texture. Default = 0.0.
+      offsety_mid = <float>;           // Y offset for mid texture. Default = 0.0.
+      offsetx_bottom = <float>;        // X offset for lower texture. Default = 0.0.
+      offsety_bottom = <float>;        // Y offset for lower texture. Default = 0.0.
+
+      comment = <string>; // A comment. Implementors should attach no special
+                          // semantic meaning to this field.
+   }
+
+   sector
+   {
+      heightfloor   = <integer>; // Floor height. Default = 0.
+      heightceiling = <integer>; // Ceiling height. Default = 0.
+
+      texturefloor   = <string>; // Floor flat. No valid default.
+      textureceiling = <string>; // Ceiling flat. No valid default.
+
+      lightlevel = <integer>;         // Light level. Default = 255.
+      lightfloor = <integer>;         // The floor's light level. Default is 0.
+      lightceiling = <integer>;       // The ceiling's light level. Default is 0.
+      lightfloorabsolute = <bool>;    // true = 'lightfloor' is an absolute value. Default is
+                                      // relative to the owning sector's light level.
+      lightceilingabsolute = <bool>;  // true = 'lightceiling' is an absolute value. Default is
+                                      // relative to the owning sector's light level.
+
+      special = <integer>; // Sector special. Default = 0.
+      id      = <integer>; // Sector tag/id. Default = 0.
+      moreids = <string>;  // Additional sector IDs/tags, specified as a space separated list of numbers (e.g. "2 666 1003 4505")
+
+      xpanningfloor = <float>;        // X texture offset of floor texture. Default = 0.0.
+      ypanningfloor = <float>;        // Y texture offset of floor texture. Default = 0.0.
+      xpanningceiling = <float>;      // X texture offset of ceiling texture. Default = 0.0.
+      ypanningceiling = <float>;      // Y texture offset of ceiling texture. Default = 0.0.
+      xscalefloor = <float>;          // X texture scale of floor texture. Default = 1.0.
+      yscalefloor = <float>;          // Y texture scale of floor texture. Default = 1.0.
+      xscaleceiling = <float>;        // X texture scale of ceiling texture. Default = 1.0.
+      yscaleceiling = <float>;        // Y texture scale of ceiling texture. Default = 1.0.
+      rotationfloor = <float>;        // Rotation of floor texture in degrees. Default = 0.0.
+      rotationceiling = <float>;      // Rotation of ceiling texture in degrees. Default = 0.0.
+      ceilingplane_a = <float>;       // Define the plane equation for the sector's ceiling. Default is a horizontal plane at 'heightceiling'.
+      ceilingplane_b = <float>;       // 'heightceiling' will still be used to calculate texture alignment.
+      ceilingplane_c = <float>;       // The plane equation will only be used if all 4 values are given.
+      ceilingplane_d = <float>;       // The plane is defined as a*x + b*y + c*z + d = 0 with the normal vector pointing downward.
+      floorplane_a = <float>;         // Define the plane equation for the sector's floor. Default is a horizontal plane at 'heightfloor'.
+      floorplane_b = <float>;         // 'heightfloor' will still be used to calculate texture alignment.
+      floorplane_c = <float>;         // The plane equation will only be used if all 4 values are given.
+      floorplane_d = <float>;         // The plane is defined as a*x + b*y + c*z + d = 0 with the normal vector pointing upward.
+
+      lightcolor          = <integer>;      // Sector's light color as RRGGBB value. Default = 0x000000.
+      lightalpha          = <integer>;      // Sector's light opacity. Default = 25.
+      fadecolor           = <integer>;      // Sector's fog color as RRGGBB value. Default = 0x000000.
+      fadealpha           = <integer>;      // Sector's fog opacity. Default = 25.
+      fadestart           = <integer>;      // Sector's fading range start. Default = 0.
+      fadeend             = <integer>;      // Sector's fading range end. Default = 31.
+      colormapfog         = <bool>;         // Sector's colormap uses fog lighting.
+      colormapfadesprites = <bool>;         // Sector's colormap affects full-bright sprites.
+      colormapprotected   = <bool>;         // Sector's colormap is not affected by colormap change specials.
+
+      flipspecial_nofloor     = <bool>;     // Trigger effects that require a plane touch are not executed when the floor is touched.
+      flipspecial_ceiling     = <bool>;     // Trigger effects that require a plane touch are executed when the ceiling is touched.
+      triggerspecial_touch    = <bool>;     // Trigger effects are executed anywhere in the sector.
+      triggerspecial_headbump = <bool>;     // Trigger effects are executed if the top of the triggerer object touches a plane.
+      triggerline_plane       = <bool>;     // Trigger effects require a plane touch to be executed.
+      triggerline_mobj        = <bool>;     // Trigger effects can be executed by non-pushable objects.
+
+      invertprecip   = <bool>;         // Inverts the precipitation effect; if the sector is considered to be indoors,
+                                       // precipitation is generated, and if the sector is considered to be outdoors,
+                                       // precipitation is not generated.
+      gravityflip        = <bool>;     // Sector flips any objects in it, if the sector has negative gravity.
+      heatwave           = <bool>;     // Sector has the heat wave effect.
+      noclipcamera       = <bool>;     // Sector is not tangible to the camera.
+      outerspace         = <bool>;     // Sector has the space countdown effect.
+      doublestepup       = <bool>;     // Sector has half the vertical height needed for objects to walk into it.
+      nostepdown         = <bool>;     // Sector has the staircase effect disabled.
+      speedpad           = <bool>;     // Sector is a speed pad.
+      starpostactivator  = <bool>;     // Sector activates any Star Posts in it when walked into by a plyer.
+      exit               = <bool>;     // Sector is an exit sector.
+      specialstagepit    = <bool>;     // Sector is a Special Stage pit.
+      returnflag         = <bool>;     // Sector returns any Capture the Flag flags that come in contact with it to their respective bases.
+      redteambase        = <bool>;     // Sector is a Red Team base.
+      blueteambase       = <bool>;     // Sector is Blue Team base.
+      fan                = <bool>;     // Sector is a fan sector.
+      supertransform     = <bool>;     // Sector transforms any players that come in contact with it into their 'super' mode.
+      forcespin          = <bool>;     // Sector forces any players that come in contact with it to roll.
+      zoomtubestart      = <bool>;     // Sector is the starting point of a zoom tube path.
+      zoomtubeend        = <bool>;     // Sector is the ending point of a zoom tube path.
+      finishline         = <bool>;     // Sector is a Circuit finish line.
+      ropehang           = <bool>;     // Sector is a rope hang. Must be applied to a 3D floor.
+      jumpflip           = <bool>;     // Sector flips the gravity of players who jump from it.
+      gravityoverride    = <bool>;     // Reverse gravity effect is only applied when an object is in the sector.
+
+      friction            = <float>;   // Sector's friction factor.
+      gravity             = <float>;   // Sector's gravity. Default is 1.0.
+      damagetype          = <integer>; // Damage type for sector damage. Can be:
+                                       // - "None"
+                                       // - "Generic"
+                                       // - "Water"
+                                       // - "Fire"
+                                       // - "Lava"
+                                       // - "Electric"
+                                       // - "Spike"
+                                       // - "DeathPitTilt"
+                                       // - "DeathPitNoTilt"
+                                       // - "Instakill"
+                                       // - "SpecialStage"
+                                       // Default = "None".
+      triggertag          = <integer>; // Tag to trigger when this sector is entered. Default = 0.
+      triggerer           = <string>;  // Who can execute the trigger tag when this sector is entered. Can be:
+                                       // - "Player"
+                                       // - "AllPlayers"
+                                       // - "Mobj"
+                                       // Default = "Player".
+
+      comment = <string>; // A comment. Implementors should attach no special
+                          // semantic meaning to this field.
+   }
+
+   thing
+   {
+      id      = <integer>; // Thing ID. Default = 0.
+      moreids = <string>;  // Additional thing IDs, specified as a space separated list of numbers (e.g. "2 666 1003 4505")
+
+      x = <float>; // X coordinate. No valid default.
+      y = <float>; // Y coordinate. No valid default.
+
+      height = <float>; // Z height relative to floor.
+                        // Relative to ceiling if flip = true.
+                        // Absolute if absolutez = true.
+                        // Default = 0.
+
+      angle     = <integer>; // Map angle of thing in degrees. Default = 0 (East).
+      pitch     = <integer>; // Pitch of thing in degrees. Default = 0 (horizontal).
+      roll      = <integer>; // Roll of thing in degrees. Default = 0.
+      scalex    = <float>;   // Horizontal visual scaling on thing. Default = 1.0.
+      scaley    = <float>;   // Vertical visual scaling on thing. Default = 1.0.
+      scale     = <float>;   // Vertical and horizontal visual scaling on thing. Default = 1.0.
+      mobjscale = <float>;   // Physical scale of the thing. Default = 1.0.
+
+      type = <integer>; // Thing type. No valid default.
+
+      flip       = <bool>; // Thing spawns flipped, on the ceiling.
+      absolutez  = <bool>; // If true, the thing height is absolute, instead of being relative to the floor or ceiling.
+
+      arg0       = <integer>; // Argument 0. Default = 0.
+      arg1       = <integer>; // Argument 1. Default = 0.
+      arg2       = <integer>; // Argument 2. Default = 0.
+      arg3       = <integer>; // Argument 3. Default = 0.
+      arg4       = <integer>; // Argument 4. Default = 0.
+      arg5       = <integer>; // Argument 5. Default = 0.
+      arg6       = <integer>; // Argument 6. Default = 0.
+      arg7       = <integer>; // Argument 7. Default = 0.
+      arg8       = <integer>; // Argument 8. Default = 0.
+      arg9       = <integer>; // Argument 9. Default = 0.
+      stringarg0 = <string>;  // String argument 0.
+      stringarg1 = <string>;  // String argument 1.
+
+      comment = <string>; // A comment. Implementors should attach no special
+                          // semantic meaning to this field.
+   }
+
+
+=======================================
+Changelog
+=======================================
+
+1.0: 19.02.2024
+Initial version.
+
+===============================================================================
+EOF
+===============================================================================
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a7527461597db16e43f7a3ea83bb5f34497268c6..160174080c44996c49d8fafa91e2ff6a297acee5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -154,6 +154,10 @@ if (UNIX)
 	target_compile_definitions(SRB2SDL2 PRIVATE -DUNIXCOMMON)
 endif()
 
+if (BSD MATCHES "FreeBSD")
+	target_compile_definitions(SRB2SDL2 PRIVATE -DFREEBSD)
+endif()
+
 if(CMAKE_COMPILER_IS_GNUCC)
 	find_program(OBJCOPY objcopy)
 endif()
diff --git a/src/console.c b/src/console.c
index 4143e5e066b78e320a22e60b3f0ecb9712f848e3..0d296ca74138c46564878e911b7892eb64fec145 100644
--- a/src/console.c
+++ b/src/console.c
@@ -120,7 +120,7 @@ static void CONS_backcolor_Change(void);
 #ifdef macintosh
 #define CON_BUFFERSIZE 4096 // my compiler can't handle local vars >32k
 #else
-#define CON_BUFFERSIZE 16384
+#define CON_BUFFERSIZE 32768
 #endif
 
 static char con_buffer[CON_BUFFERSIZE];
diff --git a/src/dedicated/i_system.c b/src/dedicated/i_system.c
index 4dbaec8df9f73a5c7d04727942b7f224f4716139..13d5d1700b7e48f3d972bf242d7a289071bf2446 100644
--- a/src/dedicated/i_system.c
+++ b/src/dedicated/i_system.c
@@ -96,6 +96,7 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #endif
 
 #if defined (__unix__) || (defined (UNIXCOMMON) && !defined (__APPLE__))
+#include <poll.h>
 #include <errno.h>
 #include <sys/wait.h>
 #define NEWSIGNALHANDLER
@@ -855,50 +856,60 @@ static void I_GetConsoleEvents(void)
 	// we use this when sending back commands
 	event_t ev = {0};
 	char key = 0;
-	ssize_t d;
+	struct pollfd pfd =
+	{
+		.fd = STDIN_FILENO,
+		.events = POLLIN,
+		.revents = 0,
+	};
 
 	if (!consolevent)
 		return;
 
-	ev.type = ev_console;
-	ev.key = 0;
-	if (read(STDIN_FILENO, &key, 1) == -1 || !key)
-		return;
-
-	// we have something
-	// backspace?
-	// NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere
-	if ((key == tty_erase) || (key == 127) || (key == 8))
+	for (;;)
 	{
-		if (tty_con.cursor > 0)
+		if (poll(&pfd, 1, 0) < 1 || !(pfd.revents & POLLIN))
+			return;
+
+		ev.type = ev_console;
+		ev.key = 0;
+		if (read(STDIN_FILENO, &key, 1) == -1 || !key)
+			return;
+
+		// we have something
+		// backspace?
+		// NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere
+		if ((key == tty_erase) || (key == 127) || (key == 8))
 		{
-			tty_con.cursor--;
-			tty_con.buffer[tty_con.cursor] = '\0';
-			tty_Back();
+			if (tty_con.cursor > 0)
+			{
+				tty_con.cursor--;
+				tty_con.buffer[tty_con.cursor] = '\0';
+				tty_Back();
+			}
+			ev.key = KEY_BACKSPACE;
 		}
-		ev.key = KEY_BACKSPACE;
-	}
-	else if (key < ' ') // check if this is a control char
-	{
-		if (key == '\n')
+		else if (key < ' ') // check if this is a control char
+		{
+			if (key == '\n')
+			{
+				tty_Clear();
+				tty_con.cursor = 0;
+				ev.key = KEY_ENTER;
+			}
+			else continue;
+		}
+		else if (tty_con.cursor < sizeof(tty_con.buffer))
 		{
-			tty_Clear();
-			tty_con.cursor = 0;
-			ev.key = KEY_ENTER;
+			// push regular character
+			ev.key = tty_con.buffer[tty_con.cursor] = key;
+			tty_con.cursor++;
+			// print the current line (this is differential)
+			write(STDOUT_FILENO, &key, 1);
 		}
-		else return;
+		if (ev.key) D_PostEvent(&ev);
+		//tty_FlushIn();
 	}
-	else if (tty_con.cursor < sizeof(tty_con.buffer))
-	{
-		// push regular character
-		ev.key = tty_con.buffer[tty_con.cursor] = key;
-		tty_con.cursor++;
-		// print the current line (this is differential)
-		d = write(STDOUT_FILENO, &key, 1);
-	}
-	if (ev.key) D_PostEvent(&ev);
-	//tty_FlushIn();
-	(void)d;
 }
 
 #elif defined (_WIN32)
diff --git a/src/doomdef.h b/src/doomdef.h
index 4c843f9e2f07ad58439d2dcd3200726e6d62fa4d..42e3853fdba2ced729e0047de4a956827af84777 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -244,12 +244,12 @@ extern char logfilename[1024];
 #define MAXPLAYERNAME 21
 #define PLAYERSMASK (MAXPLAYERS-1)
 
-// Don't make MAXSKINS higher than 256, since skin numbers are used with an
-// UINT8 in various parts of the codebase. If you do anyway, the data type
-// of those variables will have to be changed into at least an UINT16.
+// Don't make MAXSKINS higher than 255, since skin numbers are used with an UINT8 in
+// various parts of the codebase, and one number is reserved. If you do anyway,
+// the data type of those variables will have to be changed into at least an UINT16.
 // This change must affect code such as demo recording and playback,
 // and the structure of some networking packets and commands.
-#define MAXSKINS 256
+#define MAXSKINS 255
 #define MAXCHARACTERSLOTS (MAXSKINS * 3) // Should be higher than MAXSKINS.
 
 #define COLORRAMPSIZE 16
@@ -552,7 +552,7 @@ void *M_Memcpy(void* dest, const void* src, size_t n);
 char *va(const char *format, ...) FUNCPRINTF;
 char *M_GetToken(const char *inputString);
 void M_UnGetToken(void);
-void M_TokenizerOpen(const char *inputString);
+void M_TokenizerOpen(const char *inputString, size_t len);
 void M_TokenizerClose(void);
 const char *M_TokenizerRead(UINT32 i);
 const char *M_TokenizerReadZDoom(UINT32 i);
diff --git a/src/f_finale.c b/src/f_finale.c
index cb64618535659f329bb357f65c2cec0bbe006b2d..0b618a1e3f53acd74a01b82116bd60eb698fec14 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -297,7 +297,7 @@ static void F_NewCutscene(const char *basetext)
 	cutscene_basetext = basetext;
 	memset(cutscene_disptext,0,sizeof(cutscene_disptext));
 	cutscene_writeptr = cutscene_baseptr = 0;
-	cutscene_textspeed = 9;
+	cutscene_textspeed = 8;
 	cutscene_textcount = TICRATE/2;
 }
 
@@ -313,22 +313,22 @@ const char *introtext[NUMINTROSCENES];
 static tic_t introscenetime[NUMINTROSCENES] =
 {
 	5*TICRATE,	// STJr Presents
-	11*TICRATE + (TICRATE/2),	// Two months had passed since...
-	15*TICRATE + (TICRATE/2),	// As it was about to drain the rings...
-	14*TICRATE,					// What Sonic, Tails, and Knuckles...
-	18*TICRATE,					// About once every year, a strange...
-	19*TICRATE + (TICRATE/2),	// Curses! Eggman yelled. That ridiculous...
-	19*TICRATE + (TICRATE/4),	// It was only later that he had an idea...
-	10*TICRATE + (TICRATE/2),	// Before beginning his scheme, Eggman decided to give Sonic...
-	16*TICRATE,					// We're ready to fire in 15 seconds, the robot said...
-	16*TICRATE,					// Meanwhile, Sonic was tearing across the zones...
+	10*TICRATE + (TICRATE/2),	// Two months had passed since...
+	12*TICRATE + ((TICRATE/4) * 3),	// As it was about to drain the rings...
+	12*TICRATE + (TICRATE/4),					// What Sonic, Tails, and Knuckles...
+	16*TICRATE,					// About once every year, a strange...
+	20*TICRATE + (TICRATE/2),	// Curses! Eggman yelled. That ridiculous...
+	18*TICRATE + (TICRATE/4),	// It was only later that he had an idea...
+	9*TICRATE + (TICRATE/2),	// Before beginning his scheme, Eggman decided to give Sonic...
+	14*TICRATE,					// We're ready to fire in 15 seconds, the robot said...
+	14*TICRATE + (TICRATE/2),	// Meanwhile, Sonic was tearing across the zones...
 	16*TICRATE + (TICRATE/2),	// Sonic knew he was getting closer to the city...
-	17*TICRATE,					// Greenflower City was gone...
-	 7*TICRATE,					// You're not quite as dead as we thought, huh?...
+	11*TICRATE + (TICRATE/2),	// Greenflower City was gone...
+	 8*TICRATE,					// You're not quite as dead as we thought, huh?...
 	 8*TICRATE,					// We'll see... let's give you a quick warm up...
 	18*TICRATE + (TICRATE/2),	// Eggman took this as his cue and blasted off...
-	16*TICRATE,					// Easy! We go find Eggman and stop his...
-	25*TICRATE,					// I'm just finding what mission obje...
+	15*TICRATE,					// Easy! We go find Eggman and stop his...
+	23*TICRATE,					// I'm just finding what mission obje...
 };
 
 // custom intros
@@ -1281,6 +1281,9 @@ void F_CreditDrawer(void)
 	UINT8 colornum;
 	const UINT8 *colormap;
 
+	// compensation for y on non-green resolutions, used to prevent text from disappearing before reaching the top
+	UINT16 compy = (vid.height - (BASEVIDHEIGHT * vid.dup)) / 2;
+
 	if (players[consoleplayer].skincolor)
 		colornum = players[consoleplayer].skincolor;
 	else
@@ -1312,17 +1315,17 @@ void F_CreditDrawer(void)
 			y += 80<<FRACBITS;
 			break;
 		case 1:
-			if (y>>FRACBITS > -20)
+			if (y>>FRACBITS > -20-compy)
 				V_DrawCreditString((160 - (V_CreditStringWidth(&credits[i][1])>>1))<<FRACBITS, y, 0, &credits[i][1]);
 			y += 30<<FRACBITS;
 			break;
 		case 2:
-			if (y>>FRACBITS > -10)
+			if (y>>FRACBITS > -10-compy)
 				V_DrawStringAtFixed((BASEVIDWIDTH-V_StringWidth(&credits[i][1], V_ALLOWLOWERCASE|V_YELLOWMAP))<<FRACBITS>>1, y, V_ALLOWLOWERCASE|V_YELLOWMAP, &credits[i][1]);
 			y += 12<<FRACBITS;
 			break;
 		default:
-			if (y>>FRACBITS > -10)
+			if (y>>FRACBITS > -10-compy)
 				V_DrawStringAtFixed(32<<FRACBITS, y, V_ALLOWLOWERCASE, credits[i]);
 			y += 12<<FRACBITS;
 			break;
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 4bcfb029b2ed1815e895ff126127bb628c6709fb..1ea32d0ebe92e6fdcfda930356cb36959c7acb91 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -569,7 +569,7 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 			if (rendermode == render_opengl)
 			{
 				// send in the wipe type and wipe frame because we need to cache the graphic
-				HWR_DoTintedWipe(wipetype, wipeframe-1);
+				HWR_DoWipe(wipetype, wipeframe-1);
 			}
 			else
 #endif
diff --git a/src/hardware/CMakeLists.txt b/src/hardware/CMakeLists.txt
index e7819aba97e2065d36f6f920d4725d7b294505f3..3b6135c1d768a732944d2a18b4e0d41f7fb8c461 100644
--- a/src/hardware/CMakeLists.txt
+++ b/src/hardware/CMakeLists.txt
@@ -10,5 +10,6 @@ target_sources(SRB2SDL2 PRIVATE
 	hw_md3load.c
 	hw_model.c
 	hw_batching.c
+	hw_shaders.c
 	r_opengl/r_opengl.c
 )
diff --git a/src/hardware/Sourcefile b/src/hardware/Sourcefile
index 6c374621d7b1de61f2b5a5c6fd9171f0685eccbf..4fa61470f26616ccb0dd311b4d08045054f23921 100644
--- a/src/hardware/Sourcefile
+++ b/src/hardware/Sourcefile
@@ -9,4 +9,5 @@ hw_md2load.c
 hw_md3load.c
 hw_model.c
 hw_batching.c
+hw_shaders.c
 r_opengl/r_opengl.c
diff --git a/src/hardware/hw_batching.c b/src/hardware/hw_batching.c
index a640a9917ad169cbeb8a141e6741614081d251df..b9ab2592d3f00a4fcdb4c1af31e2eaae484cd584 100644
--- a/src/hardware/hw_batching.c
+++ b/src/hardware/hw_batching.c
@@ -76,7 +76,7 @@ void HWR_SetCurrentTexture(GLMipmap_t *texture)
 // If batching is enabled, this function collects the polygon data and the chosen texture
 // for later use in HWR_RenderBatches. Otherwise the rendering backend is used to
 // render the polygon immediately.
-void HWR_ProcessPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPts, FBITFIELD PolyFlags, int shader, boolean horizonSpecial)
+void HWR_ProcessPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPts, FBITFIELD PolyFlags, int shader_target, boolean horizonSpecial)
 {
     if (currently_batching)
 	{
@@ -114,7 +114,7 @@ void HWR_ProcessPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPt
 		polygonArray[polygonArraySize].numVerts = iNumPts;
 		polygonArray[polygonArraySize].polyFlags = PolyFlags;
 		polygonArray[polygonArraySize].texture = current_texture;
-		polygonArray[polygonArraySize].shader = shader;
+		polygonArray[polygonArraySize].shader = (shader_target != -1) ? HWR_GetShaderFromTarget(shader_target) : shader_target;
 		polygonArray[polygonArraySize].horizonSpecial = horizonSpecial;
 		// default to polygonArraySize so we don't lose order on horizon lines
 		// (yes, it's supposed to be negative, since we're sorting in that direction)
@@ -134,7 +134,7 @@ void HWR_ProcessPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPt
 			DIGEST(hash, pSurf->PolyColor.rgba);
 			if (cv_glshaders.value && gl_shadersavailable)
 			{
-				DIGEST(hash, shader);
+				DIGEST(hash, shader_target);
 				DIGEST(hash, pSurf->TintColor.rgba);
 				DIGEST(hash, pSurf->FadeColor.rgba);
 				DIGEST(hash, pSurf->LightInfo.light_level);
@@ -151,10 +151,9 @@ void HWR_ProcessPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPt
 	}
 	else
 	{
-        if (shader)
-            HWD.pfnSetShader(shader);
-        HWD.pfnDrawPolygon(pSurf, pOutVerts, iNumPts, PolyFlags);
-    }
+		HWD.pfnSetShader((shader_target != SHADER_NONE) ? HWR_GetShaderFromTarget(shader_target) : shader_target);
+		HWD.pfnDrawPolygon(pSurf, pOutVerts, iNumPts, PolyFlags);
+	}
 }
 
 static int comparePolygons(const void *p1, const void *p2)
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 58e16d62361a9ca3f87479ea273343c7c36065bc..f1f0668be51d99dd3e5e07bcb4760083afb76c33 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -32,6 +32,14 @@
 INT32 patchformat = GL_TEXFMT_AP_88; // use alpha for holes
 INT32 textureformat = GL_TEXFMT_P_8; // use chromakey for hole
 
+RGBA_t mapPalette[256] = {0}; // the palette for the currently loaded level or menu etc.
+
+// Returns a pointer to the palette which should be used for caching textures.
+RGBA_t *HWR_GetTexturePalette(void)
+{
+	return HWR_ShouldUsePaletteRendering() ? mapPalette : pLocalPalette;
+}
+
 static INT32 format2bpp(GLTextureFormat_t format)
 {
 	if (format == GL_TEXFMT_RGBA)
@@ -49,7 +57,7 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 								INT32 pblockheight, INT32 blockmodulo,
 								fixed_t yfracstep, fixed_t scale_y,
 								texpatch_t *originPatch, INT32 patchheight,
-								INT32 bpp)
+								INT32 bpp, RGBA_t *palette)
 {
 	fixed_t yfrac, position, count;
 	UINT8 *dest;
@@ -113,7 +121,7 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 					memcpy(dest, &texelu16, sizeof(UINT16));
 					break;
 				case 3:
-					colortemp = V_GetColor(texel);
+					colortemp = palette[texel];
 					if ((originPatch != NULL) && (originPatch->style != AST_COPY))
 					{
 						RGBA_t rgbatexel;
@@ -123,7 +131,7 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 					memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 					break;
 				case 4:
-					colortemp = V_GetColor(texel);
+					colortemp = palette[texel];
 					colortemp.s.alpha = alpha;
 					if ((originPatch != NULL) && (originPatch->style != AST_COPY))
 					{
@@ -152,7 +160,7 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 								INT32 pblockheight, INT32 blockmodulo,
 								fixed_t yfracstep, fixed_t scale_y,
 								texpatch_t *originPatch, INT32 patchheight,
-								INT32 bpp)
+								INT32 bpp, RGBA_t *palette)
 {
 	fixed_t yfrac, position, count;
 	UINT8 *dest;
@@ -217,7 +225,7 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 					memcpy(dest, &texelu16, sizeof(UINT16));
 					break;
 				case 3:
-					colortemp = V_GetColor(texel);
+					colortemp = palette[texel];
 					if ((originPatch != NULL) && (originPatch->style != AST_COPY))
 					{
 						RGBA_t rgbatexel;
@@ -227,7 +235,7 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 					memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 					break;
 				case 4:
-					colortemp = V_GetColor(texel);
+					colortemp = palette[texel];
 					colortemp.s.alpha = alpha;
 					if ((originPatch != NULL) && (originPatch->style != AST_COPY))
 					{
@@ -269,10 +277,13 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 	UINT8 *block = mipmap->data;
 	INT32 bpp;
 	INT32 blockmodulo;
+	RGBA_t *palette;
 
 	if (pwidth <= 0 || pheight <= 0)
 		return;
 
+	palette = HWR_GetTexturePalette();
+
 	ncols = pwidth;
 
 	// source advance
@@ -298,7 +309,7 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 								pblockheight, blockmodulo,
 								yfracstep, scale_y,
 								NULL, pheight, // not that pheight is going to get used anyway...
-								bpp);
+								bpp, palette);
 	}
 }
 
@@ -317,16 +328,19 @@ static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
 	INT32 bpp;
 	INT32 blockmodulo;
 	INT32 width, height;
+	RGBA_t *palette;
 	// Column drawing function pointer.
 	static void (*ColumnDrawerPointer)(const column_t *patchcol, UINT8 *block, GLMipmap_t *mipmap,
 								INT32 pblockheight, INT32 blockmodulo,
 								fixed_t yfracstep, fixed_t scale_y,
 								texpatch_t *originPatch, INT32 patchheight,
-								INT32 bpp);
+								INT32 bpp, RGBA_t *palette);
 
 	if (texture->width <= 0 || texture->height <= 0)
 		return;
 
+	palette = HWR_GetTexturePalette();
+
 	ColumnDrawerPointer = (patch->flip & 2) ? HWR_DrawFlippedColumnInCache : HWR_DrawColumnInCache;
 
 	x1 = patch->originx;
@@ -386,7 +400,7 @@ static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
 								pblockheight, blockmodulo,
 								yfracstep, scale_y,
 								patch, height,
-								bpp);
+								bpp, palette);
 	}
 }
 
@@ -429,6 +443,9 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *grtex)
 	INT32 i;
 	boolean skyspecial = false; //poor hack for Legacy large skies..
 
+	RGBA_t *palette;
+	palette = HWR_GetTexturePalette();
+
 	texture = textures[texnum];
 
 	// hack the Legacy skies..
@@ -447,7 +464,10 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *grtex)
 
 	grtex->mipmap.width = (UINT16)texture->width;
 	grtex->mipmap.height = (UINT16)texture->height;
-	grtex->mipmap.format = textureformat;
+	if (skyspecial)
+		grtex->mipmap.format = GL_TEXFMT_RGBA; // that skyspecial code below assumes this format ...
+	else
+		grtex->mipmap.format = textureformat;
 
 	blockwidth = texture->width;
 	blockheight = texture->height;
@@ -459,7 +479,7 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *grtex)
 		INT32 j;
 		RGBA_t col;
 
-		col = V_GetColor(HWR_PATCHES_CHROMAKEY_COLORINDEX);
+		col = palette[HWR_PATCHES_CHROMAKEY_COLORINDEX];
 		for (j = 0; j < blockheight; j++)
 		{
 			for (i = 0; i < blockwidth; i++)
@@ -739,19 +759,6 @@ void HWR_LoadMapTextures(size_t pnumtextures)
 	gl_maptexturesloaded = true;
 }
 
-void HWR_SetPalette(RGBA_t *palette)
-{
-	HWD.pfnSetPalette(palette);
-
-	// hardware driver will flush there own cache if cache is non paletized
-	// now flush data texture cache so 32 bit texture are recomputed
-	if (patchformat == GL_TEXFMT_RGBA || textureformat == GL_TEXFMT_RGBA)
-	{
-		Z_FreeTag(PU_HWRCACHE);
-		Z_FreeTag(PU_HWRCACHE_UNLOCKED);
-	}
-}
-
 // --------------------------------------------------------------------------
 // Make sure texture is downloaded and set it as the source
 // --------------------------------------------------------------------------
@@ -823,18 +830,13 @@ void HWR_GetRawFlat(lumpnum_t flatlumpnum)
 
 void HWR_GetLevelFlat(levelflat_t *levelflat)
 {
-	if (levelflat->type == LEVELFLAT_NONE)
+	if (levelflat->type == LEVELFLAT_NONE || levelflat->texture_id < 0)
 	{
 		HWR_SetCurrentTexture(NULL);
 		return;
 	}
 
 	INT32 texturenum = texturetranslation[levelflat->texture_id];
-	if (texturenum <= 0)
-	{
-		HWR_SetCurrentTexture(NULL);
-		return;
-	}
 
 	GLMapTexture_t *grtex = &gl_flats[texturenum];
 	GLMipmap_t *grMipmap = &grtex->mipmap;
@@ -970,6 +972,139 @@ void HWR_UnlockCachedPatch(GLPatch_t *gpatch)
 	Z_ChangeTag(gpatch->mipmap->data, PU_HWRCACHE_UNLOCKED);
 }
 
+static const INT32 picmode2GR[] =
+{
+	GL_TEXFMT_P_8,                // PALETTE
+	0,                            // INTENSITY          (unsupported yet)
+	GL_TEXFMT_ALPHA_INTENSITY_88, // INTENSITY_ALPHA    (corona use this)
+	0,                            // RGB24              (unsupported yet)
+	GL_TEXFMT_RGBA,               // RGBA32             (opengl only)
+};
+
+static void HWR_DrawPicInCache(UINT8 *block, INT32 pblockwidth, INT32 pblockheight,
+	INT32 blockmodulo, pic_t *pic, INT32 bpp)
+{
+	INT32 i,j;
+	fixed_t posx, posy, stepx, stepy;
+	UINT8 *dest, *src, texel;
+	UINT16 texelu16;
+	INT32 picbpp;
+	RGBA_t col;
+	RGBA_t *palette = HWR_GetTexturePalette();
+
+	stepy = ((INT32)SHORT(pic->height)<<FRACBITS)/pblockheight;
+	stepx = ((INT32)SHORT(pic->width)<<FRACBITS)/pblockwidth;
+	picbpp = format2bpp(picmode2GR[pic->mode]);
+	posy = 0;
+	for (j = 0; j < pblockheight; j++)
+	{
+		posx = 0;
+		dest = &block[j*blockmodulo];
+		src = &pic->data[(posy>>FRACBITS)*SHORT(pic->width)*picbpp];
+		for (i = 0; i < pblockwidth;i++)
+		{
+			switch (pic->mode)
+			{ // source bpp
+				case PALETTE :
+					texel = src[(posx+FRACUNIT/2)>>FRACBITS];
+					switch (bpp)
+					{ // destination bpp
+						case 1 :
+							*dest++ = texel; break;
+						case 2 :
+							texelu16 = (UINT16)(texel | 0xff00);
+							memcpy(dest, &texelu16, sizeof(UINT16));
+							dest += sizeof(UINT16);
+							break;
+						case 3 :
+							col = palette[texel];
+							memcpy(dest, &col, sizeof(RGBA_t)-sizeof(UINT8));
+							dest += sizeof(RGBA_t)-sizeof(UINT8);
+							break;
+						case 4 :
+							memcpy(dest, &palette[texel], sizeof(RGBA_t));
+							dest += sizeof(RGBA_t);
+							break;
+					}
+					break;
+				case INTENSITY :
+					*dest++ = src[(posx+FRACUNIT/2)>>FRACBITS];
+					break;
+				case INTENSITY_ALPHA : // assume dest bpp = 2
+					memcpy(dest, src + ((posx+FRACUNIT/2)>>FRACBITS)*sizeof(UINT16), sizeof(UINT16));
+					dest += sizeof(UINT16);
+					break;
+				case RGB24 :
+					break;  // not supported yet
+				case RGBA32 : // assume dest bpp = 4
+					dest += sizeof(UINT32);
+					memcpy(dest, src + ((posx+FRACUNIT/2)>>FRACBITS)*sizeof(UINT32), sizeof(UINT32));
+					break;
+			}
+			posx += stepx;
+		}
+		posy += stepy;
+	}
+}
+
+// -----------------+
+// HWR_GetPic       : Download a Doom pic (raw row encoded with no 'holes')
+// Returns          :
+// -----------------+
+patch_t *HWR_GetPic(lumpnum_t lumpnum)
+{
+	patch_t *patch = HWR_GetCachedGLPatch(lumpnum);
+	GLPatch_t *grPatch = (GLPatch_t *)(patch->hardware);
+
+	if (!grPatch->mipmap->downloaded && !grPatch->mipmap->data)
+	{
+		pic_t *pic;
+		UINT8 *block;
+		size_t len;
+
+		pic = W_CacheLumpNum(lumpnum, PU_CACHE);
+		patch->width = SHORT(pic->width);
+		patch->height = SHORT(pic->height);
+		len = W_LumpLength(lumpnum) - sizeof (pic_t);
+
+		grPatch->mipmap->width = (UINT16)patch->width;
+		grPatch->mipmap->height = (UINT16)patch->height;
+
+		if (pic->mode == PALETTE)
+			grPatch->mipmap->format = textureformat; // can be set by driver
+		else
+			grPatch->mipmap->format = picmode2GR[pic->mode];
+
+		Z_Free(grPatch->mipmap->data);
+
+		// allocate block
+		block = MakeBlock(grPatch->mipmap);
+
+		if (patch->width  == SHORT(pic->width) &&
+			patch->height == SHORT(pic->height) &&
+			format2bpp(grPatch->mipmap->format) == format2bpp(picmode2GR[pic->mode]))
+		{
+			// no conversion needed
+			M_Memcpy(grPatch->mipmap->data, pic->data,len);
+		}
+		else
+			HWR_DrawPicInCache(block, SHORT(pic->width), SHORT(pic->height),
+			                   SHORT(pic->width)*format2bpp(grPatch->mipmap->format),
+			                   pic,
+			                   format2bpp(grPatch->mipmap->format));
+
+		Z_Unlock(pic);
+		Z_ChangeTag(block, PU_HWRCACHE_UNLOCKED);
+
+		grPatch->mipmap->flags = 0;
+		grPatch->max_s = grPatch->max_t = 1.0f;
+	}
+	HWD.pfnSetTexture(grPatch->mipmap);
+	//CONS_Debug(DBG_RENDER, "picloaded at %x as texture %d\n",grPatch->mipmap->data, grPatch->mipmap->downloaded);
+
+	return patch;
+}
+
 patch_t *HWR_GetCachedGLPatchPwad(UINT16 wadnum, UINT16 lumpnum)
 {
 	lumpcache_t *lumpcache = wadfiles[wadnum]->patchcache;
@@ -997,6 +1132,7 @@ static void HWR_DrawFadeMaskInCache(GLMipmap_t *mipmap, INT32 pblockwidth, INT32
 	UINT8 *flat;
 	UINT8 *dest, *src, texel;
 	RGBA_t col;
+	RGBA_t *palette = HWR_GetTexturePalette();
 
 	// Place the flats data into flat
 	W_ReadLump(fademasklumpnum, Z_Malloc(W_LumpLength(fademasklumpnum),
@@ -1014,7 +1150,7 @@ static void HWR_DrawFadeMaskInCache(GLMipmap_t *mipmap, INT32 pblockwidth, INT32
 		{
 			// fademask bpp is always 1, and is used just for alpha
 			texel = src[(posx)>>FRACBITS];
-			col = V_GetColor(texel);
+			col = palette[texel];
 			*dest = col.s.red; // take the red level of the colour and use it for alpha, as fademasks do
 
 			dest++;
@@ -1086,4 +1222,185 @@ void HWR_GetFadeMask(lumpnum_t fademasklumpnum)
 	Z_ChangeTag(grmip->data, PU_HWRCACHE_UNLOCKED);
 }
 
+// =================================================
+//             PALETTE HANDLING
+// =================================================
+
+void HWR_SetPalette(RGBA_t *palette)
+{
+	if (HWR_ShouldUsePaletteRendering())
+	{
+		// set the palette for palette postprocessing
+
+		if (cv_glpalettedepth.value == 16)
+		{
+			// crush to 16-bit rgb565, like software currently does in the standard configuration
+			// Note: Software's screenshots have the 24-bit palette, but the screen gets
+			// the 16-bit version! For making comparison screenshots either use an external screenshot
+			// tool or set the palette depth to 24 bits.
+			RGBA_t crushed_palette[256];
+			int i;
+			for (i = 0; i < 256; i++)
+			{
+				float fred = (float)(palette[i].s.red >> 3);
+				float fgreen = (float)(palette[i].s.green >> 2);
+				float fblue = (float)(palette[i].s.blue >> 3);
+				crushed_palette[i].s.red = (UINT8)(fred / 31.0f * 255.0f);
+				crushed_palette[i].s.green = (UINT8)(fgreen / 63.0f * 255.0f);
+				crushed_palette[i].s.blue = (UINT8)(fblue / 31.0f * 255.0f);
+				crushed_palette[i].s.alpha = 255;
+			}
+			HWD.pfnSetScreenPalette(crushed_palette);
+		}
+		else
+		{
+			HWD.pfnSetScreenPalette(palette);
+		}
+
+		// this part is responsible for keeping track of the palette OUTSIDE of a level.
+		if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
+			HWR_SetMapPalette();
+	}
+	else
+	{
+		// set the palette for the textures
+		HWD.pfnSetTexturePalette(palette);
+		// reset mapPalette so next call to HWR_SetMapPalette will update everything correctly
+		memset(mapPalette, 0, sizeof(mapPalette));
+		// hardware driver will flush there own cache if cache is non paletized
+		// now flush data texture cache so 32 bit texture are recomputed
+		if (patchformat == GL_TEXFMT_RGBA || textureformat == GL_TEXFMT_RGBA)
+		{
+			Z_FreeTag(PU_HWRCACHE);
+			Z_FreeTag(PU_HWRCACHE_UNLOCKED);
+		}
+	}
+}
+
+static void HWR_SetPaletteLookup(RGBA_t *palette)
+{
+	int r, g, b;
+	UINT8 *lut = Z_Malloc(
+		HWR_PALETTE_LUT_SIZE*HWR_PALETTE_LUT_SIZE*HWR_PALETTE_LUT_SIZE*sizeof(UINT8),
+		PU_STATIC, NULL);
+#define STEP_SIZE (256/HWR_PALETTE_LUT_SIZE)
+	for (b = 0; b < HWR_PALETTE_LUT_SIZE; b++)
+	{
+		for (g = 0; g < HWR_PALETTE_LUT_SIZE; g++)
+		{
+			for (r = 0; r < HWR_PALETTE_LUT_SIZE; r++)
+			{
+				lut[b*HWR_PALETTE_LUT_SIZE*HWR_PALETTE_LUT_SIZE+g*HWR_PALETTE_LUT_SIZE+r] =
+					NearestPaletteColor(r*STEP_SIZE, g*STEP_SIZE, b*STEP_SIZE, palette);
+			}
+		}
+	}
+#undef STEP_SIZE
+	HWD.pfnSetPaletteLookup(lut);
+	Z_Free(lut);
+}
+
+// Updates mapPalette to reflect the loaded level or other game state.
+// Textures are flushed if needed.
+// Call this function only in palette rendering mode.
+void HWR_SetMapPalette(void)
+{
+	RGBA_t RGBA_converted[256];
+	RGBA_t *palette;
+	int i;
+
+	if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
+	{
+		// outside of a level, pMasterPalette should have PLAYPAL ready for us
+		palette = pMasterPalette;
+	}
+	else
+	{
+		// in a level pMasterPalette might have a flash palette, but we
+		// want the map's original palette.
+		lumpnum_t lumpnum = W_GetNumForName(GetPalette());
+		size_t palsize = W_LumpLength(lumpnum);
+		UINT8 *RGB_data;
+		if (palsize < 768) // 256 * 3
+			I_Error("HWR_SetMapPalette: A programmer assumed palette lumps are at least 768 bytes long, but apparently this was a wrong assumption!\n");
+		RGB_data = W_CacheLumpNum(lumpnum, PU_CACHE);
+		// we got the RGB palette now, but we need it in RGBA format.
+		for (i = 0; i < 256; i++)
+		{
+			RGBA_converted[i].s.red = *(RGB_data++);
+			RGBA_converted[i].s.green = *(RGB_data++);
+			RGBA_converted[i].s.blue = *(RGB_data++);
+			RGBA_converted[i].s.alpha = 255;
+		}
+		palette = RGBA_converted;
+	}
+
+	// check if the palette has changed from the previous one
+	if (memcmp(mapPalette, palette, sizeof(mapPalette)))
+	{
+		memcpy(mapPalette, palette, sizeof(mapPalette));
+		// in palette rendering mode, this means that all rgba textures now have wrong colors
+		// and the lookup table is outdated
+		HWR_SetPaletteLookup(mapPalette);
+		HWD.pfnSetTexturePalette(mapPalette);
+		if (patchformat == GL_TEXFMT_RGBA || textureformat == GL_TEXFMT_RGBA)
+		{
+			Z_FreeTag(PU_HWRCACHE);
+			Z_FreeTag(PU_HWRCACHE_UNLOCKED);
+		}
+	}
+}
+
+// Creates a hardware lighttable from the supplied lighttable.
+// Returns the id of the hw lighttable, usable in FSurfaceInfo.
+UINT32 HWR_CreateLightTable(UINT8 *lighttable)
+{
+	UINT32 i, id;
+	RGBA_t *palette = HWR_GetTexturePalette();
+	RGBA_t *hw_lighttable = Z_Malloc(256 * 32 * sizeof(RGBA_t), PU_STATIC, NULL);
+
+	// To make the palette index -> RGBA mapping easier for the shader,
+	// the hardware lighttable is composed of RGBA colors instead of palette indices.
+	for (i = 0; i < 256 * 32; i++)
+		hw_lighttable[i] = palette[lighttable[i]];
+
+	id = HWD.pfnCreateLightTable(hw_lighttable);
+	Z_Free(hw_lighttable);
+	return id;
+}
+
+// get hwr lighttable id for colormap, create it if it doesn't already exist
+UINT32 HWR_GetLightTableID(extracolormap_t *colormap)
+{
+	boolean default_colormap = false;
+	if (!colormap)
+	{
+		colormap = R_GetDefaultColormap(); // a place to store the hw lighttable id
+		// alternatively could just store the id in a global variable if there are issues
+		default_colormap = true;
+	}
+
+	// create hw lighttable if there isn't one
+	if (!colormap->gl_lighttable_id)
+	{
+		UINT8 *colormap_pointer;
+
+		if (default_colormap)
+			colormap_pointer = colormaps; // don't actually use the data from the "default colormap"
+		else
+			colormap_pointer = colormap->colormap;
+		colormap->gl_lighttable_id = HWR_CreateLightTable(colormap_pointer);
+	}
+
+	return colormap->gl_lighttable_id;
+}
+
+// Note: all hardware lighttable ids assigned before this
+// call become invalid and must not be used.
+void HWR_ClearLightTables(void)
+{
+	if (vid.glstate == VID_GL_LIBRARY_LOADED)
+		HWD.pfnClearLightTables();
+}
+
 #endif //HWRENDER
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 3b660cc70c36515dab60b4ae3eabb914b5de7d5d..2d55eef2d8fd72271fef9c529d4be862318f93da 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -18,6 +18,12 @@
 #define ZCLIP_PLANE 4.0f // Used for the actual game drawing
 #define NZCLIP_PLANE 0.9f // Seems to be only used for the HUD and screen textures
 
+// The width/height/depth of the palette lookup table used by palette rendering.
+// Changing this also requires changing the shader code!
+// Also assumed to be a power of two in some parts of the code.
+// 64 seems to work perfectly for the vanilla palette.
+#define HWR_PALETTE_LUT_SIZE 64
+
 // ==========================================================================
 //                                                               SIMPLE TYPES
 // ==========================================================================
@@ -122,33 +128,31 @@ typedef struct
 } FOutVector;
 
 #ifdef GL_SHADERS
-// Predefined shader types
+
+// Shader targets used to render specific types of geometry.
+// A shader target is resolved to an actual shader with HWR_GetShaderFromTarget.
+// The shader returned may be a base shader or a custom shader.
 enum
 {
 	SHADER_NONE = -1,
-	SHADER_DEFAULT = 0,
 
-	SHADER_FLOOR,
+	SHADER_FLOOR = 0,
 	SHADER_WALL,
 	SHADER_SPRITE,
-	SHADER_MODEL, SHADER_MODEL_LIGHTING,
+	SHADER_MODEL,
 	SHADER_WATER,
 	SHADER_FOG,
 	SHADER_SKY,
+	SHADER_PALETTE_POSTPROCESS,
+	SHADER_UI_COLORMAP_FADE,
+	SHADER_UI_TINTED_WIPE,
 
-	NUMBASESHADERS,
+	NUMSHADERTARGETS
 };
 
 // Maximum amount of shader programs
-// Must be higher than NUMBASESHADERS
-#define HWR_MAXSHADERS 16
-
-// Shader sources (vertex and fragment)
-typedef struct
-{
-	char *vertex;
-	char *fragment;
-} shadersource_t;
+// Must be at least NUMSHADERTARGETS*2 to fit base and custom shaders for each shader target.
+#define HWR_MAXSHADERS NUMSHADERTARGETS*2
 
 // Custom shader reference table
 typedef struct
@@ -272,11 +276,15 @@ struct FSurfaceInfo
 	RGBA_t			PolyColor;
 	RGBA_t			TintColor;
 	RGBA_t			FadeColor;
+	UINT32			LightTableId;
 	FLightInfo		LightInfo;
 };
 typedef struct FSurfaceInfo FSurfaceInfo;
 
-//Hurdler: added for backward compatibility
+#define GL_DEFAULTMIX 0x00000000
+#define GL_DEFAULTFOG 0xFF000000
+
+// Various settings and states for the rendering backend.
 enum hwdsetspecialstate
 {
 	HWD_SET_MODEL_LIGHTING = 1,
@@ -289,15 +297,13 @@ enum hwdsetspecialstate
 
 typedef enum hwdsetspecialstate hwdspecialstate_t;
 
-// Lactozilla: Shader options
-enum hwdshaderoption
+enum hwdshaderstage
 {
-	HWD_SHADEROPTION_OFF,
-	HWD_SHADEROPTION_ON,
-	HWD_SHADEROPTION_NOCUSTOM,
+	HWD_SHADERSTAGE_VERTEX,
+	HWD_SHADERSTAGE_FRAGMENT,
 };
 
-typedef enum hwdshaderoption hwdshaderoption_t;
+typedef enum hwdshaderstage hwdshaderstage_t;
 
 // Lactozilla: Shader info
 // Generally set at the start of the frame.
@@ -318,5 +324,18 @@ enum hwdfiltermode
 	HWD_SET_TEXTUREFILTER_MIXED3,
 };
 
+// Screen texture slots
+enum hwdscreentexture
+{
+	HWD_SCREENTEXTURE_WIPE_START, // source image for the wipe/fade effect
+	HWD_SCREENTEXTURE_WIPE_END,   // destination image for the wipe/fade effect
+	HWD_SCREENTEXTURE_GENERIC1,   // underwater/heat effect, intermission background
+	HWD_SCREENTEXTURE_GENERIC2,   // palette-based colormap fade, screen before palette rendering's postprocessing
+	HWD_SCREENTEXTURE_GENERIC3,   // screen after palette rendering's postprocessing
+	NUMSCREENTEXTURES,            // (generic3 is unused if palette rendering is disabled)
+};
+
+typedef enum hwdscreentexture hwdscreentexture_t;
+
 
 #endif //_HWR_DEFS_
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index ba1f339d0fe9af16d7eee7ab0f73d7558de0936e..e07484137c83433edc89207a3b85ae5f921d348d 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -30,6 +30,7 @@
 #include "../st_stuff.h"
 #include "../p_local.h" // stplyr
 #include "../g_game.h" // players
+#include "../f_finale.h" // fade color factors
 
 #include <fcntl.h>
 #include "../i_video.h"  // for rendermode != render_glide
@@ -707,6 +708,7 @@ void HWR_FadeScreenMenuBack(UINT16 color, UINT8 strength)
 {
 	FOutVector  v[4];
 	FSurfaceInfo Surf;
+	FBITFIELD poly_flags = PF_NoTexture|PF_Modulated|PF_NoDepthTest;
 
 	v[0].x = v[3].x = -1.0f;
 	v[2].x = v[1].x =  1.0f;
@@ -719,17 +721,59 @@ void HWR_FadeScreenMenuBack(UINT16 color, UINT8 strength)
 	v[0].t = v[1].t = 1.0f;
 	v[2].t = v[3].t = 0.0f;
 
-	if (color & 0xFF00) // Do COLORMAP fade.
+	if (color & 0xFF00) // Special fade options
 	{
-		Surf.PolyColor.rgba = UINT2RGBA(0x01010160);
-		Surf.PolyColor.s.alpha = (strength*8);
+		UINT16 option = color & 0x0F00;
+		if (option == 0x0A00 || option == 0x0B00) // Tinted fades
+		{
+			INT32 r, g, b;
+			int fade = strength * 8;
+
+			r = FADEREDFACTOR*fade/10;
+			g = FADEGREENFACTOR*fade/10;
+			b = FADEBLUEFACTOR*fade/10;
+
+			Surf.PolyColor.s.red = min(r, 255);
+			Surf.PolyColor.s.green = min(g, 255);
+			Surf.PolyColor.s.blue = min(b, 255);
+			Surf.PolyColor.s.alpha = 255;
+
+			if (option == 0x0A00) // Tinted subtractive fade
+				poly_flags |= PF_ReverseSubtract;
+			else if (option == 0x0B00) // Tinted additive fade
+				poly_flags |= PF_Additive;
+		}
+		else // COLORMAP fade
+		{
+			if (HWR_ShouldUsePaletteRendering())
+			{
+				const hwdscreentexture_t scr_tex = HWD_SCREENTEXTURE_GENERIC2;
+
+				Surf.LightTableId = HWR_GetLightTableID(NULL);
+				Surf.LightInfo.light_level = strength;
+				HWD.pfnMakeScreenTexture(scr_tex);
+				HWD.pfnSetShader(HWR_GetShaderFromTarget(SHADER_UI_COLORMAP_FADE));
+				HWD.pfnDrawScreenTexture(scr_tex, &Surf, PF_ColorMapped|PF_NoDepthTest);
+				HWD.pfnUnSetShader();
+
+				return;
+			}
+			else
+			{
+				Surf.PolyColor.rgba = UINT2RGBA(0x01010160);
+				Surf.PolyColor.s.alpha = (strength*8);
+				poly_flags |= PF_Translucent;
+			}
+		}
 	}
 	else // Do TRANSMAP** fade.
 	{
-		Surf.PolyColor.rgba = V_GetColor(color).rgba;
+		RGBA_t *palette = HWR_GetTexturePalette();
+		Surf.PolyColor.rgba = palette[color&0xFF].rgba;
 		Surf.PolyColor.s.alpha = softwaretranstogl[strength];
+		poly_flags |= PF_Translucent;
 	}
-	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
+	HWD.pfnDrawPolygon(&Surf, v, 4, poly_flags);
 }
 
 // -----------------+
@@ -897,7 +941,8 @@ void HWR_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT16 ac
 	}
 	else // Do TRANSMAP** fade.
 	{
-		Surf.PolyColor.rgba = V_GetColor(actualcolor).rgba;
+		RGBA_t *palette = HWR_GetTexturePalette();
+		Surf.PolyColor.rgba = palette[actualcolor&0xFF].rgba;
 		Surf.PolyColor.s.alpha = softwaretranstogl[strength];
 	}
 	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
@@ -1102,8 +1147,9 @@ void HWR_drawAMline(const fline_t *fl, INT32 color)
 {
 	F2DCoord v1, v2;
 	RGBA_t color_rgba;
+	RGBA_t *palette = HWR_GetTexturePalette();
 
-	color_rgba = V_GetColor(color);
+	color_rgba = palette[color&0xFF];
 
 	v1.x = ((float)fl->a.x-(vid.width/2.0f))*(2.0f/vid.width);
 	v1.y = ((float)fl->a.y-(vid.height/2.0f))*(2.0f/vid.height);
@@ -1288,6 +1334,7 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 	FOutVector v[4];
 	FSurfaceInfo Surf;
 	float fx, fy, fw, fh;
+	RGBA_t *palette = HWR_GetTexturePalette();
 	UINT8 alphalevel = ((color & V_ALPHAMASK) >> V_ALPHASHIFT);
 
 	UINT8 perplayershuffle = 0;
@@ -1374,7 +1421,7 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 	{
 		if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
 		{
-			RGBA_t rgbaColour = V_GetColor(color);
+			RGBA_t rgbaColour = palette[color&0xFF];
 			FRGBAFloat clearColour;
 			clearColour.red = (float)rgbaColour.s.red / 255;
 			clearColour.green = (float)rgbaColour.s.green / 255;
@@ -1451,7 +1498,7 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 	v[0].t = v[1].t = 0.0f;
 	v[2].t = v[3].t = 1.0f;
 
-	Surf.PolyColor = V_GetColor(color);
+	Surf.PolyColor = palette[color&0xFF];
 
 	if (alphalevel)
 	{
@@ -1539,11 +1586,12 @@ static inline boolean saveTGA(const char *file_name, void *buffer,
 UINT8 *HWR_GetScreenshot(void)
 {
 	UINT8 *buf = malloc(vid.width * vid.height * 3 * sizeof (*buf));
+	int tex = HWR_ShouldUsePaletteRendering() ? HWD_SCREENTEXTURE_GENERIC3 : HWD_SCREENTEXTURE_GENERIC2;
 
 	if (!buf)
 		return NULL;
 	// returns 24bit 888 RGB
-	HWD.pfnReadRect(0, 0, vid.width, vid.height, vid.width * 3, (void *)buf);
+	HWD.pfnReadScreenTexture(tex, (void *)buf);
 	return buf;
 }
 
@@ -1551,6 +1599,7 @@ boolean HWR_Screenshot(const char *pathname)
 {
 	boolean ret;
 	UINT8 *buf = malloc(vid.width * vid.height * 3 * sizeof (*buf));
+	int tex = HWR_ShouldUsePaletteRendering() ? HWD_SCREENTEXTURE_GENERIC3 : HWD_SCREENTEXTURE_GENERIC2;
 
 	if (!buf)
 	{
@@ -1559,7 +1608,7 @@ boolean HWR_Screenshot(const char *pathname)
 	}
 
 	// returns 24bit 888 RGB
-	HWD.pfnReadRect(0, 0, vid.width, vid.height, vid.width * 3, (void *)buf);
+	HWD.pfnReadScreenTexture(tex, (void *)buf);
 
 #ifdef USE_PNG
 	ret = M_SavePNG(pathname, buf, vid.width, vid.height, NULL);
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 1c4cd99ab03d34498fa13d132a01ef53af9e6e61..ba0258c120de3bd388513c827d03ecaa8689424e 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -32,7 +32,7 @@ EXPORT void HWRAPI(Shutdown) (void);
 #ifdef _WINDOWS
 EXPORT void HWRAPI(GetModeList) (vmode_t **pvidmodes, INT32 *numvidmodes);
 #endif
-EXPORT void HWRAPI(SetPalette) (RGBA_t *ppal);
+EXPORT void HWRAPI(SetTexturePalette) (RGBA_t *ppal);
 EXPORT void HWRAPI(FinishUpdate) (INT32 waitvbl);
 EXPORT void HWRAPI(Draw2DLine) (F2DCoord *v1, F2DCoord *v2, RGBA_t Color);
 EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPts, FBITFIELD PolyFlags);
@@ -43,11 +43,10 @@ EXPORT void HWRAPI(ClearBuffer) (FBOOLEAN ColorMask, FBOOLEAN DepthMask, FRGBAFl
 EXPORT void HWRAPI(SetTexture) (GLMipmap_t *TexInfo);
 EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *TexInfo);
 EXPORT void HWRAPI(DeleteTexture) (GLMipmap_t *TexInfo);
-EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height, INT32 dst_stride, UINT16 *dst_data);
+EXPORT void HWRAPI(ReadScreenTexture) (int tex, UINT8 *dst_data);
 EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip);
 EXPORT void HWRAPI(ClearMipMapCache) (void);
 
-//Hurdler: added for backward compatibility
 EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value);
 
 //Hurdler: added for new development
@@ -57,24 +56,26 @@ EXPORT void HWRAPI(SetTransform) (FTransform *ptransform);
 EXPORT INT32 HWRAPI(GetTextureUsed) (void);
 
 EXPORT void HWRAPI(FlushScreenTextures) (void);
-EXPORT void HWRAPI(StartScreenWipe) (void);
-EXPORT void HWRAPI(EndScreenWipe) (void);
-EXPORT void HWRAPI(DoScreenWipe) (void);
-EXPORT void HWRAPI(DrawIntermissionBG) (void);
-EXPORT void HWRAPI(MakeScreenTexture) (void);
-EXPORT void HWRAPI(MakeScreenFinalTexture) (void);
-EXPORT void HWRAPI(DrawScreenFinalTexture) (int width, int height);
+EXPORT void HWRAPI(DoScreenWipe) (int wipeStart, int wipeEnd, FSurfaceInfo *surf, FBITFIELD polyFlags);
+EXPORT void HWRAPI(DrawScreenTexture) (int tex, FSurfaceInfo *surf, FBITFIELD polyflags);
+EXPORT void HWRAPI(MakeScreenTexture) (int tex);
+EXPORT void HWRAPI(DrawScreenFinalTexture) (int tex, int width, int height);
 
 #define SCREENVERTS 10
 EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2]);
 
-EXPORT boolean HWRAPI(CompileShaders) (void);
-EXPORT void HWRAPI(CleanShaders) (void);
-EXPORT void HWRAPI(SetShader) (int type);
+EXPORT boolean HWRAPI(InitShaders) (void);
+EXPORT void HWRAPI(LoadShader) (int slot, char *code, hwdshaderstage_t stage);
+EXPORT boolean HWRAPI(CompileShader) (int slot);
+EXPORT void HWRAPI(SetShader) (int slot);
 EXPORT void HWRAPI(UnSetShader) (void);
 
 EXPORT void HWRAPI(SetShaderInfo) (hwdshaderinfo_t info, INT32 value);
-EXPORT void HWRAPI(LoadCustomShader) (int number, char *code, size_t size, boolean isfragment);
+
+EXPORT void HWRAPI(SetPaletteLookup)(UINT8 *lut);
+EXPORT UINT32 HWRAPI(CreateLightTable)(RGBA_t *hw_lighttable);
+EXPORT void HWRAPI(ClearLightTables)(void);
+EXPORT void HWRAPI(SetScreenPalette)(RGBA_t *palette);
 
 // ==========================================================================
 //                                      HWR DRIVER OBJECT, FOR CLIENT PROGRAM
@@ -85,7 +86,7 @@ EXPORT void HWRAPI(LoadCustomShader) (int number, char *code, size_t size, boole
 struct hwdriver_s
 {
 	Init                pfnInit;
-	SetPalette          pfnSetPalette;
+	SetTexturePalette   pfnSetTexturePalette;
 	FinishUpdate        pfnFinishUpdate;
 	Draw2DLine          pfnDraw2DLine;
 	DrawPolygon         pfnDrawPolygon;
@@ -96,10 +97,10 @@ struct hwdriver_s
 	SetTexture          pfnSetTexture;
 	UpdateTexture       pfnUpdateTexture;
 	DeleteTexture       pfnDeleteTexture;
-	ReadRect            pfnReadRect;
+	ReadScreenTexture   pfnReadScreenTexture;
 	GClipRect           pfnGClipRect;
 	ClearMipMapCache    pfnClearMipMapCache;
-	SetSpecialState     pfnSetSpecialState;//Hurdler: added for backward compatibility
+	SetSpecialState     pfnSetSpecialState;
 	DrawModel           pfnDrawModel;
 	CreateModelVBOs     pfnCreateModelVBOs;
 	SetTransform        pfnSetTransform;
@@ -112,21 +113,23 @@ struct hwdriver_s
 #endif
 	PostImgRedraw       pfnPostImgRedraw;
 	FlushScreenTextures pfnFlushScreenTextures;
-	StartScreenWipe     pfnStartScreenWipe;
-	EndScreenWipe       pfnEndScreenWipe;
 	DoScreenWipe        pfnDoScreenWipe;
-	DrawIntermissionBG  pfnDrawIntermissionBG;
+	DrawScreenTexture   pfnDrawScreenTexture;
 	MakeScreenTexture   pfnMakeScreenTexture;
-	MakeScreenFinalTexture  pfnMakeScreenFinalTexture;
 	DrawScreenFinalTexture  pfnDrawScreenFinalTexture;
 
-	CompileShaders      pfnCompileShaders;
-	CleanShaders        pfnCleanShaders;
+	InitShaders         pfnInitShaders;
+	LoadShader          pfnLoadShader;
+	CompileShader       pfnCompileShader;
 	SetShader           pfnSetShader;
 	UnSetShader         pfnUnSetShader;
 
 	SetShaderInfo       pfnSetShaderInfo;
-	LoadCustomShader    pfnLoadCustomShader;
+
+	SetPaletteLookup    pfnSetPaletteLookup;
+	CreateLightTable    pfnCreateLightTable;
+	ClearLightTables    pfnClearLightTables;
+	SetScreenPalette    pfnSetScreenPalette;
 };
 
 extern struct hwdriver_s hwdriver;
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index fbb02f46322c614107fc5523884e7081064c5c4a..094d356d530a24f010896141a8f6b8bc6b0777ae 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -107,6 +107,8 @@ void HWR_FreeExtraSubsectors(void);
 // --------
 // hw_cache.c
 // --------
+RGBA_t *HWR_GetTexturePalette(void);
+
 void HWR_InitMapTextures(void);
 void HWR_LoadMapTextures(size_t pnumtextures);
 void HWR_FreeMapTextures(void);
@@ -131,6 +133,10 @@ void HWR_FreeColormapCache(void);
 void HWR_UnlockCachedPatch(GLPatch_t *gpatch);
 
 void HWR_SetPalette(RGBA_t *palette);
+void HWR_SetMapPalette(void);
+UINT32 HWR_CreateLightTable(UINT8 *lighttable);
+UINT32 HWR_GetLightTableID(extracolormap_t *colormap);
+void HWR_ClearLightTables(void);
 
 
 // --------
@@ -139,4 +145,18 @@ void HWR_SetPalette(RGBA_t *palette);
 extern INT32 patchformat;
 extern INT32 textureformat;
 
+// --------
+// hw_shaders.c
+// --------
+boolean HWR_InitShaders(void);
+void HWR_CompileShaders(void);
+
+int HWR_GetShaderFromTarget(int shader_target);
+
+void HWR_LoadAllCustomShaders(void);
+void HWR_LoadCustomShadersFromFile(UINT16 wadnum, boolean PK3);
+const char *HWR_GetShaderName(INT32 shader);
+
+extern customshaderxlat_t shaderxlat[];
+
 #endif //_HW_GLOB_
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 9c1a95c9316626ce5f02747eaba356b60368ee62..a08e8e24d84fbaa9493337dff0efb192fccbddbf 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -130,26 +130,6 @@ static line_t *gl_linedef;
 static sector_t *gl_frontsector;
 static sector_t *gl_backsector;
 
-// --------------------------------------------------------------------------
-//                                              STUFF FOR THE PROJECTION CODE
-// --------------------------------------------------------------------------
-
-FTransform atransform;
-// duplicates of the main code, set after R_SetupFrame() passed them into sharedstruct,
-// copied here for local use
-static fixed_t dup_viewx, dup_viewy, dup_viewz;
-static angle_t dup_viewangle;
-
-static float gl_viewx, gl_viewy, gl_viewz;
-float gl_viewsin, gl_viewcos;
-
-// Maybe not necessary with the new T&L code (needs to be checked!)
-static float gl_viewludsin, gl_viewludcos; // look up down kik test
-static float gl_fovlud;
-
-static angle_t gl_aimingangle;
-static void HWR_SetTransformAiming(FTransform *trans, player_t *player, boolean skybox);
-
 // Render stats
 ps_metric_t ps_hw_skyboxtime = {0};
 ps_metric_t ps_hw_nodesorttime = {0};
@@ -170,13 +150,39 @@ ps_metric_t ps_hw_batchdrawtime = {0};
 
 boolean gl_init = false;
 boolean gl_maploaded = false;
-boolean gl_shadersavailable = true;
+boolean gl_sessioncommandsadded = false;
+// false if shaders have not been initialized yet, or if shaders are not available
+boolean gl_shadersavailable = false;
+
+// Whether the internal state is set to palette rendering or not.
+static boolean gl_palette_rendering_state = false;
+
+// --------------------------------------------------------------------------
+//                                              STUFF FOR THE PROJECTION CODE
+// --------------------------------------------------------------------------
+
+FTransform atransform;
+// duplicates of the main code, set after R_SetupFrame() passed them into sharedstruct,
+// copied here for local use
+static fixed_t dup_viewx, dup_viewy, dup_viewz;
+static angle_t dup_viewangle;
+
+static float gl_viewx, gl_viewy, gl_viewz;
+float gl_viewsin, gl_viewcos;
+
+// Maybe not necessary with the new T&L code (needs to be checked!)
+static float gl_viewludsin, gl_viewludcos; // look up down kik test
+static float gl_fovlud;
+
+static angle_t gl_aimingangle;
+static void HWR_SetTransformAiming(FTransform *trans, player_t *player, boolean skybox);
 
 // ==========================================================================
 // Lighting
 // ==========================================================================
 
-static boolean HWR_UseShader(void)
+// Returns true if shaders can be used.
+boolean HWR_UseShader(void)
 {
 	return (cv_glshaders.value && gl_shadersavailable);
 }
@@ -242,6 +248,11 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col
 	Surface->LightInfo.light_level = light_level;
 	Surface->LightInfo.fade_start = (colormap != NULL) ? colormap->fadestart : 0;
 	Surface->LightInfo.fade_end = (colormap != NULL) ? colormap->fadeend : 31;
+
+	if (HWR_ShouldUsePaletteRendering())
+		Surface->LightTableId = HWR_GetLightTableID(colormap);
+	else
+		Surface->LightTableId = 0;
 }
 
 UINT8 HWR_FogBlockAlpha(INT32 light, extracolormap_t *colormap) // Let's see if this can work
@@ -372,7 +383,7 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	FOutVector *v3d;
 	polyvertex_t *pv;
 	pslope_t *slope = NULL;
-	INT32 shader = SHADER_DEFAULT;
+	INT32 shader = SHADER_NONE;
 
 	size_t nrPlaneVerts;
 	INT32 i;
@@ -533,6 +544,9 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 		PolyFlags |= PF_ColorMapped;
 	}
 
+	if (!cv_renderfloors.value)
+		return;
+
 	HWR_ProcessPolygon(&Surf, planeVerts, nrPlaneVerts, PolyFlags, shader, false);
 
 	if (subsector)
@@ -759,7 +773,10 @@ static void HWR_AddTransparentWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, I
 //
 static void HWR_ProjectWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blendmode, INT32 lightlevel, extracolormap_t *wallcolormap)
 {
-	INT32 shader = SHADER_DEFAULT;
+	INT32 shader = SHADER_NONE;
+
+	if (!cv_renderwalls.value)
+		return;
 
 	HWR_Lighting(pSurf, lightlevel, wallcolormap);
 
@@ -834,6 +851,9 @@ static void HWR_SplitWall(sector_t *sector, FOutVector *wallVerts, INT32 texnum,
 	FUINT lightnum = HWR_CalcWallLight(sector->lightlevel, v1x, v1y, v2x, v2y);
 	extracolormap_t *colormap = NULL;
 
+	if (!cv_renderwalls.value)
+		return;
+
 	realtop = top = wallVerts[3].y;
 	realbot = bot = wallVerts[0].y;
 	diff = top - bot;
@@ -872,13 +892,15 @@ static void HWR_SplitWall(sector_t *sector, FOutVector *wallVerts, INT32 texnum,
 		{
 			if (pfloor && (pfloor->fofflags & FOF_FOG))
 			{
-				lightnum = HWR_CalcWallLight(pfloor->master->frontsector->lightlevel, v1x, v1y, v2x, v2y);
+				lightnum = pfloor->master->frontsector->lightlevel;
 				colormap = pfloor->master->frontsector->extra_colormap;
+				lightnum = colormap ? lightnum : HWR_CalcWallLight(lightnum, v1x, v1y, v2x, v2y);
 			}
 			else
 			{
-				lightnum = HWR_CalcWallLight(*list[i].lightlevel, v1x, v1y, v2x, v2y);
+				lightnum = *list[i].lightlevel;
 				colormap = *list[i].extra_colormap;
+				lightnum = colormap ? lightnum : HWR_CalcWallLight(lightnum, v1x, v1y, v2x, v2y);
 			}
 		}
 
@@ -1109,8 +1131,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 	float cliplow = (float)texturehpeg;
 	float cliphigh = (float)(texturehpeg + (gl_curline->flength*FRACUNIT));
 
-	FUINT lightnum = HWR_CalcWallLight(gl_frontsector->lightlevel, vs.x, vs.y, ve.x, ve.y);
+	FUINT lightnum = gl_frontsector->lightlevel;
 	extracolormap_t *colormap = gl_frontsector->extra_colormap;
+	lightnum = colormap ? lightnum : HWR_CalcWallLight(lightnum, vs.x, vs.y, ve.x, ve.y);
 
 	FSurfaceInfo Surf;
 	Surf.PolyColor.s.alpha = 255;
@@ -1705,8 +1728,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 					blendmode = PF_Fog|PF_NoTexture;
 
-					lightnum = HWR_CalcWallLight(rover->master->frontsector->lightlevel, vs.x, vs.y, ve.x, ve.y);
+					lightnum = rover->master->frontsector->lightlevel;
 					colormap = rover->master->frontsector->extra_colormap;
+					lightnum = colormap ? lightnum : HWR_CalcWallLight(lightnum, vs.x, vs.y, ve.x, ve.y);
 
 					Surf.PolyColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel, rover->master->frontsector->extra_colormap);
 
@@ -1827,8 +1851,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 					blendmode = PF_Fog|PF_NoTexture;
 
-					lightnum = HWR_CalcWallLight(rover->master->frontsector->lightlevel, vs.x, vs.y, ve.x, ve.y);
+					lightnum = rover->master->frontsector->lightlevel;
 					colormap = rover->master->frontsector->extra_colormap;
+					lightnum = colormap ? lightnum : HWR_CalcWallLight(lightnum, vs.x, vs.y, ve.x, ve.y);
 
 					Surf.PolyColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel, rover->master->frontsector->extra_colormap);
 
@@ -2694,7 +2719,7 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 {
 	FSurfaceInfo Surf;
 	FOutVector *v3d;
-	INT32 shader = SHADER_DEFAULT;
+	INT32 shader = SHADER_NONE;
 
 	size_t nrPlaneVerts = polysector->numVertices;
 	INT32 i;
@@ -3527,7 +3552,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	float fscale; float fx; float fy; float offset;
 	extracolormap_t *colormap = NULL;
 	FBITFIELD blendmode = PF_Translucent|PF_Modulated;
-	INT32 shader = SHADER_DEFAULT;
+	INT32 shader = SHADER_NONE;
 	UINT8 i;
 	INT32 heightsec, phs;
 	SINT8 flip = P_MobjFlip(thing);
@@ -3735,7 +3760,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	boolean lightset = true;
 	FBITFIELD blend = 0;
 	FBITFIELD occlusion;
-	INT32 shader = SHADER_DEFAULT;
+	INT32 shader = SHADER_NONE;
 	boolean use_linkdraw_hack = false;
 	UINT8 alpha;
 
@@ -4306,7 +4331,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 	}
 
 	{
-		INT32 shader = SHADER_DEFAULT;
+		INT32 shader = SHADER_NONE;
 		FBITFIELD blend = 0;
 		FBITFIELD occlusion;
 		boolean use_linkdraw_hack = false;
@@ -4380,7 +4405,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 // Sprite drawer for precipitation
 static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 {
-	INT32 shader = SHADER_DEFAULT;
+	INT32 shader = SHADER_NONE;
 	FBITFIELD blend = 0;
 	FOutVector wallVerts[4];
 	patch_t *gpatch;
@@ -4825,7 +4850,6 @@ static void HWR_CreateDrawNodes(void)
 
 	// Okay! Let's draw it all! Woo!
 	HWD.pfnSetTransform(&atransform);
-	HWD.pfnSetShader(SHADER_DEFAULT);
 
 	for (i = 0; i < p; i++)
 	{
@@ -5058,6 +5082,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	// uncapped/interpolation
 	interpmobjstate_t interp = {0};
 
+	if (!cv_renderthings.value)
+		return;
+
 	if (!thing)
 		return;
 
@@ -5884,7 +5911,8 @@ static void HWR_DrawSkyBackground(player_t *player)
 			HWR_BuildSkyDome();
 		}
 
-		HWD.pfnSetShader(SHADER_SKY); // sky shader
+		if (HWR_UseShader())
+			HWD.pfnSetShader(HWR_GetShaderFromTarget(SHADER_SKY));
 		HWD.pfnSetTransform(&dometransform);
 		HWD.pfnRenderSkyDome(&gl_sky);
 	}
@@ -5970,8 +5998,6 @@ static void HWR_DrawSkyBackground(player_t *player)
 		HWD.pfnUnSetShader();
 		HWD.pfnDrawPolygon(NULL, v, 4, 0);
 	}
-
-	HWD.pfnSetShader(SHADER_DEFAULT);
 }
 
 
@@ -6061,13 +6087,7 @@ static void HWR_SetTransformAiming(FTransform *trans, player_t *player, boolean
 //
 static void HWR_SetShaderState(void)
 {
-	hwdshaderoption_t state = cv_glshaders.value;
-
-	if (!cv_glallowshaders.value)
-		state = (cv_glshaders.value == HWD_SHADEROPTION_ON ? HWD_SHADEROPTION_NOCUSTOM : cv_glshaders.value);
-
-	HWD.pfnSetSpecialState(HWD_SET_SHADERS, (INT32)state);
-	HWD.pfnSetShader(SHADER_DEFAULT);
+	HWD.pfnSetSpecialState(HWD_SET_SHADERS, (INT32)HWR_UseShader());
 }
 
 // ==========================================================================
@@ -6083,6 +6103,7 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	else
 		type = &postimgtype;
 
+	if (!HWR_ShouldUsePaletteRendering())
 	{
 		// do we really need to save player (is it not the same)?
 		player_t *saved_player = stplyr;
@@ -6275,6 +6296,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 		HWR_RenderSkyboxView(viewnumber, player); // This is drawn before everything else so it is placed behind
 	PS_STOP_TIMING(ps_hw_skyboxtime);
 
+	if (!HWR_ShouldUsePaletteRendering())
 	{
 		// do we really need to save player (is it not the same)?
 		player_t *saved_player = stplyr;
@@ -6451,6 +6473,56 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
 }
 
+// Returns whether palette rendering is "actually enabled."
+// Can't have palette rendering if shaders are disabled.
+boolean HWR_ShouldUsePaletteRendering(void)
+{
+	return (cv_glpaletterendering.value && HWR_UseShader());
+}
+
+// enable or disable palette rendering state depending on settings and availability
+// called when relevant settings change
+// shader recompilation is done in the cvar callback
+static void HWR_TogglePaletteRendering(void)
+{
+	// which state should we go to?
+	if (HWR_ShouldUsePaletteRendering())
+	{
+		// are we not in that state already?
+		if (!gl_palette_rendering_state)
+		{
+			gl_palette_rendering_state = true;
+
+			// The textures will still be converted to RGBA by r_opengl.
+			// This however makes hw_cache use paletted blending for composite textures!
+			// (patchformat is not touched)
+			textureformat = GL_TEXFMT_P_8;
+
+			HWR_SetMapPalette();
+			HWR_SetPalette(pLocalPalette);
+
+			// If the r_opengl "texture palette" stays the same during this switch, these textures
+			// will not be cleared out. However they are still out of date since the
+			// composite texture blending method has changed. Therefore they need to be cleared.
+			HWR_LoadMapTextures(numtextures);
+		}
+	}
+	else
+	{
+		// are we not in that state already?
+		if (gl_palette_rendering_state)
+		{
+			gl_palette_rendering_state = false;
+			textureformat = GL_TEXFMT_RGBA;
+			HWR_SetPalette(pLocalPalette);
+			// If the r_opengl "texture palette" stays the same during this switch, these textures
+			// will not be cleared out. However they are still out of date since the
+			// composite texture blending method has changed. Therefore they need to be cleared.
+			HWR_LoadMapTextures(numtextures);
+		}
+	}
+}
+
 void HWR_LoadLevel(void)
 {
 #ifdef ALAM_LIGHTING
@@ -6464,6 +6536,9 @@ void HWR_LoadLevel(void)
 	HWR_ClearSkyDome();
 	HWR_BuildSkyDome();
 
+	if (HWR_ShouldUsePaletteRendering())
+		HWR_SetMapPalette();
+
 	gl_maploaded = true;
 }
 
@@ -6471,13 +6546,17 @@ void HWR_LoadLevel(void)
 //                                                         3D ENGINE COMMANDS
 // ==========================================================================
 
-static CV_PossibleValue_t glshaders_cons_t[] = {{HWD_SHADEROPTION_OFF, "Off"}, {HWD_SHADEROPTION_ON, "On"}, {HWD_SHADEROPTION_NOCUSTOM, "Ignore custom shaders"}, {0, NULL}};
+static CV_PossibleValue_t glshaders_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Ignore custom shaders"}, {0, NULL}};
 static CV_PossibleValue_t glmodelinterpolation_cons_t[] = {{0, "Off"}, {1, "Sometimes"}, {2, "Always"}, {0, NULL}};
 static CV_PossibleValue_t glfakecontrast_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Smooth"}, {0, NULL}};
 static CV_PossibleValue_t glshearing_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Third-person"}, {0, NULL}};
 
 static void CV_glfiltermode_OnChange(void);
 static void CV_glanisotropic_OnChange(void);
+static void CV_glmodellighting_OnChange(void);
+static void CV_glpaletterendering_OnChange(void);
+static void CV_glpalettedepth_OnChange(void);
+static void CV_glshaders_OnChange(void);
 
 static CV_PossibleValue_t glfiltermode_cons_t[]= {{HWD_SET_TEXTUREFILTER_POINTSAMPLED, "Nearest"},
 	{HWD_SET_TEXTUREFILTER_BILINEAR, "Bilinear"}, {HWD_SET_TEXTUREFILTER_TRILINEAR, "Trilinear"},
@@ -6487,7 +6566,7 @@ static CV_PossibleValue_t glfiltermode_cons_t[]= {{HWD_SET_TEXTUREFILTER_POINTSA
 	{0, NULL}};
 CV_PossibleValue_t glanisotropicmode_cons_t[] = {{1, "MIN"}, {16, "MAX"}, {0, NULL}};
 
-consvar_t cv_glshaders = CVAR_INIT ("gr_shaders", "On", CV_SAVE, glshaders_cons_t, NULL);
+consvar_t cv_glshaders = CVAR_INIT ("gr_shaders", "On", CV_SAVE|CV_CALL, glshaders_cons_t, CV_glshaders_OnChange);
 
 #ifdef ALAM_LIGHTING
 consvar_t cv_gldynamiclighting = CVAR_INIT ("gr_dynamiclighting", "On", CV_SAVE, CV_OnOff, NULL);
@@ -6498,7 +6577,7 @@ consvar_t cv_glcoronasize = CVAR_INIT ("gr_coronasize", "1", CV_SAVE|CV_FLOAT, 0
 
 consvar_t cv_glmodels = CVAR_INIT ("gr_models", "Off", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_glmodelinterpolation = CVAR_INIT ("gr_modelinterpolation", "Sometimes", CV_SAVE, glmodelinterpolation_cons_t, NULL);
-consvar_t cv_glmodellighting = CVAR_INIT ("gr_modellighting", "Off", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_glmodellighting = CVAR_INIT ("gr_modellighting", "Off", CV_SAVE|CV_CALL, CV_OnOff, CV_glmodellighting_OnChange);
 
 consvar_t cv_glshearing = CVAR_INIT ("gr_shearing", "Off", CV_SAVE, glshearing_cons_t, NULL);
 consvar_t cv_glspritebillboarding = CVAR_INIT ("gr_spritebillboarding", "Off", CV_SAVE, CV_OnOff, NULL);
@@ -6513,18 +6592,61 @@ consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", 0, CV_OnOff, NULL)
 
 consvar_t cv_glbatching = CVAR_INIT ("gr_batching", "On", 0, CV_OnOff, NULL);
 
+static CV_PossibleValue_t glpalettedepth_cons_t[] = {{16, "16 bits"}, {24, "24 bits"}, {0, NULL}};
+
+consvar_t cv_glpaletterendering = CVAR_INIT ("gr_paletterendering", "Off", CV_SAVE|CV_CALL, CV_OnOff, CV_glpaletterendering_OnChange);
+consvar_t cv_glpalettedepth = CVAR_INIT ("gr_palettedepth", "16 bits", CV_SAVE|CV_CALL, glpalettedepth_cons_t, CV_glpalettedepth_OnChange);
+
+#define ONLY_IF_GL_LOADED if (vid.glstate != VID_GL_LIBRARY_LOADED) return;
 consvar_t cv_glwireframe = CVAR_INIT ("gr_wireframe", "Off", 0, CV_OnOff, NULL);
 
 static void CV_glfiltermode_OnChange(void)
 {
-	if (rendermode == render_opengl)
-		HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_glfiltermode.value);
+	ONLY_IF_GL_LOADED
+	HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_glfiltermode.value);
 }
 
 static void CV_glanisotropic_OnChange(void)
 {
-	if (rendermode == render_opengl)
-		HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_glanisotropicmode.value);
+	ONLY_IF_GL_LOADED
+	HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_glanisotropicmode.value);
+}
+
+static void CV_glmodellighting_OnChange(void)
+{
+	ONLY_IF_GL_LOADED
+	// if shaders have been compiled, then they now need to be recompiled.
+	if (gl_shadersavailable)
+		HWR_CompileShaders();
+}
+
+static void CV_glpaletterendering_OnChange(void)
+{
+	ONLY_IF_GL_LOADED
+	if (gl_shadersavailable)
+	{
+		HWR_CompileShaders();
+		HWR_TogglePaletteRendering();
+	}
+}
+
+static void CV_glpalettedepth_OnChange(void)
+{
+	ONLY_IF_GL_LOADED
+	// refresh the screen palette
+	if (HWR_ShouldUsePaletteRendering())
+		HWR_SetPalette(pLocalPalette);
+}
+
+static void CV_glshaders_OnChange(void)
+{
+	ONLY_IF_GL_LOADED
+	HWR_SetShaderState();
+	if (cv_glpaletterendering.value)
+	{
+		// can't do palette rendering without shaders, so update the state if needed
+		HWR_TogglePaletteRendering();
+	}
 }
 
 //added by Hurdler: console varibale that are saved
@@ -6553,6 +6675,8 @@ void HWR_AddCommands(void)
 
 	CV_RegisterVar(&cv_glbatching);
 
+	CV_RegisterVar(&cv_glpaletterendering);
+	CV_RegisterVar(&cv_glpalettedepth);
 	CV_RegisterVar(&cv_glwireframe);
 
 #ifndef NEWCLIP
@@ -6569,6 +6693,8 @@ void HWR_Startup(void)
 	{
 		CONS_Printf("HWR_Startup()...\n");
 
+		textureformat = patchformat = GL_TEXFMT_RGBA;
+
 		HWR_InitPolyPool();
 		HWR_InitMapTextures();
 		HWR_InitModels();
@@ -6576,14 +6702,12 @@ void HWR_Startup(void)
 		HWR_InitLight();
 #endif
 
+		gl_shadersavailable = HWR_InitShaders();
+		HWR_SetShaderState();
 		HWR_LoadAllCustomShaders();
-		if (!HWR_CompileShaders())
-			gl_shadersavailable = false;
+		HWR_TogglePaletteRendering();
 	}
 
-	if (rendermode == render_opengl)
-		textureformat = patchformat = GL_TEXFMT_RGBA;
-
 	gl_init = true;
 }
 
@@ -6647,6 +6771,9 @@ void HWR_AddTransparentWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, INT32 te
 {
 	static size_t allocedwalls = 0;
 
+	if (!cv_renderwalls.value)
+		return;
+
 	// Force realloc if buffer has been freed
 	if (!wallinfo)
 		allocedwalls = 0;
@@ -6673,7 +6800,10 @@ void HWR_RenderWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blend,
 	FBITFIELD blendmode = blend;
 	UINT8 alpha = pSurf->PolyColor.s.alpha; // retain the alpha
 
-	INT32 shader = SHADER_DEFAULT;
+	INT32 shader = SHADER_NONE;
+
+	if (!cv_renderwalls.value)
+		return;
 
 	// Lighting is done here instead so that fog isn't drawn incorrectly on transparent walls after sorting
 	HWR_Lighting(pSurf, lightlevel, wallcolormap);
@@ -6718,7 +6848,7 @@ void HWR_DoPostProcessor(player_t *player)
 
 	// Armageddon Blast Flash!
 	// Could this even be considered postprocessor?
-	if (player->flashcount)
+	if (player->flashcount && !HWR_ShouldUsePaletteRendering())
 	{
 		FOutVector      v[4];
 		FSurfaceInfo Surf;
@@ -6743,7 +6873,7 @@ void HWR_DoPostProcessor(player_t *player)
 
 	// Capture the screen for intermission and screen waving
 	if(gamestate != GS_INTERMISSION)
-		HWD.pfnMakeScreenTexture();
+		HWD.pfnMakeScreenTexture(HWD_SCREENTEXTURE_GENERIC1);
 
 	if (splitscreen) // Not supported in splitscreen - someone want to add support?
 		return;
@@ -6787,7 +6917,7 @@ void HWR_DoPostProcessor(player_t *player)
 
 		// Capture the screen again for screen waving on the intermission
 		if(gamestate != GS_INTERMISSION)
-			HWD.pfnMakeScreenTexture();
+			HWD.pfnMakeScreenTexture(HWD_SCREENTEXTURE_GENERIC1);
 	}
 	// Flipping of the screen isn't done here anymore
 }
@@ -6795,18 +6925,18 @@ void HWR_DoPostProcessor(player_t *player)
 void HWR_StartScreenWipe(void)
 {
 	//CONS_Debug(DBG_RENDER, "In HWR_StartScreenWipe()\n");
-	HWD.pfnStartScreenWipe();
+	HWD.pfnMakeScreenTexture(HWD_SCREENTEXTURE_WIPE_START);
 }
 
 void HWR_EndScreenWipe(void)
 {
 	//CONS_Debug(DBG_RENDER, "In HWR_EndScreenWipe()\n");
-	HWD.pfnEndScreenWipe();
+	HWD.pfnMakeScreenTexture(HWD_SCREENTEXTURE_WIPE_END);
 }
 
 void HWR_DrawIntermissionBG(void)
 {
-	HWD.pfnDrawIntermissionBG();
+	HWD.pfnDrawScreenTexture(HWD_SCREENTEXTURE_GENERIC1, NULL, 0);
 }
 
 //
@@ -6851,201 +6981,40 @@ void HWR_DoWipe(UINT8 wipenum, UINT8 scrnnum)
 		return;
 
 	HWR_GetFadeMask(wipelumpnum);
-	HWD.pfnDoScreenWipe();
-}
+	if (wipestyle == WIPESTYLE_COLORMAP && HWR_UseShader())
+	{
+		FSurfaceInfo surf = {0};
+		FBITFIELD polyflags = PF_Modulated|PF_NoDepthTest;
 
-void HWR_DoTintedWipe(UINT8 wipenum, UINT8 scrnnum)
-{
-	// It does the same thing
-	HWR_DoWipe(wipenum, scrnnum);
+		polyflags |= (wipestyleflags & WSF_TOWHITE) ? PF_Additive : PF_ReverseSubtract;
+		surf.PolyColor.s.red = FADEREDFACTOR;
+		surf.PolyColor.s.green = FADEGREENFACTOR;
+		surf.PolyColor.s.blue = FADEBLUEFACTOR;
+		// polycolor alpha communicates fadein / fadeout to the shader and the backend
+		surf.PolyColor.s.alpha = (wipestyleflags & WSF_FADEIN) ? 255 : 0;
+
+		HWD.pfnSetShader(HWR_GetShaderFromTarget(SHADER_UI_TINTED_WIPE));
+		HWD.pfnDoScreenWipe(HWD_SCREENTEXTURE_WIPE_START, HWD_SCREENTEXTURE_WIPE_END,
+			&surf, polyflags);
+		HWD.pfnUnSetShader();
+	}
+	else
+	{
+		HWD.pfnDoScreenWipe(HWD_SCREENTEXTURE_WIPE_START, HWD_SCREENTEXTURE_WIPE_END,
+			NULL, 0);
+	}
 }
 
 void HWR_MakeScreenFinalTexture(void)
 {
-    HWD.pfnMakeScreenFinalTexture();
+	int tex = HWR_ShouldUsePaletteRendering() ? HWD_SCREENTEXTURE_GENERIC3 : HWD_SCREENTEXTURE_GENERIC2;
+	HWD.pfnMakeScreenTexture(tex);
 }
 
 void HWR_DrawScreenFinalTexture(int width, int height)
 {
-    HWD.pfnDrawScreenFinalTexture(width, height);
-}
-
-static inline UINT16 HWR_FindShaderDefs(UINT16 wadnum)
-{
-	UINT16 i;
-	lumpinfo_t *lump_p;
-
-	lump_p = wadfiles[wadnum]->lumpinfo;
-	for (i = 0; i < wadfiles[wadnum]->numlumps; i++, lump_p++)
-		if (memcmp(lump_p->name, "SHADERS", 7) == 0)
-			return i;
-
-	return INT16_MAX;
-}
-
-boolean HWR_CompileShaders(void)
-{
-	return HWD.pfnCompileShaders();
-}
-
-customshaderxlat_t shaderxlat[] =
-{
-	{"Flat", SHADER_FLOOR},
-	{"WallTexture", SHADER_WALL},
-	{"Sprite", SHADER_SPRITE},
-	{"Model", SHADER_MODEL},
-	{"ModelLighting", SHADER_MODEL_LIGHTING},
-	{"WaterRipple", SHADER_WATER},
-	{"Fog", SHADER_FOG},
-	{"Sky", SHADER_SKY},
-	{NULL, 0},
-};
-
-void HWR_LoadAllCustomShaders(void)
-{
-	INT32 i;
-
-	// read every custom shader
-	for (i = 0; i < numwadfiles; i++)
-		HWR_LoadCustomShadersFromFile(i, W_FileHasFolders(wadfiles[i]));
-}
-
-void HWR_LoadCustomShadersFromFile(UINT16 wadnum, boolean PK3)
-{
-	UINT16 lump;
-	char *shaderdef, *line;
-	char *stoken;
-	char *value;
-	size_t size;
-	int linenum = 1;
-	int shadertype = 0;
-	int i;
-
-	lump = HWR_FindShaderDefs(wadnum);
-	if (lump == INT16_MAX)
-		return;
-
-	shaderdef = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE);
-	size = W_LumpLengthPwad(wadnum, lump);
-
-	line = Z_Malloc(size+1, PU_STATIC, NULL);
-	M_Memcpy(line, shaderdef, size);
-	line[size] = '\0';
-
-	stoken = strtok(line, "\r\n ");
-	while (stoken)
-	{
-		if ((stoken[0] == '/' && stoken[1] == '/')
-			|| (stoken[0] == '#'))// skip comments
-		{
-			stoken = strtok(NULL, "\r\n");
-			goto skip_field;
-		}
-
-		if (!stricmp(stoken, "GLSL"))
-		{
-			value = strtok(NULL, "\r\n ");
-			if (!value)
-			{
-				CONS_Alert(CONS_WARNING, "HWR_LoadCustomShadersFromFile: Missing shader type (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
-				stoken = strtok(NULL, "\r\n"); // skip end of line
-				goto skip_lump;
-			}
-
-			if (!stricmp(value, "VERTEX"))
-				shadertype = 1;
-			else if (!stricmp(value, "FRAGMENT"))
-				shadertype = 2;
-
-skip_lump:
-			stoken = strtok(NULL, "\r\n ");
-			linenum++;
-		}
-		else
-		{
-			value = strtok(NULL, "\r\n= ");
-			if (!value)
-			{
-				CONS_Alert(CONS_WARNING, "HWR_LoadCustomShadersFromFile: Missing shader target (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
-				stoken = strtok(NULL, "\r\n"); // skip end of line
-				goto skip_field;
-			}
-
-			if (!shadertype)
-			{
-				CONS_Alert(CONS_ERROR, "HWR_LoadCustomShadersFromFile: Missing shader type (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
-				Z_Free(line);
-				return;
-			}
-
-			for (i = 0; shaderxlat[i].type; i++)
-			{
-				if (!stricmp(shaderxlat[i].type, stoken))
-				{
-					size_t shader_size;
-					char *shader_source;
-					char *shader_lumpname;
-					UINT16 shader_lumpnum;
-
-					if (PK3)
-					{
-						shader_lumpname = Z_Malloc(strlen(value) + 12, PU_STATIC, NULL);
-						strcpy(shader_lumpname, "Shaders/sh_");
-						strcat(shader_lumpname, value);
-						shader_lumpnum = W_CheckNumForFullNamePK3(shader_lumpname, wadnum, 0);
-					}
-					else
-					{
-						shader_lumpname = Z_Malloc(strlen(value) + 4, PU_STATIC, NULL);
-						strcpy(shader_lumpname, "SH_");
-						strcat(shader_lumpname, value);
-						shader_lumpnum = W_CheckNumForNamePwad(shader_lumpname, wadnum, 0);
-					}
-
-					if (shader_lumpnum == INT16_MAX)
-					{
-						CONS_Alert(CONS_ERROR, "HWR_LoadCustomShadersFromFile: Missing shader source %s (file %s, line %d)\n", shader_lumpname, wadfiles[wadnum]->filename, linenum);
-						Z_Free(shader_lumpname);
-						continue;
-					}
-
-					shader_size = W_LumpLengthPwad(wadnum, shader_lumpnum);
-					shader_source = Z_Malloc(shader_size, PU_STATIC, NULL);
-					W_ReadLumpPwad(wadnum, shader_lumpnum, shader_source);
-
-					HWD.pfnLoadCustomShader(shaderxlat[i].id, shader_source, shader_size, (shadertype == 2));
-
-					Z_Free(shader_source);
-					Z_Free(shader_lumpname);
-				}
-			}
-
-skip_field:
-			stoken = strtok(NULL, "\r\n= ");
-			linenum++;
-		}
-	}
-
-	Z_Free(line);
-	return;
-}
-
-const char *HWR_GetShaderName(INT32 shader)
-{
-	INT32 i;
-
-	if (shader)
-	{
-		for (i = 0; shaderxlat[i].type; i++)
-		{
-			if (shaderxlat[i].id == shader)
-				return shaderxlat[i].type;
-		}
-
-		return "Unknown";
-	}
-
-	return "Default";
+	int tex = HWR_ShouldUsePaletteRendering() ? HWD_SCREENTEXTURE_GENERIC3 : HWD_SCREENTEXTURE_GENERIC2;
+	HWD.pfnDrawScreenFinalTexture(tex, width, height);
 }
 
 #endif // HWRENDER
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 0639bcffeb6446989575bfffb44490433f8473c6..2d4c74583a1c47d35ab22b26e119874dab68e190 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -61,11 +61,11 @@ void HWR_StartScreenWipe(void);
 void HWR_EndScreenWipe(void);
 void HWR_DrawIntermissionBG(void);
 void HWR_DoWipe(UINT8 wipenum, UINT8 scrnnum);
-void HWR_DoTintedWipe(UINT8 wipenum, UINT8 scrnnum);
 void HWR_MakeScreenFinalTexture(void);
 void HWR_DrawScreenFinalTexture(int width, int height);
 
 // This stuff is put here so models can use them
+boolean HWR_UseShader(void);
 void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *colormap);
 UINT8 HWR_FogBlockAlpha(INT32 light, extracolormap_t *colormap); // Let's see if this can work
 
@@ -74,13 +74,7 @@ FBITFIELD HWR_GetBlendModeFlag(INT32 style);
 FBITFIELD HWR_SurfaceBlend(INT32 style, INT32 transtablenum, FSurfaceInfo *pSurf);
 FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf);
 
-boolean HWR_CompileShaders(void);
-
-void HWR_LoadAllCustomShaders(void);
-void HWR_LoadCustomShadersFromFile(UINT16 wadnum, boolean PK3);
-const char *HWR_GetShaderName(INT32 shader);
-
-extern customshaderxlat_t shaderxlat[];
+boolean HWR_ShouldUsePaletteRendering(void);
 
 extern CV_PossibleValue_t glanisotropicmode_cons_t[];
 
@@ -103,8 +97,9 @@ extern consvar_t cv_glspritebillboarding;
 extern consvar_t cv_glskydome;
 extern consvar_t cv_glfakecontrast;
 extern consvar_t cv_glslopecontrast;
-
 extern consvar_t cv_glbatching;
+extern consvar_t cv_glpaletterendering;
+extern consvar_t cv_glpalettedepth;
 
 extern consvar_t cv_glwireframe;
 
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 9797a93312e2df6159f64ef472abe1beb5f2f024..ef0341bd5da7b73b5b9757a06a8eab2df521967f 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -390,8 +390,6 @@ static void md2_loadTexture(md2_t *model)
 	if (!grPatch->mipmap->downloaded && !grPatch->mipmap->data)
 	{
 		int w = 0, h = 0;
-		UINT32 size;
-		RGBA_t *image;
 
 #ifdef HAVE_PNG
 		grPatch->mipmap->format = PNG_Load(filename, &w, &h, grPatch);
@@ -412,13 +410,19 @@ static void md2_loadTexture(md2_t *model)
 		grPatch->mipmap->width = (UINT16)w;
 		grPatch->mipmap->height = (UINT16)h;
 
-		// Lactozilla: Apply colour cube
-		image = grPatch->mipmap->data;
-		size = w*h;
-		while (size--)
+		// for palette rendering, color cube is applied in post-processing instead of here
+		if (!HWR_ShouldUsePaletteRendering())
 		{
-			V_CubeApply(&image->s.red, &image->s.green, &image->s.blue);
-			image++;
+			UINT32 size;
+			RGBA_t *image;
+			// Lactozilla: Apply colour cube
+			image = grPatch->mipmap->data;
+			size = w*h;
+			while (size--)
+			{
+				V_CubeApply(&image->s.red, &image->s.green, &image->s.blue);
+				image++;
+			}
 		}
 	}
 	HWD.pfnSetTexture(grPatch->mipmap);
@@ -1550,7 +1554,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		p.flip = atransform.flip;
 		p.mirror = atransform.mirror;
 
-		HWD.pfnSetShader(SHADER_MODEL);	// model shader
+		if (HWR_UseShader())
+			HWD.pfnSetShader(HWR_GetShaderFromTarget(SHADER_MODEL));
 		{
 			float this_scale = FIXED_TO_FLOAT(interp.scale);
 
diff --git a/src/hardware/hw_shaders.c b/src/hardware/hw_shaders.c
new file mode 100644
index 0000000000000000000000000000000000000000..36cbb5db949c7fae0e28c82b6c0fcb6bd1dd3a73
--- /dev/null
+++ b/src/hardware/hw_shaders.c
@@ -0,0 +1,636 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2021 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file hw_shaders.c
+/// \brief Handles the shaders used by the game.
+
+#ifdef HWRENDER
+
+#include "hw_glob.h"
+#include "hw_drv.h"
+#include "hw_shaders.h"
+#include "../z_zone.h"
+
+// ================
+//  Shader sources
+// ================
+
+static struct {
+	const char *vertex;
+	const char *fragment;
+} const gl_shadersources[] = {
+	// Floor shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_FLOOR_FRAGMENT_SHADER},
+
+	// Wall shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_WALL_FRAGMENT_SHADER},
+
+	// Sprite shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_WALL_FRAGMENT_SHADER},
+
+	// Model shader
+	{GLSL_MODEL_VERTEX_SHADER, GLSL_MODEL_FRAGMENT_SHADER},
+
+	// Water shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_WATER_FRAGMENT_SHADER},
+
+	// Fog shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_FOG_FRAGMENT_SHADER},
+
+	// Sky shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SKY_FRAGMENT_SHADER},
+
+	// Palette postprocess shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_PALETTE_POSTPROCESS_FRAGMENT_SHADER},
+
+	// UI colormap fade shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_UI_COLORMAP_FADE_FRAGMENT_SHADER},
+
+	// UI tinted wipe shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_UI_TINTED_WIPE_FRAGMENT_SHADER},
+
+	{NULL, NULL},
+};
+
+typedef struct
+{
+	int base_shader; // index of base shader_t
+	int custom_shader; // index of custom shader_t
+} shadertarget_t;
+
+typedef struct
+{
+	char *vertex;
+	char *fragment;
+	boolean compiled;
+} shader_t; // these are in an array and accessed by indices
+
+// the array has NUMSHADERTARGETS entries for base shaders and for custom shaders
+// the array could be expanded in the future to fit "dynamic" custom shaders that
+// aren't fixed to shader targets
+static shader_t gl_shaders[NUMSHADERTARGETS*2];
+
+static shadertarget_t gl_shadertargets[NUMSHADERTARGETS];
+
+#define WHITESPACE_CHARS " \t"
+
+#define MODEL_LIGHTING_DEFINE "#define SRB2_MODEL_LIGHTING"
+#define PALETTE_RENDERING_DEFINE "#define SRB2_PALETTE_RENDERING"
+
+// Initialize shader variables and the backend's shader system. Load the base shaders.
+// Returns false if shaders cannot be used.
+boolean HWR_InitShaders(void)
+{
+	int i;
+
+	if (!HWD.pfnInitShaders())
+		return false;
+
+	for (i = 0; i < NUMSHADERTARGETS; i++)
+	{
+		// set up string pointers for base shaders
+		gl_shaders[i].vertex = Z_StrDup(gl_shadersources[i].vertex);
+		gl_shaders[i].fragment = Z_StrDup(gl_shadersources[i].fragment);
+		// set shader target indices to correct values
+		gl_shadertargets[i].base_shader = i;
+		gl_shadertargets[i].custom_shader = -1;
+	}
+
+	HWR_CompileShaders();
+
+	return true;
+}
+
+// helper function: strstr but returns an int with the substring position
+// returns INT32_MAX if not found
+static INT32 strstr_int(const char *str1, const char *str2)
+{
+	char *location = strstr(str1, str2);
+	if (location)
+		return location - str1;
+	else
+		return INT32_MAX;
+}
+
+// Creates a preprocessed copy of the shader according to the current graphics settings
+// Returns a pointer to the results on success and NULL on failure.
+// Remember memory management of the returned string.
+static char *HWR_PreprocessShader(char *original)
+{
+	const char *line_ending = "\n";
+	int line_ending_len;
+	char *read_pos = original;
+	int original_len = strlen(original);
+	int distance_to_end = original_len;
+	int new_len;
+	char *new_shader;
+	char *write_pos;
+	char shader_glsl_version[3];
+	int version_pos = -1;
+	int version_len = 0;
+
+	if (strstr(original, "\r\n"))
+	{
+		line_ending = "\r\n";
+		// check if all line endings are same
+		while ((read_pos = strchr(read_pos, '\n')))
+		{
+			read_pos--;
+			if (*read_pos != '\r')
+			{
+				// this file contains mixed CRLF and LF line endings.
+				// treating it as a LF file during parsing should keep
+				// the results sane enough as long as the gpu driver is fine
+				// with these kinds of weirdly formatted shader sources.
+				line_ending = "\n";
+				break;
+			}
+			read_pos += 2;
+		}
+		read_pos = original;
+	}
+
+	line_ending_len = strlen(line_ending);
+
+	// Find the #version directive, if it exists. Also don't get fooled if it's
+	// inside a comment. Copy the version digits so they can be used in the preamble.
+	// Time for some string parsing :D
+
+#define STARTSWITH(str, with_what) !strncmp(str, with_what, sizeof(with_what)-1)
+#define ADVANCE(amount) read_pos += (amount); distance_to_end -= (amount);
+	while (true)
+	{
+		// we're at the start of a line or at the end of a block comment.
+		// first get any possible whitespace out of the way
+		int whitespace_len = strspn(read_pos, WHITESPACE_CHARS);
+		if (whitespace_len == distance_to_end)
+			break; // we got to the end
+		ADVANCE(whitespace_len)
+
+		if (STARTSWITH(read_pos, "#version"))
+		{
+			// found a version directive (and it's not inside a comment)
+			// now locate, verify and read the version number
+			int version_number_len;
+			version_pos = read_pos - original;
+			ADVANCE(sizeof("#version") - 1)
+			whitespace_len = strspn(read_pos, WHITESPACE_CHARS);
+			if (!whitespace_len)
+			{
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Syntax error in #version. Expected space after #version, but got other text.\n");
+				return NULL;
+			}
+			else if (whitespace_len == distance_to_end)
+			{
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Syntax error in #version. Expected version number, but got end of file.\n");
+				return NULL;
+			}
+			ADVANCE(whitespace_len)
+			version_number_len = strspn(read_pos, "0123456789");
+			if (!version_number_len)
+			{
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Syntax error in #version. Expected version number, but got other text.\n");
+				return NULL;
+			}
+			else if (version_number_len != 3)
+			{
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Syntax error in #version. Expected version with 3 digits, but got %d digits.\n", version_number_len);
+				return NULL;
+			}
+			M_Memcpy(shader_glsl_version, read_pos, 3);
+			ADVANCE(version_number_len)
+			version_len = (read_pos - original) - version_pos;
+			whitespace_len = strspn(read_pos, WHITESPACE_CHARS);
+			ADVANCE(whitespace_len)
+			if (STARTSWITH(read_pos, "es"))
+			{
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Support for ES shaders is not implemented.\n");
+				return NULL;
+			}
+			break;
+		}
+		else
+		{
+			// go to next newline or end of next block comment if it starts before the newline
+			// and is not inside a line comment
+			INT32 newline_pos = strstr_int(read_pos, line_ending);
+			INT32 line_comment_pos;
+			INT32 block_comment_pos;
+			// optimization: temporarily put a null at the line ending, so strstr does not needlessly
+			// look past it since we're only interested in the current line
+			if (newline_pos != INT32_MAX)
+				read_pos[newline_pos] = '\0';
+			line_comment_pos = strstr_int(read_pos, "//");
+			block_comment_pos = strstr_int(read_pos, "/*");
+			// restore the line ending, remove the null we just put there
+			if (newline_pos != INT32_MAX)
+				read_pos[newline_pos] = line_ending[0];
+			if (line_comment_pos < block_comment_pos)
+			{
+				// line comment found, skip rest of the line
+				if (newline_pos != INT32_MAX)
+				{
+					ADVANCE(newline_pos + line_ending_len)
+				}
+				else
+				{
+					// we got to the end
+					break;
+				}
+			}
+			else if (block_comment_pos < line_comment_pos)
+			{
+				// block comment found, skip past it
+				INT32 block_comment_end;
+				ADVANCE(block_comment_pos + 2)
+				block_comment_end = strstr_int(read_pos, "*/");
+				if (block_comment_end == INT32_MAX)
+				{
+					// could also leave insertion_pos at 0 and let the GLSL compiler
+					// output an error message for this broken comment
+					CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Encountered unclosed block comment in shader.\n");
+					return NULL;
+				}
+				ADVANCE(block_comment_end + 2)
+			}
+			else if (newline_pos == INT32_MAX)
+			{
+				// we got to the end
+				break;
+			}
+			else
+			{
+				// nothing special on this line, move to the next one
+				ADVANCE(newline_pos + line_ending_len)
+			}
+		}
+	}
+#undef STARTSWITH
+#undef ADVANCE
+
+#define ADD_TO_LEN(def) new_len += sizeof(def) - 1 + line_ending_len;
+
+	// Calculate length of modified shader.
+	new_len = original_len;
+	if (cv_glmodellighting.value)
+		ADD_TO_LEN(MODEL_LIGHTING_DEFINE)
+	if (cv_glpaletterendering.value)
+		ADD_TO_LEN(PALETTE_RENDERING_DEFINE)
+
+#undef ADD_TO_LEN
+
+#define VERSION_PART "#version "
+
+	if (new_len != original_len)
+	{
+		if (version_pos != -1)
+			new_len += sizeof(VERSION_PART) - 1 + 3 + line_ending_len;
+		new_len += sizeof("#line 0") - 1 + line_ending_len;
+	}
+
+	// Allocate memory for modified shader.
+	new_shader = Z_Malloc(new_len + 1, PU_STATIC, NULL);
+
+	read_pos = original;
+	write_pos = new_shader;
+
+	if (new_len != original_len && version_pos != -1)
+	{
+		strcpy(write_pos, VERSION_PART);
+		write_pos += sizeof(VERSION_PART) - 1;
+		M_Memcpy(write_pos, shader_glsl_version, 3);
+		write_pos += 3;
+		strcpy(write_pos, line_ending);
+		write_pos += line_ending_len;
+	}
+
+#undef VERSION_PART
+
+#define WRITE_DEFINE(define) \
+	{ \
+		strcpy(write_pos, define); \
+		write_pos += sizeof(define) - 1; \
+		strcpy(write_pos, line_ending); \
+		write_pos += line_ending_len; \
+	}
+
+	// Write the defines.
+	if (cv_glmodellighting.value)
+		WRITE_DEFINE(MODEL_LIGHTING_DEFINE)
+	if (cv_glpaletterendering.value)
+		WRITE_DEFINE(PALETTE_RENDERING_DEFINE)
+
+#undef WRITE_DEFINE
+
+	// Write a #line directive, so compiler errors will report line numbers from the
+	// original shader without our preamble lines.
+	if (new_len != original_len)
+	{
+		// line numbering in the #line directive is different for versions 110-150
+		if (version_pos == -1 || shader_glsl_version[0] == '1')
+			strcpy(write_pos, "#line 0");
+		else
+			strcpy(write_pos, "#line 1");
+		write_pos += sizeof("#line 0") - 1;
+		strcpy(write_pos, line_ending);
+		write_pos += line_ending_len;
+	}
+
+	// Copy the original shader.
+	M_Memcpy(write_pos, read_pos, original_len);
+
+	// Erase the original #version directive, if it exists and was copied.
+	if (new_len != original_len && version_pos != -1)
+		memset(write_pos + version_pos, ' ', version_len);
+
+	// Terminate the new string.
+	new_shader[new_len] = '\0';
+
+	return new_shader;
+}
+
+// preprocess and compile shader at gl_shaders[index]
+static void HWR_CompileShader(int index)
+{
+	char *vertex_source = gl_shaders[index].vertex;
+	char *fragment_source = gl_shaders[index].fragment;
+
+	if (vertex_source)
+	{
+		char *preprocessed = HWR_PreprocessShader(vertex_source);
+		if (!preprocessed) return;
+		HWD.pfnLoadShader(index, preprocessed, HWD_SHADERSTAGE_VERTEX);
+	}
+	if (fragment_source)
+	{
+		char *preprocessed = HWR_PreprocessShader(fragment_source);
+		if (!preprocessed) return;
+		HWD.pfnLoadShader(index, preprocessed, HWD_SHADERSTAGE_FRAGMENT);
+	}
+
+	gl_shaders[index].compiled = HWD.pfnCompileShader(index);
+}
+
+// compile or recompile shaders
+void HWR_CompileShaders(void)
+{
+	int i;
+
+	for (i = 0; i < NUMSHADERTARGETS; i++)
+	{
+		int custom_index = gl_shadertargets[i].custom_shader;
+		HWR_CompileShader(i);
+		if (!gl_shaders[i].compiled)
+			CONS_Alert(CONS_ERROR, "HWR_CompileShaders: Compilation failed for base %s shader!\n", shaderxlat[i].type);
+		if (custom_index != -1)
+		{
+			HWR_CompileShader(custom_index);
+			if (!gl_shaders[custom_index].compiled)
+				CONS_Alert(CONS_ERROR, "HWR_CompileShaders: Recompilation failed for the custom %s shader! See the console messages above for more information.\n", shaderxlat[i].type);
+		}
+	}
+}
+
+int HWR_GetShaderFromTarget(int shader_target)
+{
+	int custom_shader = gl_shadertargets[shader_target].custom_shader;
+	// use custom shader if following are true
+	// - custom shader exists
+	// - custom shader has been compiled successfully
+	// - custom shaders are enabled
+	// - custom shaders are allowed by the server
+	if (custom_shader != -1 && gl_shaders[custom_shader].compiled &&
+		cv_glshaders.value == 1 && cv_glallowshaders.value)
+		return custom_shader;
+	else
+		return gl_shadertargets[shader_target].base_shader;
+}
+
+static inline UINT16 HWR_FindShaderDefs(UINT16 wadnum)
+{
+	UINT16 i;
+	lumpinfo_t *lump_p;
+
+	lump_p = wadfiles[wadnum]->lumpinfo;
+	for (i = 0; i < wadfiles[wadnum]->numlumps; i++, lump_p++)
+		if (memcmp(lump_p->name, "SHADERS", 7) == 0)
+			return i;
+
+	return INT16_MAX;
+}
+
+customshaderxlat_t shaderxlat[] =
+{
+	{"Flat", SHADER_FLOOR},
+	{"WallTexture", SHADER_WALL},
+	{"Sprite", SHADER_SPRITE},
+	{"Model", SHADER_MODEL},
+	{"WaterRipple", SHADER_WATER},
+	{"Fog", SHADER_FOG},
+	{"Sky", SHADER_SKY},
+	{"PalettePostprocess", SHADER_PALETTE_POSTPROCESS},
+	{"UIColormapFade", SHADER_UI_COLORMAP_FADE},
+	{"UITintedWipe", SHADER_UI_TINTED_WIPE},
+	{NULL, 0},
+};
+
+void HWR_LoadAllCustomShaders(void)
+{
+	INT32 i;
+
+	// read every custom shader
+	for (i = 0; i < numwadfiles; i++)
+		HWR_LoadCustomShadersFromFile(i, W_FileHasFolders(wadfiles[i]));
+}
+
+void HWR_LoadCustomShadersFromFile(UINT16 wadnum, boolean PK3)
+{
+	UINT16 lump;
+	char *shaderdef, *line;
+	char *stoken;
+	char *value;
+	size_t size;
+	int linenum = 1;
+	int shadertype = 0;
+	int i;
+	boolean modified_shaders[NUMSHADERTARGETS] = {0};
+
+	if (!gl_shadersavailable)
+		return;
+
+	lump = HWR_FindShaderDefs(wadnum);
+	if (lump == INT16_MAX)
+		return;
+
+	shaderdef = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE);
+	size = W_LumpLengthPwad(wadnum, lump);
+
+	line = Z_Malloc(size+1, PU_STATIC, NULL);
+	M_Memcpy(line, shaderdef, size);
+	line[size] = '\0';
+
+	stoken = strtok(line, "\r\n ");
+	while (stoken)
+	{
+		if ((stoken[0] == '/' && stoken[1] == '/')
+			|| (stoken[0] == '#'))// skip comments
+		{
+			stoken = strtok(NULL, "\r\n");
+			goto skip_field;
+		}
+
+		if (!stricmp(stoken, "GLSL"))
+		{
+			value = strtok(NULL, "\r\n ");
+			if (!value)
+			{
+				CONS_Alert(CONS_WARNING, "HWR_LoadCustomShadersFromFile: Missing shader type (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
+				stoken = strtok(NULL, "\r\n"); // skip end of line
+				goto skip_lump;
+			}
+
+			if (!stricmp(value, "VERTEX"))
+				shadertype = 1;
+			else if (!stricmp(value, "FRAGMENT"))
+				shadertype = 2;
+
+skip_lump:
+			stoken = strtok(NULL, "\r\n ");
+			linenum++;
+		}
+		else
+		{
+			value = strtok(NULL, "\r\n= ");
+			if (!value)
+			{
+				CONS_Alert(CONS_WARNING, "HWR_LoadCustomShadersFromFile: Missing shader target (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
+				stoken = strtok(NULL, "\r\n"); // skip end of line
+				goto skip_field;
+			}
+
+			if (!shadertype)
+			{
+				CONS_Alert(CONS_ERROR, "HWR_LoadCustomShadersFromFile: Missing shader type (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
+				Z_Free(line);
+				return;
+			}
+
+			for (i = 0; shaderxlat[i].type; i++)
+			{
+				if (!stricmp(shaderxlat[i].type, stoken))
+				{
+					size_t shader_string_length;
+					char *shader_source;
+					char *shader_lumpname;
+					UINT16 shader_lumpnum;
+					int shader_index; // index in gl_shaders
+
+					if (PK3)
+					{
+						shader_lumpname = Z_Malloc(strlen(value) + 12, PU_STATIC, NULL);
+						strcpy(shader_lumpname, "Shaders/sh_");
+						strcat(shader_lumpname, value);
+						shader_lumpnum = W_CheckNumForFullNamePK3(shader_lumpname, wadnum, 0);
+					}
+					else
+					{
+						shader_lumpname = Z_Malloc(strlen(value) + 4, PU_STATIC, NULL);
+						strcpy(shader_lumpname, "SH_");
+						strcat(shader_lumpname, value);
+						shader_lumpnum = W_CheckNumForNamePwad(shader_lumpname, wadnum, 0);
+					}
+
+					if (shader_lumpnum == INT16_MAX)
+					{
+						CONS_Alert(CONS_ERROR, "HWR_LoadCustomShadersFromFile: Missing shader source %s (file %s, line %d)\n", shader_lumpname, wadfiles[wadnum]->filename, linenum);
+						Z_Free(shader_lumpname);
+						continue;
+					}
+
+					shader_string_length = W_LumpLengthPwad(wadnum, shader_lumpnum) + 1;
+					shader_source = Z_Malloc(shader_string_length, PU_STATIC, NULL);
+					W_ReadLumpPwad(wadnum, shader_lumpnum, shader_source);
+					shader_source[shader_string_length-1] = '\0';
+
+					shader_index = shaderxlat[i].id + NUMSHADERTARGETS;
+					if (!modified_shaders[shaderxlat[i].id])
+					{
+						// this will clear any old custom shaders from previously loaded files
+						// Z_Free checks if the pointer is NULL!
+						Z_Free(gl_shaders[shader_index].vertex);
+						gl_shaders[shader_index].vertex = NULL;
+						Z_Free(gl_shaders[shader_index].fragment);
+						gl_shaders[shader_index].fragment = NULL;
+					}
+					modified_shaders[shaderxlat[i].id] = true;
+
+					if (shadertype == 1)
+					{
+						if (gl_shaders[shader_index].vertex)
+						{
+							CONS_Alert(CONS_WARNING, "HWR_LoadCustomShadersFromFile: %s is overwriting another %s vertex shader from the same addon! (file %s, line %d)\n", shader_lumpname, shaderxlat[i].type, wadfiles[wadnum]->filename, linenum);
+							Z_Free(gl_shaders[shader_index].vertex);
+						}
+						gl_shaders[shader_index].vertex = shader_source;
+					}
+					else
+					{
+						if (gl_shaders[shader_index].fragment)
+						{
+							CONS_Alert(CONS_WARNING, "HWR_LoadCustomShadersFromFile: %s is overwriting another %s fragment shader from the same addon! (file %s, line %d)\n", shader_lumpname, shaderxlat[i].type, wadfiles[wadnum]->filename, linenum);
+							Z_Free(gl_shaders[shader_index].fragment);
+						}
+						gl_shaders[shader_index].fragment = shader_source;
+					}
+
+					Z_Free(shader_lumpname);
+				}
+			}
+
+skip_field:
+			stoken = strtok(NULL, "\r\n= ");
+			linenum++;
+		}
+	}
+
+	for (i = 0; i < NUMSHADERTARGETS; i++)
+	{
+		if (modified_shaders[i])
+		{
+			int shader_index = i + NUMSHADERTARGETS; // index to gl_shaders
+			gl_shadertargets[i].custom_shader = shader_index;
+			// if only one stage (vertex/fragment) is defined, the other one
+			// is copied from the base shaders.
+			if (!gl_shaders[shader_index].fragment)
+				gl_shaders[shader_index].fragment = Z_StrDup(gl_shadersources[i].fragment);
+			if (!gl_shaders[shader_index].vertex)
+				gl_shaders[shader_index].vertex = Z_StrDup(gl_shadersources[i].vertex);
+			HWR_CompileShader(shader_index);
+			if (!gl_shaders[shader_index].compiled)
+				CONS_Alert(CONS_ERROR, "HWR_LoadCustomShadersFromFile: A compilation error occured for the %s shader in file %s. See the console messages above for more information.\n", shaderxlat[i].type, wadfiles[wadnum]->filename);
+		}
+	}
+
+	Z_Free(line);
+	return;
+}
+
+const char *HWR_GetShaderName(INT32 shader)
+{
+	INT32 i;
+
+	for (i = 0; shaderxlat[i].type; i++)
+	{
+		if (shaderxlat[i].id == shader)
+			return shaderxlat[i].type;
+	}
+
+	return "Unknown";
+}
+
+#endif // HWRENDER
diff --git a/src/hardware/hw_shaders.h b/src/hardware/hw_shaders.h
new file mode 100644
index 0000000000000000000000000000000000000000..bb0e6a232edc45c2a39ac21a9f56beca4c973ebb
--- /dev/null
+++ b/src/hardware/hw_shaders.h
@@ -0,0 +1,424 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2021 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file hw_shaders.h
+/// \brief Handles the shaders used by the game.
+
+#ifndef _HW_SHADERS_H_
+#define _HW_SHADERS_H_
+
+#include "../doomtype.h"
+
+// ================
+//  Vertex shaders
+// ================
+
+//
+// Generic vertex shader
+//
+
+#define GLSL_DEFAULT_VERTEX_SHADER \
+	"void main()\n" \
+	"{\n" \
+		"gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
+		"gl_FrontColor = gl_Color;\n" \
+		"gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
+		"gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" \
+	"}\0"
+
+// replicates the way fixed function lighting is used by the model lighting option,
+// stores the lighting result to gl_Color
+// (ambient lighting of 0.75 and diffuse lighting from above)
+#define GLSL_MODEL_VERTEX_SHADER \
+	"void main()\n" \
+	"{\n" \
+		"#ifdef SRB2_MODEL_LIGHTING\n" \
+		"float nDotVP = dot(gl_Normal, vec3(0, 1, 0));\n" \
+		"float light = min(0.75 + max(nDotVP, 0.0), 1.0);\n" \
+		"gl_FrontColor = vec4(light, light, light, 1.0);\n" \
+		"#else\n" \
+		"gl_FrontColor = gl_Color;\n" \
+		"#endif\n" \
+		"gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
+		"gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
+		"gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" \
+	"}\0"
+
+// ==================
+//  Fragment shaders
+// ==================
+
+//
+// Generic fragment shader
+//
+
+#define GLSL_DEFAULT_FRAGMENT_SHADER \
+	"uniform sampler2D tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"void main(void) {\n" \
+		"gl_FragColor = texture2D(tex, gl_TexCoord[0].st) * poly_color;\n" \
+	"}\0"
+
+//
+// Software fragment shader
+//
+
+// Include GLSL_FLOOR_FUDGES or GLSL_WALL_FUDGES or define the fudges in shaders that use this macro.
+#define GLSL_DOOM_COLORMAP \
+	"float R_DoomColormap(float light, float z)\n" \
+	"{\n" \
+		"float lightnum = clamp(light / 17.0, 0.0, 15.0);\n" \
+		"float lightz = clamp(z / 16.0, 0.0, 127.0);\n" \
+		"float startmap = (15.0 - lightnum) * 4.0;\n" \
+		"float scale = 160.0 / (lightz + 1.0);\n" \
+		"float cap = (155.0 - light) * 0.26;\n" \
+		"return max(startmap * STARTMAP_FUDGE - scale * 0.5 * SCALE_FUDGE, cap);\n" \
+	"}\n"
+// lighting cap adjustment:
+// first num (155.0), increase to make it start to go dark sooner
+// second num (0.26), increase to make it go dark faster
+
+#define GLSL_DOOM_LIGHT_EQUATION \
+	"float R_DoomLightingEquation(float light)\n" \
+	"{\n" \
+		"float z = gl_FragCoord.z / gl_FragCoord.w;\n" \
+		"float colormap = floor(R_DoomColormap(light, z)) + 0.5;\n" \
+		"return clamp(colormap, 0.0, 31.0) / 32.0;\n" \
+	"}\n"
+
+#define GLSL_SOFTWARE_TINT_EQUATION \
+	"if (tint_color.a > 0.0) {\n" \
+		"float color_bright = sqrt((base_color.r * base_color.r) + (base_color.g * base_color.g) + (base_color.b * base_color.b));\n" \
+		"float strength = sqrt(tint_color.a);\n" \
+		"final_color.r = clamp((color_bright * (tint_color.r * strength)) + (base_color.r * (1.0 - strength)), 0.0, 1.0);\n" \
+		"final_color.g = clamp((color_bright * (tint_color.g * strength)) + (base_color.g * (1.0 - strength)), 0.0, 1.0);\n" \
+		"final_color.b = clamp((color_bright * (tint_color.b * strength)) + (base_color.b * (1.0 - strength)), 0.0, 1.0);\n" \
+	"}\n"
+
+#define GLSL_SOFTWARE_FADE_EQUATION \
+	"float darkness = R_DoomLightingEquation(lighting);\n" \
+	"if (fade_start != 0.0 || fade_end != 31.0) {\n" \
+		"float fs = fade_start / 31.0;\n" \
+		"float fe = fade_end / 31.0;\n" \
+		"float fd = fe - fs;\n" \
+		"darkness = clamp((darkness - fs) * (1.0 / fd), 0.0, 1.0);\n" \
+	"}\n" \
+	"final_color = mix(final_color, fade_color, darkness);\n"
+
+#define GLSL_PALETTE_RENDERING \
+	"float tex_pal_idx = texture3D(palette_lookup_tex, vec3((texel * 63.0 + 0.5) / 64.0))[0] * 255.0;\n" \
+	"float z = gl_FragCoord.z / gl_FragCoord.w;\n" \
+	"float light_y = clamp(floor(R_DoomColormap(lighting, z)), 0.0, 31.0);\n" \
+	"vec2 lighttable_coord = vec2((tex_pal_idx + 0.5) / 256.0, (light_y + 0.5) / 32.0);\n" \
+	"vec4 final_color = texture2D(lighttable_tex, lighttable_coord);\n" \
+	"final_color.a = texel.a * poly_color.a;\n" \
+	"gl_FragColor = final_color;\n" \
+
+#define GLSL_SOFTWARE_FRAGMENT_SHADER \
+	"#ifdef SRB2_PALETTE_RENDERING\n" \
+	"uniform sampler2D tex;\n" \
+	"uniform sampler3D palette_lookup_tex;\n" \
+	"uniform sampler2D lighttable_tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"uniform float lighting;\n" \
+	GLSL_DOOM_COLORMAP \
+	"void main(void) {\n" \
+		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
+		GLSL_PALETTE_RENDERING \
+	"}\n" \
+	"#else\n" \
+	"uniform sampler2D tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"uniform vec4 tint_color;\n" \
+	"uniform vec4 fade_color;\n" \
+	"uniform float lighting;\n" \
+	"uniform float fade_start;\n" \
+	"uniform float fade_end;\n" \
+	GLSL_DOOM_COLORMAP \
+	GLSL_DOOM_LIGHT_EQUATION \
+	"void main(void) {\n" \
+		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
+		"vec4 base_color = texel * poly_color;\n" \
+		"vec4 final_color = base_color;\n" \
+		GLSL_SOFTWARE_TINT_EQUATION \
+		GLSL_SOFTWARE_FADE_EQUATION \
+		"final_color.a = texel.a * poly_color.a;\n" \
+		"gl_FragColor = final_color;\n" \
+	"}\n" \
+	"#endif\0"
+
+// hand tuned adjustments for light level calculation
+#define GLSL_FLOOR_FUDGES \
+	"#define STARTMAP_FUDGE 1.06\n" \
+	"#define SCALE_FUDGE 1.15\n"
+
+#define GLSL_WALL_FUDGES \
+	"#define STARTMAP_FUDGE 1.05\n" \
+	"#define SCALE_FUDGE 2.2\n"
+
+#define GLSL_FLOOR_FRAGMENT_SHADER \
+	GLSL_FLOOR_FUDGES \
+	GLSL_SOFTWARE_FRAGMENT_SHADER
+
+#define GLSL_WALL_FRAGMENT_SHADER \
+	GLSL_WALL_FUDGES \
+	GLSL_SOFTWARE_FRAGMENT_SHADER
+
+// same as above but multiplies results with the lighting value from the
+// accompanying vertex shader (stored in gl_Color) if model lighting is enabled
+#define GLSL_MODEL_FRAGMENT_SHADER \
+	GLSL_WALL_FUDGES \
+	"#ifdef SRB2_PALETTE_RENDERING\n" \
+	"uniform sampler2D tex;\n" \
+	"uniform sampler3D palette_lookup_tex;\n" \
+	"uniform sampler2D lighttable_tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"uniform float lighting;\n" \
+	GLSL_DOOM_COLORMAP \
+	"void main(void) {\n" \
+		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
+		"#ifdef SRB2_MODEL_LIGHTING\n" \
+		"texel *= gl_Color;\n" \
+		"#endif\n" \
+		GLSL_PALETTE_RENDERING \
+	"}\n" \
+	"#else\n" \
+	"uniform sampler2D tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"uniform vec4 tint_color;\n" \
+	"uniform vec4 fade_color;\n" \
+	"uniform float lighting;\n" \
+	"uniform float fade_start;\n" \
+	"uniform float fade_end;\n" \
+	GLSL_DOOM_COLORMAP \
+	GLSL_DOOM_LIGHT_EQUATION \
+	"void main(void) {\n" \
+		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
+		"vec4 base_color = texel * poly_color;\n" \
+		"vec4 final_color = base_color;\n" \
+		GLSL_SOFTWARE_TINT_EQUATION \
+		GLSL_SOFTWARE_FADE_EQUATION \
+		"#ifdef SRB2_MODEL_LIGHTING\n" \
+		"final_color *= gl_Color;\n" \
+		"#endif\n" \
+		"final_color.a = texel.a * poly_color.a;\n" \
+		"gl_FragColor = final_color;\n" \
+	"}\n" \
+	"#endif\0"
+
+//
+// Water surface shader
+//
+// Mostly guesstimated, rather than the rest being built off Software science.
+// Still needs to distort things underneath/around the water...
+//
+
+#define GLSL_WATER_TEXEL \
+	"float water_z = (gl_FragCoord.z / gl_FragCoord.w) / 2.0;\n" \
+	"float a = -pi * (water_z * freq) + (leveltime * speed);\n" \
+	"float sdistort = sin(a) * amp;\n" \
+	"float cdistort = cos(a) * amp;\n" \
+	"vec4 texel = texture2D(tex, vec2(gl_TexCoord[0].s - sdistort, gl_TexCoord[0].t - cdistort));\n"
+
+#define GLSL_WATER_FRAGMENT_SHADER \
+	GLSL_FLOOR_FUDGES \
+	"const float freq = 0.025;\n" \
+	"const float amp = 0.025;\n" \
+	"const float speed = 2.0;\n" \
+	"const float pi = 3.14159;\n" \
+	"#ifdef SRB2_PALETTE_RENDERING\n" \
+	"uniform sampler2D tex;\n" \
+	"uniform sampler3D palette_lookup_tex;\n" \
+	"uniform sampler2D lighttable_tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"uniform float lighting;\n" \
+	"uniform float leveltime;\n" \
+	GLSL_DOOM_COLORMAP \
+	"void main(void) {\n" \
+		GLSL_WATER_TEXEL \
+		GLSL_PALETTE_RENDERING \
+	"}\n" \
+	"#else\n" \
+	"uniform sampler2D tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"uniform vec4 tint_color;\n" \
+	"uniform vec4 fade_color;\n" \
+	"uniform float lighting;\n" \
+	"uniform float fade_start;\n" \
+	"uniform float fade_end;\n" \
+	"uniform float leveltime;\n" \
+	GLSL_DOOM_COLORMAP \
+	GLSL_DOOM_LIGHT_EQUATION \
+	"void main(void) {\n" \
+		GLSL_WATER_TEXEL \
+		"vec4 base_color = texel * poly_color;\n" \
+		"vec4 final_color = base_color;\n" \
+		GLSL_SOFTWARE_TINT_EQUATION \
+		GLSL_SOFTWARE_FADE_EQUATION \
+		"final_color.a = texel.a * poly_color.a;\n" \
+		"gl_FragColor = final_color;\n" \
+	"}\n" \
+	"#endif\0"
+
+//
+// Fog block shader
+//
+// Alpha of the planes themselves are still slightly off -- see HWR_FogBlockAlpha
+//
+
+// The floor fudges are used, but should the wall fudges be used instead? or something inbetween?
+// or separate values for floors and walls? (need to change more than this shader for that)
+#define GLSL_FOG_FRAGMENT_SHADER \
+	GLSL_FLOOR_FUDGES \
+	"uniform vec4 tint_color;\n" \
+	"uniform vec4 fade_color;\n" \
+	"uniform float lighting;\n" \
+	"uniform float fade_start;\n" \
+	"uniform float fade_end;\n" \
+	GLSL_DOOM_COLORMAP \
+	GLSL_DOOM_LIGHT_EQUATION \
+	"void main(void) {\n" \
+		"vec4 base_color = gl_Color;\n" \
+		"vec4 final_color = base_color;\n" \
+		GLSL_SOFTWARE_TINT_EQUATION \
+		GLSL_SOFTWARE_FADE_EQUATION \
+		"gl_FragColor = final_color;\n" \
+	"}\0"
+
+//
+// Sky fragment shader
+// Modulates poly_color with gl_Color
+//
+#define GLSL_SKY_FRAGMENT_SHADER \
+	"uniform sampler2D tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"void main(void) {\n" \
+		"gl_FragColor = texture2D(tex, gl_TexCoord[0].st) * gl_Color * poly_color;\n" \
+	"}\0"
+
+// Shader for the palette rendering postprocess step
+#define GLSL_PALETTE_POSTPROCESS_FRAGMENT_SHADER \
+	"uniform sampler2D tex;\n" \
+	"uniform sampler3D palette_lookup_tex;\n" \
+	"uniform sampler1D palette_tex;\n" \
+	"void main(void) {\n" \
+		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
+		"float tex_pal_idx = texture3D(palette_lookup_tex, vec3((texel * 63.0 + 0.5) / 64.0))[0] * 255.0;\n" \
+		"float palette_coord = (tex_pal_idx + 0.5) / 256.0;\n" \
+		"vec4 final_color = texture1D(palette_tex, palette_coord);\n" \
+		"gl_FragColor = final_color;\n" \
+	"}\0"
+
+// Applies a palettized colormap fade to tex
+#define GLSL_UI_COLORMAP_FADE_FRAGMENT_SHADER \
+	"uniform sampler2D tex;\n" \
+	"uniform float lighting;\n" \
+	"uniform sampler3D palette_lookup_tex;\n" \
+	"uniform sampler2D lighttable_tex;\n" \
+	"void main(void) {\n" \
+		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
+		"float tex_pal_idx = texture3D(palette_lookup_tex, vec3((texel * 63.0 + 0.5) / 64.0))[0] * 255.0;\n" \
+		"vec2 lighttable_coord = vec2((tex_pal_idx + 0.5) / 256.0, (lighting + 0.5) / 32.0);\n" \
+		"gl_FragColor = texture2D(lighttable_tex, lighttable_coord);\n" \
+	"}\0"
+
+// For wipes that use additive and subtractive blending.
+// alpha_factor = 31 * 8 / 10 = 24.8
+// Calculated based on the use of the "fade" variable from the GETCOLOR macro
+// in r_data.c:R_CreateFadeColormaps.
+// However this value created some ugliness in fades to white (special stage entry)
+// while palette rendering is enabled, so I raised the value just a bit.
+#define GLSL_UI_TINTED_WIPE_FRAGMENT_SHADER \
+	"uniform sampler2D tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"const float alpha_factor = 24.875;\n" \
+	"void main(void) {\n" \
+		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
+		"vec4 final_color = poly_color;\n" \
+		"float alpha = texel.a;\n" \
+		"if (final_color.a >= 0.5)\n" \
+			"alpha = 1.0 - alpha;\n" \
+		"alpha *= alpha_factor;\n" \
+		"final_color *= alpha;\n" \
+		"final_color.a = 1.0;\n" \
+		"gl_FragColor = final_color;\n" \
+	"}\0"
+
+//
+// Generic vertex shader
+//
+
+#define GLSL_FALLBACK_VERTEX_SHADER \
+	"void main()\n" \
+	"{\n" \
+		"gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
+		"gl_FrontColor = gl_Color;\n" \
+		"gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
+		"gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" \
+	"}\0"
+
+//
+// Generic fragment shader
+//
+
+#define GLSL_FALLBACK_FRAGMENT_SHADER \
+	"uniform sampler2D tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"void main(void) {\n" \
+		"gl_FragColor = texture2D(tex, gl_TexCoord[0].st) * poly_color;\n" \
+	"}\0"
+
+//
+// Software fragment shader
+//
+
+#define GLSL_SOFTWARE_FADE_EQUATION \
+	"float darkness = R_DoomLightingEquation(lighting);\n" \
+	"if (fade_start != 0.0 || fade_end != 31.0) {\n" \
+		"float fs = fade_start / 31.0;\n" \
+		"float fe = fade_end / 31.0;\n" \
+		"float fd = fe - fs;\n" \
+		"darkness = clamp((darkness - fs) * (1.0 / fd), 0.0, 1.0);\n" \
+	"}\n" \
+	"final_color = mix(final_color, fade_color, darkness);\n"
+
+// same as above but multiplies results with the lighting value from the
+// accompanying vertex shader (stored in gl_Color)
+#define GLSL_SOFTWARE_MODEL_LIGHTING_FRAGMENT_SHADER \
+	"uniform sampler2D tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"uniform vec4 tint_color;\n" \
+	"uniform vec4 fade_color;\n" \
+	"uniform float lighting;\n" \
+	"uniform float fade_start;\n" \
+	"uniform float fade_end;\n" \
+	GLSL_DOOM_COLORMAP \
+	GLSL_DOOM_LIGHT_EQUATION \
+	"void main(void) {\n" \
+		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
+		"vec4 base_color = texel * poly_color;\n" \
+		"vec4 final_color = base_color;\n" \
+		GLSL_SOFTWARE_TINT_EQUATION \
+		GLSL_SOFTWARE_FADE_EQUATION \
+		"final_color *= gl_Color;\n" \
+		"final_color.a = texel.a * poly_color.a;\n" \
+		"gl_FragColor = final_color;\n" \
+	"}\0"
+
+//
+// Sky fragment shader
+// Modulates poly_color with gl_Color
+//
+#define GLSL_SKY_FRAGMENT_SHADER \
+	"uniform sampler2D tex;\n" \
+	"uniform vec4 poly_color;\n" \
+	"void main(void) {\n" \
+		"gl_FragColor = texture2D(tex, gl_TexCoord[0].st) * gl_Color * poly_color;\n" \
+	"}\0"
+
+#endif
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index ea831e41dee3b7afff7eed47d4ead3f858870648..acd09f614318f433567a5764f2cf453d26315e9d 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -24,6 +24,7 @@
 #include "../../r_local.h" // For rendertimefrac, used for the leveltime shader uniform
 #include "r_opengl.h"
 #include "r_vbo.h"
+#include "../hw_shaders.h"
 
 #if defined (HWRENDER) && !defined (NOROPENGL)
 
@@ -35,12 +36,21 @@ struct GLRGBAFloat
 	GLfloat alpha;
 };
 typedef struct GLRGBAFloat GLRGBAFloat;
-static const GLubyte white[4] = { 255, 255, 255, 255 };
+
+// lighttable list item
+struct LTListItem
+{
+	UINT32 id;
+	struct LTListItem *next;
+};
+typedef struct LTListItem LTListItem;
 
 // ==========================================================================
 //                                                                  CONSTANTS
 // ==========================================================================
 
+static const GLubyte white[4] = { 255, 255, 255, 255 };
+
 // With OpenGL 1.1+, the first texture should be 1
 static GLuint NOTEXTURE_NUM = 0;
 
@@ -56,6 +66,7 @@ static float NEAR_CLIPPING_PLANE =   NZCLIP_PLANE;
 
 
 static  GLuint      tex_downloaded  = 0;
+static  GLuint      lt_downloaded   = 0; // currently bound lighttable texture
 static  GLfloat     fov             = 90.0f;
 static  FBITFIELD   CurrentPolyFlags;
 
@@ -66,7 +77,15 @@ static FTextureInfo *TexCacheHead = NULL;
 static RGBA_t *textureBuffer = NULL;
 static size_t textureBufferSize = 0;
 
-RGBA_t  myPaletteData[256];
+// Linked list of all lighttables.
+static LTListItem *LightTablesTail = NULL;
+static LTListItem *LightTablesHead = NULL;
+
+static RGBA_t screenPalette[256] = {0}; // the palette for the postprocessing step in palette rendering
+static GLuint screenPaletteTex = 0; // 1D texture containing the screen palette
+static GLuint paletteLookupTex = 0; // 3D texture containing RGB -> palette index lookup table
+RGBA_t  myPaletteData[256]; // the palette for converting textures to RGBA
+
 GLint   screen_width    = 0;               // used by Draw2DLine()
 GLint   screen_height   = 0;
 GLbyte  screen_depth    = 0;
@@ -91,10 +110,7 @@ static GLint   viewport[4];
 //			flush all of the stored textures, leaving them unavailable at times such as between levels
 //			These need to start at 0 and be set to their number, and be reset to 0 when deleted so that intel GPUs
 //			can know when the textures aren't there, as textures are always considered resident in their virtual memory
-static GLuint screentexture = 0;
-static GLuint startScreenWipe = 0;
-static GLuint endScreenWipe = 0;
-static GLuint finalScreenTexture = 0;
+static GLuint screenTextures[NUMSCREENTEXTURES] = {0};
 
 // shortcut for ((float)1/i)
 static const GLfloat byte2float[256] = {
@@ -378,10 +394,14 @@ typedef void (APIENTRY * PFNglTexEnvi) (GLenum target, GLenum pname, GLint param
 static PFNglTexEnvi pglTexEnvi;
 typedef void (APIENTRY * PFNglTexParameteri) (GLenum target, GLenum pname, GLint param);
 static PFNglTexParameteri pglTexParameteri;
+typedef void (APIENTRY * PFNglTexImage1D) (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+static PFNglTexImage1D pglTexImage1D;
 typedef void (APIENTRY * PFNglTexImage2D) (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
 static PFNglTexImage2D pglTexImage2D;
 typedef void (APIENTRY * PFNglTexSubImage2D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);
 static PFNglTexSubImage2D pglTexSubImage2D;
+typedef void (APIENTRY * PFNglGetTexImage) (GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels);
+static PFNglGetTexImage pglGetTexImage;
 
 /* 1.1 functions */
 /* texture objects */ //GL_EXT_texture_object
@@ -401,6 +421,10 @@ static PFNglCopyTexSubImage2D pglCopyTexSubImage2D;
 typedef GLint (APIENTRY * PFNgluBuild2DMipmaps) (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *data);
 static PFNgluBuild2DMipmaps pgluBuild2DMipmaps;
 
+/* 1.2 functions for 3D textures */
+typedef void (APIENTRY * PFNglTexImage3D) (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+static PFNglTexImage3D pglTexImage3D;
+
 /* 1.3 functions for multitexturing */
 typedef void (APIENTRY *PFNglActiveTexture) (GLenum);
 static PFNglActiveTexture pglActiveTexture;
@@ -445,6 +469,9 @@ static PFNglBlendEquation pglBlendEquation;
 #ifndef GL_TEXTURE1
 #define GL_TEXTURE1 0x84C1
 #endif
+#ifndef GL_TEXTURE2
+#define GL_TEXTURE2 0x84C2
+#endif
 
 /* 1.5 Parms */
 #ifndef GL_ARRAY_BUFFER
@@ -517,8 +544,10 @@ boolean SetupGLfunc(void)
 
 	GETOPENGLFUNC(pglTexEnvi, glTexEnvi)
 	GETOPENGLFUNC(pglTexParameteri, glTexParameteri)
+	GETOPENGLFUNC(pglTexImage1D, glTexImage1D)
 	GETOPENGLFUNC(pglTexImage2D, glTexImage2D)
 	GETOPENGLFUNC(pglTexSubImage2D, glTexSubImage2D)
+	GETOPENGLFUNC(pglGetTexImage, glGetTexImage)
 
 	GETOPENGLFUNC(pglGenTextures, glGenTextures)
 	GETOPENGLFUNC(pglDeleteTextures, glDeleteTextures)
@@ -534,7 +563,7 @@ boolean SetupGLfunc(void)
 }
 
 static boolean gl_shadersenabled = false;
-static hwdshaderoption_t gl_allowshaders = HWD_SHADEROPTION_OFF;
+static INT32 gl_allowshaders = 0;
 
 #ifdef GL_SHADERS
 typedef GLuint 	(APIENTRY *PFNglCreateShader)		(GLenum);
@@ -592,7 +621,12 @@ typedef enum
 	gluniform_fade_start,
 	gluniform_fade_end,
 
-	// misc. (custom shaders)
+	// palette rendering
+	gluniform_palette_tex, // 1d texture containing a palette
+	gluniform_palette_lookup_tex, // 3d texture containing the rgb->index lookup table
+	gluniform_lighttable_tex, // 2d texture containing a light table
+
+	// misc.
 	gluniform_leveltime,
 
 	gluniform_max,
@@ -600,14 +634,15 @@ typedef enum
 
 typedef struct gl_shader_s
 {
+	char *vertex_shader;
+	char *fragment_shader;
 	GLuint program;
 	GLint uniforms[gluniform_max+1];
-	boolean custom;
 } gl_shader_t;
 
 static gl_shader_t gl_shaders[HWR_MAXSHADERS];
-static gl_shader_t gl_usershaders[HWR_MAXSHADERS];
-static shadersource_t gl_customshaders[HWR_MAXSHADERS];
+
+static gl_shader_t gl_fallback_shader;
 
 // 09102020
 typedef struct gl_shaderstate_s
@@ -623,253 +658,19 @@ static gl_shaderstate_t gl_shaderstate;
 static float shader_leveltime = 0;
 
 // Lactozilla: Shader functions
-static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i, const GLchar *vert_shader, const GLchar *frag_shader);
+static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i);
 static void Shader_CompileError(const char *message, GLuint program, INT32 shadernum);
 static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade);
 
 static GLRGBAFloat shader_defaultcolor = {1.0f, 1.0f, 1.0f, 1.0f};
 
-// ================
-//  Vertex shaders
-// ================
-
-//
-// Generic vertex shader
-//
-
-#define GLSL_DEFAULT_VERTEX_SHADER \
-	"void main()\n" \
-	"{\n" \
-		"gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
-		"gl_FrontColor = gl_Color;\n" \
-		"gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
-		"gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" \
-	"}\0"
-
-// replicates the way fixed function lighting is used by the model lighting option,
-// stores the lighting result to gl_Color
-// (ambient lighting of 0.75 and diffuse lighting from above)
-#define GLSL_MODEL_LIGHTING_VERTEX_SHADER \
-	"void main()\n" \
-	"{\n" \
-		"float nDotVP = dot(gl_Normal, vec3(0, 1, 0));\n" \
-		"float light = 0.75 + max(nDotVP, 0.0);\n" \
-		"gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
-		"gl_FrontColor = vec4(light, light, light, 1.0);\n" \
-		"gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
-		"gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" \
-	"}\0"
-
-// ==================
-//  Fragment shaders
-// ==================
-
-//
-// Generic fragment shader
-//
-
-#define GLSL_DEFAULT_FRAGMENT_SHADER \
-	"uniform sampler2D tex;\n" \
-	"uniform vec4 poly_color;\n" \
-	"void main(void) {\n" \
-		"gl_FragColor = texture2D(tex, gl_TexCoord[0].st) * poly_color;\n" \
-	"}\0"
-
-//
-// Software fragment shader
-//
-
-#define GLSL_DOOM_COLORMAP \
-	"float R_DoomColormap(float light, float z)\n" \
-	"{\n" \
-		"float lightnum = clamp(light / 17.0, 0.0, 15.0);\n" \
-		"float lightz = clamp(z / 16.0, 0.0, 127.0);\n" \
-		"float startmap = (15.0 - lightnum) * 4.0;\n" \
-		"float scale = 160.0 / (lightz + 1.0);\n" \
-		"return startmap - scale * 0.5;\n" \
-	"}\n"
-
-#define GLSL_DOOM_LIGHT_EQUATION \
-	"float R_DoomLightingEquation(float light)\n" \
-	"{\n" \
-		"float z = gl_FragCoord.z / gl_FragCoord.w;\n" \
-		"float colormap = floor(R_DoomColormap(light, z)) + 0.5;\n" \
-		"return clamp(colormap, 0.0, 31.0) / 32.0;\n" \
-	"}\n"
-
-#define GLSL_SOFTWARE_TINT_EQUATION \
-	"if (tint_color.a > 0.0) {\n" \
-		"float color_bright = sqrt((base_color.r * base_color.r) + (base_color.g * base_color.g) + (base_color.b * base_color.b));\n" \
-		"float strength = sqrt(tint_color.a);\n" \
-		"final_color.r = clamp((color_bright * (tint_color.r * strength)) + (base_color.r * (1.0 - strength)), 0.0, 1.0);\n" \
-		"final_color.g = clamp((color_bright * (tint_color.g * strength)) + (base_color.g * (1.0 - strength)), 0.0, 1.0);\n" \
-		"final_color.b = clamp((color_bright * (tint_color.b * strength)) + (base_color.b * (1.0 - strength)), 0.0, 1.0);\n" \
-	"}\n"
-
-#define GLSL_SOFTWARE_FADE_EQUATION \
-	"float darkness = R_DoomLightingEquation(lighting);\n" \
-	"if (fade_start != 0.0 || fade_end != 31.0) {\n" \
-		"float fs = fade_start / 31.0;\n" \
-		"float fe = fade_end / 31.0;\n" \
-		"float fd = fe - fs;\n" \
-		"darkness = clamp((darkness - fs) * (1.0 / fd), 0.0, 1.0);\n" \
-	"}\n" \
-	"final_color = mix(final_color, fade_color, darkness);\n"
-
-#define GLSL_SOFTWARE_FRAGMENT_SHADER \
-	"uniform sampler2D tex;\n" \
-	"uniform vec4 poly_color;\n" \
-	"uniform vec4 tint_color;\n" \
-	"uniform vec4 fade_color;\n" \
-	"uniform float lighting;\n" \
-	"uniform float fade_start;\n" \
-	"uniform float fade_end;\n" \
-	GLSL_DOOM_COLORMAP \
-	GLSL_DOOM_LIGHT_EQUATION \
-	"void main(void) {\n" \
-		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
-		"vec4 base_color = texel * poly_color;\n" \
-		"vec4 final_color = base_color;\n" \
-		GLSL_SOFTWARE_TINT_EQUATION \
-		GLSL_SOFTWARE_FADE_EQUATION \
-		"final_color.a = texel.a * poly_color.a;\n" \
-		"gl_FragColor = final_color;\n" \
-	"}\0"
-
-// same as above but multiplies results with the lighting value from the
-// accompanying vertex shader (stored in gl_Color)
-#define GLSL_SOFTWARE_MODEL_LIGHTING_FRAGMENT_SHADER \
-	"uniform sampler2D tex;\n" \
-	"uniform vec4 poly_color;\n" \
-	"uniform vec4 tint_color;\n" \
-	"uniform vec4 fade_color;\n" \
-	"uniform float lighting;\n" \
-	"uniform float fade_start;\n" \
-	"uniform float fade_end;\n" \
-	GLSL_DOOM_COLORMAP \
-	GLSL_DOOM_LIGHT_EQUATION \
-	"void main(void) {\n" \
-		"vec4 texel = texture2D(tex, gl_TexCoord[0].st);\n" \
-		"vec4 base_color = texel * poly_color;\n" \
-		"vec4 final_color = base_color;\n" \
-		GLSL_SOFTWARE_TINT_EQUATION \
-		GLSL_SOFTWARE_FADE_EQUATION \
-		"final_color *= gl_Color;\n" \
-		"final_color.a = texel.a * poly_color.a;\n" \
-		"gl_FragColor = final_color;\n" \
-	"}\0"
-
-//
-// Water surface shader
-//
-// Mostly guesstimated, rather than the rest being built off Software science.
-// Still needs to distort things underneath/around the water...
-//
-
-#define GLSL_WATER_FRAGMENT_SHADER \
-	"uniform sampler2D tex;\n" \
-	"uniform vec4 poly_color;\n" \
-	"uniform vec4 tint_color;\n" \
-	"uniform vec4 fade_color;\n" \
-	"uniform float lighting;\n" \
-	"uniform float fade_start;\n" \
-	"uniform float fade_end;\n" \
-	"uniform float leveltime;\n" \
-	"const float freq = 0.025;\n" \
-	"const float amp = 0.025;\n" \
-	"const float speed = 2.0;\n" \
-	"const float pi = 3.14159;\n" \
-	GLSL_DOOM_COLORMAP \
-	GLSL_DOOM_LIGHT_EQUATION \
-	"void main(void) {\n" \
-		"float z = (gl_FragCoord.z / gl_FragCoord.w) / 2.0;\n" \
-		"float a = -pi * (z * freq) + (leveltime * speed);\n" \
-		"float sdistort = sin(a) * amp;\n" \
-		"float cdistort = cos(a) * amp;\n" \
-		"vec4 texel = texture2D(tex, vec2(gl_TexCoord[0].s - sdistort, gl_TexCoord[0].t - cdistort));\n" \
-		"vec4 base_color = texel * poly_color;\n" \
-		"vec4 final_color = base_color;\n" \
-		GLSL_SOFTWARE_TINT_EQUATION \
-		GLSL_SOFTWARE_FADE_EQUATION \
-		"final_color.a = texel.a * poly_color.a;\n" \
-		"gl_FragColor = final_color;\n" \
-	"}\0"
-
-//
-// Fog block shader
-//
-// Alpha of the planes themselves are still slightly off -- see HWR_FogBlockAlpha
-//
-
-#define GLSL_FOG_FRAGMENT_SHADER \
-	"uniform vec4 tint_color;\n" \
-	"uniform vec4 fade_color;\n" \
-	"uniform float lighting;\n" \
-	"uniform float fade_start;\n" \
-	"uniform float fade_end;\n" \
-	GLSL_DOOM_COLORMAP \
-	GLSL_DOOM_LIGHT_EQUATION \
-	"void main(void) {\n" \
-		"vec4 base_color = gl_Color;\n" \
-		"vec4 final_color = base_color;\n" \
-		GLSL_SOFTWARE_TINT_EQUATION \
-		GLSL_SOFTWARE_FADE_EQUATION \
-		"gl_FragColor = final_color;\n" \
-	"}\0"
-
-//
-// Sky fragment shader
-// Modulates poly_color with gl_Color
-//
-#define GLSL_SKY_FRAGMENT_SHADER \
-	"uniform sampler2D tex;\n" \
-	"uniform vec4 poly_color;\n" \
-	"void main(void) {\n" \
-		"gl_FragColor = texture2D(tex, gl_TexCoord[0].st) * gl_Color * poly_color;\n" \
-	"}\0"
-
-// ================
-//  Shader sources
-// ================
-
-static struct {
-	const char *vertex;
-	const char *fragment;
-} const gl_shadersources[] = {
-	// Default shader
-	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_DEFAULT_FRAGMENT_SHADER},
-
-	// Floor shader
-	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SOFTWARE_FRAGMENT_SHADER},
-
-	// Wall shader
-	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SOFTWARE_FRAGMENT_SHADER},
-
-	// Sprite shader
-	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SOFTWARE_FRAGMENT_SHADER},
-
-	// Model shader
-	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SOFTWARE_FRAGMENT_SHADER},
-
-	// Model shader + diffuse lighting from above
-	{GLSL_MODEL_LIGHTING_VERTEX_SHADER, GLSL_SOFTWARE_MODEL_LIGHTING_FRAGMENT_SHADER},
-
-	// Water shader
-	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_WATER_FRAGMENT_SHADER},
-
-	// Fog shader
-	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_FOG_FRAGMENT_SHADER},
-
-	// Sky shader
-	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SKY_FRAGMENT_SHADER},
-
-	{NULL, NULL},
-};
-
 #endif	// GL_SHADERS
 
 void SetupGLFunc4(void)
 {
+	/* 1.2 funcs */
+	pglTexImage3D = GetGLFunc("glTexImage3D");
+	/* 1.3 funcs */
 	pglActiveTexture = GetGLFunc("glActiveTexture");
 	pglMultiTexCoord2f = GetGLFunc("glMultiTexCoord2f");
 	pglClientActiveTexture = GetGLFunc("glClientActiveTexture");
@@ -912,59 +713,75 @@ void SetupGLFunc4(void)
 	pgluBuild2DMipmaps = GetGLFunc("gluBuild2DMipmaps");
 }
 
-EXPORT boolean HWRAPI(CompileShaders) (void)
+EXPORT boolean HWRAPI(InitShaders) (void)
 {
 #ifdef GL_SHADERS
-	GLint i;
-
 	if (!pglUseProgram)
 		return false;
+	
+	gl_fallback_shader.vertex_shader = Z_StrDup(GLSL_FALLBACK_VERTEX_SHADER);
+	gl_fallback_shader.fragment_shader = Z_StrDup(GLSL_FALLBACK_FRAGMENT_SHADER);
 
-	gl_customshaders[SHADER_DEFAULT].vertex = NULL;
-	gl_customshaders[SHADER_DEFAULT].fragment = NULL;
-
-	for (i = 0; gl_shadersources[i].vertex && gl_shadersources[i].fragment; i++)
+	if (!Shader_CompileProgram(&gl_fallback_shader, -1))
 	{
-		gl_shader_t *shader, *usershader;
-		const GLchar *vert_shader = gl_shadersources[i].vertex;
-		const GLchar *frag_shader = gl_shadersources[i].fragment;
+		GL_MSG_Error("Failed to compile the fallback shader program!\n");
+		return false;
+	}
 
-		if (i >= HWR_MAXSHADERS)
-			break;
+	return true;
+#else
+	return false;
+#endif
+}
 
-		shader = &gl_shaders[i];
-		usershader = &gl_usershaders[i];
+EXPORT void HWRAPI(LoadShader) (int slot, char *code, hwdshaderstage_t stage)
+{
+#ifdef GL_SHADERS
+	gl_shader_t *shader;
 
-		if (shader->program)
-			pglDeleteProgram(shader->program);
-		if (usershader->program)
-			pglDeleteProgram(usershader->program);
+	if (slot < 0 || slot >= HWR_MAXSHADERS)
+		I_Error("LoadShader: Invalid slot %d", slot);
 
-		shader->program = 0;
-		usershader->program = 0;
+	shader = &gl_shaders[slot];
 
-		if (!Shader_CompileProgram(shader, i, vert_shader, frag_shader))
-			shader->program = 0;
+#define LOADSHADER(source) { \
+	if (shader->source) \
+		Z_Free(shader->source); \
+	shader->source = code; \
+	}
 
-		// Compile custom shader
-		if ((i == SHADER_DEFAULT) || !(gl_customshaders[i].vertex || gl_customshaders[i].fragment))
-			continue;
+	if (stage == HWD_SHADERSTAGE_VERTEX)
+		LOADSHADER(vertex_shader)
+	else if (stage == HWD_SHADERSTAGE_FRAGMENT)
+		LOADSHADER(fragment_shader)
+	else
+		I_Error("LoadShader: invalid shader stage");
 
-		// 18032019
-		if (gl_customshaders[i].vertex)
-			vert_shader = gl_customshaders[i].vertex;
-		if (gl_customshaders[i].fragment)
-			frag_shader = gl_customshaders[i].fragment;
+#undef LOADSHADER
+#else
+	(void)slot;
+	(void)code;
+	(void)stage;
+#endif
+}
 
-		if (!Shader_CompileProgram(usershader, i, vert_shader, frag_shader))
-		{
-			GL_MSG_Warning("CompileShaders: Could not compile custom shader program for %s\n", HWR_GetShaderName(i));
-			usershader->program = 0;
-		}
-	}
+EXPORT boolean HWRAPI(CompileShader) (int slot)
+{
+#ifdef GL_SHADERS
+	if (slot < 0 || slot >= HWR_MAXSHADERS)
+		I_Error("CompileShader: Invalid slot %d", slot);
 
-	return true;
+	if (Shader_CompileProgram(&gl_shaders[slot], slot))
+	{
+		return true;
+	}
+	else
+	{
+		gl_shaders[slot].program = 0;
+		return false;
+	}
 #else
+	(void)slot;
 	return false;
 #endif
 }
@@ -991,90 +808,36 @@ EXPORT void HWRAPI(SetShaderInfo) (hwdshaderinfo_t info, INT32 value)
 #endif
 }
 
-//
-// Custom shader loading
-//
-EXPORT void HWRAPI(LoadCustomShader) (int number, char *code, size_t size, boolean isfragment)
+EXPORT void HWRAPI(SetShader) (int slot)
 {
 #ifdef GL_SHADERS
-	shadersource_t *shader;
-
-	if (!pglUseProgram)
-		return;
-
-	if (number < 1 || number > HWR_MAXSHADERS)
-		I_Error("LoadCustomShader: cannot load shader %d (min 1, max %d)", number, HWR_MAXSHADERS);
-	else if (code == NULL)
-		I_Error("LoadCustomShader: empty shader");
-
-	shader = &gl_customshaders[number];
-
-#define COPYSHADER(source) { \
-	if (shader->source) \
-		free(shader->source); \
-	shader->source = malloc(size+1); \
-	strncpy(shader->source, code, size); \
-	shader->source[size] = 0; \
-	}
-
-	if (isfragment)
-		COPYSHADER(fragment)
-	else
-		COPYSHADER(vertex)
-
-#else
-	(void)number;
-	(void)shader;
-	(void)size;
-	(void)fragment;
-#endif
-}
-
-EXPORT void HWRAPI(SetShader) (int type)
-{
-#ifdef GL_SHADERS
-	if (type == SHADER_NONE)
+	if (slot == SHADER_NONE)
 	{
 		UnSetShader();
 		return;
 	}
-
-	if (gl_allowshaders != HWD_SHADEROPTION_OFF)
+	if (gl_allowshaders)
 	{
-		gl_shader_t *shader = gl_shaderstate.current;
+		gl_shader_t *next_shader = &gl_shaders[slot]; // the gl_shader_t we are going to switch to
 
-		// If using model lighting, set the appropriate shader.
-		// However don't override a custom shader.
-		if (type == SHADER_MODEL && model_lighting
-		&& !(gl_shaders[SHADER_MODEL].custom && !gl_shaders[SHADER_MODEL_LIGHTING].custom))
-			type = SHADER_MODEL_LIGHTING;
+		if (!next_shader->program)
+			next_shader = &gl_fallback_shader; // unusable shader, use fallback instead
 
-		if ((shader == NULL) || (GLuint)type != gl_shaderstate.type)
+		// update gl_shaderstate if an actual shader switch is needed
+		if (gl_shaderstate.current != next_shader)
 		{
-			gl_shader_t *baseshader = &gl_shaders[type];
-			gl_shader_t *usershader = &gl_usershaders[type];
-
-			if (usershader->program)
-				shader = (gl_allowshaders == HWD_SHADEROPTION_NOCUSTOM) ? baseshader : usershader;
-			else
-				shader = baseshader;
-
-			gl_shaderstate.current = shader;
-			gl_shaderstate.type = type;
+			gl_shaderstate.current = next_shader;
+			gl_shaderstate.program = next_shader->program;
+			gl_shaderstate.type = slot;
 			gl_shaderstate.changed = true;
 		}
 
-		if (gl_shaderstate.program != shader->program)
-		{
-			gl_shaderstate.program = shader->program;
-			gl_shaderstate.changed = true;
-		}
+		gl_shadersenabled = true;
 
-		gl_shadersenabled = (shader->program != 0);
 		return;
 	}
 #else
-	(void)type;
+	(void)slot;
 #endif
 	gl_shadersenabled = false;
 }
@@ -1082,36 +845,20 @@ EXPORT void HWRAPI(SetShader) (int type)
 EXPORT void HWRAPI(UnSetShader) (void)
 {
 #ifdef GL_SHADERS
-	gl_shaderstate.current = NULL;
-	gl_shaderstate.type = 0;
-	gl_shaderstate.program = 0;
+	if (gl_shadersenabled) // don't repeatedly call glUseProgram if not needed
+	{
+		gl_shaderstate.current = NULL;
+		gl_shaderstate.type = 0;
+		gl_shaderstate.program = 0;
 
-	if (pglUseProgram)
-		pglUseProgram(0);
+		if (pglUseProgram)
+			pglUseProgram(0);
+	}
 #endif
 
 	gl_shadersenabled = false;
 }
 
-EXPORT void HWRAPI(CleanShaders) (void)
-{
-	INT32 i;
-
-	for (i = 1; i < HWR_MAXSHADERS; i++)
-	{
-		shadersource_t *shader = &gl_customshaders[i];
-
-		if (shader->vertex)
-			free(shader->vertex);
-
-		if (shader->fragment)
-			free(shader->fragment);
-
-		shader->vertex = NULL;
-		shader->fragment = NULL;
-	}
-}
-
 // -----------------+
 // SetNoTexture     : Disable texture
 // -----------------+
@@ -1407,55 +1154,38 @@ EXPORT void HWRAPI(ClearMipMapCache) (void)
 }
 
 
-// -----------------+
-// ReadRect         : Read a rectangle region of the truecolor framebuffer
-//                  : store pixels as 16bit 565 RGB
-// Returns          : 16bit 565 RGB pixel array stored in dst_data
-// -----------------+
-EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height,
-                                INT32 dst_stride, UINT16 * dst_data)
+// Writes screen texture tex into dst_data.
+// Pixel format is 24-bit RGB. Row order is top to bottom.
+// Dimensions are screen_width * screen_height.
+EXPORT void HWRAPI(ReadScreenTexture) (int tex, UINT8 *dst_data)
 {
 	INT32 i;
-	// GL_DBG_Printf ("ReadRect()\n");
-	if (dst_stride == width*3)
-	{
-		GLubyte*top = (GLvoid*)dst_data, *bottom = top + dst_stride * (height - 1);
-		GLubyte *row = malloc(dst_stride);
-		if (!row) return;
-		pglPixelStorei(GL_PACK_ALIGNMENT, 1);
-		pglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, dst_data);
-		pglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-		for(i = 0; i < height/2; i++)
-		{
-			memcpy(row, top, dst_stride);
-			memcpy(top, bottom, dst_stride);
-			memcpy(bottom, row, dst_stride);
-			top += dst_stride;
-			bottom -= dst_stride;
-		}
-		free(row);
-	}
-	else
-	{
-		INT32 j;
-		GLubyte *image = malloc(width*height*3*sizeof (*image));
-		if (!image) return;
-		pglPixelStorei(GL_PACK_ALIGNMENT, 1);
-		pglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, image);
-		pglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-		for (i = height-1; i >= 0; i--)
-		{
-			for (j = 0; j < width; j++)
-			{
-				dst_data[(height-1-i)*width+j] =
-				(UINT16)(
-				                 ((image[(i*width+j)*3]>>3)<<11) |
-				                 ((image[(i*width+j)*3+1]>>2)<<5) |
-				                 ((image[(i*width+j)*3+2]>>3)));
-			}
-		}
-		free(image);
-	}
+	int dst_stride = screen_width * 3; // stride between rows of image data
+	GLubyte*top = (GLvoid*)dst_data, *bottom = top + dst_stride * (screen_height - 1);
+	GLubyte *row;
+	row = malloc(dst_stride);
+	if (!row) return;
+	// at the time this function is called, generic2 can be found drawn on the framebuffer
+	// if some other screen texture is needed, draw it to the framebuffer
+	// and draw generic2 back after reading the framebuffer.
+	// this hack is for some reason **much** faster than the simple solution of using glGetTexImage.
+	if (tex != HWD_SCREENTEXTURE_GENERIC2)
+		DrawScreenTexture(tex, NULL, 0);
+	pglPixelStorei(GL_PACK_ALIGNMENT, 1);
+	pglReadPixels(0, 0, screen_width, screen_height, GL_RGB, GL_UNSIGNED_BYTE, dst_data);
+	if (tex != HWD_SCREENTEXTURE_GENERIC2)
+		DrawScreenTexture(HWD_SCREENTEXTURE_GENERIC2, NULL, 0);
+	// Flip image upside down.
+	// In other words, convert OpenGL's "bottom->top" row order into "top->bottom".
+	for(i = 0; i < screen_height/2; i++)
+	{
+		memcpy(row, top, dst_stride);
+		memcpy(top, bottom, dst_stride);
+		memcpy(bottom, row, dst_stride);
+		top += dst_stride;
+		bottom -= dst_stride;
+	}
+	free(row);
 }
 
 
@@ -2071,69 +1801,91 @@ static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAF
 #endif
 }
 
-static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i, const GLchar *vert_shader, const GLchar *frag_shader)
+static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i)
 {
-	GLuint gl_vertShader, gl_fragShader;
+	GLuint gl_vertShader = 0;
+	GLuint gl_fragShader = 0;
 	GLint result;
+	const GLchar *vert_shader = shader->vertex_shader;
+	const GLchar *frag_shader = shader->fragment_shader;
 
-	//
-	// Load and compile vertex shader
-	//
-	gl_vertShader = pglCreateShader(GL_VERTEX_SHADER);
-	if (!gl_vertShader)
+	if (shader->program)
+		pglDeleteProgram(shader->program);
+
+	if (!vert_shader && !frag_shader)
 	{
-		GL_MSG_Error("Shader_CompileProgram: Error creating vertex shader %s\n", HWR_GetShaderName(i));
+		GL_MSG_Error("Shader_CompileProgram: Missing shaders for shader program %s\n", HWR_GetShaderName(i));
 		return false;
 	}
 
-	pglShaderSource(gl_vertShader, 1, &vert_shader, NULL);
-	pglCompileShader(gl_vertShader);
-
-	// check for compile errors
-	pglGetShaderiv(gl_vertShader, GL_COMPILE_STATUS, &result);
-	if (result == GL_FALSE)
+	if (vert_shader)
 	{
-		Shader_CompileError("Error compiling vertex shader", gl_vertShader, i);
-		pglDeleteShader(gl_vertShader);
-		return false;
+		//
+		// Load and compile vertex shader
+		//
+		gl_vertShader = pglCreateShader(GL_VERTEX_SHADER);
+		if (!gl_vertShader)
+		{
+			GL_MSG_Error("Shader_CompileProgram: Error creating vertex shader %s\n", HWR_GetShaderName(i));
+			return false;
+		}
+
+		pglShaderSource(gl_vertShader, 1, &vert_shader, NULL);
+		pglCompileShader(gl_vertShader);
+
+		// check for compile errors
+		pglGetShaderiv(gl_vertShader, GL_COMPILE_STATUS, &result);
+		if (result == GL_FALSE)
+		{
+			Shader_CompileError("Error compiling vertex shader", gl_vertShader, i);
+			pglDeleteShader(gl_vertShader);
+			return false;
+		}
 	}
 
-	//
-	// Load and compile fragment shader
-	//
-	gl_fragShader = pglCreateShader(GL_FRAGMENT_SHADER);
-	if (!gl_fragShader)
+	if (frag_shader)
 	{
-		GL_MSG_Error("Shader_CompileProgram: Error creating fragment shader %s\n", HWR_GetShaderName(i));
-		pglDeleteShader(gl_vertShader);
-		pglDeleteShader(gl_fragShader);
-		return false;
-	}
+		//
+		// Load and compile fragment shader
+		//
+		gl_fragShader = pglCreateShader(GL_FRAGMENT_SHADER);
+		if (!gl_fragShader)
+		{
+			GL_MSG_Error("Shader_CompileProgram: Error creating fragment shader %s\n", HWR_GetShaderName(i));
+			pglDeleteShader(gl_vertShader);
+			pglDeleteShader(gl_fragShader);
+			return false;
+		}
 
-	pglShaderSource(gl_fragShader, 1, &frag_shader, NULL);
-	pglCompileShader(gl_fragShader);
+		pglShaderSource(gl_fragShader, 1, &frag_shader, NULL);
+		pglCompileShader(gl_fragShader);
 
-	// check for compile errors
-	pglGetShaderiv(gl_fragShader, GL_COMPILE_STATUS, &result);
-	if (result == GL_FALSE)
-	{
-		Shader_CompileError("Error compiling fragment shader", gl_fragShader, i);
-		pglDeleteShader(gl_vertShader);
-		pglDeleteShader(gl_fragShader);
-		return false;
+		// check for compile errors
+		pglGetShaderiv(gl_fragShader, GL_COMPILE_STATUS, &result);
+		if (result == GL_FALSE)
+		{
+			Shader_CompileError("Error compiling fragment shader", gl_fragShader, i);
+			pglDeleteShader(gl_vertShader);
+			pglDeleteShader(gl_fragShader);
+			return false;
+		}
 	}
 
 	shader->program = pglCreateProgram();
-	pglAttachShader(shader->program, gl_vertShader);
-	pglAttachShader(shader->program, gl_fragShader);
+	if (vert_shader)
+		pglAttachShader(shader->program, gl_vertShader);
+	if (frag_shader)
+		pglAttachShader(shader->program, gl_fragShader);
 	pglLinkProgram(shader->program);
 
 	// check link status
 	pglGetProgramiv(shader->program, GL_LINK_STATUS, &result);
 
 	// delete the shader objects
-	pglDeleteShader(gl_vertShader);
-	pglDeleteShader(gl_fragShader);
+	if (vert_shader)
+		pglDeleteShader(gl_vertShader);
+	if (frag_shader)
+		pglDeleteShader(gl_fragShader);
 
 	// couldn't link?
 	if (result != GL_TRUE)
@@ -2154,11 +1906,31 @@ static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i, const GLchar
 	shader->uniforms[gluniform_fade_start] = GETUNI("fade_start");
 	shader->uniforms[gluniform_fade_end] = GETUNI("fade_end");
 
-	// misc. (custom shaders)
-	shader->uniforms[gluniform_leveltime] = GETUNI("leveltime");
+	// palette rendering
+	shader->uniforms[gluniform_palette_tex] = GETUNI("palette_tex");
+	shader->uniforms[gluniform_palette_lookup_tex] = GETUNI("palette_lookup_tex");
+	shader->uniforms[gluniform_lighttable_tex] = GETUNI("lighttable_tex");
 
+	// misc.
+	shader->uniforms[gluniform_leveltime] = GETUNI("leveltime");
 #undef GETUNI
 
+	// set permanent uniform values
+#define UNIFORM_1(uniform, a, function) \
+	if (uniform != -1) \
+		function (uniform, a);
+
+	pglUseProgram(shader->program);
+
+	// texture unit numbers for the samplers used for palette rendering
+	UNIFORM_1(shader->uniforms[gluniform_palette_tex], 2, pglUniform1i);
+	UNIFORM_1(shader->uniforms[gluniform_palette_lookup_tex], 1, pglUniform1i);
+	UNIFORM_1(shader->uniforms[gluniform_lighttable_tex], 2, pglUniform1i);
+
+	// restore gl shader state
+	pglUseProgram(gl_shaderstate.program);
+#undef UNIFORM_1
+
 	return true;
 }
 
@@ -2182,6 +1954,7 @@ static void Shader_CompileError(const char *message, GLuint program, INT32 shade
 }
 
 // code that is common between DrawPolygon and DrawIndexedTriangles
+// DrawScreenTexture also can use this function for fancier screen texture drawing
 // the corona thing is there too, i have no idea if that stuff works with DrawIndexedTriangles and batching
 static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FBITFIELD PolyFlags)
 {
@@ -2221,6 +1994,14 @@ static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FBITFIELD
 			fade.green = byte2float[pSurf->FadeColor.s.green];
 			fade.blue  = byte2float[pSurf->FadeColor.s.blue];
 			fade.alpha = byte2float[pSurf->FadeColor.s.alpha];
+
+			if (pSurf->LightTableId && pSurf->LightTableId != lt_downloaded)
+			{
+				pglActiveTexture(GL_TEXTURE2);
+				pglBindTexture(GL_TEXTURE_2D, pSurf->LightTableId);
+				pglActiveTexture(GL_TEXTURE0);
+				lt_downloaded = pSurf->LightTableId;
+			}
 		}
 	}
 
@@ -2413,9 +2194,6 @@ EXPORT void HWRAPI(RenderSkyDome) (gl_sky_t *sky)
 	pglDisableClientState(GL_COLOR_ARRAY);
 }
 
-// ==========================================================================
-//
-// ==========================================================================
 EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 {
 	switch (IdState)
@@ -2425,7 +2203,7 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 			break;
 
 		case HWD_SET_SHADERS:
-			gl_allowshaders = (hwdshaderoption_t)Value;
+			gl_allowshaders = Value;
 			break;
 
 		case HWD_SET_TEXTUREFILTERMODE:
@@ -2784,6 +2562,14 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, float duration, float
 	else if (Surface->PolyColor.s.alpha == 0xFF)
 		flags |= (PF_Occlude | PF_Masked);
 
+	if (Surface->LightTableId && Surface->LightTableId != lt_downloaded)
+	{
+		pglActiveTexture(GL_TEXTURE2);
+		pglBindTexture(GL_TEXTURE_2D, Surface->LightTableId);
+		pglActiveTexture(GL_TEXTURE0);
+		lt_downloaded = Surface->LightTableId;
+	}
+
 	SetBlend(flags);
 	Shader_SetUniforms(Surface, &poly, &tint, &fade);
 
@@ -3071,7 +2857,7 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 	INT32 x, y;
 	float float_x, float_y, float_nextx, float_nexty;
 	float xfix, yfix;
-	INT32 texsize = 2048;
+	INT32 texsize = 512;
 
 	const float blackBack[16] =
 	{
@@ -3081,11 +2867,9 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 		16.0f, -16.0f, 6.0f
 	};
 
-	// Use a power of two texture, dammit
-	if(screen_width <= 1024)
-		texsize = 1024;
-	if(screen_width <= 512)
-		texsize = 512;
+	// look for power of two that is large enough for the screen
+	while (texsize < screen_width || texsize < screen_height)
+		texsize <<= 1;
 
 	// X/Y stretch fix for all resolutions(!)
 	xfix = (float)(texsize)/((float)((screen_width)/(float)(SCREENVERTS-1)));
@@ -3159,84 +2943,16 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 //			a new size
 EXPORT void HWRAPI(FlushScreenTextures) (void)
 {
-	pglDeleteTextures(1, &screentexture);
-	pglDeleteTextures(1, &startScreenWipe);
-	pglDeleteTextures(1, &endScreenWipe);
-	pglDeleteTextures(1, &finalScreenTexture);
-	screentexture = 0;
-	startScreenWipe = 0;
-	endScreenWipe = 0;
-	finalScreenTexture = 0;
-}
-
-// Create Screen to fade from
-EXPORT void HWRAPI(StartScreenWipe) (void)
-{
-	INT32 texsize = 2048;
-	boolean firstTime = (startScreenWipe == 0);
-
-	// Use a power of two texture, dammit
-	if(screen_width <= 512)
-		texsize = 512;
-	else if(screen_width <= 1024)
-		texsize = 1024;
-
-	// Create screen texture
-	if (firstTime)
-		pglGenTextures(1, &startScreenWipe);
-	pglBindTexture(GL_TEXTURE_2D, startScreenWipe);
-
-	if (firstTime)
-	{
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-		Clamp2D(GL_TEXTURE_WRAP_S);
-		Clamp2D(GL_TEXTURE_WRAP_T);
-		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
-	}
-	else
-		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
-
-	tex_downloaded = startScreenWipe;
+	int i;
+	pglDeleteTextures(NUMSCREENTEXTURES, screenTextures);
+	for (i = 0; i < NUMSCREENTEXTURES; i++)
+		screenTextures[i] = 0;
 }
 
-// Create Screen to fade to
-EXPORT void HWRAPI(EndScreenWipe)(void)
-{
-	INT32 texsize = 2048;
-	boolean firstTime = (endScreenWipe == 0);
-
-	// Use a power of two texture, dammit
-	if(screen_width <= 512)
-		texsize = 512;
-	else if(screen_width <= 1024)
-		texsize = 1024;
-
-	// Create screen texture
-	if (firstTime)
-		pglGenTextures(1, &endScreenWipe);
-	pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
-
-	if (firstTime)
-	{
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-		Clamp2D(GL_TEXTURE_WRAP_S);
-		Clamp2D(GL_TEXTURE_WRAP_T);
-		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
-	}
-	else
-		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
-
-	tex_downloaded = endScreenWipe;
-}
-
-
-// Draw the last scene under the intermission
-EXPORT void HWRAPI(DrawIntermissionBG)(void)
+EXPORT void HWRAPI(DrawScreenTexture)(int tex, FSurfaceInfo *surf, FBITFIELD polyflags)
 {
 	float xfix, yfix;
-	INT32 texsize = 2048;
+	INT32 texsize = 512;
 
 	const float screenVerts[12] =
 	{
@@ -3248,10 +2964,9 @@ EXPORT void HWRAPI(DrawIntermissionBG)(void)
 
 	float fix[8];
 
-	if(screen_width <= 1024)
-		texsize = 1024;
-	if(screen_width <= 512)
-		texsize = 512;
+	// look for power of two that is large enough for the screen
+	while (texsize < screen_width || texsize < screen_height)
+		texsize <<= 1;
 
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
@@ -3270,20 +2985,23 @@ EXPORT void HWRAPI(DrawIntermissionBG)(void)
 
 	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
-	pglBindTexture(GL_TEXTURE_2D, screentexture);
-	pglColor4ubv(white);
+	pglBindTexture(GL_TEXTURE_2D, screenTextures[tex]);
+	PreparePolygon(surf, NULL, surf ? polyflags : (PF_NoDepthTest));
+	if (!surf)
+		pglColor4ubv(white);
 
 	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
 	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-	tex_downloaded = screentexture;
+	tex_downloaded = screenTextures[tex];
 }
 
 // Do screen fades!
-EXPORT void HWRAPI(DoScreenWipe)(void)
+EXPORT void HWRAPI(DoScreenWipe)(int wipeStart, int wipeEnd, FSurfaceInfo *surf,
+		FBITFIELD polyFlags)
 {
-	INT32 texsize = 2048;
+	INT32 texsize = 512;
 	float xfix, yfix;
 
 	INT32 fademaskdownloaded = tex_downloaded; // the fade mask that has been set
@@ -3306,11 +3024,15 @@ EXPORT void HWRAPI(DoScreenWipe)(void)
 		1.0f, 1.0f
 	};
 
-	// Use a power of two texture, dammit
-	if(screen_width <= 1024)
-		texsize = 1024;
-	if(screen_width <= 512)
-		texsize = 512;
+	int firstScreen;
+	if (surf && surf->PolyColor.s.alpha == 255)
+		firstScreen = wipeEnd; // it's a tinted fade-in, we need wipeEnd
+	else
+		firstScreen = wipeStart;
+
+	// look for power of two that is large enough for the screen
+	while (texsize < screen_width || texsize < screen_height)
+		texsize <<= 1;
 
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
@@ -3332,91 +3054,71 @@ EXPORT void HWRAPI(DoScreenWipe)(void)
 	SetBlend(PF_Modulated|PF_NoDepthTest);
 	pglEnable(GL_TEXTURE_2D);
 
-	// Draw the original screen
-	pglBindTexture(GL_TEXTURE_2D, startScreenWipe);
+	pglBindTexture(GL_TEXTURE_2D, screenTextures[firstScreen]);
 	pglColor4ubv(white);
 	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
 	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest);
+	if (surf)
+	{
+		// Draw fade mask to screen using surf and polyFlags
+		// Used for colormap/tinted wipes.
+		pglBindTexture(GL_TEXTURE_2D, fademaskdownloaded);
+		pglTexCoordPointer(2, GL_FLOAT, 0, defaultST);
+		pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+		PreparePolygon(surf, NULL, polyFlags);
+		pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+	}
+	else // Blend wipeEnd into screen with the fade mask
+	{
+		SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest);
 
-	// Draw the end screen that fades in
-	pglActiveTexture(GL_TEXTURE0);
-	pglEnable(GL_TEXTURE_2D);
-	pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
-	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+		// Draw the end screen that fades in
+		pglActiveTexture(GL_TEXTURE0);
+		pglEnable(GL_TEXTURE_2D);
+		pglBindTexture(GL_TEXTURE_2D, screenTextures[wipeEnd]);
+		pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
 
-	pglActiveTexture(GL_TEXTURE1);
-	pglEnable(GL_TEXTURE_2D);
-	pglBindTexture(GL_TEXTURE_2D, fademaskdownloaded);
+		pglActiveTexture(GL_TEXTURE1);
+		pglEnable(GL_TEXTURE_2D);
+		pglBindTexture(GL_TEXTURE_2D, fademaskdownloaded);
 
-	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+		pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 
-	// const float defaultST[8]
+		// const float defaultST[8]
 
-	pglClientActiveTexture(GL_TEXTURE0);
-	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
-	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
-	pglClientActiveTexture(GL_TEXTURE1);
-	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
-	pglTexCoordPointer(2, GL_FLOAT, 0, defaultST);
-	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+		pglClientActiveTexture(GL_TEXTURE0);
+		pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+		pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+		pglClientActiveTexture(GL_TEXTURE1);
+		pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
+		pglTexCoordPointer(2, GL_FLOAT, 0, defaultST);
+		pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-	pglDisable(GL_TEXTURE_2D); // disable the texture in the 2nd texture unit
-	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
+		pglDisable(GL_TEXTURE_2D); // disable the texture in the 2nd texture unit
+		pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
 
-	pglActiveTexture(GL_TEXTURE0);
-	pglClientActiveTexture(GL_TEXTURE0);
-	tex_downloaded = endScreenWipe;
-}
-
-// Create a texture from the screen.
-EXPORT void HWRAPI(MakeScreenTexture) (void)
-{
-	INT32 texsize = 2048;
-	boolean firstTime = (screentexture == 0);
-
-	// Use a power of two texture, dammit
-	if(screen_width <= 512)
-		texsize = 512;
-	else if(screen_width <= 1024)
-		texsize = 1024;
-
-	// Create screen texture
-	if (firstTime)
-		pglGenTextures(1, &screentexture);
-	pglBindTexture(GL_TEXTURE_2D, screentexture);
-
-	if (firstTime)
-	{
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-		Clamp2D(GL_TEXTURE_WRAP_S);
-		Clamp2D(GL_TEXTURE_WRAP_T);
-		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
+		pglActiveTexture(GL_TEXTURE0);
+		pglClientActiveTexture(GL_TEXTURE0);
+		tex_downloaded = screenTextures[wipeEnd];
 	}
-	else
-		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
-
-	tex_downloaded = screentexture;
 }
 
-EXPORT void HWRAPI(MakeScreenFinalTexture) (void)
+// Create a texture from the screen.
+EXPORT void HWRAPI(MakeScreenTexture) (int tex)
 {
-	INT32 texsize = 2048;
-	boolean firstTime = (finalScreenTexture == 0);
+	INT32 texsize = 512;
+	boolean firstTime = (screenTextures[tex] == 0);
 
-	// Use a power of two texture, dammit
-	if(screen_width <= 512)
-		texsize = 512;
-	else if(screen_width <= 1024)
-		texsize = 1024;
+	// look for power of two that is large enough for the screen
+	while (texsize < screen_width || texsize < screen_height)
+		texsize <<= 1;
 
 	// Create screen texture
 	if (firstTime)
-		pglGenTextures(1, &finalScreenTexture);
-	pglBindTexture(GL_TEXTURE_2D, finalScreenTexture);
+		pglGenTextures(1, &screenTextures[tex]);
+	pglBindTexture(GL_TEXTURE_2D, screenTextures[tex]);
 
 	if (firstTime)
 	{
@@ -3429,24 +3131,23 @@ EXPORT void HWRAPI(MakeScreenFinalTexture) (void)
 	else
 		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
 
-	tex_downloaded = finalScreenTexture;
+	tex_downloaded = screenTextures[tex];
 }
 
-EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
+EXPORT void HWRAPI(DrawScreenFinalTexture)(int tex, int width, int height)
 {
 	float xfix, yfix;
 	float origaspect, newaspect;
 	float xoff = 1, yoff = 1; // xoffset and yoffset for the polygon to have black bars around the screen
 	FRGBAFloat clearColour;
-	INT32 texsize = 2048;
+	INT32 texsize = 512;
 
 	float off[12];
 	float fix[8];
 
-	if(screen_width <= 1024)
-		texsize = 1024;
-	if(screen_width <= 512)
-		texsize = 512;
+	// look for power of two that is large enough for the screen
+	while (texsize < screen_width || texsize < screen_height)
+		texsize <<= 1;
 
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
@@ -3493,7 +3194,8 @@ EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 	clearColour.red = clearColour.green = clearColour.blue = 0;
 	clearColour.alpha = 1;
 	ClearBuffer(true, false, &clearColour);
-	pglBindTexture(GL_TEXTURE_2D, finalScreenTexture);
+	SetBlend(PF_NoDepthTest);
+	pglBindTexture(GL_TEXTURE_2D, screenTextures[tex]);
 
 	pglColor4ubv(white);
 
@@ -3501,7 +3203,92 @@ EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 	pglVertexPointer(3, GL_FLOAT, 0, off);
 
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
-	tex_downloaded = finalScreenTexture;
+	tex_downloaded = screenTextures[tex];
+}
+
+EXPORT void HWRAPI(SetPaletteLookup)(UINT8 *lut)
+{
+	GLenum internalFormat;
+	if (gl_version[0] == '1' || gl_version[0] == '2')
+	{
+		// if the OpenGL version is below 3.0, then the GL_R8 format may not be available.
+		// so use GL_LUMINANCE8 instead to get a single component 8-bit format
+		// (it is possible to have access to shaders even in some OpenGL 1.x systems,
+		// so palette rendering can still possibly be achieved there)
+		internalFormat = GL_LUMINANCE8;
+	}
+	else
+	{
+		internalFormat = GL_R8;
+	}
+	if (!paletteLookupTex)
+		pglGenTextures(1, &paletteLookupTex);
+	pglActiveTexture(GL_TEXTURE1);
+	pglBindTexture(GL_TEXTURE_3D, paletteLookupTex);
+	pglTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	pglTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	pglTexImage3D(GL_TEXTURE_3D, 0, internalFormat, HWR_PALETTE_LUT_SIZE, HWR_PALETTE_LUT_SIZE, HWR_PALETTE_LUT_SIZE,
+		0, GL_RED, GL_UNSIGNED_BYTE, lut);
+	pglActiveTexture(GL_TEXTURE0);
+}
+
+EXPORT UINT32 HWRAPI(CreateLightTable)(RGBA_t *hw_lighttable)
+{
+	LTListItem *item = malloc(sizeof(LTListItem));
+	if (!LightTablesTail)
+	{
+		LightTablesHead = LightTablesTail = item;
+	}
+	else
+	{
+		LightTablesTail->next = item;
+		LightTablesTail = item;
+	}
+	item->next = NULL;
+	pglGenTextures(1, &item->id);
+	pglBindTexture(GL_TEXTURE_2D, item->id);
+	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+	pglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, hw_lighttable);
+
+	// restore previously bound texture
+	pglBindTexture(GL_TEXTURE_2D, tex_downloaded);
+
+	return item->id;
+}
+
+// Delete light table textures, ids given before become invalid and must not be used.
+EXPORT void HWRAPI(ClearLightTables)(void)
+{
+	while (LightTablesHead)
+	{
+		LTListItem *item = LightTablesHead;
+		pglDeleteTextures(1, (GLuint *)&item->id);
+		LightTablesHead = item->next;
+		free(item);
+	}
+
+	LightTablesTail = NULL;
+
+	// we no longer have a bound light table (if we had one), we just deleted it!
+	lt_downloaded = 0;
+}
+
+// This palette is used for the palette rendering postprocessing step.
+EXPORT void HWRAPI(SetScreenPalette)(RGBA_t *palette)
+{
+	if (memcmp(screenPalette, palette, sizeof(screenPalette)))
+	{
+		memcpy(screenPalette, palette, sizeof(screenPalette));
+		if (!screenPaletteTex)
+			pglGenTextures(1, &screenPaletteTex);
+		pglActiveTexture(GL_TEXTURE2);
+		pglBindTexture(GL_TEXTURE_1D, screenPaletteTex);
+		pglTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, palette);
+		pglActiveTexture(GL_TEXTURE0);
+	}
 }
 
 #endif //HWRENDER
diff --git a/src/hardware/r_opengl/r_opengl.h b/src/hardware/r_opengl/r_opengl.h
index f44e0818bbeff0cae3160cd788d67b11587e7961..f7e33c46aa36b9c416a80dc9158c5c7321fb78d0 100644
--- a/src/hardware/r_opengl/r_opengl.h
+++ b/src/hardware/r_opengl/r_opengl.h
@@ -46,6 +46,7 @@
 #define  _CREATE_DLL_  // necessary for Unix AND Windows
 #include "../../doomdef.h"
 #include "../hw_drv.h"
+#include "../../z_zone.h"
 
 // ==========================================================================
 //                                                                DEFINITIONS
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index bb2b837fcb299e8b0003eaa24764302d2e5bf7ee..4b199f6b3a669b51acdf7f7a45197abd465e78cd 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -620,7 +620,7 @@ static void Command_CSay_f(void)
 	DoSayCommand(0, 1, HU_CSAY);
 }
 
-static tic_t spam_tokens[MAXPLAYERS];
+static tic_t spam_tokens[MAXPLAYERS] = { 1 }; // fill the buffer with 1 so the motd can be sent.
 static tic_t spam_tics[MAXPLAYERS];
 
 /** Receives a message, processing an ::XD_SAY command.
diff --git a/src/m_anigif.c b/src/m_anigif.c
index 5bc7717e0f44a4293e52a8907c57b0cfda6eade5..6e6ec68aa49760ddc5ab9ffa025356e0a13ffee3 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -21,6 +21,7 @@
 #include "i_system.h" // I_GetPreciseTime
 #include "m_misc.h"
 #include "st_stuff.h" // st_palette
+#include "doomstat.h" // singletics
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -604,7 +605,7 @@ static void GIF_framewrite(void)
 		UINT16 delay = 0;
 		INT32 startline;
 
-		if (gif_dynamicdelay ==(UINT8) 2)
+		if (gif_dynamicdelay ==(UINT8) 2 && !singletics)
 		{
 			// golden's attempt at creating a "dynamic delay"
 			UINT16 mingifdelay = 10; // minimum gif delay in milliseconds (keep at 10 because gifs can't get more precise).
@@ -617,7 +618,7 @@ static void GIF_framewrite(void)
 				gif_delayus -= frames*(mingifdelay*1000); // remove frames by the amount of milliseconds they take. don't reset to 0, the microseconds help consistency.
 			}
 		}
-		else if (gif_dynamicdelay ==(UINT8) 1)
+		else if (gif_dynamicdelay ==(UINT8) 1 && !singletics)
 		{
 			float delayf = ceil(100.0f/NEWTICRATE);
 
diff --git a/src/m_cheat.c b/src/m_cheat.c
index e61db2c2ee3d49e58f570567ee576ba94d3959eb..36438a47575ab2028223245096424ad04b398119 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -1098,15 +1098,23 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c
 		fixed_t fheight = P_GetSectorFloorZAt(sec, mt->x << FRACBITS, mt->y << FRACBITS);
 		mt->z = (UINT16)((player->mo->z - fheight)>>FRACBITS);
 	}
+
 	mt->angle = (INT16)(FixedInt(AngleFixed(player->mo->angle)));
 
-	mt->options = (mt->z << ZSHIFT) | (UINT16)cv_opflags.value;
+	mt->options = (UINT16)cv_opflags.value;
 	mt->scale = player->mo->scale;
 	mt->spritexscale = player->mo->spritexscale;
 	mt->spriteyscale = player->mo->spriteyscale;
 	memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 	memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 	mt->pitch = mt->roll = 0;
+
+	// Ignore offsets
+	if (mt->type == MT_EMBLEM)
+		mt->args[1] = 1;
+	else
+		mt->args[0] = 1;
+
 	return mt;
 }
 
diff --git a/src/m_menu.c b/src/m_menu.c
index edbbdf2c1587a785479f8333c5ff0a8cbb997806..3c6ef0fe3a145837ef1401603b99d8e321ee1b97 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1406,18 +1406,19 @@ static menuitem_t OP_OpenGLOptionsMenu[] =
 
 	{IT_HEADER, NULL, "General", NULL, 51},
 	{IT_STRING|IT_CVAR,         NULL, "Shaders",             &cv_glshaders,            63},
-	{IT_STRING|IT_CVAR,         NULL, "Lack of perspective", &cv_glshearing,           73},
-	{IT_STRING|IT_CVAR,         NULL, "Field of view",       &cv_fov,                  83},
-
-	{IT_HEADER, NULL, "Miscellaneous", NULL, 102},
-	{IT_STRING|IT_CVAR,         NULL, "Bit depth",           &cv_scr_depth,           114},
-	{IT_STRING|IT_CVAR,         NULL, "Texture filter",      &cv_glfiltermode,        124},
-	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",         &cv_glanisotropicmode,   134},
+	{IT_STRING|IT_CVAR,         NULL, "Palette rendering",   &cv_glpaletterendering,   73},
+	{IT_STRING|IT_CVAR,         NULL, "Lack of perspective", &cv_glshearing,           83},
+	{IT_STRING|IT_CVAR,         NULL, "Field of view",       &cv_fov,                  93},
+
+	{IT_HEADER, NULL, "Miscellaneous", NULL, 112},
+	{IT_STRING|IT_CVAR,         NULL, "Bit depth",           &cv_scr_depth,           124},
+	{IT_STRING|IT_CVAR,         NULL, "Texture filter",      &cv_glfiltermode,        134},
+	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",         &cv_glanisotropicmode,   144},
 #ifdef ALAM_LIGHTING
-	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",         &OP_OpenGLLightingDef,   144},
+	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",         &OP_OpenGLLightingDef,   154},
 #endif
 #if defined (_WINDOWS) && (!(defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL)))
-	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",          &cv_fullscreen,          154},
+	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",          &cv_fullscreen,          164},
 #endif
 };
 
diff --git a/src/m_misc.c b/src/m_misc.c
index 1b6a90c50acd6230cb9d8c56f99df7178926c777..55c5485a149a2b2c43e48fab973c5ad898091036 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -1254,7 +1254,7 @@ void M_SaveFrame(void)
 	// paranoia: should be unnecessary without singletics
 	static tic_t oldtic = 0;
 
-	if (oldtic == I_GetTime())
+	if (oldtic == I_GetTime() && !singletics)
 		return;
 	else
 		oldtic = I_GetTime();
@@ -1978,9 +1978,9 @@ void M_UnGetToken(void)
 
 static tokenizer_t *globalTokenizer = NULL;
 
-void M_TokenizerOpen(const char *inputString)
+void M_TokenizerOpen(const char *inputString, size_t len)
 {
-	globalTokenizer = Tokenizer_Open(inputString, 2);
+	globalTokenizer = Tokenizer_Open(inputString, len, 2);
 }
 
 void M_TokenizerClose(void)
diff --git a/src/m_tokenizer.c b/src/m_tokenizer.c
index f36f7f6f323133c51c4975992beea9919c27e4bd..8bb094caea7ec94db70ccdffe57fd4191f5a9773 100644
--- a/src/m_tokenizer.c
+++ b/src/m_tokenizer.c
@@ -12,11 +12,18 @@
 #include "m_tokenizer.h"
 #include "z_zone.h"
 
-tokenizer_t *Tokenizer_Open(const char *inputString, unsigned numTokens)
+tokenizer_t *Tokenizer_Open(const char *inputString, size_t len, unsigned numTokens)
 {
 	tokenizer_t *tokenizer = Z_Malloc(sizeof(tokenizer_t), PU_STATIC, NULL);
+	const size_t lenpan = 2;
 
-	tokenizer->input = inputString;
+	tokenizer->zdup = malloc(len+lenpan);
+	for (size_t i = 0; i < lenpan; i++)
+	{
+		tokenizer->zdup[len+i] = 0x00;
+	}
+
+	tokenizer->input = M_Memcpy(tokenizer->zdup, inputString, len);
 	tokenizer->startPos = 0;
 	tokenizer->endPos = 0;
 	tokenizer->inputLength = 0;
@@ -51,6 +58,7 @@ void Tokenizer_Close(tokenizer_t *tokenizer)
 		Z_Free(tokenizer->token[i]);
 	Z_Free(tokenizer->capacity);
 	Z_Free(tokenizer->token);
+	free(tokenizer->zdup);
 	Z_Free(tokenizer);
 }
 
diff --git a/src/m_tokenizer.h b/src/m_tokenizer.h
index f5111730194915c69fea455a53ba1da3cf66068b..7ee856b3c9331d8dbb0b97711bd5e8bcae7ec2e7 100644
--- a/src/m_tokenizer.h
+++ b/src/m_tokenizer.h
@@ -16,6 +16,7 @@
 
 typedef struct Tokenizer
 {
+	char *zdup;
 	const char *input;
 	unsigned numTokens;
 	UINT32 *capacity;
@@ -29,7 +30,7 @@ typedef struct Tokenizer
 	const char *(*get)(struct Tokenizer*, UINT32);
 } tokenizer_t;
 
-tokenizer_t *Tokenizer_Open(const char *inputString, unsigned numTokens);
+tokenizer_t *Tokenizer_Open(const char *inputString, size_t len, unsigned numTokens);
 void Tokenizer_Close(tokenizer_t *tokenizer);
 
 const char *Tokenizer_Read(tokenizer_t *tokenizer, UINT32 i);
diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c
index bb098e02973393ebccfcc923923046c7356687c3..6bdcaa650f3b703082aa9fd4255b7d1aed7dc15e 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -394,7 +394,7 @@ consvar_t cv_ps_descriptor = CVAR_INIT ("ps_descriptor", "Average", 0, ps_descri
 consvar_t cv_freedemocamera = CVAR_INIT("freedemocamera", "Off", CV_SAVE, CV_OnOff, NULL);
 
 // NOTE: this should be in hw_main.c, but we can't put it there as it breaks dedicated build
-consvar_t cv_glallowshaders = CVAR_INIT ("gr_allowclientshaders", "On", CV_NETVAR, CV_OnOff, NULL);
+consvar_t cv_glallowshaders = CVAR_INIT ("gr_allowcustomshaders", "On", CV_NETVAR, CV_OnOff, NULL);
 
 char timedemo_name[256];
 boolean timedemo_csv;
@@ -893,6 +893,9 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_renderhitboxinterpolation);
 	CV_RegisterVar(&cv_renderhitboxgldepth);
 	CV_RegisterVar(&cv_renderhitbox);
+	CV_RegisterVar(&cv_renderwalls);
+	CV_RegisterVar(&cv_renderfloors);
+	CV_RegisterVar(&cv_renderthings);
 	CV_RegisterVar(&cv_renderer);
 	CV_RegisterVar(&cv_scr_depth);
 	CV_RegisterVar(&cv_scr_width);
@@ -4663,15 +4666,28 @@ static void Command_Cheats_f(void)
 			CV_ResetCheatNetVars();
 		return;
 	}
+	else if (COM_CheckParm("on"))
+	{
+		if (!(server || (IsPlayerAdmin(consoleplayer))))
+			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
+		else
+			G_SetUsedCheats(false);
+		return;
+	}
+	
+	if (usedCheats)
+		CONS_Printf(M_GetText("Cheats are enabled, the game cannot be saved.\n"));
+	else
+		CONS_Printf(M_GetText("Cheats are disabled, the game can be saved.\n"));
 
 	if (CV_CheatsEnabled())
 	{
-		CONS_Printf(M_GetText("At least one CHEAT-marked variable has been changed -- Cheats are enabled.\n"));
+		CONS_Printf(M_GetText("At least one CHEAT-marked variable has been changed.\n"));
 		if (server || (IsPlayerAdmin(consoleplayer)))
 			CONS_Printf(M_GetText("Type CHEATS OFF to reset all cheat variables to default.\n"));
 	}
 	else
-		CONS_Printf(M_GetText("No CHEAT-marked variables are changed -- Cheats are disabled.\n"));
+		CONS_Printf(M_GetText("No CHEAT-marked variables are changed.\n"));
 }
 
 #ifdef _DEBUG
diff --git a/src/netcode/d_netfil.c b/src/netcode/d_netfil.c
index 03ad8303e6571a477b9f107154d9ed3102ac58a9..a8a10d475d4afcc9c965573c1deaf59969c00b0f 100644
--- a/src/netcode/d_netfil.c
+++ b/src/netcode/d_netfil.c
@@ -1640,7 +1640,7 @@ boolean CURLPrepareFile(const char* url, int dfilenum)
 #endif
 
 		// Set user agent, as some servers won't accept invalid user agents.
-		curl_easy_setopt(http_handle, CURLOPT_USERAGENT, va("Sonic Robo Blast 2/v%d.%d", VERSION, SUBVERSION));
+		curl_easy_setopt(http_handle, CURLOPT_USERAGENT, va("Sonic Robo Blast 2/%s", VERSIONSTRING));
 
 		// Authenticate if the user so wishes
 		login = CURLGetLogin(url, NULL);
diff --git a/src/netcode/i_tcp.c b/src/netcode/i_tcp.c
index 6d9a2725a3c65fb93e3f1f3b89e47dcf0464ad05..0148c485ab1fd5fd29cd4ca0bc6effca93ff69f1 100644
--- a/src/netcode/i_tcp.c
+++ b/src/netcode/i_tcp.c
@@ -265,7 +265,7 @@ static const char* inet_ntopA(short af, const void *cp, char *buf, socklen_t len
 #ifdef HAVE_MINIUPNPC // based on old XChat patch
 static void I_ShutdownUPnP(void);
 static void I_InitUPnP(void);
-I_mutex upnp_mutex;
+static I_mutex upnp_mutex;
 static struct UPNPUrls urls;
 static struct IGDdatas data;
 static char lanaddr[64];
@@ -300,7 +300,11 @@ init_upnpc_once(struct upnpdata *upnpuserdata)
 	int upnp_error = -2;
 	int scope_id = 0;
 	int status_code = 0;
-	CONS_Printf(M_GetText("Looking for UPnP Internet Gateway Device\n"));
+
+	memset(&urls, 0, sizeof(struct UPNPUrls));
+	memset(&data, 0, sizeof(struct IGDdatas));
+
+	I_OutputMsg(M_GetText("Looking for UPnP Internet Gateway Device\n"));
 	devlist = upnpDiscoverDevices(deviceTypes, 500, NULL, NULL, 0, false, 2, &upnp_error, 0);
 	if (devlist)
 	{
@@ -316,39 +320,37 @@ init_upnpc_once(struct upnpdata *upnpuserdata)
 		if (!dev)
 			dev = devlist; /* defaulting to first device */
 
-		CONS_Printf(M_GetText("Found UPnP device:\n desc: %s\n st: %s\n"),
+		I_OutputMsg(M_GetText("Found UPnP device:\n desc: %s\n st: %s\n"),
 		           dev->descURL, dev->st);
 
 		UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
-		CONS_Printf(M_GetText("Local LAN IP address: %s\n"), lanaddr);
+		I_OutputMsg(M_GetText("Local LAN IP address: %s\n"), lanaddr);
 		descXML = miniwget(dev->descURL, &descXMLsize, scope_id, &status_code);
 		if (descXML)
 		{
 			parserootdesc(descXML, descXMLsize, &data);
 			free(descXML);
 			descXML = NULL;
-			memset(&urls, 0, sizeof(struct UPNPUrls));
-			memset(&data, 0, sizeof(struct IGDdatas));
 			GetUPNPUrls(&urls, &data, dev->descURL, status_code);
 			I_AddExitFunc(I_ShutdownUPnP);
 		}
 		freeUPNPDevlist(devlist);
-		I_unlock_mutex(upnp_mutex);
 	}
 	else if (upnp_error == UPNPDISCOVER_SOCKET_ERROR)
 	{
-		CONS_Printf(M_GetText("No UPnP devices discovered\n"));
+		I_OutputMsg(M_GetText("No UPnP devices discovered\n"));
 	}
+	I_unlock_mutex(upnp_mutex);
 	upnpuserdata->upnpc_started =1;
 }
 
 static inline void I_UPnP_add(const char * addr, const char *port, const char * servicetype)
 {
+	if (!urls.controlURL || urls.controlURL[0] == '\0')
+		return;
 	I_lock_mutex(&upnp_mutex);
 	if (addr == NULL)
 		addr = lanaddr;
-	if (!urls.controlURL || urls.controlURL[0] == '\0')
-		return;
 	UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
 	                    port, port, addr, "SRB2", servicetype, NULL, NULL);
 	I_unlock_mutex(upnp_mutex);
@@ -356,9 +358,9 @@ static inline void I_UPnP_add(const char * addr, const char *port, const char *
 
 static inline void I_UPnP_rem(const char *port, const char * servicetype)
 {
-	I_lock_mutex(&upnp_mutex);
 	if (!urls.controlURL || urls.controlURL[0] == '\0')
 		return;
+	I_lock_mutex(&upnp_mutex);
 	UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype,
 	                       port, servicetype, NULL);
 	I_unlock_mutex(upnp_mutex);
@@ -1132,10 +1134,10 @@ boolean I_InitTcpDriver(void)
 	{
 		I_AddExitFunc(I_ShutdownTcpDriver);
 #ifdef HAVE_MINIUPNPC
-		if (M_CheckParm("-noUPnP"))
-			UPNP_support = false;
-		else
+		if (M_CheckParm("-useUPnP"))
 			I_InitUPnP();
+		else
+			UPNP_support = false;
 #endif
 	}
 	return init_tcp_driver;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 43454517c6a900df797ccfc31221e846748f8f7b..0fa392b28fb72249a663e2251e91ae2c3cbb3cee 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -541,7 +541,7 @@ boolean P_Move(mobj_t *actor, fixed_t speed)
 
 	if (!P_TryMove(actor, tryx, tryy, false))
 	{
-		if (actor->flags & MF_FLOAT && floatok)
+		if (!P_MobjWasRemoved(actor) && actor->flags & MF_FLOAT && floatok)
 		{
 			// must adjust height
 			if (actor->z < tmfloorz)
@@ -585,6 +585,7 @@ void P_NewChaseDir(mobj_t *actor)
 	dirtype_t d[3];
 	dirtype_t tdir = DI_NODIR, olddir, turnaround;
 
+	I_Assert(!P_MobjWasRemoved(actor));
 	I_Assert(actor->target != NULL);
 	I_Assert(!P_MobjWasRemoved(actor->target));
 
@@ -623,7 +624,7 @@ void P_NewChaseDir(mobj_t *actor)
 		dirtype_t newdir = diags[((deltay < 0)<<1) + (deltax > 0)];
 
 		actor->movedir = newdir;
-		if ((newdir != turnaround) && P_TryWalk(actor))
+		if ((newdir != turnaround) && (P_TryWalk(actor) || P_MobjWasRemoved(actor)))
 			return;
 	}
 
@@ -644,7 +645,7 @@ void P_NewChaseDir(mobj_t *actor)
 	{
 		actor->movedir = d[1];
 
-		if (P_TryWalk(actor))
+		if (P_TryWalk(actor) || P_MobjWasRemoved(actor))
 			return; // either moved forward or attacked
 	}
 
@@ -652,7 +653,7 @@ void P_NewChaseDir(mobj_t *actor)
 	{
 		actor->movedir = d[2];
 
-		if (P_TryWalk(actor))
+		if (P_TryWalk(actor) || P_MobjWasRemoved(actor))
 			return;
 	}
 
@@ -661,7 +662,7 @@ void P_NewChaseDir(mobj_t *actor)
 	{
 		actor->movedir =olddir;
 
-		if (P_TryWalk(actor))
+		if (P_TryWalk(actor) || P_MobjWasRemoved(actor))
 			return;
 	}
 
@@ -674,7 +675,7 @@ void P_NewChaseDir(mobj_t *actor)
 			{
 				actor->movedir = tdir;
 
-				if (P_TryWalk(actor))
+				if (P_TryWalk(actor) || P_MobjWasRemoved(actor))
 					return;
 			}
 		}
@@ -687,7 +688,7 @@ void P_NewChaseDir(mobj_t *actor)
 			{
 				actor->movedir = tdir;
 
-				if (P_TryWalk(actor))
+				if (P_TryWalk(actor) || P_MobjWasRemoved(actor))
 					return;
 			}
 		}
@@ -697,7 +698,7 @@ void P_NewChaseDir(mobj_t *actor)
 	{
 		actor->movedir = turnaround;
 
-		if (P_TryWalk(actor))
+		if (P_TryWalk(actor) || P_MobjWasRemoved(actor))
 			return;
 	}
 
@@ -1100,7 +1101,7 @@ nomissile:
 		return; // got a new target
 
 	// chase towards player
-	if (--actor->movecount < 0 || !P_Move(actor, actor->info->speed))
+	if (--actor->movecount < 0 || (!P_Move(actor, actor->info->speed) && !P_MobjWasRemoved(actor)))
 		P_NewChaseDir(actor);
 }
 
@@ -1188,7 +1189,7 @@ nomissile:
 		return; // got a new target
 
 	// chase towards player
-	if (--actor->movecount < 0 || !P_Move(actor, actor->info->speed))
+	if (--actor->movecount < 0 || (!P_Move(actor, actor->info->speed) && !P_MobjWasRemoved(actor)))
 		P_NewChaseDir(actor);
 }
 
@@ -1266,7 +1267,8 @@ void A_FaceStabRev(mobj_t *actor)
 		else
 		{
 			P_TryMove(actor, actor->x - P_ReturnThrustX(actor, actor->angle, 2<<FRACBITS), actor->y - P_ReturnThrustY(actor, actor->angle, 2<<FRACBITS), false);
-			P_FaceStabFlume(actor);
+			if (!P_MobjWasRemoved(actor))
+				P_FaceStabFlume(actor);
 		}
 	}
 }
@@ -1332,8 +1334,9 @@ void A_FaceStabHurl(mobj_t *actor)
 
 				while (step > 0)
 				{
-					if (!hwork->hnext)
+					if (P_MobjWasRemoved(hwork->hnext))
 						P_SetTarget(&hwork->hnext, P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_FACESTABBERSPEAR));
+
 					if (!P_MobjWasRemoved(hwork->hnext))
 					{
 						hwork = hwork->hnext;
@@ -1341,6 +1344,20 @@ void A_FaceStabHurl(mobj_t *actor)
 						P_SetScale(hwork, FixedSqrt(step*basesize), true);
 						hwork->fuse = 2;
 						P_MoveOrigin(hwork, actor->x + xo*(15-step), actor->y + yo*(15-step), actor->z + (actor->height - hwork->height)/2 + (P_MobjFlip(actor)*(8<<FRACBITS)));
+						if (P_MobjWasRemoved(hwork))
+						{
+							// if one of the sections are removed, erase the entire damn thing.
+							mobj_t *hnext = actor->hnext;
+							hwork = actor;
+							do
+							{
+								hnext = hwork->hnext;
+								P_RemoveMobj(hwork);
+								hwork = hnext;
+							}
+							while (!P_MobjWasRemoved(hwork));
+							return;
+						}
 					}
 					step -= NUMGRADS;
 				}
@@ -1357,11 +1374,14 @@ void A_FaceStabHurl(mobj_t *actor)
 #undef NUMGRADS
 #undef NUMSTEPS
 			}
+			if (P_MobjWasRemoved(actor))
+				return;
 		}
 	}
 
 	P_SetMobjState(actor, locvar2);
-	actor->reactiontime = actor->info->reactiontime;
+	if (!P_MobjWasRemoved(actor))
+		actor->reactiontime = actor->info->reactiontime;
 }
 
 // Function: A_FaceStabMiss
@@ -1391,6 +1411,8 @@ void A_FaceStabMiss(mobj_t *actor)
 		actor->y + P_ReturnThrustY(actor, actor->angle, actor->extravalue2<<FRACBITS),
 		false))
 	{
+		if (P_MobjWasRemoved(actor))
+			return;
 		actor->extravalue2 = 0;
 		P_SetMobjState(actor, locvar2);
 	}
@@ -1423,6 +1445,8 @@ void A_StatueBurst(mobj_t *actor)
 	P_SetTarget(&new->target, actor->target);
 	if (locvar2)
 		P_SetMobjState(new, (statenum_t)locvar2);
+	if (P_MobjWasRemoved(new))
+		return;
 	S_StartSound(new, new->info->attacksound);
 	S_StopSound(actor);
 	S_StartSound(actor, sfx_s3k96);
@@ -1518,7 +1542,7 @@ void A_JetJawChomp(mobj_t *actor)
 	}
 
 	// chase towards player
-	if (--actor->movecount < 0 || !P_Move(actor, actor->info->speed))
+	if (--actor->movecount < 0 || (!P_Move(actor, actor->info->speed) && !P_MobjWasRemoved(actor)))
 		P_NewChaseDir(actor);
 }
 
@@ -1939,14 +1963,15 @@ void A_SharpChase(mobj_t *actor)
 		}
 
 		// chase towards player
-		if (--actor->movecount < 0 || !P_Move(actor, actor->info->speed))
+		if (--actor->movecount < 0 || (!P_Move(actor, actor->info->speed) && !P_MobjWasRemoved(actor)))
 			P_NewChaseDir(actor);
 	}
 	else
 	{
 		actor->threshold = actor->info->painchance;
 		P_SetMobjState(actor, actor->info->missilestate);
-		S_StartSound(actor, actor->info->attacksound);
+		if (!P_MobjWasRemoved(actor))
+			S_StartSound(actor, actor->info->attacksound);
 	}
 }
 
@@ -2032,6 +2057,8 @@ void A_CrushstaceanWalk(mobj_t *actor)
 		false)
 	|| (actor->reactiontime-- <= 0))
 	{
+		if (P_MobjWasRemoved(actor))
+			return;
 		actor->flags2 ^= MF2_AMBUSH;
 		P_SetTarget(&actor->target, NULL);
 		P_SetMobjState(actor, locvar2);
@@ -2213,6 +2240,8 @@ void A_CrushclawLaunch(mobj_t *actor)
 		true)
 		&& !locvar1)
 	{
+		if (P_MobjWasRemoved(actor))
+			return;
 		actor->extravalue1 = 0;
 		actor->extravalue2 = FixedHypot(actor->x - actor->target->x, actor->y - actor->target->y)>>FRACBITS;
 		P_SetMobjState(actor, locvar2);
@@ -2221,6 +2250,8 @@ void A_CrushclawLaunch(mobj_t *actor)
 	}
 	else
 	{
+		if (P_MobjWasRemoved(actor))
+			return;
 		actor->z = actor->target->z;
 		if ((!locvar1 && (actor->extravalue2 > 256)) || (locvar1 && (actor->extravalue2 < 16)))
 		{
@@ -2646,7 +2677,7 @@ nomissile:
 		return; // got a new target
 
 	// chase towards player
-	if (--actor->movecount < 0 || !P_Move(actor, actor->info->speed))
+	if (--actor->movecount < 0 || (!P_Move(actor, actor->info->speed) && !P_MobjWasRemoved(actor)))
 		P_NewChaseDir(actor);
 }
 
@@ -5771,7 +5802,11 @@ void A_MinusDigging(mobj_t *actor)
 	if (P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y) < actor->radius*2)
 	{
 		P_SetMobjState(actor, actor->info->meleestate);
+		if (P_MobjWasRemoved(actor))
+			return;
 		P_TryMove(actor, actor->target->x, actor->target->y, false);
+		if (P_MobjWasRemoved(actor))
+			return;
 		S_StartSound(actor, actor->info->attacksound);
 
 		// Spawn growing dirt pile.
@@ -5779,7 +5814,9 @@ void A_MinusDigging(mobj_t *actor)
 		if (P_MobjWasRemoved(par))
 			return;
 		P_SetMobjState(par, actor->info->raisestate);
-		P_SetScale(par, 2*actor->scale, false);
+		if (P_MobjWasRemoved(par))
+			return;
+		P_SetScale(par, actor->scale*2);
 		par->old_scale = par->scale;
 		if (actor->eflags & MFE_VERTICALFLIP)
 			par->eflags |= MFE_VERTICALFLIP;
@@ -5793,6 +5830,8 @@ void A_MinusDigging(mobj_t *actor)
 	// Move
 	var1 = 3;
 	A_Chase(actor);
+	if (P_MobjWasRemoved(actor))
+		return;
 
 	// Carry over shit, maybe
 	if (P_MobjWasRemoved(actor->tracer) || !actor->tracer->health)
@@ -5816,7 +5855,7 @@ void A_MinusDigging(mobj_t *actor)
 	{
 		if (P_TryMove(actor->tracer, actor->x, actor->y, false))
 			actor->tracer->z = mz;
-		else
+		else if (!P_MobjWasRemoved(actor))
 			P_SetTarget(&actor->tracer, NULL);
 	}
 }
@@ -7290,7 +7329,7 @@ nomissile:
 	// chase towards player
 	if (P_AproxDistance(actor->target->x-actor->x, actor->target->y-actor->y) > actor->radius+actor->target->radius)
 	{
-		if (--actor->movecount < 0 || !P_Move(actor, actor->info->speed))
+		if (--actor->movecount < 0 || (!P_Move(actor, actor->info->speed) && !P_MobjWasRemoved(actor)))
 			P_NewChaseDir(actor);
 	}
 	// too close, don't want to chase.
@@ -7647,7 +7686,7 @@ void A_Boss7Chase(mobj_t *actor)
 	if (leveltime & 1)
 	{
 		// chase towards player
-		if (--actor->movecount < 0 || !P_Move(actor, actor->info->speed))
+		if (--actor->movecount < 0 || (!P_Move(actor, actor->info->speed) && !P_MobjWasRemoved(actor)))
 			P_NewChaseDir(actor);
 	}
 }
@@ -8105,6 +8144,8 @@ void A_GuardChase(mobj_t *actor)
 			false)
 		&& speed > 0) // can't be the same check as previous so that P_TryMove gets to happen.
 		{
+			if (P_MobjWasRemoved(actor))
+				return;
 			INT32 direction = actor->spawnpoint ? actor->spawnpoint->args[0] : TMGD_BACK;
 
 			switch (direction)
@@ -8121,6 +8162,8 @@ void A_GuardChase(mobj_t *actor)
 					break;
 			}
 		}
+		if (P_MobjWasRemoved(actor))
+			return;
 
 		if (actor->extravalue1 < actor->info->speed)
 			actor->extravalue1++;
@@ -8157,7 +8200,11 @@ void A_GuardChase(mobj_t *actor)
 		// chase towards player
 		if (--actor->movecount < 0 || !P_Move(actor, (actor->flags2 & MF2_AMBUSH) ? actor->info->speed * 2 : actor->info->speed))
 		{
+			if (P_MobjWasRemoved(actor))
+				return;
 			P_NewChaseDir(actor);
+			if (P_MobjWasRemoved(actor))
+				return;
 			actor->movecount += 5; // Increase tics before change in direction allowed.
 		}
 	}
@@ -8628,6 +8675,9 @@ void A_PlaySeeSound(mobj_t *actor)
 	if (LUA_CallAction(A_PLAYSEESOUND, actor))
 		return;
 
+	if (P_MobjWasRemoved(actor))
+		return;
+
 	if (actor->info->seesound)
 		S_StartScreamSound(actor, actor->info->seesound);
 }
@@ -11718,7 +11768,13 @@ void A_BrakChase(mobj_t *actor)
 
 	// chase towards player
 	if (--actor->movecount < 0 || !P_Move(actor, actor->info->speed))
+	{
+		if (P_MobjWasRemoved(actor))
+			return;
 		P_NewChaseDir(actor);
+		if (P_MobjWasRemoved(actor))
+			return;
+	}
 
 	// Optionally play a sound effect
 	if (locvar2 > 0 && locvar2 < NUMSFX)
@@ -13296,6 +13352,8 @@ void A_DoNPCSkid(mobj_t *actor)
 	if ((FixedHypot(actor->momx, actor->momy) < locvar2)
 	|| !P_TryMove(actor, actor->x + actor->momx, actor->y + actor->momy, false))
 	{
+		if (P_MobjWasRemoved(actor))
+			return;
 		actor->momx = actor->momy = 0;
 		P_SetMobjState(actor, locvar1);
 		return;
@@ -13838,6 +13896,8 @@ static boolean PIT_DustDevilLaunch(mobj_t *thing)
 				y = dustdevil->y;
 			}
 			P_TryMove(thing, x - thing->momx, y - thing->momy, true);
+			if (P_MobjWasRemoved(thing))
+				return false;
 		}
 		else
 		{ //Player on the top of the tornado.
@@ -14243,6 +14303,8 @@ static void P_SnapperLegPlace(mobj_t *mo)
 
 	seg->z = mo->z + ((mo->eflags & MFE_VERTICALFLIP) ? (((mo->height<<1)/3) - seg->height) : mo->height/3);
 	P_TryMove(seg, mo->x + FixedMul(c, rad) + necklen*c, mo->y + FixedMul(s, rad) + necklen*s, true);
+	if (P_MobjWasRemoved(seg))
+		return;
 	seg->angle = a;
 
 	// Move as many legs as available.
@@ -14264,6 +14326,8 @@ static void P_SnapperLegPlace(mobj_t *mo)
 			y = s*o2 - c*o1;
 			seg->z = mo->z + (((mo->eflags & MFE_VERTICALFLIP) ? (mo->height - seg->height) : 0));
 			P_TryMove(seg, mo->x + x, mo->y + y, true);
+			if (P_MobjWasRemoved(seg))
+				return;
 			P_SetMobjState(seg, seg->info->raisestate);
 		}
 		else
@@ -14407,6 +14471,8 @@ void A_SnapperThinker(mobj_t *actor)
 		s = FINESINE(fa);
 
 		P_TryMove(actor, actor->x + c*speed, actor->y + s*speed, false);
+		if (P_MobjWasRemoved(actor))
+			return;
 
 		// The snapper spawns dust if going fast!
 		if (actor->reactiontime < 4)
diff --git a/src/p_inter.c b/src/p_inter.c
index a4fe8f850662de859277ae6aa24c0c18a4175d10..5534f7865c23afc2d70ceae209e6ea432f6223f8 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1397,11 +1397,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			i = 0;
 			for (; special->type == MT_HOOP; special = special->hnext)
 			{
-				special->fuse = 11;
-				special->movedir = i;
-				special->extravalue1 = special->target->extravalue1;
-				special->extravalue2 = special->target->extravalue2;
-				special->target->threshold = 4242;
+				if (!P_MobjWasRemoved(special->target))
+				{
+					special->fuse = 11;
+					special->movedir = i;
+					special->extravalue1 = special->target->extravalue1;
+					special->extravalue2 = special->target->extravalue2;
+					special->target->threshold = 4242;
+				}
 				i++;
 			}
 			// Make the collision detectors disappear.
diff --git a/src/p_map.c b/src/p_map.c
index 7887c117de56907d7a51eccb3715ec0a32e14d61..7b64fe3bb782de111f0b70053306ce851a1ea190 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2734,7 +2734,7 @@ increment_move
 				tryy = y;
 		}
 
-		if (!P_CheckPosition(thing, tryx, tryy))
+		if (!P_CheckPosition(thing, tryx, tryy) || P_MobjWasRemoved(thing))
 			return false; // solid wall or thing
 
 		if (!(thing->flags & MF_NOCLIP))
@@ -2958,6 +2958,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y)
 {
 	fixed_t tryx, tryy;
+	I_Assert(!P_MobjWasRemoved(thing));
 
 	tryx = thing->x;
 	tryy = thing->y;
@@ -2975,7 +2976,7 @@ boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y)
 		else
 			tryy = y;
 
-		if (!P_CheckPosition(thing, tryx, tryy))
+		if (!P_CheckPosition(thing, tryx, tryy) || P_MobjWasRemoved(thing))
 			return false; // solid wall or thing
 
 		if (!(thing->flags & MF_NOCLIP))
@@ -3714,6 +3715,12 @@ static void P_CheckLavaWall(mobj_t *mo, sector_t *sec)
 	}
 }
 
+static inline void P_StairStepSlideMove(mobj_t *mo)
+{
+	if (!P_TryMove(mo, mo->x, mo->y + mo->momy, true) && !P_MobjWasRemoved(mo)) //Allow things to drop off.
+		P_TryMove(mo, mo->x + mo->momx, mo->y, true);
+}
+
 //
 // P_SlideMove
 // The momx / momy move is bad, so try to slide
@@ -3735,6 +3742,8 @@ void P_SlideMove(mobj_t *mo)
 
 	memset(&junk, 0x00, sizeof(junk));
 
+	I_Assert(!P_MobjWasRemoved(mo));
+
 	if (tmhitthing && mo->z + mo->height > tmhitthing->z && mo->z < tmhitthing->z + tmhitthing->height)
 	{
 		// Don't mess with your momentum if it's a pushable object. Pushables do their own crazy things already.
@@ -3869,7 +3878,10 @@ void P_SlideMove(mobj_t *mo)
 
 retry:
 	if ((++hitcount == 3) || papercol)
-		goto stairstep; // don't loop forever
+	{
+		P_StairStepSlideMove(mo);
+		return;
+	}
 
 	// trace along the three leading corners
 	if (mo->momx > 0)
@@ -3921,9 +3933,7 @@ papercollision:
 	if (bestslidefrac == FRACUNIT+1)
 	{
 		// the move must have hit the middle, so stairstep
-stairstep:
-		if (!P_TryMove(mo, mo->x, mo->y + mo->momy, true)) //Allow things to drop off.
-			P_TryMove(mo, mo->x + mo->momx, mo->y, true);
+		P_StairStepSlideMove(mo);
 		return;
 	}
 
@@ -3935,7 +3945,13 @@ stairstep:
 		newy = FixedMul(mo->momy, bestslidefrac);
 
 		if (!P_TryMove(mo, mo->x + newx, mo->y + newy, true))
-			goto stairstep;
+		{
+			if (!P_MobjWasRemoved(mo))
+				P_StairStepSlideMove(mo);
+			return;
+		}
+		if (P_MobjWasRemoved(mo))
+			return;
 	}
 
 	// Now continue along the wall.
@@ -3986,11 +4002,13 @@ stairstep:
 			tmymove = 0;
 		}
 		if (!P_TryMove(mo, newx, newy, true)) {
-			if (success)
+			if (success || P_MobjWasRemoved(mo))
 				return; // Good enough!!
 			else
 				goto retry;
 		}
+		if (P_MobjWasRemoved(mo))
+			return;
 		success = true;
 	} while(tmxmove || tmymove);
 }
diff --git a/src/p_mobj.c b/src/p_mobj.c
index b0ae364c1979ae72ff0b9e92cdc8d15f1bec35d9..518c731da16c35ded6f89f7a542e4df1897be543 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -2118,7 +2118,7 @@ void P_RingXYMovement(mobj_t *mo)
 	I_Assert(mo != NULL);
 	I_Assert(!P_MobjWasRemoved(mo));
 
-	if (!P_SceneryTryMove(mo, mo->x + mo->momx, mo->y + mo->momy))
+	if (!P_SceneryTryMove(mo, mo->x + mo->momx, mo->y + mo->momy) && !P_MobjWasRemoved(mo))
 		P_SlideMove(mo);
 }
 
@@ -2132,8 +2132,10 @@ void P_SceneryXYMovement(mobj_t *mo)
 	oldx = mo->x;
 	oldy = mo->y;
 
-	if (!P_SceneryTryMove(mo, mo->x + mo->momx, mo->y + mo->momy))
+	if (!P_SceneryTryMove(mo, mo->x + mo->momx, mo->y + mo->momy) && !P_MobjWasRemoved(mo))
 		P_SlideMove(mo);
+	if (P_MobjWasRemoved(mo))
+		return;
 
 	if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z > mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height < mo->ceilingz))
 		return; // no friction when airborne
@@ -2329,12 +2331,15 @@ boolean P_CheckDeathPitCollide(mobj_t *mo)
 	if (mo->player && mo->player->pflags & PF_GODMODE)
 		return false;
 
-	if (((mo->z <= mo->subsector->sector->floorheight
+	fixed_t sectorFloor = P_GetSectorFloorZAt(mo->subsector->sector, mo->x, mo->y);
+	fixed_t sectorCeiling = P_GetSectorCeilingZAt(mo->subsector->sector, mo->x, mo->y);
+
+	if (((mo->z <= sectorFloor
 		&& ((mo->subsector->sector->flags & MSF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & MSF_FLIPSPECIAL_FLOOR))
-	|| (mo->z + mo->height >= mo->subsector->sector->ceilingheight
-		&& ((mo->subsector->sector->flags & MSF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & MSF_FLIPSPECIAL_CEILING)))
-	&& (mo->subsector->sector->damagetype == SD_DEATHPITTILT
-	|| mo->subsector->sector->damagetype == SD_DEATHPITNOTILT))
+		|| (mo->z + mo->height >= sectorCeiling
+			&& ((mo->subsector->sector->flags & MSF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & MSF_FLIPSPECIAL_CEILING)))
+		&& (mo->subsector->sector->damagetype == SD_DEATHPITTILT
+			|| mo->subsector->sector->damagetype == SD_DEATHPITNOTILT))
 		return true;
 
 	return false;
@@ -3906,6 +3911,8 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 	}
 	else
 		P_TryMove(mobj, mobj->x, mobj->y, true);
+	if (P_MobjWasRemoved(mobj))
+		return;
 
 	P_CheckCrumblingPlatforms(mobj);
 
@@ -4700,6 +4707,8 @@ static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
 		{
 			seg->z = bz + (dz*(9-s));
 			P_TryMove(seg, workx + (dx*s), worky + (dy*s), true);
+			if (P_MobjWasRemoved(seg))
+				return;
 		}
 		angle += ANGLE_MAX/3;
 	}
@@ -4937,6 +4946,8 @@ static void P_Boss4Thinker(mobj_t *mobj)
 				(mobj->spawnpoint->x<<FRACBITS) - P_ReturnThrustX(mobj, mobj->angle, mobj->movefactor),
 				(mobj->spawnpoint->y<<FRACBITS) - P_ReturnThrustY(mobj, mobj->angle, mobj->movefactor),
 				true);
+		if (P_MobjWasRemoved(mobj))
+			return;
 
 		P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2);
 
@@ -5505,6 +5516,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 	{
 		P_InstaThrust(mobj, mobj->angle, -4*FRACUNIT);
 		P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true);
+		if (P_MobjWasRemoved(mobj))
+			return;
 		mobj->momz -= gravity;
 		if (mobj->z < mobj->watertop || mobj->z < (mobj->floorz + 16*FRACUNIT))
 		{
@@ -5848,6 +5861,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 					P_InstaThrust(mobj, mobj->angle, 30*FRACUNIT);
 				if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true))
 				{ // Hit a wall? Find a direction to bounce
+					if (P_MobjWasRemoved(mobj))
+						return;
 					mobj->threshold--;
 					if (!mobj->threshold) { // failed bounce!
 						S_StartSound(mobj, sfx_mspogo);
@@ -5888,6 +5903,8 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			P_InstaThrust(mobj, mobj->angle, -speed);
 			while (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true) && tries++ < 16)
 			{
+				if (P_MobjWasRemoved(mobj))
+					return;
 				S_StartSound(mobj, sfx_mspogo);
 				P_BounceMove(mobj);
 				mobj->angle = R_PointToAngle2(mobj->momx, mobj->momy,0,0);
@@ -7487,6 +7504,8 @@ static void P_RosySceneryThink(mobj_t *mobj)
 			fixed_t x = mobj->x, y = mobj->y, z = mobj->z;
 			angle_t angletoplayer = R_PointToAngle2(x, y, mobj->target->x, mobj->target->y);
 			boolean allowed = P_TryMove(mobj, mobj->target->x, mobj->target->y, false);
+			if (P_MobjWasRemoved(mobj))
+				return;
 
 			P_UnsetThingPosition(mobj);
 			mobj->x = x;
@@ -8052,7 +8071,8 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 		break;
 	}
 
-	P_SceneryThinker(mobj);
+	if (!P_MobjWasRemoved(mobj))
+		P_SceneryThinker(mobj);
 }
 
 static boolean P_MobjPushableThink(mobj_t *mobj)
@@ -10282,6 +10302,8 @@ void P_MobjThinker(mobj_t *mobj)
 		P_SetTarget(&mobj->hnext, NULL);
 	if (mobj->hprev && P_MobjWasRemoved(mobj->hprev))
 		P_SetTarget(&mobj->hprev, NULL);
+	if (mobj->dontdrawforviewmobj && P_MobjWasRemoved(mobj->dontdrawforviewmobj))
+		P_SetTarget(&mobj->dontdrawforviewmobj, NULL);
 
 	mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG);
 
@@ -10412,6 +10434,8 @@ void P_MobjThinker(mobj_t *mobj)
 		|| mobj->type == MT_CANNONBALLDECOR
 		|| mobj->type == MT_FALLINGROCK) {
 		P_TryMove(mobj, mobj->x, mobj->y, true); // Sets mo->standingslope correctly
+		if (P_MobjWasRemoved(mobj))
+			return;
 		//if (mobj->standingslope) CONS_Printf("slope physics on mobj\n");
 		P_ButteredSlope(mobj);
 	}
@@ -10513,6 +10537,8 @@ void P_PushableThinker(mobj_t *mobj)
 	// it has to be pushable RIGHT NOW for this part to happen
 	if (mobj->flags & MF_PUSHABLE && !(mobj->momx || mobj->momy))
 		P_TryMove(mobj, mobj->x, mobj->y, true);
+	if (P_MobjWasRemoved(mobj))
+		return;
 
 	if (mobj->type == MT_MINECART && mobj->health)
 	{
@@ -10855,9 +10881,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type, ...)
 	)))
 		mobj->flags2 |= MF2_DONTRESPAWN;
 
-	if (!(mobj->flags & MF_NOTHINK))
-		P_AddThinker(THINK_MOBJ, &mobj->thinker);
-
 	if (type == MT_PLAYER)
 	{
 		// when spawning MT_PLAYER, set mobj->player before calling MobjSpawn hook to prevent P_RemoveMobj from succeeding on player mobj.
@@ -10867,6 +10890,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type, ...)
 		va_end(args);
 	}
 
+	if (!(mobj->flags & MF_NOTHINK) || (titlemapinaction && mobj->type == MT_ALTVIEWMAN))
+		P_AddThinker(THINK_MOBJ, &mobj->thinker);
+
 	// increment mobj reference, so we don't get a dangling reference in case MobjSpawn calls P_RemoveMobj
 	mobj->thinker.references++;
 
@@ -14006,7 +14032,8 @@ boolean P_CheckMissileSpawn(mobj_t *th)
 
 	if (!P_TryMove(th, th->x, th->y, true))
 	{
-		P_ExplodeMissile(th);
+		if (!P_MobjWasRemoved(th))
+			P_ExplodeMissile(th);
 		return false;
 	}
 	return true;
diff --git a/src/p_setup.c b/src/p_setup.c
index 1d985beb4f07399a40607f1ec8f32f480df0e65c..396639b7263e7801f8bc0b8ce0fc48213c153cff 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -585,17 +585,17 @@ Ploadflat (levelflat_t *levelflat, const char *flatname, boolean resize)
 
 	// Look for a flat
 	int texturenum = R_CheckFlatNumForName(levelflat->name);
-	if (texturenum <= 0)
+	if (texturenum < 0)
 	{
 		// If we can't find a flat, try looking for a texture!
 		texturenum = R_CheckTextureNumForName(levelflat->name);
-		if (texturenum <= 0)
+		if (texturenum < 0)
 		{
 			// Use "not found" texture
 			texturenum = R_CheckTextureNumForName("REDWALL");
 
 			// Give up?
-			if (texturenum <= 0)
+			if (texturenum < 0)
 			{
 				levelflat->type = LEVELFLAT_NONE;
 				texturenum = -1;
@@ -1525,6 +1525,12 @@ static boolean TextmapCount(size_t size)
 	numvertexes = 0;
 	numsectors = 0;
 
+	if(!tkn)
+	{
+		CONS_Alert(CONS_ERROR, "No text in lump!\n");
+		return true;
+	}
+
 	// Look for namespace at the beginning.
 	if (!fastcmp(tkn, "namespace"))
 	{
@@ -3109,7 +3115,12 @@ static boolean P_LoadMapData(const virtres_t *virt)
 	if (udmf) // Count how many entries for each type we got in textmap.
 	{
 		virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
-		M_TokenizerOpen((char *)textmap->data);
+		if (textmap->size == 0)
+		{
+			CONS_Alert(CONS_ERROR, "Emtpy TEXTMAP Lump!\n");
+			return false;
+		}
+		M_TokenizerOpen((char *)textmap->data, textmap->size);
 		if (!TextmapCount(textmap->size))
 		{
 			M_TokenizerClose();
diff --git a/src/p_tick.c b/src/p_tick.c
index 4ab388486db62be9d1fa36d9289ac2b043219e47..6d7d4fd969fb70964e4e0cf6663d26bd6d81e8a7 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -844,7 +844,7 @@ void P_Ticker(boolean run)
 		if (quake.time)
 			--quake.time;
 
-		if (metalplayback)
+		if (!P_MobjWasRemoved(metalplayback))
 			G_ReadMetalTic(metalplayback);
 		if (metalrecording)
 			G_WriteMetalTic(players[consoleplayer].mo);
diff --git a/src/p_user.c b/src/p_user.c
index 0623a41dd345b551bbd879945c427b90035bc083..b493e1b8b910a5447b2f1f8680e9e0cb5a80654e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -11084,7 +11084,8 @@ static void P_MinecartThink(player_t *player)
 	fa = (minecart->angle >> ANGLETOFINESHIFT) & FINEMASK;
 	if (!P_TryMove(minecart, minecart->x + FINECOSINE(fa), minecart->y + FINESINE(fa), true))
 	{
-		P_KillMobj(minecart, NULL, NULL, 0);
+		if (!P_MobjWasRemoved(minecart))
+			P_KillMobj(minecart, NULL, NULL, 0);
 		return;
 	}
 
@@ -12441,7 +12442,7 @@ void P_PlayerThink(player_t *player)
 			player->texttimer = 4*TICRATE;
 			player->textvar = 2; // GET n RINGS!
 
-			if (player->capsule && player->capsule->health != player->capsule->spawnpoint->angle)
+			if (!P_MobjWasRemoved(player->capsule) && player->capsule->health != player->capsule->spawnpoint->angle)
 				player->textvar++; // GET n MORE RINGS!
 		}
 	}
diff --git a/src/r_data.c b/src/r_data.c
index 0a13d27dbaf55f9e9092adeb0de4635523c96606..56fe9403983ab29e2d60428672e8b4551e715ae4 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -31,6 +31,10 @@
 #include "byteptr.h"
 #include "dehacked.h"
 
+#ifdef HWRENDER
+#include "hardware/hw_glob.h" // HWR_ClearLightTables
+#endif
+
 //
 // Graphics.
 // SRB2 graphics for walls and sprites
@@ -426,6 +430,9 @@ void R_ClearColormaps(void)
 {
 	// Purged by PU_LEVEL, just overwrite the pointer
 	extra_colormaps = R_CreateDefaultColormap(true);
+#ifdef HWRENDER
+	HWR_ClearLightTables();
+#endif
 }
 
 //
diff --git a/src/r_defs.h b/src/r_defs.h
index d556b540f8a68794986e04da72c58e6d7af8f9a2..65fd883c91a9ccb7b02fbcfa7864d1783ca987ef 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -25,6 +25,10 @@
 
 #include "screen.h" // MAXVIDWIDTH, MAXVIDHEIGHT
 
+#ifdef HWRENDER
+#include "m_aatree.h"
+#endif
+
 #include "taglist.h"
 
 //
@@ -69,6 +73,11 @@ typedef struct extracolormap_s
 
 	lighttable_t *colormap;
 
+#ifdef HWRENDER
+	// The id of the hardware lighttable. Zero means it does not exist yet.
+	UINT32 gl_lighttable_id;
+#endif
+
 #ifdef EXTRACOLORMAPLUMPS
 	lumpnum_t lump; // for colormap lump matching, init to LUMPERROR
 	char lumpname[9]; // for netsyncing
@@ -897,6 +906,26 @@ typedef struct
 	// the [0] is &columnofs[width]
 } ATTRPACK softwarepatch_t;
 
+#ifdef _MSC_VER
+#pragma warning(disable :  4200)
+#endif
+
+// a pic is an unmasked block of pixels, stored in horizontal way
+typedef struct
+{
+	INT16 width;
+	UINT8 zero;       // set to 0 allow autodetection of pic_t
+	                 // mode instead of patch or raw
+	UINT8 mode;       // see pic_mode_t above
+	INT16 height;
+	INT16 reserved1; // set to 0
+	UINT8 data[0];
+} ATTRPACK pic_t;
+
+#ifdef _MSC_VER
+#pragma warning(default : 4200)
+#endif
+
 #if defined(_MSC_VER)
 #pragma pack()
 #endif
diff --git a/src/r_main.c b/src/r_main.c
index aaab234ad1f168f2df77d2ed55b430b5cc3ff6fa..560ad68b30ddb4e8f034b97d0b62c9f3241e8582 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1363,7 +1363,7 @@ void R_SkyboxFrame(player_t *player)
 			newview->z += campos.z * -mh->skybox_scalez;
 	}
 
-	if (r_viewmobj->subsector)
+	if (!P_MobjWasRemoved(r_viewmobj) && r_viewmobj->subsector)
 		newview->sector = r_viewmobj->subsector->sector;
 	else
 		newview->sector = R_PointInSubsector(newview->x, newview->y)->sector;
diff --git a/src/r_plane.c b/src/r_plane.c
index 4f9ce9ec84bdf7443a16b811504a30df674d78f7..2fb2eb33e40b1d5838ae8b7ac96f3b1bc06c3c53 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -881,6 +881,9 @@ void R_DrawSinglePlane(visplane_t *pl)
 	if (!(pl->minx <= pl->maxx))
 		return;
 
+	if (!cv_renderfloors.value)
+		return;
+
 	// sky flat
 	if (pl->picnum == skyflatnum)
 	{
diff --git a/src/r_segs.c b/src/r_segs.c
index 267c1d47d63ff32a56d6e6b0330783397dacfee3..2e3c55366315c7cf5fdb3f530adf5c27c84039df 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -117,6 +117,9 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	INT32 range;
 	unsigned lengthcol;
 
+	if (!cv_renderwalls.value)
+		return;
+
 	// Calculate light table.
 	// Use different light tables
 	//   for horizontal / vertical / diagonal. Diagonal?
@@ -441,7 +444,7 @@ static void R_DrawRepeatMaskedColumn(column_t *col, unsigned lengthcol)
 {
 	while (sprtopscreen < sprbotscreen) {
 		R_DrawMaskedColumn(col, lengthcol);
-		if ((INT64)sprtopscreen + dc_texheight*spryscale > (INT64)INT32_MAX) // prevent overflow
+		if ((INT64)sprtopscreen + (INT64)dc_texheight*spryscale > (INT64)INT32_MAX) // prevent overflow
 			sprtopscreen = INT32_MAX;
 		else
 			sprtopscreen += dc_texheight*spryscale;
@@ -505,6 +508,9 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 
 	void (*colfunc_2s) (column_t *, unsigned);
 
+	if (!cv_renderwalls.value)
+		return;
+
 	// Calculate light table.
 	// Use different light tables
 	//   for horizontal / vertical / diagonal. Diagonal?
@@ -1247,29 +1253,32 @@ static void R_RenderSegLoop (void)
 			// single sided line
 			if (yl <= yh && yh >= 0 && yl < viewheight)
 			{
-				fixed_t offset = texturecolumn + rw_offsetx;
+				if (cv_renderwalls.value)
+				{
+					fixed_t offset = texturecolumn + rw_offsetx;
 
-				dc_yl = yl;
-				dc_yh = yh;
-				dc_texturemid = rw_midtexturemid;
-				dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_midtexturescaley);
-				dc_source = R_GetColumn(midtexture, offset >> FRACBITS)->pixels;
-				dc_texheight = textureheight[midtexture]>>FRACBITS;
+					dc_yl = yl;
+					dc_yh = yh;
+					dc_texturemid = rw_midtexturemid;
+					dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_midtexturescaley);
+					dc_source = R_GetColumn(midtexture, offset >> FRACBITS)->pixels;
+					dc_texheight = textureheight[midtexture]>>FRACBITS;
 
-				//profile stuff ---------------------------------------------------------
+					//profile stuff ---------------------------------------------------------
 #ifdef TIMING
-				ProfZeroTimer();
+					ProfZeroTimer();
 #endif
-				colfunc();
+					colfunc();
 #ifdef TIMING
-				RDMSR(0x10,&mycount);
-				mytotal += mycount;      //64bit add
+					RDMSR(0x10,&mycount);
+					mytotal += mycount;      //64bit add
 
-				if (nombre--==0)
-					I_Error("R_DrawColumn CPU Spy reports: 0x%d %d\n", *((INT32 *)&mytotal+1),
-						(INT32)mytotal);
+					if (nombre--==0)
+						I_Error("R_DrawColumn CPU Spy reports: 0x%d %d\n", *((INT32 *)&mytotal+1),
+							(INT32)mytotal);
 #endif
-				//profile stuff ---------------------------------------------------------
+					//profile stuff ---------------------------------------------------------
+				}
 
 				// dont draw anything more for this column, since
 				// a midtexture blocks the view
@@ -1313,18 +1322,21 @@ static void R_RenderSegLoop (void)
 					}
 					else if (mid >= 0) // safe to draw top texture
 					{
-						fixed_t offset = rw_offset_top;
-						if (rw_toptexturescalex < 0)
-							offset = -offset;
-						offset = toptexturecolumn + offset;
-
-						dc_yl = yl;
-						dc_yh = mid;
-						dc_texturemid = rw_toptexturemid;
-						dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_toptexturescaley);
-						dc_source = R_GetColumn(toptexture, offset >> FRACBITS)->pixels;
-						dc_texheight = textureheight[toptexture]>>FRACBITS;
-						colfunc();
+						if (cv_renderwalls.value)
+						{
+							fixed_t offset = rw_offset_top;
+							if (rw_toptexturescalex < 0)
+								offset = -offset;
+							offset = toptexturecolumn + offset;
+
+							dc_yl = yl;
+							dc_yh = mid;
+							dc_texturemid = rw_toptexturemid;
+							dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_toptexturescaley);
+							dc_source = R_GetColumn(toptexture, offset >> FRACBITS)->pixels;
+							dc_texheight = textureheight[toptexture]>>FRACBITS;
+							colfunc();
+						}
 						ceilingclip[rw_x] = (INT16)mid;
 					}
 					else if (!rw_ceilingmarked) // entirely off top of screen
@@ -1361,18 +1373,21 @@ static void R_RenderSegLoop (void)
 					}
 					else if (mid < viewheight) // safe to draw bottom texture
 					{
-						fixed_t offset = rw_offset_bottom;
-						if (rw_bottomtexturescalex < 0)
-							offset = -offset;
-						offset = bottomtexturecolumn + offset;
-
-						dc_yl = mid;
-						dc_yh = yh;
-						dc_texturemid = rw_bottomtexturemid;
-						dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_bottomtexturescaley);
-						dc_source = R_GetColumn(bottomtexture, offset >> FRACBITS)->pixels;
-						dc_texheight = textureheight[bottomtexture]>>FRACBITS;
-						colfunc();
+						if (cv_renderwalls.value)
+						{
+							fixed_t offset = rw_offset_bottom;
+							if (rw_bottomtexturescalex < 0)
+								offset = -offset;
+							offset = bottomtexturecolumn + offset;
+
+							dc_yl = mid;
+							dc_yh = yh;
+							dc_texturemid = rw_bottomtexturemid;
+							dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_bottomtexturescaley);
+							dc_source = R_GetColumn(bottomtexture, offset >> FRACBITS)->pixels;
+							dc_texheight = textureheight[bottomtexture]>>FRACBITS;
+							colfunc();
+						}
 						floorclip[rw_x] = (INT16)mid;
 					}
 					else if (!rw_floormarked)  // entirely off bottom of screen
diff --git a/src/r_textures.c b/src/r_textures.c
index 0175a080e7cbd901bee865429660ddb3de51af9f..59cc114139c5abb24df0f47fa0dddd0e70ec8d72 100644
--- a/src/r_textures.c
+++ b/src/r_textures.c
@@ -193,6 +193,8 @@ static void R_DrawBlendColumnInCache(column_t *column, UINT8 *cache, texpatch_t
 		{
 			for (; dest < cache + position + count; source++, dest++, is_opaque++)
 			{
+				if (originPatch->alpha <= ASTTextureBlendingThreshold[1] && !(*is_opaque))
+					continue;
 				*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
 				*is_opaque = true;
 			}
@@ -237,6 +239,8 @@ static void R_DrawBlendFlippedColumnInCache(column_t *column, UINT8 *cache, texp
 		{
 			for (; dest < cache + position + count; --source, dest++, is_opaque++)
 			{
+				if (originPatch->alpha <= ASTTextureBlendingThreshold[1] && !(*is_opaque))
+					continue;
 				*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
 				*is_opaque = true;
 			}
diff --git a/src/r_things.c b/src/r_things.c
index d6ef72b9d29980134757df4547ffd03837379957..1ab666a02d49c2b14d7eb5912073c568c096ab6f 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1528,6 +1528,9 @@ static void R_ProjectSprite(mobj_t *thing)
 	// uncapped/interpolation
 	interpmobjstate_t interp = {0};
 
+	if (!cv_renderthings.value)
+		return;
+
 	// do interpolation
 	if (R_UsingFrameInterpolation() && !paused)
 	{
diff --git a/src/r_translation.c b/src/r_translation.c
index 3b123f14ca1732e2e9605dad3153fd5f6d0e2338..3905458026df20945e566861fc6c7a087bcab244 100644
--- a/src/r_translation.c
+++ b/src/r_translation.c
@@ -784,9 +784,9 @@ static struct PaletteRemapParseResult *PaletteRemap_ParseString(tokenizer_t *sc)
 	return NULL;
 }
 
-static struct PaletteRemapParseResult *PaletteRemap_ParseTranslation(const char *translation)
+static struct PaletteRemapParseResult *PaletteRemap_ParseTranslation(const char *translation, size_t len)
 {
-	tokenizer_t *sc = Tokenizer_Open(translation, 1);
+	tokenizer_t *sc = Tokenizer_Open(translation, len, 1);
 	struct PaletteRemapParseResult *result = PaletteRemap_ParseString(sc);
 	Tokenizer_Close(sc);
 	return result;
@@ -918,7 +918,7 @@ void R_ParseTrnslate(INT32 wadNum, UINT16 lumpnum)
 	text[lumpLength] = '\0';
 	Z_Free(lumpData);
 
-	sc = Tokenizer_Open(text, 1);
+	sc = Tokenizer_Open(text, lumpLength, 1);
 	tkn = sc->get(sc, 0);
 
 	struct NewTranslation *list = NULL;
@@ -963,7 +963,7 @@ void R_ParseTrnslate(INT32 wadNum, UINT16 lumpnum)
 
 		// Parse all of the translations
 		do {
-			struct PaletteRemapParseResult *parse_result = PaletteRemap_ParseTranslation(tkn);
+			struct PaletteRemapParseResult *parse_result = PaletteRemap_ParseTranslation(tkn, strlen(tkn));
 			if (parse_result->error)
 			{
 				PrintError(name, "%s", parse_result->error);
diff --git a/src/screen.c b/src/screen.c
index 76f546402ef38e0a4882221945ff602de73aadf9..92a8a9e2ae5b75734fea2b9347eae8700a2fb9ba 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -71,6 +71,9 @@ consvar_t cv_scr_height_w = CVAR_INIT ("scr_height_w", "400", CV_SAVE, CV_Unsign
 consvar_t cv_scr_depth = CVAR_INIT ("scr_depth", "16 bits", CV_SAVE, scr_depth_cons_t, NULL);
 
 consvar_t cv_renderview = CVAR_INIT ("renderview", "On", 0, CV_OnOff, NULL);
+consvar_t cv_renderwalls = CVAR_INIT ("renderwalls", "On", CV_NOTINNET|CV_CHEAT, CV_OnOff, NULL);
+consvar_t cv_renderfloors = CVAR_INIT ("renderfloors", "On", CV_NOTINNET|CV_CHEAT, CV_OnOff, NULL);
+consvar_t cv_renderthings = CVAR_INIT ("renderthings", "On", CV_NOTINNET|CV_CHEAT, CV_OnOff, NULL);
 
 CV_PossibleValue_t cv_renderer_t[] = {
 	{1, "Software"},
diff --git a/src/screen.h b/src/screen.h
index 64d92b9d3ef99cf3c4e066ddc5cc9e57edd94afd..1f5ff147d8c0195f58c3f8d32171e76cf8108aca 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -187,6 +187,7 @@ extern INT32 scr_bpp;
 extern UINT8 *scr_borderpatch; // patch used to fill the view borders
 
 extern consvar_t cv_scr_width, cv_scr_height, cv_scr_width_w, cv_scr_height_w, cv_scr_depth, cv_fullscreen;
+extern consvar_t cv_renderwalls, cv_renderfloors, cv_renderthings;
 extern consvar_t cv_renderview, cv_renderer;
 extern consvar_t cv_renderhitbox, cv_renderhitboxinterpolation, cv_renderhitboxgldepth;
 // wait for page flipping to end or not
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index 0f0355c6f28ad2afbd6d201f7fba46897e73f1f3..5e30970286724f98e6a220ff658207be0119dd84 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -495,6 +495,7 @@
     <ClCompile Include="..\hardware\hw_md2load.c" />
     <ClCompile Include="..\hardware\hw_md3load.c" />
     <ClCompile Include="..\hardware\hw_model.c" />
+    <ClCompile Include="..\hardware\hw_shaders.c" />
     <ClCompile Include="..\hardware\r_opengl\r_opengl.c" />
     <ClCompile Include="..\lua_colorlib.c" />
     <ClCompile Include="..\r_bbox.c" />
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index 183843018f84087b8555fbce59e638e67cfa3546..35d47fad1fb38f008138348d77d6c242b7e71ccf 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -751,6 +751,12 @@
     <ClCompile Include="..\hardware\hw_model.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\hw_shaders.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\u_list.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
     <ClCompile Include="..\filesrch.c">
       <Filter>I_Interface</Filter>
     </ClCompile>
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index 96e3d7d6926ef23771c8dcf489b4d8d2a16c0a1c..ca87fcc7951758e9a5de7ca1a405f88b1f202204 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -74,7 +74,7 @@ void *hwSym(const char *funcName,void *handle)
 {
 	void *funcPointer = NULL;
 #ifdef HWRENDER
-	if (0 == strcmp("SetPalette", funcName))
+	if (0 == strcmp("SetTexturePalette", funcName))
 		funcPointer = &OglSdlSetPalette;
 
 	GETFUNC(Init);
@@ -87,7 +87,7 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(SetTexture);
 	GETFUNC(UpdateTexture);
 	GETFUNC(DeleteTexture);
-	GETFUNC(ReadRect);
+	GETFUNC(ReadScreenTexture);
 	GETFUNC(GClipRect);
 	GETFUNC(ClearMipMapCache);
 	GETFUNC(SetSpecialState);
@@ -97,21 +97,23 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(SetTransform);
 	GETFUNC(PostImgRedraw);
 	GETFUNC(FlushScreenTextures);
-	GETFUNC(StartScreenWipe);
-	GETFUNC(EndScreenWipe);
 	GETFUNC(DoScreenWipe);
-	GETFUNC(DrawIntermissionBG);
+	GETFUNC(DrawScreenTexture);
 	GETFUNC(MakeScreenTexture);
-	GETFUNC(MakeScreenFinalTexture);
 	GETFUNC(DrawScreenFinalTexture);
 
-	GETFUNC(CompileShaders);
-	GETFUNC(CleanShaders);
+	GETFUNC(InitShaders);
+	GETFUNC(LoadShader);
+	GETFUNC(CompileShader);
 	GETFUNC(SetShader);
 	GETFUNC(UnSetShader);
 
 	GETFUNC(SetShaderInfo);
-	GETFUNC(LoadCustomShader);
+
+	GETFUNC(SetPaletteLookup);
+	GETFUNC(CreateLightTable);
+	GETFUNC(ClearLightTables);
+	GETFUNC(SetScreenPalette);
 
 #else //HWRENDER
 	if (0 == strcmp("FinishUpdate", funcName))
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index f03ea6226516836612195a39022723e5df11e148..5509f8cab072ddb8d8eb55c118dedd107d09dc29 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -109,6 +109,7 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #endif
 
 #if defined (__unix__) || (defined (UNIXCOMMON) && !defined (__APPLE__))
+#include <poll.h>
 #include <errno.h>
 #include <sys/wait.h>
 #define NEWSIGNALHANDLER
@@ -616,50 +617,60 @@ void I_GetConsoleEvents(void)
 	// we use this when sending back commands
 	event_t ev = {0};
 	char key = 0;
-	ssize_t d;
+	struct pollfd pfd =
+	{
+		.fd = STDIN_FILENO,
+		.events = POLLIN,
+		.revents = 0,
+	};
 
 	if (!consolevent)
 		return;
 
-	ev.type = ev_console;
-	ev.key = 0;
-	if (read(STDIN_FILENO, &key, 1) == -1 || !key)
-		return;
-
-	// we have something
-	// backspace?
-	// NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere
-	if ((key == tty_erase) || (key == 127) || (key == 8))
+	for (;;)
 	{
-		if (tty_con.cursor > 0)
+		if (poll(&pfd, 1, 0) < 1 || !(pfd.revents & POLLIN))
+			return;
+
+		ev.type = ev_console;
+		ev.key = 0;
+		if (read(STDIN_FILENO, &key, 1) == -1 || !key)
+			return;
+
+		// we have something
+		// backspace?
+		// NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere
+		if ((key == tty_erase) || (key == 127) || (key == 8))
 		{
-			tty_con.cursor--;
-			tty_con.buffer[tty_con.cursor] = '\0';
-			tty_Back();
+			if (tty_con.cursor > 0)
+			{
+				tty_con.cursor--;
+				tty_con.buffer[tty_con.cursor] = '\0';
+				tty_Back();
+			}
+			ev.key = KEY_BACKSPACE;
 		}
-		ev.key = KEY_BACKSPACE;
-	}
-	else if (key < ' ') // check if this is a control char
-	{
-		if (key == '\n')
+		else if (key < ' ') // check if this is a control char
+		{
+			if (key == '\n')
+			{
+				tty_Clear();
+				tty_con.cursor = 0;
+				ev.key = KEY_ENTER;
+			}
+			else continue;
+		}
+		else if (tty_con.cursor < sizeof (tty_con.buffer))
 		{
-			tty_Clear();
-			tty_con.cursor = 0;
-			ev.key = KEY_ENTER;
+			// push regular character
+			ev.key = tty_con.buffer[tty_con.cursor] = key;
+			tty_con.cursor++;
+			// print the current line (this is differential)
+			write(STDOUT_FILENO, &key, 1);
 		}
-		else return;
+		if (ev.key) D_PostEvent(&ev);
+		//tty_FlushIn();
 	}
-	else if (tty_con.cursor < sizeof (tty_con.buffer))
-	{
-		// push regular character
-		ev.key = tty_con.buffer[tty_con.cursor] = key;
-		tty_con.cursor++;
-		// print the current line (this is differential)
-		d = write(STDOUT_FILENO, &key, 1);
-	}
-	if (ev.key) D_PostEvent(&ev);
-	//tty_FlushIn();
-	(void)d;
 }
 
 #elif defined (_WIN32)
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index d3a602c05803fcd5d6aced670321aad5c723ea74..1005af9d91b41fef8789b1c44cdff72dc3c9349d 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1297,6 +1297,14 @@ void I_FinishUpdate(void)
 #ifdef HWRENDER
 	else if (rendermode == render_opengl)
 	{
+		// Final postprocess step of palette rendering, after everything else has been drawn.
+		if (HWR_ShouldUsePaletteRendering())
+		{
+			HWD.pfnMakeScreenTexture(HWD_SCREENTEXTURE_GENERIC2);
+			HWD.pfnSetShader(HWR_GetShaderFromTarget(SHADER_PALETTE_POSTPROCESS));
+			HWD.pfnDrawScreenTexture(HWD_SCREENTEXTURE_GENERIC2, NULL, 0);
+			HWD.pfnUnSetShader();
+		}
 		OglSdlFinishUpdate(cv_vidwait.value);
 	}
 #endif
@@ -1958,32 +1966,34 @@ void VID_StartupOpenGL(void)
 		HWD.pfnSetTexture       = hwSym("SetTexture",NULL);
 		HWD.pfnUpdateTexture    = hwSym("UpdateTexture",NULL);
 		HWD.pfnDeleteTexture    = hwSym("DeleteTexture",NULL);
-		HWD.pfnReadRect         = hwSym("ReadRect",NULL);
+		HWD.pfnReadScreenTexture= hwSym("ReadScreenTexture",NULL);
 		HWD.pfnGClipRect        = hwSym("GClipRect",NULL);
 		HWD.pfnClearMipMapCache = hwSym("ClearMipMapCache",NULL);
 		HWD.pfnSetSpecialState  = hwSym("SetSpecialState",NULL);
-		HWD.pfnSetPalette       = hwSym("SetPalette",NULL);
+		HWD.pfnSetTexturePalette= hwSym("SetTexturePalette",NULL);
 		HWD.pfnGetTextureUsed   = hwSym("GetTextureUsed",NULL);
 		HWD.pfnDrawModel        = hwSym("DrawModel",NULL);
 		HWD.pfnCreateModelVBOs  = hwSym("CreateModelVBOs",NULL);
 		HWD.pfnSetTransform     = hwSym("SetTransform",NULL);
 		HWD.pfnPostImgRedraw    = hwSym("PostImgRedraw",NULL);
 		HWD.pfnFlushScreenTextures=hwSym("FlushScreenTextures",NULL);
-		HWD.pfnStartScreenWipe  = hwSym("StartScreenWipe",NULL);
-		HWD.pfnEndScreenWipe    = hwSym("EndScreenWipe",NULL);
 		HWD.pfnDoScreenWipe     = hwSym("DoScreenWipe",NULL);
-		HWD.pfnDrawIntermissionBG=hwSym("DrawIntermissionBG",NULL);
+		HWD.pfnDrawScreenTexture= hwSym("DrawScreenTexture",NULL);
 		HWD.pfnMakeScreenTexture= hwSym("MakeScreenTexture",NULL);
-		HWD.pfnMakeScreenFinalTexture=hwSym("MakeScreenFinalTexture",NULL);
 		HWD.pfnDrawScreenFinalTexture=hwSym("DrawScreenFinalTexture",NULL);
 
-		HWD.pfnCompileShaders   = hwSym("CompileShaders",NULL);
-		HWD.pfnCleanShaders     = hwSym("CleanShaders",NULL);
+		HWD.pfnInitShaders      = hwSym("InitShaders",NULL);
+		HWD.pfnLoadShader       = hwSym("LoadShader",NULL);
+		HWD.pfnCompileShader    = hwSym("CompileShader",NULL);
 		HWD.pfnSetShader        = hwSym("SetShader",NULL);
 		HWD.pfnUnSetShader      = hwSym("UnSetShader",NULL);
 
 		HWD.pfnSetShaderInfo    = hwSym("SetShaderInfo",NULL);
-		HWD.pfnLoadCustomShader = hwSym("LoadCustomShader",NULL);
+
+		HWD.pfnSetPaletteLookup = hwSym("SetPaletteLookup",NULL);
+		HWD.pfnCreateLightTable = hwSym("CreateLightTable",NULL);
+		HWD.pfnClearLightTables = hwSym("ClearLightTables",NULL);
+		HWD.pfnSetScreenPalette = hwSym("SetScreenPalette",NULL);
 
 		vid.glstate = HWD.pfnInit() ? VID_GL_LIBRARY_LOADED : VID_GL_LIBRARY_ERROR; // let load the OpenGL library
 
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index db0538195bbb6d98badb0fc27c5b0a9fac34fa81..e7347547e224b43f2fcdf66e527c8adc2681e243 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -232,7 +232,9 @@ void OglSdlFinishUpdate(boolean waitvbl)
 
 	// Sryder:	We need to draw the final screen texture again into the other buffer in the original position so that
 	//			effects that want to take the old screen can do so after this
-	HWR_DrawScreenFinalTexture(realwidth, realheight);
+	// Generic2 has the screen image without palette rendering brightness adjustments.
+	// Using that here will prevent brightness adjustments being applied twice.
+	DrawScreenTexture(HWD_SCREENTEXTURE_GENERIC2, NULL, 0);
 }
 
 EXPORT void HWRAPI(OglSdlSetPalette) (RGBA_t *palette)
diff --git a/src/st_stuff.c b/src/st_stuff.c
index be676cff46d08541b56577b2cfdd4fd9f0971247..67838ddb42074939d495b2a69d178712a8755816 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -227,8 +227,8 @@ void ST_doPaletteStuff(void)
 		palette = 0;
 
 #ifdef HWRENDER
-	if (rendermode == render_opengl)
-		palette = 0; // No flashpals here in OpenGL
+	if (rendermode == render_opengl && !HWR_ShouldUsePaletteRendering())
+		palette = 0; // Don't set the palette to a flashpal in OpenGL's truecolor mode
 #endif
 
 	if (palette != st_palette)
@@ -1945,7 +1945,7 @@ static void ST_drawNiGHTSHUD(void)
 		total_ringcount = stplyr->spheres;
 	}
 
-	if (stplyr->capsule)
+	if (!P_MobjWasRemoved(stplyr->capsule))
 	{
 		INT32 amount;
 		const INT32 length = 88;
@@ -2893,7 +2893,7 @@ void ST_Drawer(void)
 	//25/08/99: Hurdler: palette changes is done for all players,
 	//                   not only player1! That's why this part
 	//                   of code is moved somewhere else.
-	if (rendermode == render_soft)
+	if (rendermode == render_soft || HWR_ShouldUsePaletteRendering())
 #endif
 		if (rendermode != render_none) ST_doPaletteStuff();
 
diff --git a/src/w_wad.c b/src/w_wad.c
index 3a50646930e984762ffb720f2c2022edac075e33..0666c4a600bcf20678f88539fb4055a0d5466992 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -821,10 +821,7 @@ static void W_ReadFileShaders(wadfile_t *wadfile)
 {
 #ifdef HWRENDER
 	if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
-	{
 		HWR_LoadCustomShadersFromFile(numwadfiles - 1, W_FileHasFolders(wadfile));
-		HWR_CompileShaders();
-	}
 #else
 	(void)wadfile;
 #endif
@@ -1171,6 +1168,7 @@ UINT16 W_InitFolder(const char *path, boolean mainfile, boolean startup)
 	numwadfiles++;
 
 	W_ReadFileShaders(wadfile);
+	W_LoadTrnslateLumps(numwadfiles - 1);
 	W_LoadDehackedLumpsPK3(numwadfiles - 1, mainfile);
 	W_InvalidateLumpnumCache();
 
@@ -2707,7 +2705,7 @@ virtres_t* vres_GetMap(lumpnum_t lumpnum)
 		UINT8 *wadData = W_CacheLumpNum(lumpnum, PU_LEVEL);
 		filelump_t *fileinfo = (filelump_t *)(wadData + ((wadinfo_t *)wadData)->infotableofs);
 		numlumps = ((wadinfo_t *)wadData)->numlumps;
-		vlumps = Z_Malloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL);
+		vlumps = Z_Calloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL);
 
 		// Build the lumps.
 		for (i = 0; i < numlumps; i++)
@@ -2731,7 +2729,7 @@ virtres_t* vres_GetMap(lumpnum_t lumpnum)
 				break;
 		numlumps++;
 
-		vlumps = Z_Malloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL);
+		vlumps = Z_Calloc(sizeof(virtlump_t)*numlumps, PU_LEVEL, NULL);
 		for (i = 0; i < numlumps; i++, lumpnum++)
 		{
 			vlumps[i].size = W_LumpLength(lumpnum);