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/android/i_video.c b/src/android/i_video.c
index bf0decb74118385ff2b776d8d470e5ea3a03a2ba..896a5b899c008197df570376d2e0c406a1a8351e 100644
--- a/src/android/i_video.c
+++ b/src/android/i_video.c
@@ -11,8 +11,6 @@
 rendermode_t rendermode = render_soft;
 rendermode_t chosenrendermode = render_none;
 
-boolean highcolor = false;
-
 boolean allow_fullscreen = false;
 
 
diff --git a/src/console.c b/src/console.c
index 4143e5e066b78e320a22e60b3f0ecb9712f848e3..5ef0ce919be356655ac52ab9d7e7e9a9e63c3783 100644
--- a/src/console.c
+++ b/src/console.c
@@ -70,14 +70,9 @@ static boolean consoleready;  // console prompt is ready
        INT32 con_destlines; // vid lines used by console at final position
 static INT32 con_curlines;  // vid lines currently used by console
 
-       INT32 con_clipviewtop; // (useless)
-
 static UINT8  con_hudlines;             // number of console heads up message lines
 static UINT32 con_hudtime[MAXHUDLINES]; // remaining time of display for hud msg lines
 
-       INT32 con_clearlines;      // top screen lines to refresh when view reduced
-       boolean con_hudupdate;   // when messages scroll, we need a backgrnd refresh
-
 // console text output
 static char *con_line;          // console text output current line
 static size_t con_cx;           // cursor position in current line
@@ -120,7 +115,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];
@@ -473,9 +468,6 @@ void CON_Init(void)
 
 	Lock_state();
 
-	//note: CON_Ticker should always execute at least once before D_Display()
-	con_clipviewtop = -1; // -1 does not clip
-
 	con_hudlines = atoi(cons_hudlines.defaultvalue);
 
 	Unlock_state();
@@ -751,7 +743,6 @@ void CON_ToggleOff(void)
 	con_curlines = 0;
 	CON_ClearHUD();
 	con_forcepic = 0;
-	con_clipviewtop = -1; // remove console clipping of view
 
 	I_UpdateMouseGrab();
 
@@ -800,18 +791,6 @@ void CON_Ticker(void)
 			CON_ChangeHeight();
 	}
 
-	// clip the view, so that the part under the console is not drawn
-	con_clipviewtop = -1;
-	if (cons_backpic.value) // clip only when using an opaque background
-	{
-		if (con_curlines > 0)
-			con_clipviewtop = con_curlines - viewwindowy - 1 - 10;
-		// NOTE: BIG HACK::SUBTRACT 10, SO THAT WATER DON'T COPY LINES OF THE CONSOLE
-		//       WINDOW!!! (draw some more lines behind the bottom of the console)
-		if (con_clipviewtop < 0)
-			con_clipviewtop = -1; // maybe not necessary, provided it's < 0
-	}
-
 	// check if console ready for prompt
 	if (con_destlines >= minheight)
 		consoleready = true;
@@ -1358,9 +1337,6 @@ static void CON_Linefeed(void)
 
 	con_line = &con_buffer[(con_cy%con_totallines)*con_width];
 	memset(con_line, ' ', con_width);
-
-	// make sure the view borders are refreshed if hud messages scroll
-	con_hudupdate = true; // see HU_Erase()
 }
 
 // Outputs text into the console text buffer
@@ -1749,9 +1725,6 @@ static void CON_DrawHudlines(void)
 		//V_DrawCharacter(x, y, (p[c]&0xff) | cv_constextsize.value | V_NOSCALESTART, true);
 		y += charheight;
 	}
-
-	// top screen lines that might need clearing when view is reduced
-	con_clearlines = y; // this is handled by HU_Erase();
 }
 
 // Lactozilla: Draws the console's background picture.
@@ -1817,10 +1790,6 @@ static void CON_DrawConsole(void)
 	if (con_curlines <= 0)
 		return;
 
-	//FIXME: refresh borders only when console bg is translucent
-	con_clearlines = con_curlines; // clear console draw from view borders
-	con_hudupdate = true; // always refresh while console is on
-
 	// draw console background
 	if (cons_backpic.value || con_forcepic)
 		CON_DrawBackpic();
diff --git a/src/console.h b/src/console.h
index f22f8dcbc18e595b6fb57b942bca8c0eb3cf15e2..6ad1dba1e458c19d47bb2599f9d3a87ce0fb17c1 100644
--- a/src/console.h
+++ b/src/console.h
@@ -34,14 +34,9 @@ extern boolean con_startup;
 // needs explicit screen refresh until we are in the main game loop
 extern boolean con_refresh;
 
-// top clip value for view render: do not draw part of view hidden by console
-extern INT32 con_clipviewtop;
-
 // 0 means console if off, or moving out
 extern INT32 con_destlines;
 
-extern INT32 con_clearlines; // lines of top of screen to refresh
-extern boolean con_hudupdate; // hud messages have changed, need refresh
 extern UINT32 con_scalefactor; // console text scale factor
 
 extern consvar_t cons_backcolor;
diff --git a/src/d_main.c b/src/d_main.c
index 83cb425c98bd76b8b931c91db2e1699682c20a0e..3886ad1ad157a8957eec081c7a38a7f901d19714 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -408,13 +408,11 @@ static void D_Display(void)
 		case GS_LEVEL:
 			if (!gametic)
 				break;
-			HU_Erase();
 			AM_Drawer();
 			break;
 
 		case GS_INTERMISSION:
 			Y_IntermissionDrawer();
-			HU_Erase();
 			HU_Drawer();
 			break;
 
@@ -429,13 +427,11 @@ static void D_Display(void)
 
 		case GS_ENDING:
 			F_EndingDrawer();
-			HU_Erase();
 			HU_Drawer();
 			break;
 
 		case GS_CUTSCENE:
 			F_CutsceneDrawer();
-			HU_Erase();
 			HU_Drawer();
 			break;
 
@@ -445,7 +441,6 @@ static void D_Display(void)
 
 		case GS_EVALUATION:
 			F_GameEvaluationDrawer();
-			HU_Erase();
 			HU_Drawer();
 			break;
 
@@ -455,7 +450,6 @@ static void D_Display(void)
 
 		case GS_CREDITS:
 			F_CreditDrawer();
-			HU_Erase();
 			HU_Drawer();
 			break;
 
@@ -465,7 +459,6 @@ static void D_Display(void)
 			{
 				// I don't think HOM from nothing drawing is independent...
 				F_WaitingPlayersDrawer();
-				HU_Erase();
 				HU_Drawer();
 			}
 		case GS_DEDICATEDSERVER:
@@ -480,8 +473,6 @@ static void D_Display(void)
 	{
 		wipegamestate = gamestate;
 
-		// clean up border stuff
-		// see if the border needs to be initially drawn
 		if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
 		{
 			// draw the view directly
@@ -506,23 +497,21 @@ static void D_Display(void)
 				// render the second screen
 				if (splitscreen && players[secondarydisplayplayer].mo)
 				{
-	#ifdef HWRENDER
-					if (rendermode != render_soft)
+					viewwindowy = vid.height / 2;
+
+#ifdef HWRENDER
+					if (rendermode == render_opengl)
 						HWR_RenderPlayerView(1, &players[secondarydisplayplayer]);
 					else
-	#endif
+#endif
 					if (rendermode != render_none)
 					{
-						viewwindowy = vid.height / 2;
-						M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0]));
-
 						topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
 
 						R_RenderPlayerView(&players[secondarydisplayplayer]);
-
-						viewwindowy = 0;
-						M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0]));
 					}
+
+					viewwindowy = 0;
 				}
 
 				// Image postprocessing effect
@@ -1512,9 +1501,7 @@ void D_SRB2Main(void)
 	G_LoadGameData(clientGamedata);
 	M_CopyGameData(serverGamedata, clientGamedata);
 
-#if defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL)
 	VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen
-#endif
 
 	// set user default mode or mode set at cmdline
 	SCR_CheckDefaultMode();
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/dedicated/i_video.c b/src/dedicated/i_video.c
index bb796b6767a1d55990209ba46cd8245377b013d9..2c998117accb71be25850438bdac2ee6c4db0509 100644
--- a/src/dedicated/i_video.c
+++ b/src/dedicated/i_video.c
@@ -5,8 +5,6 @@
 rendermode_t rendermode = render_none;
 rendermode_t chosenrendermode = render_none;
 
-boolean highcolor = false;
-
 boolean allow_fullscreen = false;
 
 consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", CV_SAVE, CV_OnOff, NULL);
diff --git a/src/doomdef.h b/src/doomdef.h
index 4c843f9e2f07ad58439d2dcd3200726e6d62fa4d..60e7dc2031ba6b6d5b100410d5cc8c410de8c241 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);
@@ -709,13 +709,6 @@ extern int
 /// Experimental attempts at preventing MF_PAPERCOLLISION objects from getting stuck in walls.
 //#define PAPER_COLLISIONCORRECTION
 
-/// FINALLY some real clipping that doesn't make walls dissappear AND speeds the game up
-/// (that was the original comment from SRB2CB, sadly it is a lie and actually slows game down)
-/// on the bright side it fixes some weird issues with translucent walls
-/// \note	SRB2CB port.
-///      	SRB2CB itself ported this from PrBoom+
-#define NEWCLIP
-
 /// OpenGL shaders
 #define GL_SHADERS
 
diff --git a/src/dummy/i_video.c b/src/dummy/i_video.c
index bb796b6767a1d55990209ba46cd8245377b013d9..2c998117accb71be25850438bdac2ee6c4db0509 100644
--- a/src/dummy/i_video.c
+++ b/src/dummy/i_video.c
@@ -5,8 +5,6 @@
 rendermode_t rendermode = render_none;
 rendermode_t chosenrendermode = render_none;
 
-boolean highcolor = false;
-
 boolean allow_fullscreen = false;
 
 consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", CV_SAVE, CV_OnOff, NULL);
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..64f74b1f4e9bae98553b881df46309148344a00a 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;
@@ -997,6 +999,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 +1017,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 +1089,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_clip.c b/src/hardware/hw_clip.c
index 86e0c58d25a72956dd9b10d6950baf10fd731ae1..74268423f5526c8d2c8733775b9f2ca60fb6141e 100644
--- a/src/hardware/hw_clip.c
+++ b/src/hardware/hw_clip.c
@@ -320,16 +320,13 @@ void gld_clipper_Clear(void)
 
 #define RMUL (1.6f/1.333333f)
 
-angle_t gld_FrustumAngle(angle_t tiltangle)
+angle_t gld_FrustumAngle(float render_fov, angle_t tiltangle)
 {
 	double floatangle;
 	angle_t a1;
 
 	float tilt = (float)fabs(((double)(int)tiltangle) / ANG1);
 
-	// NEWCLIP TODO: SRB2CBTODO: make a global render_fov for this function
-
-	float render_fov = FIXED_TO_FLOAT(cv_fov.value);
 	float render_fovratio = (float)BASEVIDWIDTH / (float)BASEVIDHEIGHT; // SRB2CBTODO: NEWCLIPTODO: Is this right?
 	float render_multiplier = 64.0f / render_fovratio / RMUL;
 
diff --git a/src/hardware/hw_clip.h b/src/hardware/hw_clip.h
index 27a2ed1efa2de4b9f6d54278a5052fb03c4a1d46..e3bb4c3193ca0094871f15d89e892a8c769d14d0 100644
--- a/src/hardware/hw_clip.h
+++ b/src/hardware/hw_clip.h
@@ -17,7 +17,7 @@
 boolean gld_clipper_SafeCheckRange(angle_t startAngle, angle_t endAngle);
 void gld_clipper_SafeAddClipRange(angle_t startangle, angle_t endangle);
 void gld_clipper_Clear(void);
-angle_t gld_FrustumAngle(angle_t tiltangle);
+angle_t gld_FrustumAngle(float render_fov, angle_t tiltangle);
 #ifdef HAVE_SPHEREFRUSTRUM
 void gld_FrustrumSetup(void);
 boolean gld_SphereInFrustum(float x, float y, float z, float radius);
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..ddce7d9885d7607e1cabf9bfc5c74ecf6258612b 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -22,7 +22,6 @@
 #include "hw_drv.h"
 
 #include "../m_misc.h" //FIL_WriteFile()
-#include "../r_draw.h" //viewborderlump
 #include "../r_main.h"
 #include "../w_wad.h"
 #include "../z_zone.h"
@@ -30,13 +29,10 @@
 #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
-
-#ifndef O_BINARY
-#define O_BINARY 0
-#endif
+#include "../i_video.h"
 
 #if defined(_MSC_VER)
 #pragma pack(1)
@@ -62,63 +58,6 @@ static UINT8 softwaretranstogl[11]    = {  0, 25, 51, 76,102,127,153,178,204,229
 static UINT8 softwaretranstogl_hi[11] = {  0, 51,102,153,204,255,255,255,255,255,255};
 static UINT8 softwaretranstogl_lo[11] = {  0, 12, 24, 36, 48, 60, 71, 83, 95,111,127};
 
-//
-// -----------------+
-// HWR_DrawPatch    : Draw a 'tile' graphic
-// Notes            : x,y : positions relative to the original Doom resolution
-//                  : textes(console+score) + menus + status bar
-// -----------------+
-void HWR_DrawPatch(patch_t *gpatch, INT32 x, INT32 y, INT32 option)
-{
-	FOutVector v[4];
-	FBITFIELD flags;
-	GLPatch_t *hwrPatch;
-
-//  3--2
-//  | /|
-//  |/ |
-//  0--1
-	float sdup = FIXED_TO_FLOAT(vid.fdup)*2.0f;
-	float pdup = FIXED_TO_FLOAT(vid.fdup)*2.0f;
-
-	// make patch ready in hardware cache
-	HWR_GetPatch(gpatch);
-	hwrPatch = ((GLPatch_t *)gpatch->hardware);
-
-	switch (option & V_SCALEPATCHMASK)
-	{
-	case V_NOSCALEPATCH:
-		pdup = 2.0f;
-		break;
-	case V_SMALLSCALEPATCH:
-		pdup = 2.0f * FIXED_TO_FLOAT(vid.fsmalldup);
-		break;
-	case V_MEDSCALEPATCH:
-		pdup = 2.0f * FIXED_TO_FLOAT(vid.fmeddup);
-		break;
-	}
-
-	if (option & V_NOSCALESTART)
-		sdup = 2.0f;
-
-	v[0].x = v[3].x = (x*sdup-(gpatch->leftoffset)*pdup)/vid.width - 1;
-	v[2].x = v[1].x = (x*sdup+(gpatch->width-gpatch->leftoffset)*pdup)/vid.width - 1;
-	v[0].y = v[1].y = 1-(y*sdup-(gpatch->topoffset)*pdup)/vid.height;
-	v[2].y = v[3].y = 1-(y*sdup+(gpatch->height-gpatch->topoffset)*pdup)/vid.height;
-
-	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
-
-	v[0].s = v[3].s = 0.0f;
-	v[2].s = v[1].s = hwrPatch->max_s;
-	v[0].t = v[1].t = 0.0f;
-	v[2].t = v[3].t = hwrPatch->max_t;
-
-	flags = PF_Translucent|PF_NoDepthTest;
-
-	// clip it since it is used for bunny scroll in doom I
-	HWD.pfnDrawPolygon(NULL, v, 4, flags);
-}
-
 void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap)
 {
 	FOutVector v[4];
@@ -707,6 +646,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 +659,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 +879,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);
@@ -960,136 +943,6 @@ void HWR_DrawTutorialBack(UINT32 color, INT32 boxheight)
 	HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest);
 }
 
-
-// ==========================================================================
-//                                                             R_DRAW.C STUFF
-// ==========================================================================
-
-// ------------------
-// HWR_DrawViewBorder
-// Fill the space around the view window with a Doom flat texture, draw the
-// beveled edges.
-// 'clearlines' is useful to clear the heads up messages, when the view
-// window is reduced, it doesn't refresh all the view borders.
-// ------------------
-void HWR_DrawViewBorder(INT32 clearlines)
-{
-	INT32 x, y;
-	INT32 top, side;
-	INT32 baseviewwidth, baseviewheight;
-	INT32 basewindowx, basewindowy;
-	patch_t *patch;
-
-//    if (gl_viewwidth == vid.width)
-//        return;
-
-	if (!clearlines)
-		clearlines = BASEVIDHEIGHT; // refresh all
-
-	// calc view size based on original game resolution
-	baseviewwidth =  FixedInt(FixedDiv(FLOAT_TO_FIXED(gl_viewwidth), vid.fdup)); //(cv_viewsize.value * BASEVIDWIDTH/10)&~7;
-	baseviewheight = FixedInt(FixedDiv(FLOAT_TO_FIXED(gl_viewheight), vid.fdup));
-	top = FixedInt(FixedDiv(FLOAT_TO_FIXED(gl_baseviewwindowy), vid.fdup));
-	side = FixedInt(FixedDiv(FLOAT_TO_FIXED(gl_viewwindowx), vid.fdup));
-
-	// top
-	HWR_DrawFlatFill(0, 0,
-		BASEVIDWIDTH, (top < clearlines ? top : clearlines),
-		st_borderpatchnum);
-
-	// left
-	if (top < clearlines)
-		HWR_DrawFlatFill(0, top, side,
-			(clearlines-top < baseviewheight ? clearlines-top : baseviewheight),
-			st_borderpatchnum);
-
-	// right
-	if (top < clearlines)
-		HWR_DrawFlatFill(side + baseviewwidth, top, side,
-			(clearlines-top < baseviewheight ? clearlines-top : baseviewheight),
-			st_borderpatchnum);
-
-	// bottom
-	if (top + baseviewheight < clearlines)
-		HWR_DrawFlatFill(0, top + baseviewheight,
-			BASEVIDWIDTH, BASEVIDHEIGHT, st_borderpatchnum);
-
-	//
-	// draw the view borders
-	//
-
-	basewindowx = (BASEVIDWIDTH - baseviewwidth)>>1;
-	if (baseviewwidth == BASEVIDWIDTH)
-		basewindowy = 0;
-	else
-		basewindowy = top;
-
-	// top edge
-	if (clearlines > basewindowy - 8)
-	{
-		patch = W_CachePatchNum(viewborderlump[BRDR_T], PU_PATCH);
-		for (x = 0; x < baseviewwidth; x += 8)
-			HWR_DrawPatch(patch, basewindowx + x, basewindowy - 8,
-				0);
-	}
-
-	// bottom edge
-	if (clearlines > basewindowy + baseviewheight)
-	{
-		patch = W_CachePatchNum(viewborderlump[BRDR_B], PU_PATCH);
-		for (x = 0; x < baseviewwidth; x += 8)
-			HWR_DrawPatch(patch, basewindowx + x,
-				basewindowy + baseviewheight, 0);
-	}
-
-	// left edge
-	if (clearlines > basewindowy)
-	{
-		patch = W_CachePatchNum(viewborderlump[BRDR_L], PU_PATCH);
-		for (y = 0; y < baseviewheight && basewindowy + y < clearlines;
-			y += 8)
-		{
-			HWR_DrawPatch(patch, basewindowx - 8, basewindowy + y,
-				0);
-		}
-	}
-
-	// right edge
-	if (clearlines > basewindowy)
-	{
-		patch = W_CachePatchNum(viewborderlump[BRDR_R], PU_PATCH);
-		for (y = 0; y < baseviewheight && basewindowy+y < clearlines;
-			y += 8)
-		{
-			HWR_DrawPatch(patch, basewindowx + baseviewwidth,
-				basewindowy + y, 0);
-		}
-	}
-
-	// Draw beveled corners.
-	if (clearlines > basewindowy - 8)
-		HWR_DrawPatch(W_CachePatchNum(viewborderlump[BRDR_TL],
-				PU_PATCH),
-			basewindowx - 8, basewindowy - 8, 0);
-
-	if (clearlines > basewindowy - 8)
-		HWR_DrawPatch(W_CachePatchNum(viewborderlump[BRDR_TR],
-				PU_PATCH),
-			basewindowx + baseviewwidth, basewindowy - 8, 0);
-
-	if (clearlines > basewindowy+baseviewheight)
-		HWR_DrawPatch(W_CachePatchNum(viewborderlump[BRDR_BL],
-				PU_PATCH),
-			basewindowx - 8, basewindowy + baseviewheight, 0);
-
-	if (clearlines > basewindowy + baseviewheight)
-		HWR_DrawPatch(W_CachePatchNum(viewborderlump[BRDR_BR],
-				PU_PATCH),
-			basewindowx + baseviewwidth,
-			basewindowy + baseviewheight, 0);
-}
-
-
 // ==========================================================================
 //                                                     AM_MAP.C DRAWING STUFF
 // ==========================================================================
@@ -1102,8 +955,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 +1142,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 +1229,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 +1306,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)
 	{
@@ -1499,7 +1354,7 @@ static inline boolean saveTGA(const char *file_name, void *buffer,
 	INT32 i;
 	UINT8 *buf8 = buffer;
 
-	fd = open(file_name, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
+	fd = open(file_name, O_WRONLY | O_CREAT | O_TRUNC, 0666);
 	if (fd < 0)
 		return false;
 
@@ -1539,11 +1394,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 +1407,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 +1416,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..45ee2933a69bca328be875ec2e449ca7f74cab39 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -29,10 +29,7 @@ EXPORT boolean HWRAPI(Init) (void);
 #ifndef HAVE_SDL
 EXPORT void HWRAPI(Shutdown) (void);
 #endif
-#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 +40,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 +53,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 +83,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 +94,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 +110,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..e96ea6c7832e2dda5bb03c73b67981d35f1459e4 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);
@@ -117,7 +119,6 @@ patch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum);
 void HWR_GetPatch(patch_t *patch);
 void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap);
 void HWR_GetFadeMask(lumpnum_t fademasklumpnum);
-patch_t *HWR_GetPic(lumpnum_t lumpnum);
 
 GLMapTexture_t *HWR_GetTexture(INT32 tex);
 void HWR_GetLevelFlat(levelflat_t *levelflat);
@@ -131,6 +132,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 +144,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_light.c b/src/hardware/hw_light.c
index bcfdfa960d7b723ecb0be2437667bcf09b1030e9..e7769edbd8da2dbaa38debfabae9a34f25d121ee 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -1055,7 +1055,7 @@ void HWR_DoCoronasLighting(FOutVector *outVerts, gl_vissprite_t *spr)
 		light[3].y = cy+size*1.33f+p_lspr->light_yoffset;
 		light[3].s = 0.0f;   light[3].t = 1.0f;
 
-		HWR_GetPic(coronalumpnum);  /// \todo use different coronas
+		// HWR_GetPic(coronalumpnum);  /// \todo use different coronas
 
 		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_Additive | PF_Corona | PF_NoDepthTest);
 	}
@@ -1071,7 +1071,7 @@ void HWR_DrawCoronas(void)
 	if (!cv_glcoronas.value || dynlights->nb <= 0 || coronalumpnum == LUMPERROR)
 		return;
 
-	HWR_GetPic(coronalumpnum);  /// \todo use different coronas
+	// HWR_GetPic(coronalumpnum);  /// \todo use different coronas
 	for (j = 0;j < dynlights->nb;j++)
 	{
 		FOutVector      light[4];
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 8c4651c040a174328d5ca4bc7aaee5f12f512233..0ec4536ce3c716b0835328ddc4fbe717d4ac0b53 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -19,8 +19,10 @@
 #include "hw_light.h"
 #include "hw_drv.h"
 #include "hw_batching.h"
+#include "hw_md2.h"
+#include "hw_clip.h"
 
-#include "../i_video.h" // for rendermode == render_glide
+#include "../i_video.h"
 #include "../v_video.h"
 #include "../p_local.h"
 #include "../p_setup.h"
@@ -42,15 +44,6 @@
 #include "../r_translation.h"
 #include "../d_main.h"
 #include "../p_slopes.h"
-#include "hw_md2.h"
-
-#ifdef NEWCLIP
-#include "hw_clip.h"
-#endif
-
-#define R_FAKEFLOORS
-#define HWPRECIP
-//#define POLYSKY
 
 // ==========================================================================
 // the hardware driver object
@@ -61,95 +54,27 @@ struct hwdriver_s hwdriver;
 //                                                                     PROTOS
 // ==========================================================================
 
-
 static void HWR_AddSprites(sector_t *sec);
 static void HWR_ProjectSprite(mobj_t *thing);
-#ifdef HWPRECIP
 static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing);
-#endif
 static void HWR_ProjectBoundingBox(mobj_t *thing);
 
 void HWR_AddTransparentFloor(levelflat_t *levelflat, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap);
 void HWR_AddTransparentPolyobjectFloor(levelflat_t *levelflat, polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
                              INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, extracolormap_t *planecolormap);
 
-boolean drawsky = true;
-
-// ==========================================================================
-//                                                               VIEW GLOBALS
-// ==========================================================================
-// Fineangles in the SCREENWIDTH wide window.
-#define FIELDOFVIEW ANGLE_90
-#define ABS(x) ((x) < 0 ? -(x) : (x))
-
-static angle_t gl_clipangle;
-
-// The viewangletox[viewangle + FINEANGLES/4] lookup
-// maps the visible view angles to screen X coordinates,
-// flattening the arc to a flat projection plane.
-// There will be many angles mapped to the same X.
-static INT32 gl_viewangletox[FINEANGLES/2];
-
-// The xtoviewangleangle[] table maps a screen pixel
-// to the lowest viewangle that maps back to x ranges
-// from clipangle to -clipangle.
-static angle_t gl_xtoviewangle[MAXVIDWIDTH+1];
+static boolean drawsky = true;
 
 // ==========================================================================
 //                                                                    GLOBALS
 // ==========================================================================
 
-// uncomment to remove the plane rendering
-#define DOPLANES
-//#define DOWALLS
-
-// test of drawing sky by polygons like in software with visplane, unfortunately
-// this doesn't work since we must have z for pixel and z for texture (not like now with z = oow)
-//#define POLYSKY
-
-// test change fov when looking up/down but bsp projection messup :(
-//#define NOCRAPPYMLOOK
-
-// base values set at SetViewSize
-static float gl_basecentery;
-
-float gl_baseviewwindowy, gl_basewindowcentery;
-float gl_viewwidth, gl_viewheight; // viewport clipping boundaries (screen coords)
-float gl_viewwindowx;
-
-static float gl_centerx, gl_centery;
-static float gl_viewwindowy; // top left corner of view window
-static float gl_windowcenterx; // center of view window, for projection
-static float gl_windowcentery;
-
-static float gl_pspritexscale, gl_pspriteyscale;
-
 static seg_t *gl_curline;
 static side_t *gl_sidedef;
 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 +95,35 @@ 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;
+
+static float gl_viewx, gl_viewy, gl_viewz;
+float gl_viewsin, gl_viewcos;
+
+// For HWR_RotateSpritePolyToAim
+static float gl_viewludsin, gl_viewludcos;
+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 +189,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
@@ -361,8 +313,6 @@ static FUINT HWR_CalcSlopeLight(FUINT lightnum, angle_t dir, fixed_t delta)
 //                                   FLOOR/CEILING GENERATION FROM SUBSECTORS
 // ==========================================================================
 
-#ifdef DOPLANES
-
 // -----------------+
 // HWR_RenderPlane  : Render a floor or ceiling convex polygon
 // -----------------+
@@ -372,7 +322,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 +483,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)
@@ -551,7 +504,7 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 
 		for (i = 0; i < subsector->numlines; i++, line++)
 		{
-			if (!line->glseg && line->linedef->special == SPECIAL_HORIZON_LINE && R_PointOnSegSide(dup_viewx, dup_viewy, line) == 0)
+			if (!line->glseg && line->linedef->special == SPECIAL_HORIZON_LINE && R_PointOnSegSide(viewx, viewy, line) == 0)
 			{
 				P_ClosestPointOnLine(viewx, viewy, line->linedef, &v);
 				dist = FIXED_TO_FLOAT(R_PointToDist(v.x, v.y));
@@ -630,52 +583,6 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 #endif
 }
 
-#ifdef POLYSKY
-// this don't draw anything it only update the z-buffer so there isn't problem with
-// wall/things upper that sky (map12)
-static void HWR_RenderSkyPlane(extrasubsector_t *xsub, fixed_t fixedheight)
-{
-	polyvertex_t *pv;
-	float height; //constant y for all points on the convex flat polygon
-	FOutVector *v3d;
-	INT32 nrPlaneVerts;   //verts original define of convex flat polygon
-	INT32 i;
-
-	// no convex poly were generated for this subsector
-	if (!xsub->planepoly)
-		return;
-
-	height = FIXED_TO_FLOAT(fixedheight);
-
-	pv  = xsub->planepoly->pts;
-	nrPlaneVerts = xsub->planepoly->numpts;
-
-	if (nrPlaneVerts < 3) // not even a triangle?
-		return;
-
-	if (nrPlaneVerts > MAXPLANEVERTICES) // FIXME: exceeds plVerts size
-	{
-		CONS_Debug(DBG_RENDER, "polygon size of %d exceeds max value of %d vertices\n", nrPlaneVerts, MAXPLANEVERTICES);
-		return;
-	}
-
-	// transform
-	v3d = planeVerts;
-	for (i = 0; i < nrPlaneVerts; i++,v3d++,pv++)
-	{
-		v3d->s = 0.0f;
-		v3d->t = 0.0f;
-		v3d->x = pv->x;
-		v3d->y = height;
-		v3d->z = pv->y;
-	}
-
-	HWD.pfnDrawPolygon(NULL, planeVerts, nrPlaneVerts, PF_Invisible|PF_NoTexture|PF_Occlude);
-}
-#endif //polysky
-
-#endif //doplanes
-
 FBITFIELD HWR_GetBlendModeFlag(INT32 style)
 {
 	switch (style)
@@ -759,7 +666,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);
 
@@ -776,41 +686,6 @@ static void HWR_ProjectWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIEL
 //                                                          BSP, CULL, ETC..
 // ==========================================================================
 
-// return the frac from the interception of the clipping line
-// (in fact a clipping plane that has a constant, so can clip with simple 2d)
-// with the wall segment
-//
-#ifndef NEWCLIP
-static float HWR_ClipViewSegment(INT32 x, polyvertex_t *v1, polyvertex_t *v2)
-{
-	float num, den;
-	float v1x, v1y, v1dx, v1dy, v2dx, v2dy;
-	angle_t pclipangle = gl_xtoviewangle[x];
-
-	// a segment of a polygon
-	v1x  = v1->x;
-	v1y  = v1->y;
-	v1dx = (v2->x - v1->x);
-	v1dy = (v2->y - v1->y);
-
-	// the clipping line
-	pclipangle = pclipangle + dup_viewangle; //back to normal angle (non-relative)
-	v2dx = FIXED_TO_FLOAT(FINECOSINE(pclipangle>>ANGLETOFINESHIFT));
-	v2dy = FIXED_TO_FLOAT(FINESINE(pclipangle>>ANGLETOFINESHIFT));
-
-	den = v2dy*v1dx - v2dx*v1dy;
-	if (den == 0)
-		return -1; // parallel
-
-	// calc the frac along the polygon segment,
-	//num = (v2x - v1x)*v2dy + (v1y - v2y)*v2dx;
-	//num = -v1x * v2dy + v1y * v2dx;
-	num = (gl_viewx - v1x)*v2dy + (v1y - gl_viewy)*v2dx;
-
-	return num / den;
-}
-#endif
-
 // SoM: split up and light walls according to the lightlist.
 // This may also include leaving out parts of the wall that can't be seen
 static void HWR_SplitWall(sector_t *sector, FOutVector *wallVerts, INT32 texnum, FSurfaceInfo* Surf, INT32 cutflag, ffloor_t *pfloor, FBITFIELD polyflags)
@@ -834,6 +709,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 +750,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);
 			}
 		}
 
@@ -998,7 +878,7 @@ static void HWR_DrawSkyWall(FOutVector *wallVerts, FSurfaceInfo *Surf)
 	// PF_Occlude is set in HWR_ProjectWall to draw into the depth buffer
 }
 
-// Returns true if the midtexture is visible, and false if... it isn't...
+// Returns true if the midtexture is visible, false if not
 static boolean HWR_BlendMidtextureSurface(FSurfaceInfo *pSurf)
 {
 	FUINT blendmode = PF_Masked;
@@ -1219,14 +1099,8 @@ static void HWR_RenderMidtexture(INT32 gl_midtexture, float cliplow, float cliph
 		HWR_ProjectWall(wallVerts, &Surf, blendmode, lightnum, colormap);
 }
 
-//
-// HWR_ProcessSeg
-// A portion or all of a wall segment will be drawn, from startfrac to endfrac,
-//  where 0 is the start of the segment, 1 the end of the segment
-// Anything between means the wall segment has been clipped with solidsegs,
-//  reducing wall overdraw to a minimum
-//
-static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
+// Sort of like GLWall::Process in GZDoom
+static void HWR_ProcessSeg(void)
 {
 	FOutVector wallVerts[4];
 	v2d_t vs, ve; // start, end vertices of 2d line (view from above)
@@ -1293,8 +1167,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 	float cliplow = (float)gl_curline->offset;
 	float cliphigh = cliplow + (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;
@@ -1753,8 +1628,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);
 
@@ -1909,8 +1785,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);
 
@@ -1950,8 +1827,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 //
 // e6y: Check whether the player can look beyond this line
 //
-#ifdef NEWCLIP
-boolean checkforemptylines = true;
+static boolean checkforemptylines = true;
+
 // Don't modify anything here, just check
 // Kalaron: Modified for sloped linedefs
 static boolean CheckClip(seg_t * seg, sector_t * afrontsector, sector_t * abacksector)
@@ -2043,295 +1920,6 @@ static boolean CheckClip(seg_t * seg, sector_t * afrontsector, sector_t * abacks
 
 	return false;
 }
-#else
-//Hurdler: just like in r_bsp.c
-#if 1
-#define MAXSEGS         MAXVIDWIDTH/2+1
-#else
-//Alam_GBC: Or not (may cause overflow)
-#define MAXSEGS         128
-#endif
-
-// hw_newend is one past the last valid seg
-static cliprange_t *   hw_newend;
-static cliprange_t     gl_solidsegs[MAXSEGS];
-
-// needs fix: walls are incorrectly clipped one column less
-static consvar_t cv_glclipwalls = CVAR_INIT ("gr_clipwalls", "Off", 0, CV_OnOff, NULL);
-
-static void printsolidsegs(void)
-{
-	cliprange_t *       start;
-	if (!hw_newend)
-		return;
-	for (start = gl_solidsegs;start != hw_newend;start++)
-	{
-		CONS_Debug(DBG_RENDER, "%d-%d|",start->first,start->last);
-	}
-	CONS_Debug(DBG_RENDER, "\n\n");
-}
-
-//
-//
-//
-static void HWR_ClipSolidWallSegment(INT32 first, INT32 last)
-{
-	cliprange_t *next, *start;
-	float lowfrac, highfrac;
-	boolean poorhack = false;
-
-	// Find the first range that touches the range
-	//  (adjacent pixels are touching).
-	start = gl_solidsegs;
-	while (start->last < first-1)
-		start++;
-
-	if (first < start->first)
-	{
-		if (last < start->first-1)
-		{
-			// Post is entirely visible (above start),
-			//  so insert a new clippost.
-			HWR_StoreWallRange(first, last);
-
-			next = hw_newend;
-			hw_newend++;
-
-			while (next != start)
-			{
-				*next = *(next-1);
-				next--;
-			}
-
-			next->first = first;
-			next->last = last;
-			printsolidsegs();
-			return;
-		}
-
-		// There is a fragment above *start.
-		if (!cv_glclipwalls.value)
-		{
-			if (!poorhack) HWR_StoreWallRange(first, last);
-			poorhack = true;
-		}
-		else
-		{
-			highfrac = HWR_ClipViewSegment(start->first+1, (polyvertex_t *)gl_curline->pv1, (polyvertex_t *)gl_curline->pv2);
-			HWR_StoreWallRange(0, highfrac);
-		}
-		// Now adjust the clip size.
-		start->first = first;
-	}
-
-	// Bottom contained in start?
-	if (last <= start->last)
-	{
-		printsolidsegs();
-		return;
-	}
-	next = start;
-	while (last >= (next+1)->first-1)
-	{
-		// There is a fragment between two posts.
-		if (!cv_glclipwalls.value)
-		{
-			if (!poorhack) HWR_StoreWallRange(first,last);
-			poorhack = true;
-		}
-		else
-		{
-			lowfrac  = HWR_ClipViewSegment(next->last-1, (polyvertex_t *)gl_curline->pv1, (polyvertex_t *)gl_curline->pv2);
-			highfrac = HWR_ClipViewSegment((next+1)->first+1, (polyvertex_t *)gl_curline->pv1, (polyvertex_t *)gl_curline->pv2);
-			HWR_StoreWallRange(lowfrac, highfrac);
-		}
-		next++;
-
-		if (last <= next->last)
-		{
-			// Bottom is contained in next.
-			// Adjust the clip size.
-			start->last = next->last;
-			goto crunch;
-		}
-	}
-
-	if (first == next->first+1) // 1 line texture
-	{
-		if (!cv_glclipwalls.value)
-		{
-			if (!poorhack) HWR_StoreWallRange(first,last);
-			poorhack = true;
-		}
-		else
-			HWR_StoreWallRange(0, 1);
-	}
-	else
-	{
-	// There is a fragment after *next.
-		if (!cv_glclipwalls.value)
-		{
-			if (!poorhack) HWR_StoreWallRange(first,last);
-			poorhack = true;
-		}
-		else
-		{
-			lowfrac  = HWR_ClipViewSegment(next->last-1, (polyvertex_t *)gl_curline->pv1, (polyvertex_t *)gl_curline->pv2);
-			HWR_StoreWallRange(lowfrac, 1);
-		}
-	}
-
-	// Adjust the clip size.
-	start->last = last;
-
-	// Remove start+1 to next from the clip list,
-	// because start now covers their area.
-crunch:
-	if (next == start)
-	{
-		printsolidsegs();
-		// Post just extended past the bottom of one post.
-		return;
-	}
-
-
-	while (next++ != hw_newend)
-	{
-		// Remove a post.
-		*++start = *next;
-	}
-
-	hw_newend = start;
-	printsolidsegs();
-}
-
-//
-//  handle LineDefs with upper and lower texture (windows)
-//
-static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
-{
-	cliprange_t *start;
-	float lowfrac, highfrac;
-	//to allow noclipwalls but still solidseg reject of non-visible walls
-	boolean poorhack = false;
-
-	// Find the first range that touches the range
-	//  (adjacent pixels are touching).
-	start = gl_solidsegs;
-	while (start->last < first - 1)
-		start++;
-
-	if (first < start->first)
-	{
-		if (last < start->first-1)
-		{
-			// Post is entirely visible (above start).
-			HWR_StoreWallRange(0, 1);
-			return;
-		}
-
-		// There is a fragment above *start.
-		if (!cv_glclipwalls.value)
-		{	//20/08/99: Changed by Hurdler (taken from faB's code)
-			if (!poorhack) HWR_StoreWallRange(0, 1);
-			poorhack = true;
-		}
-		else
-		{
-			highfrac = HWR_ClipViewSegment(min(start->first + 1,
-				start->last), (polyvertex_t *)gl_curline->pv1,
-				(polyvertex_t *)gl_curline->pv2);
-			HWR_StoreWallRange(0, highfrac);
-		}
-	}
-
-	// Bottom contained in start?
-	if (last <= start->last)
-		return;
-
-	while (last >= (start+1)->first-1)
-	{
-		// There is a fragment between two posts.
-		if (!cv_glclipwalls.value)
-		{
-			if (!poorhack) HWR_StoreWallRange(0, 1);
-			poorhack = true;
-		}
-		else
-		{
-			lowfrac  = HWR_ClipViewSegment(max(start->last-1,start->first), (polyvertex_t *)gl_curline->pv1, (polyvertex_t *)gl_curline->pv2);
-			highfrac = HWR_ClipViewSegment(min((start+1)->first+1,(start+1)->last), (polyvertex_t *)gl_curline->pv1, (polyvertex_t *)gl_curline->pv2);
-			HWR_StoreWallRange(lowfrac, highfrac);
-		}
-		start++;
-
-		if (last <= start->last)
-			return;
-	}
-
-	if (first == start->first+1) // 1 line texture
-	{
-		if (!cv_glclipwalls.value)
-		{
-			if (!poorhack) HWR_StoreWallRange(0, 1);
-			poorhack = true;
-		}
-		else
-			HWR_StoreWallRange(0, 1);
-	}
-	else
-	{
-		// There is a fragment after *next.
-		if (!cv_glclipwalls.value)
-		{
-			if (!poorhack) HWR_StoreWallRange(0,1);
-			poorhack = true;
-		}
-		else
-		{
-			lowfrac = HWR_ClipViewSegment(max(start->last - 1,
-				start->first), (polyvertex_t *)gl_curline->pv1,
-				(polyvertex_t *)gl_curline->pv2);
-			HWR_StoreWallRange(lowfrac, 1);
-		}
-	}
-}
-
-// --------------------------------------------------------------------------
-//  HWR_ClipToSolidSegs check if it is hide by wall (solidsegs)
-// --------------------------------------------------------------------------
-static boolean HWR_ClipToSolidSegs(INT32 first, INT32 last)
-{
-	cliprange_t * start;
-
-	// Find the first range that touches the range
-	//  (adjacent pixels are touching).
-	start = gl_solidsegs;
-	while (start->last < first-1)
-		start++;
-
-	if (first < start->first)
-		return true;
-
-	// Bottom contained in start?
-	if (last <= start->last)
-		return false;
-
-	return true;
-}
-
-//
-// HWR_ClearClipSegs
-//
-static void HWR_ClearClipSegs(void)
-{
-	gl_solidsegs[0].first = -0x7fffffff;
-	gl_solidsegs[0].last = -1;
-	gl_solidsegs[1].first = vid.width; //viewwidth;
-	gl_solidsegs[1].last = 0x7fffffff;
-	hw_newend = gl_solidsegs+2;
-}
-#endif // NEWCLIP
 
 // -----------------+
 // HWR_AddLine      : Clips the given segment and adds any visible pieces to the line list.
@@ -2341,11 +1929,6 @@ static void HWR_ClearClipSegs(void)
 static void HWR_AddLine(seg_t * line)
 {
 	angle_t angle1, angle2;
-#ifndef NEWCLIP
-	INT32 x1, x2;
-	angle_t span, tspan;
-	boolean bothceilingssky = false, bothfloorssky = false;
-#endif
 
 	// SoM: Backsector needs to be run through R_FakeFlat
 	static sector_t tempsec;
@@ -2381,8 +1964,7 @@ static void HWR_AddLine(seg_t * line)
 	angle1 = R_PointToAngle64(v1x, v1y);
 	angle2 = R_PointToAngle64(v2x, v2y);
 
-#ifdef NEWCLIP
-	 // PrBoom: Back side, i.e. backface culling - read: endAngle >= startAngle!
+	// PrBoom: Back side, i.e. backface culling - read: endAngle >= startAngle!
 	if (angle2 - angle1 < ANGLE_180)
 		return;
 
@@ -2394,91 +1976,10 @@ static void HWR_AddLine(seg_t * line)
     }
 
 	checkforemptylines = true;
-#else
-	// Clip to view edges.
-	span = angle1 - angle2;
-
-	// backface culling : span is < ANGLE_180 if ang1 > ang2 : the seg is facing
-	if (span >= ANGLE_180)
-		return;
-
-	// Global angle needed by segcalc.
-	//rw_angle1 = angle1;
-	angle1 -= dup_viewangle;
-	angle2 -= dup_viewangle;
-
-	tspan = angle1 + gl_clipangle;
-	if (tspan > 2*gl_clipangle)
-	{
-		tspan -= 2*gl_clipangle;
-
-		// Totally off the left edge?
-		if (tspan >= span)
-			return;
-
-		angle1 = gl_clipangle;
-	}
-	tspan = gl_clipangle - angle2;
-	if (tspan > 2*gl_clipangle)
-	{
-		tspan -= 2*gl_clipangle;
-
-		// Totally off the left edge?
-		if (tspan >= span)
-			return;
-
-		angle2 = (angle_t)-(signed)gl_clipangle;
-	}
-
-#if 0
-	{
-		float fx1,fx2,fy1,fy2;
-		//BP: test with a better projection than viewangletox[R_PointToAngle(angle)]
-		// do not enable this at release 4 mul and 2 div
-		fx1 = ((polyvertex_t *)(line->pv1))->x-gl_viewx;
-		fy1 = ((polyvertex_t *)(line->pv1))->y-gl_viewy;
-		fy2 = (fx1 * gl_viewcos + fy1 * gl_viewsin);
-		if (fy2 < 0)
-			// the point is back
-			fx1 = 0;
-		else
-			fx1 = gl_windowcenterx + (fx1 * gl_viewsin - fy1 * gl_viewcos) * gl_centerx / fy2;
-
-		fx2 = ((polyvertex_t *)(line->pv2))->x-gl_viewx;
-		fy2 = ((polyvertex_t *)(line->pv2))->y-gl_viewy;
-		fy1 = (fx2 * gl_viewcos + fy2 * gl_viewsin);
-		if (fy1 < 0)
-			// the point is back
-			fx2 = vid.width;
-		else
-			fx2 = gl_windowcenterx + (fx2 * gl_viewsin - fy2 * gl_viewcos) * gl_centerx / fy1;
-
-		x1 = fx1+0.5f;
-		x2 = fx2+0.5f;
-	}
-#else
-	// The seg is in the view range,
-	// but not necessarily visible.
-	angle1 = (angle1+ANGLE_90)>>ANGLETOFINESHIFT;
-	angle2 = (angle2+ANGLE_90)>>ANGLETOFINESHIFT;
-
-	x1 = gl_viewangletox[angle1];
-	x2 = gl_viewangletox[angle2];
-#endif
-	// Does not cross a pixel?
-//	if (x1 == x2)
-/*	{
-		// BP: HERE IS THE MAIN PROBLEM !
-		//CONS_Debug(DBG_RENDER, "tineline\n");
-		return;
-	}
-*/
-#endif
 
 	gl_backsector = line->backsector;
 	bothceilingssky = bothfloorssky = false;
 
-#ifdef NEWCLIP
 	if (!line->backsector)
     {
 		gld_clipper_SafeAddClipRange(angle2, angle1);
@@ -2521,115 +2022,6 @@ static void HWR_AddLine(seg_t * line)
     }
 
 	HWR_ProcessSeg(); // Doesn't need arguments because they're defined globally :D
-	return;
-#else
-	// Single sided line?
-	if (!gl_backsector)
-		goto clipsolid;
-
-	gl_backsector = R_FakeFlat(gl_backsector, &tempsec, NULL, NULL, true);
-
-	if (gl_backsector->ceilingpic == skyflatnum && gl_frontsector->ceilingpic == skyflatnum)
-		bothceilingssky = true;
-	if (gl_backsector->floorpic == skyflatnum && gl_frontsector->floorpic == skyflatnum)
-		bothfloorssky = true;
-
-	if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then
-	{
-		if (!line->polyseg &&
-			!line->sidedef->midtexture
-			&& ((!gl_frontsector->ffloors && !gl_backsector->ffloors)
-				|| Tag_Compare(&gl_frontsector->tags, &gl_backsector->tags)))
-			return; // line is empty, don't even bother
-
-		goto clippass; // treat like wide open window instead
-	}
-
-	if (gl_frontsector->f_slope || gl_frontsector->c_slope || gl_backsector->f_slope || gl_backsector->c_slope)
-	{
-		fixed_t frontf1,frontf2, frontc1, frontc2; // front floor/ceiling ends
-		fixed_t backf1, backf2, backc1, backc2; // back floor ceiling ends
-
-#define SLOPEPARAMS(slope, end1, end2, normalheight) \
-		end1 = P_GetZAt(slope, v1x, v1y, normalheight); \
-		end2 = P_GetZAt(slope, v2x, v2y, normalheight);
-
-		SLOPEPARAMS(gl_frontsector->f_slope, frontf1, frontf2, gl_frontsector->  floorheight)
-		SLOPEPARAMS(gl_frontsector->c_slope, frontc1, frontc2, gl_frontsector->ceilingheight)
-		SLOPEPARAMS( gl_backsector->f_slope,  backf1,  backf2,  gl_backsector->  floorheight)
-		SLOPEPARAMS( gl_backsector->c_slope,  backc1,  backc2,  gl_backsector->ceilingheight)
-#undef SLOPEPARAMS
-		// if both ceilings are skies, consider it always "open"
-		// same for floors
-		if (!bothceilingssky && !bothfloorssky)
-		{
-			// Closed door.
-			if ((backc1 <= frontf1 && backc2 <= frontf2)
-				|| (backf1 >= frontc1 && backf2 >= frontc2))
-			{
-				goto clipsolid;
-			}
-
-			// Check for automap fix.
-			if (backc1 <= backf1 && backc2 <= backf2
-			&& ((backc1 >= frontc1 && backc2 >= frontc2) || gl_curline->sidedef->toptexture)
-			&& ((backf1 <= frontf1 && backf2 >= frontf2) || gl_curline->sidedef->bottomtexture))
-				goto clipsolid;
-		}
-
-		// Window.
-		if (!bothceilingssky) // ceilings are always the "same" when sky
-			if (backc1 != frontc1 || backc2 != frontc2)
-				goto clippass;
-		if (!bothfloorssky)	// floors are always the "same" when sky
-			if (backf1 != frontf1 || backf2 != frontf2)
-				goto clippass;
-	}
-	else
-	{
-		// if both ceilings are skies, consider it always "open"
-		// same for floors
-		if (!bothceilingssky && !bothfloorssky)
-		{
-			// Closed door.
-			if (gl_backsector->ceilingheight <= gl_frontsector->floorheight ||
-				gl_backsector->floorheight >= gl_frontsector->ceilingheight)
-				goto clipsolid;
-
-			// Check for automap fix.
-			if (gl_backsector->ceilingheight <= gl_backsector->floorheight
-			&& ((gl_backsector->ceilingheight >= gl_frontsector->ceilingheight) || gl_curline->sidedef->toptexture)
-			&& ((gl_backsector->floorheight <= gl_backsector->floorheight) || gl_curline->sidedef->bottomtexture))
-				goto clipsolid;
-		}
-
-		// Window.
-		if (!bothceilingssky) // ceilings are always the "same" when sky
-			if (gl_backsector->ceilingheight != gl_frontsector->ceilingheight)
-				goto clippass;
-		if (!bothfloorssky)	// floors are always the "same" when sky
-			if (gl_backsector->floorheight != gl_frontsector->floorheight)
-				goto clippass;
-	}
-
-	// Reject empty lines used for triggers and special events.
-	// Identical floor and ceiling on both sides,
-	//  identical light levels on both sides,
-	//  and no middle texture.
-	if (R_IsEmptyLine(gl_curline, gl_frontsector, gl_backsector))
-		return;
-
-clippass:
-	if (x1 == x2)
-		{  x2++;x1 -= 2; }
-	HWR_ClipPassWallSegment(x1, x2-1);
-	return;
-
-clipsolid:
-	if (x1 == x2)
-		goto clippass;
-	HWR_ClipSolidWallSegment(x1, x2-1);
-#endif
 }
 
 // HWR_CheckBBox
@@ -2644,23 +2036,19 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	INT32 boxpos;
 	fixed_t px1, py1, px2, py2;
 	angle_t angle1, angle2;
-#ifndef NEWCLIP
-	INT32 sx1, sx2;
-	angle_t span, tspan;
-#endif
 
 	// Find the corners of the box
 	// that define the edges from current viewpoint.
-	if (dup_viewx <= bspcoord[BOXLEFT])
+	if (viewx <= bspcoord[BOXLEFT])
 		boxpos = 0;
-	else if (dup_viewx < bspcoord[BOXRIGHT])
+	else if (viewx < bspcoord[BOXRIGHT])
 		boxpos = 1;
 	else
 		boxpos = 2;
 
-	if (dup_viewy >= bspcoord[BOXTOP])
+	if (viewy >= bspcoord[BOXTOP])
 		boxpos |= 0;
-	else if (dup_viewy > bspcoord[BOXBOTTOM])
+	else if (viewy > bspcoord[BOXBOTTOM])
 		boxpos |= 1<<2;
 	else
 		boxpos |= 2<<2;
@@ -2673,59 +2061,9 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	px2 = bspcoord[checkcoord[boxpos][2]];
 	py2 = bspcoord[checkcoord[boxpos][3]];
 
-#ifdef NEWCLIP
 	angle1 = R_PointToAngle64(px1, py1);
 	angle2 = R_PointToAngle64(px2, py2);
 	return gld_clipper_SafeCheckRange(angle2, angle1);
-#else
-	// check clip list for an open space
-	angle1 = R_PointToAngle2(dup_viewx>>1, dup_viewy>>1, px1>>1, py1>>1) - dup_viewangle;
-	angle2 = R_PointToAngle2(dup_viewx>>1, dup_viewy>>1, px2>>1, py2>>1) - dup_viewangle;
-
-	span = angle1 - angle2;
-
-	// Sitting on a line?
-	if (span >= ANGLE_180)
-		return true;
-
-	tspan = angle1 + gl_clipangle;
-
-	if (tspan > 2*gl_clipangle)
-	{
-		tspan -= 2*gl_clipangle;
-
-		// Totally off the left edge?
-		if (tspan >= span)
-			return false;
-
-		angle1 = gl_clipangle;
-	}
-	tspan = gl_clipangle - angle2;
-	if (tspan > 2*gl_clipangle)
-	{
-		tspan -= 2*gl_clipangle;
-
-		// Totally off the left edge?
-		if (tspan >= span)
-			return false;
-
-		angle2 = (angle_t)-(signed)gl_clipangle;
-	}
-
-	// Find the first clippost
-	//  that touches the source post
-	//  (adjacent pixels are touching).
-	angle1 = (angle1+ANGLE_90)>>ANGLETOFINESHIFT;
-	angle2 = (angle2+ANGLE_90)>>ANGLETOFINESHIFT;
-	sx1 = gl_viewangletox[angle1];
-	sx2 = gl_viewangletox[angle2];
-
-	// Does not cross a pixel.
-	if (sx1 == sx2)
-		return false;
-
-	return HWR_ClipToSolidSegs(sx1, sx2 - 1);
-#endif
 }
 
 //
@@ -2754,7 +2092,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;
@@ -3065,9 +2403,8 @@ static void HWR_Subsector(size_t num)
 	sub->sector->extra_colormap = gl_frontsector->extra_colormap;
 
 	// render floor ?
-#ifdef DOPLANES
 	// yeah, easy backface cull! :)
-	if (cullFloorHeight < dup_viewz)
+	if (cullFloorHeight < viewz)
 	{
 		if (gl_frontsector->floorpic != skyflatnum)
 		{
@@ -3081,15 +2418,9 @@ static void HWR_Subsector(size_t num)
 					PF_Occlude, floorlightlevel, &levelflats[gl_frontsector->floorpic], NULL, 255, floorcolormap);
 			}
 		}
-		else
-		{
-#ifdef POLYSKY
-			HWR_RenderSkyPlane(&extrasubsectors[num], locFloorHeight);
-#endif
-		}
 	}
 
-	if (cullCeilingHeight > dup_viewz)
+	if (cullCeilingHeight > viewz)
 	{
 		if (gl_frontsector->ceilingpic != skyflatnum)
 		{
@@ -3103,21 +2434,12 @@ static void HWR_Subsector(size_t num)
 					PF_Occlude, ceilinglightlevel, &levelflats[gl_frontsector->ceilingpic], NULL, 255, ceilingcolormap);
 			}
 		}
-		else
-		{
-#ifdef POLYSKY
-			HWR_RenderSkyPlane(&extrasubsectors[num], locCeilingHeight);
-#endif
-		}
 	}
 
-#ifndef POLYSKY
 	// Moved here because before, when above the ceiling and the floor does not have the sky flat, it doesn't draw the sky
 	if (gl_frontsector->ceilingpic == skyflatnum || gl_frontsector->floorpic == skyflatnum)
 		drawsky = true;
-#endif
 
-#ifdef R_FAKEFLOORS
 	if (gl_frontsector->ffloors)
 	{
 		/// \todo fix light, xoffs, yoffs, extracolormap ?
@@ -3146,14 +2468,14 @@ static void HWR_Subsector(size_t num)
 
 			if (centerHeight <= locCeilingHeight &&
 			    centerHeight >= locFloorHeight &&
-			    ((dup_viewz < bottomCullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) ||
-			     (dup_viewz > bottomCullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES))))
+			    ((viewz < bottomCullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) ||
+			     (viewz > bottomCullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES))))
 			{
 				if (rover->fofflags & FOF_FOG)
 				{
 					UINT8 alpha;
 
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < bottomCullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < bottomCullHeight ? true : false);
 					alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap);
 
 					HWR_AddTransparentFloor(0,
@@ -3166,7 +2488,7 @@ static void HWR_Subsector(size_t num)
 				}
 				else if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend) // SoM: Flags are more efficient
 				{
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < bottomCullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < bottomCullHeight ? true : false);
 
 					HWR_AddTransparentFloor(&levelflats[*rover->bottompic],
 					                       &extrasubsectors[num],
@@ -3180,7 +2502,7 @@ static void HWR_Subsector(size_t num)
 				else
 				{
 					HWR_GetLevelFlat(&levelflats[*rover->bottompic]);
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < bottomCullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < bottomCullHeight ? true : false);
 					HWR_RenderPlane(sub, &extrasubsectors[num], false, *rover->bottomheight, HWR_RippleBlend(gl_frontsector, rover, false)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic],
 					                rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap);
 				}
@@ -3191,14 +2513,14 @@ static void HWR_Subsector(size_t num)
 
 			if (centerHeight >= locFloorHeight &&
 			    centerHeight <= locCeilingHeight &&
-			    ((dup_viewz > topCullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) ||
-			     (dup_viewz < topCullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES))))
+			    ((viewz > topCullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) ||
+			     (viewz < topCullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES))))
 			{
 				if (rover->fofflags & FOF_FOG)
 				{
 					UINT8 alpha;
 
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < topCullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < topCullHeight ? true : false);
 					alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap);
 
 					HWR_AddTransparentFloor(0,
@@ -3211,7 +2533,7 @@ static void HWR_Subsector(size_t num)
 				}
 				else if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend)
 				{
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < topCullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < topCullHeight ? true : false);
 
 					HWR_AddTransparentFloor(&levelflats[*rover->toppic],
 					                        &extrasubsectors[num],
@@ -3225,15 +2547,13 @@ static void HWR_Subsector(size_t num)
 				else
 				{
 					HWR_GetLevelFlat(&levelflats[*rover->toppic]);
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < topCullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < topCullHeight ? true : false);
 					HWR_RenderPlane(sub, &extrasubsectors[num], true, *rover->topheight, HWR_RippleBlend(gl_frontsector, rover, false)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic],
 					                  rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 			}
 		}
 	}
-#endif
-#endif //doplanes
 
 	// Draw all the polyobjects in this subsector
 	if (sub->polyList)
@@ -3265,10 +2585,10 @@ static void HWR_Subsector(size_t num)
 		}
 	}
 
-// Hurder ici se passe les choses INT32�essantes!
-// on vient de tracer le sol et le plafond
-// on trace �pr�ent d'abord les sprites et ensuite les murs
-// hurdler: faux: on ajoute seulement les sprites, le murs sont trac� d'abord
+	// Hurdler: here interesting things are happening!
+	// we have just drawn the floor and ceiling
+	// we now draw the sprites first and then the walls
+	// hurdler: false: we only add the sprites, the walls are drawn first
 	if (line)
 	{
 		// draw sprites first, coz they are clipped to the solidsegs of
@@ -3300,59 +2620,11 @@ static void HWR_Subsector(size_t num)
 //  traversing subtree recursively.
 // Just call with BSP root.
 
-#ifdef coolhack
-//t;b;l;r
-static fixed_t hackbbox[4];
-//BOXTOP,
-//BOXBOTTOM,
-//BOXLEFT,
-//BOXRIGHT
-static boolean HWR_CheckHackBBox(fixed_t *bb)
-{
-	if (bb[BOXTOP] < hackbbox[BOXBOTTOM]) //y up
-		return false;
-	if (bb[BOXBOTTOM] > hackbbox[BOXTOP])
-		return false;
-	if (bb[BOXLEFT] > hackbbox[BOXRIGHT])
-		return false;
-	if (bb[BOXRIGHT] < hackbbox[BOXLEFT])
-		return false;
-	return true;
-}
-#endif
-
 // BP: big hack for a test in lighning ref : 1249753487AB
 fixed_t *hwbbox;
 
 static void HWR_RenderBSPNode(INT32 bspnum)
 {
-	/*//GZDoom code
-	if(bspnum == -1)
-	{
-		HWR_Subsector(subsectors);
-		return;
-	}
-	while(!((size_t)bspnum&(~NF_SUBSECTOR))) // Keep going until found a subsector
-	{
-		node_t *bsp = &nodes[bspnum];
-
-		// Decide which side the view point is on
-		INT32 side = R_PointOnSide(dup_viewx, dup_viewy, bsp);
-
-		// Recursively divide front space (toward the viewer)
-		HWR_RenderBSPNode(bsp->children[side]);
-
-		// Possibly divide back space (away from viewer)
-		side ^= 1;
-
-		if (!HWR_CheckBBox(bsp->bbox[side]))
-			return;
-
-		bspnum = bsp->children[side];
-	}
-
-	HWR_Subsector(bspnum-1);
-*/
 	node_t *bsp = &nodes[bspnum];
 
 	// Decide which side the view point is on
@@ -3376,116 +2648,22 @@ static void HWR_RenderBSPNode(INT32 bspnum)
 		return;
 	}
 
-	// Decide which side the view point is on.
-	side = R_PointOnSide(dup_viewx, dup_viewy, bsp);
-
-	// BP: big hack for a test in lighning ref : 1249753487AB
-	hwbbox = bsp->bbox[side];
-
-	// Recursively divide front space.
-	HWR_RenderBSPNode(bsp->children[side]);
-
-	// Possibly divide back space.
-	if (HWR_CheckBBox(bsp->bbox[side^1]))
-	{
-		// BP: big hack for a test in lighning ref : 1249753487AB
-		hwbbox = bsp->bbox[side^1];
-		HWR_RenderBSPNode(bsp->children[side^1]);
-	}
-}
-
-/*
-//
-// Clear 'stack' of subsectors to draw
-//
-static void HWR_ClearDrawSubsectors(void)
-{
-	gl_drawsubsector_p = gl_drawsubsectors;
-}
-
-//
-// Draw subsectors pushed on the drawsubsectors 'stack', back to front
-//
-static void HWR_RenderSubsectors(void)
-{
-	while (gl_drawsubsector_p > gl_drawsubsectors)
-	{
-		HWR_RenderBSPNode(
-		lastsubsec->nextsubsec = bspnum & (~NF_SUBSECTOR);
-	}
-}
-*/
-
-// ==========================================================================
-//                                                              FROM R_MAIN.C
-// ==========================================================================
-
-//BP : exactely the same as R_InitTextureMapping
-void HWR_InitTextureMapping(void)
-{
-	angle_t i;
-	INT32 x;
-	INT32 t;
-	fixed_t focallength;
-	fixed_t grcenterx;
-	fixed_t grcenterxfrac;
-	INT32 grviewwidth;
-
-#define clipanglefov (FIELDOFVIEW>>ANGLETOFINESHIFT)
-
-	grviewwidth = vid.width;
-	grcenterx = grviewwidth/2;
-	grcenterxfrac = grcenterx<<FRACBITS;
-
-	// Use tangent table to generate viewangletox:
-	//  viewangletox will give the next greatest x
-	//  after the view angle.
-	//
-	// Calc focallength
-	//  so FIELDOFVIEW angles covers SCREENWIDTH.
-	focallength = FixedDiv(grcenterxfrac,
-		FINETANGENT(FINEANGLES/4+clipanglefov/2));
-
-	for (i = 0; i < FINEANGLES/2; i++)
-	{
-		if (FINETANGENT(i) > FRACUNIT*2)
-			t = -1;
-		else if (FINETANGENT(i) < -FRACUNIT*2)
-			t = grviewwidth+1;
-		else
-		{
-			t = FixedMul(FINETANGENT(i), focallength);
-			t = (grcenterxfrac - t+FRACUNIT-1)>>FRACBITS;
-
-			if (t < -1)
-				t = -1;
-			else if (t > grviewwidth+1)
-				t = grviewwidth+1;
-		}
-		gl_viewangletox[i] = t;
-	}
-
-	// Scan viewangletox[] to generate xtoviewangle[]:
-	//  xtoviewangle will give the smallest view angle
-	//  that maps to x.
-	for (x = 0; x <= grviewwidth; x++)
-	{
-		i = 0;
-		while (gl_viewangletox[i]>x)
-			i++;
-		gl_xtoviewangle[x] = (i<<ANGLETOFINESHIFT) - ANGLE_90;
-	}
+	// Decide which side the view point is on.
+	side = R_PointOnSide(viewx, viewy, bsp);
+
+	// BP: big hack for a test in lighning ref : 1249753487AB
+	hwbbox = bsp->bbox[side];
+
+	// Recursively divide front space.
+	HWR_RenderBSPNode(bsp->children[side]);
 
-	// Take out the fencepost cases from viewangletox.
-	for (i = 0; i < FINEANGLES/2; i++)
+	// Possibly divide back space.
+	if (HWR_CheckBBox(bsp->bbox[side^1]))
 	{
-		if (gl_viewangletox[i] == -1)
-			gl_viewangletox[i] = 0;
-		else if (gl_viewangletox[i] == grviewwidth+1)
-			gl_viewangletox[i]  = grviewwidth;
+		// BP: big hack for a test in lighning ref : 1249753487AB
+		hwbbox = bsp->bbox[side^1];
+		HWR_RenderBSPNode(bsp->children[side^1]);
 	}
-
-	gl_clipangle = gl_xtoviewangle[0];
 }
 
 // ==========================================================================
@@ -3587,7 +2765,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);
@@ -3795,7 +2973,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;
 
@@ -4366,7 +3544,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;
@@ -4436,11 +3614,10 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 	}
 }
 
-#ifdef HWPRECIP
 // 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;
@@ -4537,7 +3714,6 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 
 	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, shader, false);
 }
-#endif
 
 // --------------------------------------------------------------------------
 // Sort vissprites by distance
@@ -4801,7 +3977,7 @@ static int CompareDrawNodePlanes(const void *p1, const void *p2)
 	size_t n2 = *(const size_t*)p2;
 	if (!sortnode[n1].plane) I_Error("CompareDrawNodePlanes: Uh.. This isn't a plane! (n1)");
 	if (!sortnode[n2].plane) I_Error("CompareDrawNodePlanes: Uh.. This isn't a plane! (n2)");
-	return ABS(sortnode[n2].plane->fixedheight - viewz) - ABS(sortnode[n1].plane->fixedheight - viewz);
+	return abs(sortnode[n2].plane->fixedheight - viewz) - abs(sortnode[n1].plane->fixedheight - viewz);
 }
 
 //
@@ -4885,7 +4061,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++)
 	{
@@ -4944,12 +4119,9 @@ static void HWR_DrawSprites(void)
 		gl_vissprite_t *spr = gl_vsprorder[i];
 		if (spr->bbox)
 			HWR_DrawBoundingBox(spr);
-		else
-#ifdef HWPRECIP
-		if (spr->precip)
+		else if (spr->precip)
 			HWR_DrawPrecipitationSprite(spr);
 		else
-#endif
 		{
 			if (spr->mobj && spr->mobj->shadowscale && cv_shadow.value && !skipshadow)
 			{
@@ -5020,9 +4192,7 @@ static UINT8 sectorlight;
 static void HWR_AddSprites(sector_t *sec)
 {
 	mobj_t *thing;
-#ifdef HWPRECIP
 	precipmobj_t *precipthing;
-#endif
 	fixed_t limit_dist, hoop_limit_dist;
 
 	// BSP is traversed by subsector.
@@ -5055,7 +4225,6 @@ static void HWR_AddSprites(sector_t *sec)
 		}
 	}
 
-#ifdef HWPRECIP
 	// no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off
 	if ((limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS))
 	{
@@ -5065,7 +4234,6 @@ static void HWR_AddSprites(sector_t *sec)
 				HWR_ProjectPrecipitationSprite(precipthing);
 		}
 	}
-#endif
 }
 
 // --------------------------------------------------------------------------
@@ -5118,6 +4286,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	// uncapped/interpolation
 	interpmobjstate_t interp = {0};
 
+	if (!cv_renderthings.value)
+		return;
+
 	if (!thing)
 		return;
 
@@ -5562,7 +4733,6 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	vis->angle = interp.angle;
 }
 
-#ifdef HWPRECIP
 // Precipitation projector for hardware mode
 static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 {
@@ -5691,7 +4861,6 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 		thing->precipflags |= PCF_THUNK;
 	}
 }
-#endif
 
 static void HWR_ProjectBoundingBox(mobj_t *thing)
 {
@@ -5901,41 +5070,18 @@ static void HWR_DrawSkyBackground(player_t *player)
 	if (cv_glskydome.value)
 	{
 		FTransform dometransform;
-		const float fpov = FixedToFloat(R_GetPlayerFov(player));
-		postimg_t *type;
 
-		if (splitscreen && player == &players[secondarydisplayplayer])
-			type = &postimgtype2;
-		else
-			type = &postimgtype;
+		memcpy(&dometransform, &atransform, sizeof(FTransform));
 
-		memset(&dometransform, 0x00, sizeof(FTransform));
+		dometransform.x      = 0.0;
+		dometransform.y      = 0.0;
+		dometransform.z      = 0.0;
 
 		//04/01/2000: Hurdler: added for T&L
 		//                     It should replace all other gl_viewxxx when finished
 		HWR_SetTransformAiming(&dometransform, player, false);
 		dometransform.angley = (float)((viewangle-ANGLE_270)>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
 
-		if (*type == postimg_flip)
-			dometransform.flip = true;
-		else
-			dometransform.flip = false;
-
-		dometransform.scalex = 1;
-		dometransform.scaley = (float)vid.width/vid.height;
-		dometransform.scalez = 1;
-		dometransform.fovxangle = fpov; // Tails
-		dometransform.fovyangle = fpov; // Tails
-		if (player->viewrollangle != 0)
-		{
-			fixed_t rol = AngleFixed(player->viewrollangle);
-			dometransform.rollangle = FIXED_TO_FLOAT(rol);
-			dometransform.roll = true;
-			dometransform.rollx = 1.0f;
-			dometransform.rollz = 0.0f;
-		}
-		dometransform.splitscreen = splitscreen;
-
 		HWR_GetTexture(texturetranslation[skytexture]);
 
 		if (gl_sky.texture != texturetranslation[skytexture])
@@ -5944,7 +5090,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);
 	}
@@ -5981,7 +5128,7 @@ static void HWR_DrawSkyBackground(player_t *player)
 		// software doesn't draw any further than 1024 for skies anyway, but this doesn't overlap properly
 		// The only time this will probably be an issue is when a sky wider than 1024 is used as a sky AND a regular wall texture
 
-		angle = (dup_viewangle + gl_xtoviewangle[0]);
+		angle = (viewangle + xtoviewangle[0]);
 
 		dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->width/256.0f);
 
@@ -6030,8 +5177,6 @@ static void HWR_DrawSkyBackground(player_t *player)
 		HWD.pfnUnSetShader();
 		HWD.pfnDrawPolygon(NULL, v, 4, 0);
 	}
-
-	HWD.pfnSetShader(SHADER_DEFAULT);
 }
 
 
@@ -6047,10 +5192,10 @@ static inline void HWR_ClearView(void)
 
 	/// \bug faB - enable depth mask, disable color mask
 
-	HWD.pfnGClipRect((INT32)gl_viewwindowx,
-	                 (INT32)gl_viewwindowy,
-	                 (INT32)(gl_viewwindowx + gl_viewwidth),
-	                 (INT32)(gl_viewwindowy + gl_viewheight),
+	HWD.pfnGClipRect((INT32)viewwindowx,
+	                 (INT32)viewwindowy,
+	                 (INT32)(viewwindowx + viewwidth),
+	                 (INT32)(viewwindowy + viewheight),
 	                 ZCLIP_PLANE);
 	HWD.pfnClearBuffer(false, true, 0);
 
@@ -6065,32 +5210,6 @@ static inline void HWR_ClearView(void)
 // -----------------+
 void HWR_SetViewSize(void)
 {
-	// setup view size
-	gl_viewwidth = (float)vid.width;
-	gl_viewheight = (float)vid.height;
-
-	if (splitscreen)
-		gl_viewheight /= 2;
-
-	gl_centerx = gl_viewwidth / 2;
-	gl_basecentery = gl_viewheight / 2; //note: this is (gl_centerx * gl_viewheight / gl_viewwidth)
-
-	gl_viewwindowx = (vid.width - gl_viewwidth) / 2;
-	gl_windowcenterx = (float)(vid.width / 2);
-	if (fabsf(gl_viewwidth - vid.width) < 1.0E-36f)
-	{
-		gl_baseviewwindowy = 0;
-		gl_basewindowcentery = gl_viewheight / 2;               // window top left corner at 0,0
-	}
-	else
-	{
-		gl_baseviewwindowy = (vid.height-gl_viewheight) / 2;
-		gl_basewindowcentery = (float)(vid.height / 2);
-	}
-
-	gl_pspritexscale = gl_viewwidth / BASEVIDWIDTH;
-	gl_pspriteyscale = ((vid.height*gl_pspritexscale*BASEVIDWIDTH)/BASEVIDHEIGHT)/vid.width;
-
 	HWD.pfnFlushScreenTextures();
 }
 
@@ -6121,21 +5240,11 @@ 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());
 }
 
-// ==========================================================================
-// Same as rendering the player view, but from the skybox object
-// ==========================================================================
-void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
+static void HWR_SetupView(player_t *player, INT32 viewnumber, float fpov, boolean skybox)
 {
-	const float fpov = FixedToFloat(R_GetPlayerFov(player));
 	postimg_t *type;
 
 	if (splitscreen && player == &players[secondarydisplayplayer])
@@ -6143,6 +5252,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;
@@ -6151,55 +5261,41 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 		stplyr = saved_player;
 #ifdef ALAM_LIGHTING
 		HWR_SetLights(viewnumber);
+#else
+		(void)viewnumber;
 #endif
 	}
 
 	// note: sets viewangle, viewx, viewy, viewz
-	R_SkyboxFrame(player);
-
-	// copy view cam position for local use
-	dup_viewx = viewx;
-	dup_viewy = viewy;
-	dup_viewz = viewz;
-	dup_viewangle = viewangle;
-
-	// set window position
-	gl_centery = gl_basecentery;
-	gl_viewwindowy = gl_baseviewwindowy;
-	gl_windowcentery = gl_basewindowcentery;
-	if (splitscreen && viewnumber == 1)
-	{
-		gl_viewwindowy += (vid.height/2);
-		gl_windowcentery += (vid.height/2);
-	}
-
-	// check for new console commands.
-	NetUpdate();
+	if (skybox)
+		R_SkyboxFrame(player);
+	else
+		R_SetupFrame(player);
 
-	gl_viewx = FIXED_TO_FLOAT(dup_viewx);
-	gl_viewy = FIXED_TO_FLOAT(dup_viewy);
-	gl_viewz = FIXED_TO_FLOAT(dup_viewz);
-	gl_viewsin = FIXED_TO_FLOAT(viewsin);
-	gl_viewcos = FIXED_TO_FLOAT(viewcos);
+	gl_viewx = FixedToFloat(viewx);
+	gl_viewy = FixedToFloat(viewy);
+	gl_viewz = FixedToFloat(viewz);
+	gl_viewsin = FixedToFloat(viewsin);
+	gl_viewcos = FixedToFloat(viewcos);
 
 	//04/01/2000: Hurdler: added for T&L
 	//                     It should replace all other gl_viewxxx when finished
 	memset(&atransform, 0x00, sizeof(FTransform));
 
-	HWR_SetTransformAiming(&atransform, player, true);
+	HWR_SetTransformAiming(&atransform, player, skybox);
 	atransform.angley = (float)(viewangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
 
-	gl_viewludsin = FIXED_TO_FLOAT(FINECOSINE(gl_aimingangle>>ANGLETOFINESHIFT));
-	gl_viewludcos = FIXED_TO_FLOAT(-FINESINE(gl_aimingangle>>ANGLETOFINESHIFT));
+	gl_viewludsin = FixedToFloat(FINECOSINE(gl_aimingangle>>ANGLETOFINESHIFT));
+	gl_viewludcos = FixedToFloat(-FINESINE(gl_aimingangle>>ANGLETOFINESHIFT));
 
 	if (*type == postimg_flip)
 		atransform.flip = true;
 	else
 		atransform.flip = false;
 
-	atransform.x      = gl_viewx;  // FIXED_TO_FLOAT(viewx)
-	atransform.y      = gl_viewy;  // FIXED_TO_FLOAT(viewy)
-	atransform.z      = gl_viewz;  // FIXED_TO_FLOAT(viewz)
+	atransform.x      = gl_viewx;
+	atransform.y      = gl_viewy;
+	atransform.z      = gl_viewz;
 	atransform.scalex = 1;
 	atransform.scaley = (float)vid.width/vid.height;
 	atransform.scalez = 1;
@@ -6209,7 +5305,7 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	if (player->viewrollangle != 0)
 	{
 		fixed_t rol = AngleFixed(player->viewrollangle);
-		atransform.rollangle = FIXED_TO_FLOAT(rol);
+		atransform.rollangle = FixedToFloat(rol);
 		atransform.roll = true;
 		atransform.rollx = 1.0f;
 		atransform.rollz = 0.0f;
@@ -6217,6 +5313,19 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	atransform.splitscreen = splitscreen;
 
 	gl_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
+}
+
+// ==========================================================================
+// Same as rendering the player view, but from the skybox object
+// ==========================================================================
+void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
+{
+	const float fpov = FixedToFloat(R_GetPlayerFov(player));
+
+	HWR_SetupView(player, viewnumber, fpov, true);
+
+	// check for new console commands.
+	NetUpdate();
 
 	//------------------------------------------------------------------------
 	HWR_ClearView();
@@ -6231,19 +5340,15 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 
 	drawcount = 0;
 
-#ifdef NEWCLIP
 	if (rendermode == render_opengl)
 	{
-		angle_t a1 = gld_FrustumAngle(gl_aimingangle);
+		angle_t a1 = gld_FrustumAngle(fpov, gl_aimingangle);
 		gld_clipper_Clear();
 		gld_clipper_SafeAddClipRange(viewangle + a1, viewangle - a1);
 #ifdef HAVE_SPHEREFRUSTRUM
 		gld_FrustrumSetup();
 #endif
 	}
-#else
-	HWR_ClearClipSegs();
-#endif
 
 	//04/01/2000: Hurdler: added for T&L
 	//                     Actually it only works on Walls and Planes
@@ -6308,17 +5413,11 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 {
 	const float fpov = FixedToFloat(R_GetPlayerFov(player));
-	postimg_t *type;
 
 	const boolean skybox = (skyboxmo[0] && cv_skybox.value); // True if there's a skybox object and skyboxes are on
 
 	FRGBAFloat ClearColor;
 
-	if (splitscreen && player == &players[secondarydisplayplayer])
-		type = &postimgtype2;
-	else
-		type = &postimgtype;
-
 	ClearColor.red = 0.0f;
 	ClearColor.green = 0.0f;
 	ClearColor.blue = 0.0f;
@@ -6335,82 +5434,13 @@ 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);
 
-	{
-		// do we really need to save player (is it not the same)?
-		player_t *saved_player = stplyr;
-		stplyr = player;
-		ST_doPaletteStuff();
-		stplyr = saved_player;
-#ifdef ALAM_LIGHTING
-		HWR_SetLights(viewnumber);
-#endif
-	}
+	HWR_SetupView(player, viewnumber, fpov, false);
 
-	// note: sets viewangle, viewx, viewy, viewz
-	R_SetupFrame(player);
 	framecount++; // timedemo
 
-	// copy view cam position for local use
-	dup_viewx = viewx;
-	dup_viewy = viewy;
-	dup_viewz = viewz;
-	dup_viewangle = viewangle;
-
-	// set window position
-	gl_centery = gl_basecentery;
-	gl_viewwindowy = gl_baseviewwindowy;
-	gl_windowcentery = gl_basewindowcentery;
-	if (splitscreen && viewnumber == 1)
-	{
-		gl_viewwindowy += (vid.height/2);
-		gl_windowcentery += (vid.height/2);
-	}
-
 	// check for new console commands.
 	NetUpdate();
 
-	gl_viewx = FIXED_TO_FLOAT(dup_viewx);
-	gl_viewy = FIXED_TO_FLOAT(dup_viewy);
-	gl_viewz = FIXED_TO_FLOAT(dup_viewz);
-	gl_viewsin = FIXED_TO_FLOAT(viewsin);
-	gl_viewcos = FIXED_TO_FLOAT(viewcos);
-
-	//04/01/2000: Hurdler: added for T&L
-	//                     It should replace all other gl_viewxxx when finished
-	memset(&atransform, 0x00, sizeof(FTransform));
-
-	HWR_SetTransformAiming(&atransform, player, false);
-	atransform.angley = (float)(viewangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
-
-	gl_viewludsin = FIXED_TO_FLOAT(FINECOSINE(gl_aimingangle>>ANGLETOFINESHIFT));
-	gl_viewludcos = FIXED_TO_FLOAT(-FINESINE(gl_aimingangle>>ANGLETOFINESHIFT));
-
-	if (*type == postimg_flip)
-		atransform.flip = true;
-	else
-		atransform.flip = false;
-
-	atransform.x      = gl_viewx;  // FIXED_TO_FLOAT(viewx)
-	atransform.y      = gl_viewy;  // FIXED_TO_FLOAT(viewy)
-	atransform.z      = gl_viewz;  // FIXED_TO_FLOAT(viewz)
-	atransform.scalex = 1;
-	atransform.scaley = (float)vid.width/vid.height;
-	atransform.scalez = 1;
-
-	atransform.fovxangle = fpov; // Tails
-	atransform.fovyangle = fpov; // Tails
-	if (player->viewrollangle != 0)
-	{
-		fixed_t rol = AngleFixed(player->viewrollangle);
-		atransform.rollangle = FIXED_TO_FLOAT(rol);
-		atransform.roll = true;
-		atransform.rollx = 1.0f;
-		atransform.rollz = 0.0f;
-	}
-	atransform.splitscreen = splitscreen;
-
-	gl_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
-
 	//------------------------------------------------------------------------
 	HWR_ClearView(); // Clears the depth buffer and resets the view I believe
 
@@ -6424,19 +5454,15 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	drawcount = 0;
 
-#ifdef NEWCLIP
 	if (rendermode == render_opengl)
 	{
-		angle_t a1 = gld_FrustumAngle(gl_aimingangle);
+		angle_t a1 = gld_FrustumAngle(fpov, gl_aimingangle);
 		gld_clipper_Clear();
 		gld_clipper_SafeAddClipRange(viewangle + a1, viewangle - a1);
 #ifdef HAVE_SPHEREFRUSTRUM
 		gld_FrustrumSetup();
 #endif
 	}
-#else
-	HWR_ClearClipSegs();
-#endif
 
 	//04/01/2000: Hurdler: added for T&L
 	//                     Actually it only works on Walls and Planes
@@ -6511,6 +5537,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
@@ -6524,6 +5600,9 @@ void HWR_LoadLevel(void)
 	HWR_ClearSkyDome();
 	HWR_BuildSkyDome();
 
+	if (HWR_ShouldUsePaletteRendering())
+		HWR_SetMapPalette();
+
 	gl_maploaded = true;
 }
 
@@ -6531,13 +5610,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"},
@@ -6547,7 +5630,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);
@@ -6558,7 +5641,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);
@@ -6573,18 +5656,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
@@ -6613,11 +5739,9 @@ void HWR_AddCommands(void)
 
 	CV_RegisterVar(&cv_glbatching);
 
+	CV_RegisterVar(&cv_glpaletterendering);
+	CV_RegisterVar(&cv_glpalettedepth);
 	CV_RegisterVar(&cv_glwireframe);
-
-#ifndef NEWCLIP
-	CV_RegisterVar(&cv_glclipwalls);
-#endif
 }
 
 // --------------------------------------------------------------------------
@@ -6629,6 +5753,8 @@ void HWR_Startup(void)
 	{
 		CONS_Printf("HWR_Startup()...\n");
 
+		textureformat = patchformat = GL_TEXFMT_RGBA;
+
 		HWR_InitPolyPool();
 		HWR_InitMapTextures();
 		HWR_InitModels();
@@ -6636,14 +5762,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;
 }
 
@@ -6707,6 +5831,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;
@@ -6733,7 +5860,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);
@@ -6778,7 +5908,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;
@@ -6803,7 +5933,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;
@@ -6847,7 +5977,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
 }
@@ -6855,18 +5985,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);
 }
 
 //
@@ -6911,201 +6041,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..ea610a48b477824fd1d59e45ce70802bce5e460a 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -35,11 +35,8 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player);
 void HWR_RenderPlayerView(INT32 viewnumber, player_t *player);
 void HWR_ClearSkyDome(void);
 void HWR_BuildSkyDome(void);
-void HWR_DrawViewBorder(INT32 clearlines);
 void HWR_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum);
-void HWR_InitTextureMapping(void);
 void HWR_SetViewSize(void);
-void HWR_DrawPatch(patch_t *gpatch, INT32 x, INT32 y, INT32 option);
 void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap);
 void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
 void HWR_MakePatch(const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipmap, boolean makebitmap);
@@ -48,7 +45,6 @@ void HWR_CreateStaticLightmaps(INT32 bspnum);
 void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color);
 void HWR_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT16 actualcolor, UINT8 strength);
 void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT32 actualcolor);	// Lat: separate flags from color since color needs to be an uint to work right.
-void HWR_DrawPic(INT32 x,INT32 y,lumpnum_t lumpnum);
 
 UINT8 *HWR_GetScreenshot(void);
 boolean HWR_Screenshot(const char *pathname);
@@ -61,11 +57,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 +70,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,21 +93,17 @@ 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;
 
-extern float gl_viewwidth, gl_viewheight, gl_baseviewwindowy;
-
-extern float gl_viewwindowx, gl_basewindowcentery;
-
 // BP: big hack for a test in lighting ref : 1249753487AB
 extern fixed_t *hwbbox;
 extern FTransform atransform;
 extern float gl_viewsin, gl_viewcos;
 
-
 // Render stats
 extern ps_metric_t ps_hw_skyboxtime;
 extern ps_metric_t ps_hw_nodesorttime;
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..c9744e5980da341af12eea3e7620808195395377 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.
@@ -2044,76 +2044,6 @@ void HU_Drawer(void)
 	}
 }
 
-//======================================================================
-//                 HUD MESSAGES CLEARING FROM SCREEN
-//======================================================================
-
-// Clear old messages from the borders around the view window
-// (only for reduced view, refresh the borders when needed)
-//
-// startline: y coord to start clear,
-// clearlines: how many lines to clear.
-//
-static INT32 oldclearlines;
-
-void HU_Erase(void)
-{
-	INT32 topline, bottomline;
-	INT32 y, yoffset;
-
-#ifdef HWRENDER
-	// clear hud msgs on double buffer (OpenGL mode)
-	boolean secondframe;
-	static INT32 secondframelines;
-#endif
-
-	if (con_clearlines == oldclearlines && !con_hudupdate && !chat_on)
-		return;
-
-#ifdef HWRENDER
-	// clear the other frame in double-buffer modes
-	secondframe = (con_clearlines != oldclearlines);
-	if (secondframe)
-		secondframelines = oldclearlines;
-#endif
-
-	// clear the message lines that go away, so use _oldclearlines_
-	bottomline = oldclearlines;
-	oldclearlines = con_clearlines;
-	if (chat_on && OLDCHAT)
-		if (bottomline < 8)
-			bottomline = 8; // only do it for consolechat. consolechat is gay.
-
-	if (automapactive || viewwindowx == 0) // hud msgs don't need to be cleared
-		return;
-
-	// software mode copies view border pattern & beveled edges from the backbuffer
-	if (rendermode == render_soft)
-	{
-		topline = 0;
-		for (y = topline, yoffset = y*vid.width; y < bottomline; y++, yoffset += vid.width)
-		{
-			if (y < viewwindowy || y >= viewwindowy + viewheight)
-				R_VideoErase(yoffset, vid.width); // erase entire line
-			else
-			{
-				R_VideoErase(yoffset, viewwindowx); // erase left border
-				// erase right border
-				R_VideoErase(yoffset + viewwindowx + viewwidth, viewwindowx);
-			}
-		}
-		con_hudupdate = false; // if it was set..
-	}
-#ifdef HWRENDER
-	else if (rendermode != render_none)
-	{
-		// refresh just what is needed from the view borders
-		HWR_DrawViewBorder(secondframelines);
-		con_hudupdate = secondframe;
-	}
-#endif
-}
-
 //======================================================================
 //                   IN-LEVEL MULTIPLAYER RANKINGS
 //======================================================================
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index 8647e4500cb2ce1ebf76015dbcc9031a0b5f4082..b3069c215f1d6f7e09d471c9393c7b250057d942 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -111,7 +111,6 @@ boolean HU_Responder(event_t *ev);
 void HU_Ticker(void);
 void HU_Drawer(void);
 char HU_dequeueChatChar(void);
-void HU_Erase(void);
 void HU_clearChatChars(void);
 void HU_drawPing(INT32 x, INT32 y, UINT32 ping, boolean notext, INT32 flags);	// Lat': Ping drawer for scoreboard.
 void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer);
diff --git a/src/i_video.h b/src/i_video.h
index 8efca5f9ab28da6312755d9e11a6c8791149f26b..4b459e25b67bd207bfae911365e1a15eec101db0 100644
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -40,10 +40,6 @@ extern rendermode_t rendermode;
 */
 extern rendermode_t chosenrendermode;
 
-/**	\brief use highcolor modes if true
-*/
-extern boolean highcolor;
-
 /**	\brief setup video mode
 */
 void I_StartupGraphics(void);
@@ -108,8 +104,8 @@ void VID_CheckGLLoaded(rendermode_t oldrender);
 	\return	name of video mode
 */
 const char *VID_GetModeName(INT32 modenum);
-void VID_PrepareModeList(void); /// note hack for SDL
 
+void VID_PrepareModeList(void);
 
 /**	\brief can video system do fullscreen
 */
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..fcde0d148212d848f6270b723823d2d9cd8f7c93 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
 };
 
@@ -4098,53 +4099,6 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
 {
 	// Solid color textbox.
 	V_DrawFill(x+5, y+5, width*8+6, boxlines*8+6, 159);
-	//V_DrawFill(x+8, y+8, width*8, boxlines*8, 31);
-/*
-	patch_t *p;
-	INT32 cx, cy, n;
-	INT32 step, boff;
-
-	step = 8;
-	boff = 8;
-
-	// draw left side
-	cx = x;
-	cy = y;
-	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TL], PU_PATCH));
-	cy += boff;
-	p = W_CachePatchNum(viewborderlump[BRDR_L], PU_PATCH);
-	for (n = 0; n < boxlines; n++)
-	{
-		V_DrawScaledPatch(cx, cy, 0, p);
-		cy += step;
-	}
-	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BL], PU_PATCH));
-
-	// draw middle
-	V_DrawFlatFill(x + boff, y + boff, width*step, boxlines*step, st_borderpatchnum);
-
-	cx += boff;
-	cy = y;
-	while (width > 0)
-	{
-		V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_T], PU_PATCH));
-		V_DrawScaledPatch(cx, y + boff + boxlines*step, 0, W_CachePatchNum(viewborderlump[BRDR_B], PU_PATCH));
-		width--;
-		cx += step;
-	}
-
-	// draw right side
-	cy = y;
-	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TR], PU_PATCH));
-	cy += boff;
-	p = W_CachePatchNum(viewborderlump[BRDR_R], PU_PATCH);
-	for (n = 0; n < boxlines; n++)
-	{
-		V_DrawScaledPatch(cx, cy, 0, p);
-		cy += step;
-	}
-	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BR], PU_PATCH));
-*/
 }
 
 //
@@ -13672,23 +13626,14 @@ static void M_VideoModeMenu(INT32 choice)
 
 	memset(modedescs, 0, sizeof(modedescs));
 
-#if defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL)
 	VID_PrepareModeList(); // FIXME: hack
-#endif
+
 	vidm_nummodes = 0;
 	vidm_selected = 0;
 	nummodes = VID_NumModes();
 
-#ifdef _WINDOWS
-	// clean that later: skip windowed mode 0, video modes menu only shows FULL SCREEN modes
-	if (nummodes <= NUMSPECIALMODES)
-		i = 0; // unless we have nothing
-	else
-		i = NUMSPECIALMODES;
-#else
-	// DOS does not skip mode 0, because mode 0 is ALWAYS present
 	i = 0;
-#endif
+
 	for (; i < nummodes && vidm_nummodes < MAXMODEDESCS; i++)
 	{
 		desc = VID_GetModeName(i);
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 a386d8ee93d75a47c24e90bf67ccdc08220bab43..accc700730d6281dea492cb4068704c7561c5239 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);
 }
 
@@ -1267,7 +1268,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);
 		}
 	}
 }
@@ -1333,8 +1335,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;
@@ -1343,6 +1346,20 @@ void A_FaceStabHurl(mobj_t *actor)
 						P_SetScale(hwork, hwork->destscale);
 						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;
 				}
@@ -1359,11 +1376,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
@@ -1393,6 +1413,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);
 	}
@@ -1425,6 +1447,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);
@@ -1520,7 +1544,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);
 }
 
@@ -1941,14 +1965,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);
 	}
 }
 
@@ -2034,6 +2059,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);
@@ -2215,6 +2242,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);
@@ -2223,6 +2252,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)))
 		{
@@ -2648,7 +2679,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);
 }
 
@@ -5788,7 +5819,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.
@@ -5796,6 +5831,8 @@ void A_MinusDigging(mobj_t *actor)
 		if (P_MobjWasRemoved(par))
 			return;
 		P_SetMobjState(par, actor->info->raisestate);
+		if (P_MobjWasRemoved(par))
+			return;
 		P_SetScale(par, actor->scale*2);
 		if (actor->eflags & MFE_VERTICALFLIP)
 			par->eflags |= MFE_VERTICALFLIP;
@@ -5809,6 +5846,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)
@@ -5832,7 +5871,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);
 	}
 }
@@ -7304,7 +7343,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.
@@ -7661,7 +7700,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);
 	}
 }
@@ -8119,6 +8158,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)
@@ -8135,6 +8176,8 @@ void A_GuardChase(mobj_t *actor)
 					break;
 			}
 		}
+		if (P_MobjWasRemoved(actor))
+			return;
 
 		if (actor->extravalue1 < actor->info->speed)
 			actor->extravalue1++;
@@ -8171,7 +8214,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.
 		}
 	}
@@ -8641,6 +8688,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);
 }
@@ -11735,7 +11785,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)
@@ -13314,6 +13370,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;
@@ -13856,6 +13914,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.
@@ -14260,6 +14320,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.
@@ -14281,6 +14343,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
@@ -14424,6 +14488,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 82169bc54304782f940fdac535456d18dc047150..8584e168a5b2084f3a43443b4fbeca67a594bbab 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 9ec667346ec44bcfa5755720ac0cc37344ebfdc3..8bc6abc54c63d47456269f23d8224c1f3fd3814f 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;
@@ -3914,6 +3919,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);
 
@@ -4708,6 +4715,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;
 	}
@@ -4945,6 +4954,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);
 
@@ -5514,6 +5525,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))
 		{
@@ -5862,6 +5875,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);
@@ -5902,6 +5917,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);
@@ -7501,6 +7518,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;
@@ -8064,7 +8083,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)
@@ -10294,6 +10314,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);
 
@@ -10424,6 +10446,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);
 	}
@@ -10525,6 +10549,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)
 	{
@@ -10867,9 +10893,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.
@@ -10879,6 +10902,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++;
 
@@ -14027,7 +14053,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 3bde51c3b9091611e5c14f8bae33f6654c7bf198..6a5e4c76933eb5aafdac1bcd0b254e88a526494c 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 25a60cf34be5ed49ccdb820dbc3d01e4f2f562d5..9d0eed288275e28a82dcbe1c0193a643be2f1419 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -11105,7 +11105,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;
 	}
 
@@ -12459,7 +12460,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..75ec0556d46f1edff4d2c473b595a40e835426ac 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
@@ -50,10 +54,6 @@ lighttable_t *fadecolormap;
 // for debugging/info purposes
 size_t flatmemory, spritememory, texturememory;
 
-// highcolor stuff
-INT16 color8to16[256]; // remap color index to highcolor rgb value
-INT16 *hicolormaps; // test a 32k colormap remaps high -> high
-
 // Blends two pixels together, using the equation
 // that matches the specified alpha style.
 UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha)
@@ -426,6 +426,9 @@ void R_ClearColormaps(void)
 {
 	// Purged by PU_LEVEL, just overwrite the pointer
 	extra_colormaps = R_CreateDefaultColormap(true);
+#ifdef HWRENDER
+	HWR_ClearLightTables();
+#endif
 }
 
 //
@@ -1170,40 +1173,6 @@ const char *R_NameForColormap(extracolormap_t *extra_colormap)
 }
 #endif
 
-//
-// build a table for quick conversion from 8bpp to 15bpp
-//
-
-//
-// added "static inline" keywords, linking with the debug version
-// of allegro, it have a makecol15 function of it's own, now
-// with "static inline" keywords,it sloves this problem ;)
-//
-FUNCMATH static inline int makecol15(int r, int g, int b)
-{
-	return (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
-}
-
-static void R_Init8to16(void)
-{
-	UINT8 *palette;
-	int i;
-
-	palette = W_CacheLumpName("PLAYPAL",PU_CACHE);
-
-	for (i = 0; i < 256; i++)
-	{
-		// PLAYPAL uses 8 bit values
-		color8to16[i] = (INT16)makecol15(palette[0], palette[1], palette[2]);
-		palette += 3;
-	}
-
-	// test a big colormap
-	hicolormaps = Z_Malloc(16384*sizeof(*hicolormaps), PU_STATIC, NULL);
-	for (i = 0; i < 16384; i++)
-		hicolormaps[i] = (INT16)(i<<1);
-}
-
 //
 // R_InitData
 //
@@ -1212,12 +1181,6 @@ static void R_Init8to16(void)
 //
 void R_InitData(void)
 {
-	if (highcolor)
-	{
-		CONS_Printf("InitHighColor...\n");
-		R_Init8to16();
-	}
-
 	CONS_Printf("R_LoadParsedTranslations()...\n");
 	R_LoadParsedTranslations();
 
diff --git a/src/r_data.h b/src/r_data.h
index 364f85b6d4b0cbdd09448144f0546db9bc661bfc..9340ed284b6524e54774cfa43ed6c24e3e92afe3 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -36,9 +36,6 @@ UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT
 
 extern INT32 ASTTextureBlendingThreshold[2];
 
-extern INT16 color8to16[256]; // remap color index to highcolor
-extern INT16 *hicolormaps; // remap high colors to high colors..
-
 extern CV_PossibleValue_t Color_cons_t[];
 
 // I/O, setting up the stuff.
diff --git a/src/r_defs.h b/src/r_defs.h
index 899d6ad73fd1bb9ef2b03992e636bf08fa0b42c0..7e92c92492a88527f2bf5685bf93797c0081494c 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
@@ -840,15 +849,6 @@ typedef struct drawseg_s
 	vertex_t leftpos, rightpos; // Used for rendering FOF walls with slopes
 } drawseg_t;
 
-typedef enum
-{
-	PALETTE         = 0,  // 1 byte is the index in the doom palette (as usual)
-	INTENSITY       = 1,  // 1 byte intensity
-	INTENSITY_ALPHA = 2,  // 2 byte: alpha then intensity
-	RGB24           = 3,  // 24 bit rgb
-	RGBA32          = 4,  // 32 bit rgba
-} pic_mode_t;
-
 #ifdef ROTSPRITE
 typedef struct
 {
diff --git a/src/r_draw.c b/src/r_draw.c
index 513a54f4aefff9ac0f57b916e6e0c12d7286e4c3..86f7e488c8502516cc4f36bb04b7a516385fc5ed 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -40,23 +40,6 @@
 */
 INT32 viewwidth, scaledviewwidth, viewheight, viewwindowx, viewwindowy;
 
-/**	\brief pointer to the start of each line of the screen,
-*/
-UINT8 *ylookup[MAXVIDHEIGHT*4];
-
-/**	\brief pointer to the start of each line of the screen, for view1 (splitscreen)
-*/
-UINT8 *ylookup1[MAXVIDHEIGHT*4];
-
-/**	\brief pointer to the start of each line of the screen, for view2 (splitscreen)
-*/
-UINT8 *ylookup2[MAXVIDHEIGHT*4];
-
-/**	\brief  x byte offset for columns inside the viewwindow,
-	so the first column starts at (SCRWIDTH - VIEWWIDTH)/2
-*/
-INT32 columnofs[MAXVIDWIDTH*4];
-
 UINT8 *topleft;
 
 // =========================================================================
@@ -67,8 +50,6 @@ lighttable_t *dc_colormap;
 INT32 dc_x = 0, dc_yl = 0, dc_yh = 0;
 
 fixed_t dc_iscale, dc_texturemid;
-UINT8 dc_hires; // under MSVC boolean is a byte, while on other systems, it a bit,
-               // soo lets make it a byte on all system for the ASM code
 UINT8 *dc_source;
 
 // -----------------------
@@ -677,7 +658,7 @@ UINT16 R_GetSuperColorByName(const char *name)
 
 void R_InitViewBuffer(INT32 width, INT32 height)
 {
-	INT32 i, bytesperpixel = vid.bpp;
+	INT32 bytesperpixel = vid.bpp;
 
 	if (width > MAXVIDWIDTH)
 		width = MAXVIDWIDTH;
@@ -689,118 +670,13 @@ void R_InitViewBuffer(INT32 width, INT32 height)
 	// Handle resize, e.g. smaller view windows with border and/or status bar.
 	viewwindowx = (vid.width - width) >> 1;
 
-	// Column offset for those columns of the view window, but relative to the entire screen
-	for (i = 0; i < width; i++)
-		columnofs[i] = (viewwindowx + i) * bytesperpixel;
-
 	// Same with base row offset.
 	if (width == vid.width)
 		viewwindowy = 0;
 	else
 		viewwindowy = (vid.height - height) >> 1;
-
-	// Precalculate all row offsets.
-	for (i = 0; i < height; i++)
-	{
-		ylookup[i] = ylookup1[i] = screens[0] + (i+viewwindowy)*vid.width*bytesperpixel;
-		ylookup2[i] = screens[0] + (i+(vid.height>>1))*vid.width*bytesperpixel; // for splitscreen
-	}
 }
 
-/**	\brief viewborder patches lump numbers
-*/
-lumpnum_t viewborderlump[8];
-
-/**	\brief Store the lumpnumber of the viewborder patches
-*/
-
-void R_InitViewBorder(void)
-{
-	viewborderlump[BRDR_T] = W_GetNumForName("brdr_t");
-	viewborderlump[BRDR_B] = W_GetNumForName("brdr_b");
-	viewborderlump[BRDR_L] = W_GetNumForName("brdr_l");
-	viewborderlump[BRDR_R] = W_GetNumForName("brdr_r");
-	viewborderlump[BRDR_TL] = W_GetNumForName("brdr_tl");
-	viewborderlump[BRDR_BL] = W_GetNumForName("brdr_bl");
-	viewborderlump[BRDR_TR] = W_GetNumForName("brdr_tr");
-	viewborderlump[BRDR_BR] = W_GetNumForName("brdr_br");
-}
-
-#if 0
-/**	\brief R_FillBackScreen
-
-	Fills the back screen with a pattern for variable screen sizes
-	Also draws a beveled edge.
-*/
-void R_FillBackScreen(void)
-{
-	UINT8 *src, *dest;
-	patch_t *patch;
-	INT32 x, y, step, boff;
-
-	// quickfix, don't cache lumps in both modes
-	if (rendermode != render_soft)
-		return;
-
-	// draw pattern around the status bar too (when hires),
-	// so return only when in full-screen without status bar.
-	if (scaledviewwidth == vid.width && viewheight == vid.height)
-		return;
-
-	src = scr_borderpatch;
-	dest = screens[1];
-
-	for (y = 0; y < vid.height; y++)
-	{
-		for (x = 0; x < vid.width/128; x++)
-		{
-			M_Memcpy (dest, src+((y&127)<<7), 128);
-			dest += 128;
-		}
-
-		if (vid.width&127)
-		{
-			M_Memcpy(dest, src+((y&127)<<7), vid.width&127);
-			dest += (vid.width&127);
-		}
-	}
-
-	// don't draw the borders when viewwidth is full vid.width.
-	if (scaledviewwidth == vid.width)
-		return;
-
-	step = 8;
-	boff = 8;
-
-	patch = W_CacheLumpNum(viewborderlump[BRDR_T], PU_CACHE);
-	for (x = 0; x < scaledviewwidth; x += step)
-		V_DrawPatch(viewwindowx + x, viewwindowy - boff, 1, patch);
-
-	patch = W_CacheLumpNum(viewborderlump[BRDR_B], PU_CACHE);
-	for (x = 0; x < scaledviewwidth; x += step)
-		V_DrawPatch(viewwindowx + x, viewwindowy + viewheight, 1, patch);
-
-	patch = W_CacheLumpNum(viewborderlump[BRDR_L], PU_CACHE);
-	for (y = 0; y < viewheight; y += step)
-		V_DrawPatch(viewwindowx - boff, viewwindowy + y, 1, patch);
-
-	patch = W_CacheLumpNum(viewborderlump[BRDR_R],PU_CACHE);
-	for (y = 0; y < viewheight; y += step)
-		V_DrawPatch(viewwindowx + scaledviewwidth, viewwindowy + y, 1,
-			patch);
-
-	// Draw beveled corners.
-	V_DrawPatch(viewwindowx - boff, viewwindowy - boff, 1,
-		W_CacheLumpNum(viewborderlump[BRDR_TL], PU_CACHE));
-	V_DrawPatch(viewwindowx + scaledviewwidth, viewwindowy - boff, 1,
-		W_CacheLumpNum(viewborderlump[BRDR_TR], PU_CACHE));
-	V_DrawPatch(viewwindowx - boff, viewwindowy + viewheight, 1,
-		W_CacheLumpNum(viewborderlump[BRDR_BL], PU_CACHE));
-	V_DrawPatch(viewwindowx + scaledviewwidth, viewwindowy + viewheight, 1,
-		W_CacheLumpNum(viewborderlump[BRDR_BR], PU_CACHE));
-}
-#endif
-
 /**	\brief	The R_VideoErase function
 
 	Copy a screen buffer.
@@ -822,55 +698,6 @@ void R_VideoErase(size_t ofs, INT32 count)
 	M_Memcpy(screens[0] + ofs, screens[1] + ofs, count);
 }
 
-#if 0
-/**	\brief The R_DrawViewBorder
-
-  Draws the border around the view
-	for different size windows?
-*/
-void R_DrawViewBorder(void)
-{
-	INT32 top, side, ofs;
-
-	if (rendermode == render_none)
-		return;
-#ifdef HWRENDER
-	if (rendermode != render_soft)
-	{
-		HWR_DrawViewBorder(0);
-		return;
-	}
-	else
-#endif
-
-#ifdef DEBUG
-	fprintf(stderr,"RDVB: vidwidth %d vidheight %d scaledviewwidth %d viewheight %d\n",
-		vid.width, vid.height, scaledviewwidth, viewheight);
-#endif
-
-	if (scaledviewwidth == vid.width)
-		return;
-
-	top = (vid.height - viewheight)>>1;
-	side = (vid.width - scaledviewwidth)>>1;
-
-	// copy top and one line of left side
-	R_VideoErase(0, top*vid.width+side);
-
-	// copy one line of right side and bottom
-	ofs = (viewheight+top)*vid.width - side;
-	R_VideoErase(ofs, top*vid.width + side);
-
-	// copy sides using wraparound
-	ofs = top*vid.width + vid.width-side;
-	side <<= 1;
-
-    // simpler using our VID_Blit routine
-	VID_BlitLinearScreen(screens[1] + ofs, screens[0] + ofs, side, viewheight - 1,
-		vid.width, vid.width);
-}
-#endif
-
 // R_CalcTiltedLighting
 // Exactly what it says on the tin. I wish I wasn't too lazy to explain things properly.
 static INT32 tiltlighting[MAXVIDWIDTH];
@@ -912,11 +739,3 @@ static void R_CalcSlopeLight(void)
 
 #include "r_draw8.c"
 #include "r_draw8_npo2.c"
-
-// ==========================================================================
-//                   INCLUDE 16bpp DRAWING CODE HERE
-// ==========================================================================
-
-#ifdef HIGHCOLOR
-#include "r_draw16.c"
-#endif
diff --git a/src/r_draw.h b/src/r_draw.h
index 7eb001ebd360df404e57a5716d96aa73aa013219..1a828312a7a922353a3ff5b969908dc61dcf0ee6 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -19,10 +19,6 @@
 // -------------------------------
 // COMMON STUFF FOR 8bpp AND 16bpp
 // -------------------------------
-extern UINT8 *ylookup[MAXVIDHEIGHT*4];
-extern UINT8 *ylookup1[MAXVIDHEIGHT*4];
-extern UINT8 *ylookup2[MAXVIDHEIGHT*4];
-extern INT32 columnofs[MAXVIDWIDTH*4];
 extern UINT8 *topleft;
 
 // -------------------------
@@ -32,7 +28,6 @@ extern UINT8 *topleft;
 extern lighttable_t *dc_colormap;
 extern INT32 dc_x, dc_yl, dc_yh;
 extern fixed_t dc_iscale, dc_texturemid;
-extern UINT8 dc_hires;
 
 extern UINT8 *dc_source; // first pixel in a column
 
@@ -77,25 +72,6 @@ extern UINT32 nflatyshift;
 extern UINT32 nflatshiftup;
 extern UINT32 nflatmask;
 
-/// \brief Top border
-#define BRDR_T 0
-/// \brief Bottom border
-#define BRDR_B 1
-/// \brief Left border
-#define BRDR_L 2
-/// \brief Right border
-#define BRDR_R 3
-/// \brief Topleft border
-#define BRDR_TL 4
-/// \brief Topright border
-#define BRDR_TR 5
-/// \brief Bottomleft border
-#define BRDR_BL 6
-/// \brief Bottomright border
-#define BRDR_BR 7
-
-extern lumpnum_t viewborderlump[8];
-
 // ------------------------------------------------
 // r_draw.c COMMON ROUTINES FOR BOTH 8bpp and 16bpp
 // ------------------------------------------------
@@ -169,17 +145,8 @@ boolean R_BlendLevelVisible(INT32 blendmode, INT32 alphalevel);
 extern boolean skincolor_modified[];
 
 void R_InitViewBuffer(INT32 width, INT32 height);
-void R_InitViewBorder(void);
 void R_VideoErase(size_t ofs, INT32 count);
 
-// Rendering function.
-#if 0
-void R_FillBackScreen(void);
-
-// If the view size is not full screen, draws a border around it.
-void R_DrawViewBorder(void);
-#endif
-
 #define TRANSPARENTPIXEL 255
 
 // -----------------
@@ -240,17 +207,5 @@ void R_DrawTiltedTransSolidColorSpan_8(void);
 void R_DrawWaterSolidColorSpan_8(void);
 void R_DrawTiltedWaterSolidColorSpan_8(void);
 
-// ------------------
-// 16bpp DRAWING CODE
-// ------------------
-
-#ifdef HIGHCOLOR
-void R_DrawColumn_16(void);
-void R_DrawWallColumn_16(void);
-void R_DrawTranslucentColumn_16(void);
-void R_DrawTranslatedColumn_16(void);
-void R_DrawSpan_16(void);
-#endif
-
 // =========================================================================
 #endif  // __R_DRAW__
diff --git a/src/r_draw16.c b/src/r_draw16.c
deleted file mode 100644
index 2ed5a2a8e3d267ab869c5a0c800423fe7eb68eba..0000000000000000000000000000000000000000
--- a/src/r_draw16.c
+++ /dev/null
@@ -1,214 +0,0 @@
-// SONIC ROBO BLAST 2
-//-----------------------------------------------------------------------------
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2023 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  r_draw16.c
-/// \brief 16bpp (HIGHCOLOR) span/column drawer functions
-/// \note  no includes because this is included as part of r_draw.c
-
-// ==========================================================================
-// COLUMNS
-// ==========================================================================
-
-/// \brief kick out the upper bit of each component (we're in 5 : 5 : 5)
-#define HIMASK1 0x7bde
-
-/**	\brief The R_DrawColumn_16 function
-	standard upto 128high posts column drawer
-*/
-void R_DrawColumn_16(void)
-{
-	INT32 count;
-	INT16 *dest;
-	fixed_t frac, fracstep;
-
-	count = dc_yh - dc_yl + 1;
-
-	// Zero length, column does not exceed a pixel.
-	if (count <= 0)
-		return;
-
-#ifdef RANGECHECK
-	if (dc_x >= vid.width || dc_yl < 0 || dc_yh >= vid.height)
-		I_Error("R_DrawColumn_16: %d to %d at %d", dc_yl, dc_yh, dc_x);
-#endif
-
-	// Framebuffer destination address.
-	// Use ylookup LUT to avoid multiply with ScreenWidth.
-	// Use columnofs LUT for subwindows?
-	dest = (INT16 *)(void *)(ylookup[dc_yl] + columnofs[dc_x]);
-
-	// Determine scaling, which is the only mapping to be done.
-	fracstep = dc_iscale;
-	frac = dc_texturemid + (dc_yl - centery)*fracstep;
-
-	// Inner loop that does the actual texture mapping, e.g. a DDA-like scaling.
-	// This is as fast as it gets.
-
-	do
-	{
-		// Re-map color indices from wall texture column using a lighting/special effects LUT.
-		*dest = hicolormaps[((INT16 *)(void *)dc_source)[(frac>>FRACBITS)&127]>>1];
-
-		dest += vid.width;
-		frac += fracstep;
-	} while (--count);
-}
-
-/**	\brief The R_DrawWallColumn_16 function
-	LAME cutnpaste: same as R_DrawColumn_16 but wraps around 256
-	instead of 128 for the tall sky textures (256x240)
-*/
-void R_DrawWallColumn_16(void)
-{
-	INT32 count;
-	INT16 *dest;
-	fixed_t frac, fracstep;
-
-	count = dc_yh - dc_yl + 1;
-
-	// Zero length, column does not exceed a pixel.
-	if (count <= 0)
-		return;
-
-#ifdef RANGECHECK
-	if (dc_x >= vid.width || dc_yl < 0 || dc_yh >= vid.height)
-		I_Error("R_DrawWallColumn_16: %d to %d at %d", dc_yl, dc_yh, dc_x);
-#endif
-
-	dest = (INT16 *)(void *)(ylookup[dc_yl] + columnofs[dc_x]);
-
-	fracstep = dc_iscale;
-	frac = dc_texturemid + (dc_yl - centery)*fracstep;
-
-	do
-	{
-		*dest = hicolormaps[((INT16 *)(void *)dc_source)[(frac>>FRACBITS)&255]>>1];
-
-		dest += vid.width;
-		frac += fracstep;
-	} while (--count);
-}
-
-/**	\brief The R_DrawTranslucentColumn_16 function
-		LAME cutnpaste: same as R_DrawColumn_16 but does
-		translucent
-*/
-void R_DrawTranslucentColumn_16(void)
-{
-	INT32 count;
-	INT16 *dest;
-	fixed_t frac, fracstep;
-
-	// check out coords for src*
-	if ((dc_yl < 0) || (dc_x >= vid.width))
-		return;
-
-	count = dc_yh - dc_yl;
-	if (count < 0)
-		return;
-
-#ifdef RANGECHECK
-	if (dc_x >= vid.width || dc_yl < 0 || dc_yh >= vid.height)
-		I_Error("R_DrawTranslucentColumn_16: %d to %d at %d", dc_yl, dc_yh, dc_x);
-#endif
-
-	// FIXME. As above.
-	dest = (INT16 *)(void *)(ylookup[dc_yl] + columnofs[dc_x]);
-
-	// Looks familiar.
-	fracstep = dc_iscale;
-	frac = dc_texturemid + (dc_yl - centery)*fracstep;
-
-	// Here we do an additional index re-mapping.
-	do
-	{
-		*dest = (INT16)((INT16)((color8to16[dc_source[frac>>FRACBITS]]>>1) & 0x39ce)
-			+ (INT16)(((*dest & HIMASK1)) & 0x7fff));
-
-		dest += vid.width;
-		frac += fracstep;
-	} while (count--);
-}
-
-/**	\brief The R_DrawTranslatedColumn_16 function
-	?
-*/
-void R_DrawTranslatedColumn_16(void)
-{
-	INT32 count;
-	INT16 *dest;
-	fixed_t frac, fracstep;
-
-	count = dc_yh - dc_yl;
-	if (count < 0)
-		return;
-
-#ifdef RANGECHECK
-	if (dc_x >= vid.width || dc_yl < 0 || dc_yh >= vid.height)
-		I_Error("R_DrawTranslatedColumn_16: %d to %d at %d", dc_yl, dc_yh, dc_x);
-#endif
-
-	dest = (INT16 *)(void *)(ylookup[dc_yl] + columnofs[dc_x]);
-
-	// Looks familiar.
-	fracstep = dc_iscale;
-	frac = dc_texturemid + (dc_yl - centery)*fracstep;
-
-	// Here we do an additional index re-mapping.
-	do
-	{
-		*dest = color8to16[dc_colormap[dc_translation[dc_source[frac>>FRACBITS]]]];
-		dest += vid.width;
-
-		frac += fracstep;
-	} while (count--);
-}
-
-// ==========================================================================
-// SPANS
-// ==========================================================================
-
-/**	\brief The R_*_16 function
-	Draws the actual span.
-*/
-void R_DrawSpan_16(void)
-{
-	fixed_t xfrac, yfrac;
-	INT16 *dest;
-	INT32 count, spot;
-
-#ifdef RANGECHECK
-	if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= vid.width || ds_y > vid.height)
-		I_Error("R_DrawSpan_16: %d to %d at %d", ds_x1, ds_x2, ds_y);
-#endif
-
-	xfrac = ds_xfrac;
-	yfrac = ds_yfrac;
-
-	dest = (INT16 *)(void *)(ylookup[ds_y] + columnofs[ds_x1]);
-
-	// We do not check for zero spans here?
-	count = ds_x2 - ds_x1;
-
-	if (count <= 0) // We do now!
-		return;
-
-	do
-	{
-		// Current texture index in u, v.
-		spot = ((yfrac>>(16-6))&(63*64)) + ((xfrac>>16)&63);
-
-		// Lookup pixel from flat texture tile, re-index using light/colormap.
-		*dest++ = hicolormaps[((INT16 *)(void *)ds_source)[spot]>>1];
-
-		// Next step in u, v.
-		xfrac += ds_xstep;
-		yfrac += ds_ystep;
-	} while (count--);
-}
diff --git a/src/r_draw8.c b/src/r_draw8.c
index 87545f41836761421f2cdf8694c07c88f6815612..99fb71e289343034bc6327314da0e64b0d687dab 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -40,18 +40,13 @@ void R_DrawColumn_8(void)
 #endif
 
 	// Framebuffer destination address.
-	// Use ylookup LUT to avoid multiply with ScreenWidth.
-	// Use columnofs LUT for subwindows?
-
-	//dest = ylookup[dc_yl] + columnofs[dc_x];
 	dest = &topleft[dc_yl*vid.width + dc_x];
 
 	count++;
 
 	// Determine scaling, which is the only mapping to be done.
 	fracstep = dc_iscale;
-	//frac = dc_texturemid + (dc_yl - centery)*fracstep;
-	frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep))*(!dc_hires);
+	frac = dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep);
 
 	// Inner loop that does the actual texture mapping, e.g. a DDA-like scaling.
 	// This is as fast as it gets.
@@ -127,14 +122,11 @@ void R_DrawShadeColumn_8(void)
 		I_Error("R_DrawShadeColumn_8: %d to %d at %d", dc_yl, dc_yh, dc_x);
 #endif
 
-	// FIXME. As above.
-	//dest = ylookup[dc_yl] + columnofs[dc_x];
 	dest = &topleft[dc_yl*vid.width + dc_x];
 
 	// Looks familiar.
 	fracstep = dc_iscale;
-	//frac = dc_texturemid + (dc_yl - centery)*fracstep;
-	frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep))*(!dc_hires);
+	frac = dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep);
 
 	// Here we do an additional index re-mapping.
 	do
@@ -166,14 +158,11 @@ void R_DrawTranslucentColumn_8(void)
 		I_Error("R_DrawTranslucentColumn_8: %d to %d at %d", dc_yl, dc_yh, dc_x);
 #endif
 
-	// FIXME. As above.
-	//dest = ylookup[dc_yl] + columnofs[dc_x];
 	dest = &topleft[dc_yl*vid.width + dc_x];
 
 	// Looks familiar.
 	fracstep = dc_iscale;
-	//frac = dc_texturemid + (dc_yl - centery)*fracstep;
-	frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep))*(!dc_hires);
+	frac = dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep);
 
 	// Inner loop that does the actual texture mapping, e.g. a DDA-like scaling.
 	// This is as fast as it gets.
@@ -271,14 +260,11 @@ void R_DrawTranslatedTranslucentColumn_8(void)
 	if (count <= 0) // Zero length, column does not exceed a pixel.
 		return;
 
-	// FIXME. As above.
-	//dest = ylookup[dc_yl] + columnofs[dc_x];
 	dest = &topleft[dc_yl*vid.width + dc_x];
 
 	// Looks familiar.
 	fracstep = dc_iscale;
-	//frac = dc_texturemid + (dc_yl - centery)*fracstep;
-	frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep))*(!dc_hires);
+	frac = dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep);
 
 	// Inner loop that does the actual texture mapping, e.g. a DDA-like scaling.
 	// This is as fast as it gets.
@@ -347,14 +333,11 @@ void R_DrawTranslatedColumn_8(void)
 		I_Error("R_DrawTranslatedColumn_8: %d to %d at %d", dc_yl, dc_yh, dc_x);
 #endif
 
-	// FIXME. As above.
-	//dest = ylookup[dc_yl] + columnofs[dc_x];
 	dest = &topleft[dc_yl*vid.width + dc_x];
 
 	// Looks familiar.
 	fracstep = dc_iscale;
-	//frac = dc_texturemid + (dc_yl-centery)*fracstep;
-	frac = (dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep))*(!dc_hires);
+	frac = dc_texturemid + FixedMul((dc_yl << FRACBITS) - centeryfrac, fracstep);
 
 	// Here we do an additional index re-mapping.
 	do
@@ -410,7 +393,7 @@ void R_DrawSpan_8 (void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	if (dest+8 > deststop)
 		return;
@@ -489,7 +472,7 @@ void R_DrawTiltedSpan_8(void)
 
 	R_CalcSlopeLight();
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = ds_source;
 	//colormap = ds_colormap;
 
@@ -611,7 +594,7 @@ void R_DrawTiltedTranslucentSpan_8(void)
 
 	R_CalcSlopeLight();
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = ds_source;
 	//colormap = ds_colormap;
 
@@ -733,7 +716,7 @@ void R_DrawTiltedWaterSpan_8(void)
 
 	R_CalcSlopeLight();
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
 	source = ds_source;
 	//colormap = ds_colormap;
@@ -854,7 +837,7 @@ void R_DrawTiltedSplat_8(void)
 
 	R_CalcSlopeLight();
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = ds_source;
 	//colormap = ds_colormap;
 
@@ -991,7 +974,7 @@ void R_DrawSplat_8 (void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	while (count >= 8)
 	{
@@ -1111,7 +1094,7 @@ void R_DrawTranslucentSplat_8 (void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	while (count >= 8)
 	{
@@ -1214,7 +1197,7 @@ void R_DrawFloorSprite_8 (void)
 	source = (UINT16 *)ds_source;
 	colormap = ds_colormap;
 	translation = ds_translation;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	while (count >= 8)
 	{
@@ -1325,7 +1308,7 @@ void R_DrawTranslucentFloorSprite_8 (void)
 	source = (UINT16 *)ds_source;
 	colormap = ds_colormap;
 	translation = ds_translation;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	while (count >= 8)
 	{
@@ -1420,7 +1403,7 @@ void R_DrawTiltedFloorSprite_8(void)
 	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
 	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = (UINT16 *)ds_source;
 	colormap = ds_colormap;
 	translation = ds_translation;
@@ -1529,7 +1512,7 @@ void R_DrawTiltedTranslucentFloorSprite_8(void)
 	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
 	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = (UINT16 *)ds_source;
 	colormap = ds_colormap;
 	translation = ds_translation;
@@ -1644,7 +1627,7 @@ void R_DrawTranslucentSpan_8 (void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	while (count >= 8)
 	{
@@ -1721,7 +1704,7 @@ void R_DrawWaterSpan_8(void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
 	count = ds_x2 - ds_x1 + 1;
 
@@ -1784,7 +1767,6 @@ void R_DrawFogSpan_8(void)
 	size_t count;
 
 	colormap = ds_colormap;
-	//dest = ylookup[ds_y] + columnofs[ds_x1];
 	dest = &topleft[ds_y *vid.width + ds_x1];
 
 	count = ds_x2 - ds_x1 + 1;
@@ -1814,7 +1796,7 @@ void R_DrawTiltedFogSpan_8(void)
 {
 	int width = ds_x2 - ds_x1;
 
-	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
+	UINT8 *dest = &topleft[ds_y*vid.width + ds_x1];
 
 	R_CalcSlopeLight();
 
@@ -1834,7 +1816,7 @@ void R_DrawSolidColorSpan_8(void)
 	size_t count = (ds_x2 - ds_x1 + 1);
 
 	UINT8 source = ds_colormap[ds_source[0]];
-	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
+	UINT8 *dest = &topleft[ds_y*vid.width + ds_x1];
 
 	memset(dest, source, count);
 }
@@ -1847,7 +1829,7 @@ void R_DrawTransSolidColorSpan_8(void)
 	size_t count = (ds_x2 - ds_x1 + 1);
 
 	UINT8 source = ds_colormap[ds_source[0]];
-	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
+	UINT8 *dest = &topleft[ds_y*vid.width + ds_x1];
 
 	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
 
@@ -1866,7 +1848,7 @@ void R_DrawTiltedSolidColorSpan_8(void)
 	int width = ds_x2 - ds_x1;
 
 	UINT8 source = ds_source[0];
-	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
+	UINT8 *dest = &topleft[ds_y*vid.width + ds_x1];
 
 	R_CalcSlopeLight();
 
@@ -1885,7 +1867,7 @@ void R_DrawTiltedTransSolidColorSpan_8(void)
 	int width = ds_x2 - ds_x1;
 
 	UINT8 source = ds_source[0];
-	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
+	UINT8 *dest = &topleft[ds_y*vid.width + ds_x1];
 
 	R_CalcSlopeLight();
 
@@ -1904,7 +1886,7 @@ void R_DrawWaterSolidColorSpan_8(void)
 {
 	UINT8 source = ds_source[0];
 	UINT8 *colormap = ds_colormap;
-	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
+	UINT8 *dest = &topleft[ds_y*vid.width + ds_x1];
 	UINT8 *dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
 
 	size_t count = (ds_x2 - ds_x1 + 1);
@@ -1925,7 +1907,7 @@ void R_DrawTiltedWaterSolidColorSpan_8(void)
 	int width = ds_x2 - ds_x1;
 
 	UINT8 source = ds_source[0];
-	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
+	UINT8 *dest = &topleft[ds_y*vid.width + ds_x1];
 	UINT8 *dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
 
 	R_CalcSlopeLight();
@@ -1957,9 +1939,6 @@ void R_DrawFogColumn_8(void)
 #endif
 
 	// Framebuffer destination address.
-	// Use ylookup LUT to avoid multiply with ScreenWidth.
-	// Use columnofs LUT for subwindows?
-	//dest = ylookup[dc_yl] + columnofs[dc_x];
 	dest = &topleft[dc_yl*vid.width + dc_x];
 
 	// Determine scaling, which is the only mapping to be done.
diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c
index 78cde8a2ce195678333916f38a1b51bf5ba8e898..1ed3a8fa406ac8cb7cef2c95729a3a4de611faf0 100644
--- a/src/r_draw8_npo2.c
+++ b/src/r_draw8_npo2.c
@@ -46,7 +46,7 @@ void R_DrawSpan_NPO2_8 (void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	if (dest+8 > deststop)
 		return;
@@ -120,7 +120,7 @@ void R_DrawTiltedSpan_NPO2_8(void)
 
 	R_CalcSlopeLight();
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = ds_source;
 	//colormap = ds_colormap;
 
@@ -309,7 +309,7 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void)
 
 	R_CalcSlopeLight();
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = ds_source;
 	//colormap = ds_colormap;
 
@@ -496,7 +496,7 @@ void R_DrawTiltedSplat_NPO2_8(void)
 
 	R_CalcSlopeLight();
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = ds_source;
 	//colormap = ds_colormap;
 
@@ -690,7 +690,7 @@ void R_DrawSplat_NPO2_8 (void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	fixedwidth = ds_flatwidth << FRACBITS;
 	fixedheight = ds_flatheight << FRACBITS;
@@ -758,7 +758,7 @@ void R_DrawTranslucentSplat_NPO2_8 (void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	fixedwidth = ds_flatwidth << FRACBITS;
 	fixedheight = ds_flatheight << FRACBITS;
@@ -828,7 +828,7 @@ void R_DrawFloorSprite_NPO2_8 (void)
 	source = (UINT16 *)ds_source;
 	colormap = ds_colormap;
 	translation = ds_translation;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	fixedwidth = ds_flatwidth << FRACBITS;
 	fixedheight = ds_flatheight << FRACBITS;
@@ -898,7 +898,7 @@ void R_DrawTranslucentFloorSprite_NPO2_8 (void)
 	source = (UINT16 *)ds_source;
 	colormap = ds_colormap;
 	translation = ds_translation;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	fixedwidth = ds_flatwidth << FRACBITS;
 	fixedheight = ds_flatheight << FRACBITS;
@@ -971,7 +971,7 @@ void R_DrawTiltedFloorSprite_NPO2_8(void)
 	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
 	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = (UINT16 *)ds_source;
 	colormap = ds_colormap;
 	translation = ds_translation;
@@ -1127,7 +1127,7 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
 	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
 	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	source = (UINT16 *)ds_source;
 	colormap = ds_colormap;
 	translation = ds_translation;
@@ -1278,7 +1278,7 @@ void R_DrawTranslucentSpan_NPO2_8 (void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 
 	fixedwidth = ds_flatwidth << FRACBITS;
 	fixedheight = ds_flatheight << FRACBITS;
@@ -1342,7 +1342,7 @@ void R_DrawWaterSpan_NPO2_8(void)
 
 	source = ds_source;
 	colormap = ds_colormap;
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
 
 	fixedwidth = ds_flatwidth << FRACBITS;
@@ -1414,7 +1414,7 @@ void R_DrawTiltedWaterSpan_NPO2_8(void)
 
 	R_CalcSlopeLight();
 
-	dest = ylookup[ds_y] + columnofs[ds_x1];
+	dest = &topleft[ds_y*vid.width + ds_x1];
 	dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
 	source = ds_source;
 	//colormap = ds_colormap;
diff --git a/src/r_main.c b/src/r_main.c
index c920031a053abe7fb0dafb2c0231bcc0ba18d0ae..233e55b8280a4f9e0ed80cf629435904910193d6 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -970,8 +970,6 @@ void R_Init(void)
 	//I_OutputMsg("\nR_InitData");
 	R_InitData();
 
-	//I_OutputMsg("\nR_InitViewBorder");
-	R_InitViewBorder();
 	R_SetViewSize(); // setsizeneeded is set true
 
 	// this is now done by SCR_Recalc() at the first mode set
@@ -1332,7 +1330,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..cad2dd49fab32e5649937a212690c90ceab96be6 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -603,6 +603,9 @@ void R_DrawPlanes(void)
 	visplane_t *pl;
 	INT32 i;
 
+	if (!cv_renderfloors.value)
+		return;
+
 	R_UpdatePlaneRipple();
 
 	for (i = 0; i < MAXVISPLANES; i++, pl++)
@@ -626,7 +629,7 @@ static void R_DrawSkyPlane(visplane_t *pl)
 {
 	INT32 texture = texturetranslation[skytexture];
 
-	// Reset column drawer function (note: couldn't we just call walldrawerfunc directly?)
+	// Reset column drawer function (note: couldn't we just call colfuncs[BASEDRAWFUNC] directly?)
 	// (that is, unless we'll need to switch drawers in future for some reason)
 	colfunc = colfuncs[BASEDRAWFUNC];
 
diff --git a/src/r_segs.c b/src/r_segs.c
index b5d4a6dae1ba1eec2491b609a7f4e991139858fe..6656127fe3d473e6ecf08cfed83f699674df704d 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -447,7 +447,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;
@@ -1014,6 +1014,12 @@ static void R_DrawFlippedWall(UINT8 *source, INT32 height)
 	R_DrawFlippedPost(source, (unsigned)height, colfunc);
 }
 
+static void R_DrawNoWall(UINT8 *source, INT32 height)
+{
+	(void)source;
+	(void)height;
+}
+
 static void R_RenderSegLoop (void)
 {
 	angle_t angle;
@@ -1060,6 +1066,13 @@ static void R_RenderSegLoop (void)
 		drawbottom = R_DrawFlippedWall;
 	}
 
+	if (!cv_renderwalls.value)
+	{
+		drawtop = R_DrawNoWall;
+		drawmiddle = R_DrawNoWall;
+		drawbottom = R_DrawNoWall;
+	}
+
 	if (midtexture)
 		R_CheckTextureCache(midtexture);
 	if (toptexture)
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 2d1e27a59dd64d8058710fd75e58922fae0d3944..80b6816dbdce552511248e844529d7ea6f80f505 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -951,7 +951,6 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	}
 
 	colfunc = colfuncs[BASEDRAWFUNC];
-	dc_hires = 0;
 
 	vis->x1 = x1;
 	vis->x2 = x2;
@@ -1538,6 +1537,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)
 	{
@@ -2692,7 +2694,7 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 	// Add the 3D floors, thicksides, and masked textures...
 	for (ds = drawsegs + mask->drawsegs[1]; ds-- > drawsegs + mask->drawsegs[0];)
 	{
-		if (ds->numthicksides)
+		if (ds->numthicksides && cv_renderwalls.value)
 		{
 			for (i = 0; i < ds->numthicksides; i++)
 			{
@@ -2711,17 +2713,19 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 			else {
 				// Put it in!
 				entry = R_CreateDrawNode(head);
-				entry->plane = plane;
-				entry->seg = ds;
+				if (cv_renderwalls.value)
+					entry->seg = ds;
+				if (cv_renderfloors.value)
+					entry->plane = plane;
 			}
 			ds->curline->polyseg->visplane = NULL;
 		}
-		if (ds->maskedtexturecol)
+		if (ds->maskedtexturecol && cv_renderwalls.value)
 		{
 			entry = R_CreateDrawNode(head);
 			entry->seg = ds;
 		}
-		if (ds->numffloorplanes)
+		if (ds->numffloorplanes && cv_renderfloors.value)
 		{
 			for (i = 0; i < ds->numffloorplanes; i++)
 			{
@@ -3021,9 +3025,6 @@ void R_InitDrawNodes(void)
 //
 // R_DrawSprite
 //
-//Fab : 26-04-98:
-// NOTE : uses con_clipviewtop, so that when console is on,
-//        don't draw the part of sprites hidden under the console
 static void R_DrawSprite(vissprite_t *spr)
 {
 	mfloorclip = spr->clipbot;
@@ -3191,9 +3192,6 @@ static void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, portal_t* port
 				(lowscale < spr->sortscale &&
 				 !R_PointOnSegSide (spr->gx, spr->gy, ds->curline)))
 			{
-				// masked mid texture?
-				/*if (ds->maskedtexturecol)
-					R_RenderMaskedSegRange (ds, r1, r2);*/
 				// seg is behind sprite
 				continue;
 			}
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..c1970d0e8888360511fc0a7990e669b8678a9b3b 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -8,7 +8,7 @@
 // See the 'LICENSE' file for more details.
 //-----------------------------------------------------------------------------
 /// \file  screen.c
-/// \brief Handles multiple resolutions, 8bpp/16bpp(highcolor) modes
+/// \brief Handles multiple resolutions
 
 #include "doomdef.h"
 #include "doomstat.h"
@@ -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"},
@@ -91,19 +94,15 @@ consvar_t cv_fullscreen = CVAR_INIT ("fullscreen", "Yes", CV_SAVE|CV_CALL, CV_Ye
 // =========================================================================
 
 INT32 scr_bpp; // current video mode bytes per pixel
-UINT8 *scr_borderpatch; // flat used to fill the reduced view borders set at ST_Init()
 
 // =========================================================================
 
-//  Short and Tall sky drawer, for the current color mode
-void (*walldrawerfunc)(void);
-
 void SCR_SetDrawFuncs(void)
 {
 	//
-	//  setup the right draw routines for either 8bpp or 16bpp
+	//  setup the right draw routines
 	//
-	if (true)//vid.bpp == 1) //Always run in 8bpp. todo: remove all 16bpp code?
+	if (vid.bpp == 1) //Always run in 8bpp.
 	{
 		colfuncs[BASEDRAWFUNC] = R_DrawColumn_8;
 		spanfuncs[BASEDRAWFUNC] = R_DrawSpan_8;
@@ -139,7 +138,6 @@ void SCR_SetDrawFuncs(void)
 		spanfuncs[SPANDRAWFUNC_FOG] = R_DrawFogSpan_8;
 		spanfuncs[SPANDRAWFUNC_TILTEDFOG] = R_DrawTiltedFogSpan_8;
 
-		// Lactozilla: Non-powers-of-two
 		spanfuncs_npo2[BASEDRAWFUNC] = R_DrawSpan_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TRANS] = R_DrawTranslucentSpan_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TILTED] = R_DrawTiltedSpan_NPO2_8;
@@ -153,26 +151,9 @@ void SCR_SetDrawFuncs(void)
 		spanfuncs_npo2[SPANDRAWFUNC_TILTEDTRANSSPRITE] = R_DrawTiltedTranslucentFloorSprite_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_WATER] = R_DrawWaterSpan_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TILTEDWATER] = R_DrawTiltedWaterSpan_NPO2_8;
-
 	}
-/*	else if (vid.bpp > 1)
-	{
-		I_OutputMsg("using highcolor mode\n");
-		spanfunc = basespanfunc = R_DrawSpan_16;
-		transcolfunc = R_DrawTranslatedColumn_16;
-		transtransfunc = R_DrawTranslucentColumn_16; // No 16bit operation for this function
-
-		colfunc = basecolfunc = R_DrawColumn_16;
-		shadecolfunc = NULL; // detect error if used somewhere..
-		fuzzcolfunc = R_DrawTranslucentColumn_16;
-		walldrawerfunc = R_DrawWallColumn_16;
-	}*/
 	else
 		I_Error("unknown bytes per pixel mode %d\n", vid.bpp);
-/*
-	if (SCR_IsAspectCorrect(vid.width, vid.height))
-		CONS_Alert(CONS_WARNING, M_GetText("Resolution is not aspect-correct!\nUse a multiple of %dx%d\n"), BASEVIDWIDTH, BASEVIDHEIGHT);
-*/
 }
 
 void SCR_SetMode(void)
diff --git a/src/screen.h b/src/screen.h
index 64d92b9d3ef99cf3c4e066ddc5cc9e57edd94afd..5df5d28892d08713bdf9a7ef2b94665c8f151a4b 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -8,7 +8,7 @@
 // See the 'LICENSE' file for more details.
 //-----------------------------------------------------------------------------
 /// \file  screen.h
-/// \brief Handles multiple resolutions, 8bpp/16bpp(highcolor) modes
+/// \brief Handles multiple resolutions
 
 #ifndef __SCREEN_H__
 #define __SCREEN_H__
@@ -61,7 +61,7 @@ typedef struct viddef_s
 	UINT8 *direct; // linear frame buffer, or vga base mem.
 	INT32 dup; // scale 1, 2, 3 value for menus & overlays
 	INT32/*fixed_t*/ fdup; // same as dup, but exact value when aspect ratio isn't 320/200
-	INT32 bpp; // BYTES per pixel: 1 = 256color, 2 = highcolor
+	INT32 bpp; // BYTES per pixel: 1 = 256color
 
 	INT32 baseratio; // Used to get the correct value for lighting walls
 
@@ -83,35 +83,6 @@ enum
 	VID_GL_LIBRARY_ERROR      = -1,
 };
 
-// internal additional info for vesa modes only
-typedef struct
-{
-	INT32 vesamode; // vesa mode number plus LINEAR_MODE bit
-	void *plinearmem; // linear address of start of frame buffer
-} vesa_extra_t;
-// a video modes from the video modes list,
-// note: video mode 0 is always standard VGA320x200.
-typedef struct vmode_s
-{
-	struct vmode_s *pnext;
-	char *name;
-	UINT32 width, height;
-	UINT32 rowbytes; // bytes per scanline
-	UINT32 bytesperpixel; // 1 for 256c, 2 for highcolor
-	INT32 windowed; // if true this is a windowed mode
-	INT32 numpages;
-	vesa_extra_t *pextradata; // vesa mode extra data
-#ifdef _WIN32
-	INT32 (WINAPI *setmode)(viddef_t *lvid, struct vmode_s *pcurrentmode);
-#else
-	INT32 (*setmode)(viddef_t *lvid, struct vmode_s *pcurrentmode);
-#endif
-	INT32 misc; // misc for display driver (r_opengl.dll etc)
-} vmode_t;
-
-#define NUMSPECIALMODES  4
-extern vmode_t specialmodes[NUMSPECIALMODES];
-
 // ---------------------------------------------
 // color mode dependent drawer function pointers
 // ---------------------------------------------
@@ -184,9 +155,9 @@ void SCR_ChangeRenderer(void);
 extern CV_PossibleValue_t cv_renderer_t[];
 
 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..bebecd8d05a10bc9456b3dbb31d8696f737e773b 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" />
@@ -572,9 +573,6 @@
     <ClCompile Include="..\r_bsp.c" />
     <ClCompile Include="..\r_data.c" />
     <ClCompile Include="..\r_draw.c" />
-    <ClCompile Include="..\r_draw16.c">
-      <ExcludedFromBuild>true</ExcludedFromBuild>
-    </ClCompile>
     <ClCompile Include="..\r_draw8.c">
       <ExcludedFromBuild>true</ExcludedFromBuild>
     </ClCompile>
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index 183843018f84087b8555fbce59e638e67cfa3546..d2f9d018f2b3ef44e59def85634aa46ef24acd95 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>
@@ -976,9 +982,6 @@
     <ClCompile Include="..\r_draw.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
-    <ClCompile Include="..\r_draw16.c">
-      <Filter>R_Rend</Filter>
-    </ClCompile>
     <ClCompile Include="..\r_draw8.c">
       <Filter>R_Rend</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..76fe172fce0e27f46ceb491be5a43f1b3e72f770 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -100,8 +100,6 @@ static char vidModeName[33][32]; // allow 33 different modes
 rendermode_t rendermode = render_soft;
 rendermode_t chosenrendermode = render_none; // set by command line arguments
 
-boolean highcolor = false;
-
 static void VidWaitChanged(void);
 
 // synchronize page flipping with screen refresh
@@ -1297,6 +1295,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
@@ -1395,103 +1401,12 @@ INT32 VID_GetModeForSize(INT32 w, INT32 h)
 		}
 	}
 	return -1;
-#if 0
-	INT32 matchMode = -1, i;
-	VID_PrepareModeList();
-	if (USE_FULLSCREEN && numVidModes != -1)
-	{
-		for (i=firstEntry; i<numVidModes; i++)
-		{
-			if (modeList[i]->w == w &&
-			    modeList[i]->h == h)
-			{
-				matchMode = i;
-				break;
-			}
-		}
-		if (-1 == matchMode) // use smaller mode
-		{
-			w -= w%BASEVIDWIDTH;
-			h -= h%BASEVIDHEIGHT;
-			for (i=firstEntry; i<numVidModes; i++)
-			{
-				if (modeList[i]->w == w &&
-				    modeList[i]->h == h)
-				{
-					matchMode = i;
-					break;
-				}
-			}
-			if (-1 == matchMode) // use smallest mode
-				matchMode = numVidModes-1;
-		}
-		matchMode -= firstEntry;
-	}
-	else
-	{
-		for (i=0; i<MAXWINMODES; i++)
-		{
-			if (windowedModes[i][0] == w &&
-			    windowedModes[i][1] == h)
-			{
-				matchMode = i;
-				break;
-			}
-		}
-		if (-1 == matchMode) // use smaller mode
-		{
-			w -= w%BASEVIDWIDTH;
-			h -= h%BASEVIDHEIGHT;
-			for (i=0; i<MAXWINMODES; i++)
-			{
-				if (windowedModes[i][0] == w &&
-				    windowedModes[i][1] == h)
-				{
-					matchMode = i;
-					break;
-				}
-			}
-			if (-1 == matchMode) // use smallest mode
-				matchMode = MAXWINMODES-1;
-		}
-	}
-	return matchMode;
-#endif
 }
 
 void VID_PrepareModeList(void)
 {
 	// Under SDL2, we just use the windowed modes list, and scale in windowed fullscreen.
 	allow_fullscreen = true;
-#if 0
-	INT32 i;
-
-	firstEntry = 0;
-
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		modeList = SDL_ListModes(NULL, SDL_OPENGL|SDL_FULLSCREEN);
-	else
-#endif
-	modeList = SDL_ListModes(NULL, surfaceFlagsF|SDL_HWSURFACE); //Alam: At least hardware surface
-
-	if (disable_fullscreen?0:cv_fullscreen.value) // only fullscreen needs preparation
-	{
-		if (-1 != numVidModes)
-		{
-			for (i=0; i<numVidModes; i++)
-			{
-				if (modeList[i]->w <= MAXVIDWIDTH &&
-					modeList[i]->h <= MAXVIDHEIGHT)
-				{
-					firstEntry = i;
-					break;
-				}
-			}
-		}
-	}
-	allow_fullscreen = true;
-#endif
 }
 
 static SDL_bool Impl_CreateContext(void)
@@ -1958,32 +1873,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..1f4e61822ba2132debe93c5b800b9308169c61eb 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)
@@ -253,10 +253,6 @@ void ST_LoadGraphics(void)
 {
 	int i;
 
-	// SRB2 border patch
-	// st_borderpatchnum = W_GetNumForName("GFZFLR01");
-	// scr_borderpatch = W_CacheLumpNum(st_borderpatchnum, PU_HUDGFX);
-
 	// the original Doom uses 'STF' as base name for all face graphics
 	// Graue 04-08-2004: face/name graphics are now indexed by skins
 	//                   but load them in R_AddSkins, that gets called
@@ -1945,7 +1941,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 +2889,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);